mirror of
https://github.com/github/codeql.git
synced 2025-12-22 03:36:30 +01:00
Merge branch 'main' into precompute-states-in-overrun-write
This commit is contained in:
@@ -254,6 +254,10 @@ module StringSizeConfig implements ProductFlow::StateConfigSig {
|
||||
|
||||
predicate isBarrier2(DataFlow::Node node, FlowState2 state) { none() }
|
||||
|
||||
predicate isBarrierOut2(DataFlow::Node node) {
|
||||
node = any(DataFlow::SsaPhiNode phi).getAnInput(true)
|
||||
}
|
||||
|
||||
predicate isAdditionalFlowStep1(
|
||||
DataFlow::Node node1, FlowState1 state1, DataFlow::Node node2, FlowState1 state2
|
||||
) {
|
||||
|
||||
@@ -83,6 +83,7 @@ edges
|
||||
| test.cpp:243:12:243:14 | str indirection [string] | test.cpp:243:16:243:21 | string indirection |
|
||||
| test.cpp:243:16:243:21 | string indirection | test.cpp:243:12:243:21 | string |
|
||||
| test.cpp:249:20:249:27 | call to my_alloc | test.cpp:250:12:250:12 | p |
|
||||
| test.cpp:256:17:256:22 | call to malloc | test.cpp:257:12:257:12 | p |
|
||||
nodes
|
||||
| test.cpp:16:11:16:21 | mk_string_t indirection [string] | semmle.label | mk_string_t indirection [string] |
|
||||
| test.cpp:18:5:18:30 | ... = ... | semmle.label | ... = ... |
|
||||
@@ -159,6 +160,8 @@ nodes
|
||||
| test.cpp:243:16:243:21 | string indirection | semmle.label | string indirection |
|
||||
| test.cpp:249:20:249:27 | call to my_alloc | semmle.label | call to my_alloc |
|
||||
| test.cpp:250:12:250:12 | p | semmle.label | p |
|
||||
| test.cpp:256:17:256:22 | call to malloc | semmle.label | call to malloc |
|
||||
| test.cpp:257:12:257:12 | p | semmle.label | p |
|
||||
subpaths
|
||||
| test.cpp:242:22:242:27 | buffer | test.cpp:235:40:235:45 | buffer | test.cpp:236:12:236:17 | p_str indirection [post update] [string] | test.cpp:242:16:242:19 | set_string output argument [string] |
|
||||
#select
|
||||
@@ -177,6 +180,5 @@ subpaths
|
||||
| test.cpp:199:9:199:15 | call to strncpy | test.cpp:147:19:147:24 | call to malloc | test.cpp:199:22:199:27 | string | This write may overflow $@ by 2 elements. | test.cpp:199:22:199:27 | string | string |
|
||||
| test.cpp:203:9:203:15 | call to strncpy | test.cpp:147:19:147:24 | call to malloc | test.cpp:203:22:203:27 | string | This write may overflow $@ by 2 elements. | test.cpp:203:22:203:27 | string | string |
|
||||
| test.cpp:207:9:207:15 | call to strncpy | test.cpp:147:19:147:24 | call to malloc | test.cpp:207:22:207:27 | string | This write may overflow $@ by 3 elements. | test.cpp:207:22:207:27 | string | string |
|
||||
| test.cpp:232:3:232:8 | call to memset | test.cpp:228:43:228:48 | call to malloc | test.cpp:232:10:232:15 | buffer | This write may overflow $@ by 2 elements. | test.cpp:232:10:232:15 | buffer | buffer |
|
||||
| test.cpp:243:5:243:10 | call to memset | test.cpp:241:27:241:32 | call to malloc | test.cpp:243:12:243:21 | string | This write may overflow $@ by 1 element. | test.cpp:243:16:243:21 | string | string |
|
||||
| test.cpp:250:5:250:10 | call to memset | test.cpp:249:20:249:27 | call to my_alloc | test.cpp:250:12:250:12 | p | This write may overflow $@ by 1 element. | test.cpp:250:12:250:12 | p | p |
|
||||
|
||||
@@ -229,7 +229,7 @@ void repeated_alerts(unsigned size, unsigned offset) {
|
||||
while(unknown()) {
|
||||
++size;
|
||||
}
|
||||
memset(buffer, 0, size); // BAD
|
||||
memset(buffer, 0, size); // BAD [NOT DETECTED]
|
||||
}
|
||||
|
||||
void set_string(string_t* p_str, char* buffer) {
|
||||
@@ -249,3 +249,11 @@ void foo(unsigned size) {
|
||||
int* p = (int*)my_alloc(size); // BAD
|
||||
memset(p, 0, size + 1);
|
||||
}
|
||||
|
||||
void test6(unsigned long n, char *p) {
|
||||
while (unknown()) {
|
||||
n++;
|
||||
p = (char *)malloc(n);
|
||||
memset(p, 0, n); // GOOD
|
||||
}
|
||||
}
|
||||
@@ -649,6 +649,10 @@ edges
|
||||
| test.cpp:280:13:280:24 | new[] | test.cpp:281:14:281:15 | xs |
|
||||
| test.cpp:290:13:290:24 | new[] | test.cpp:291:14:291:15 | xs |
|
||||
| test.cpp:290:13:290:24 | new[] | test.cpp:292:30:292:30 | x |
|
||||
| test.cpp:304:15:304:26 | new[] | test.cpp:307:5:307:6 | xs |
|
||||
| test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:6 | xs |
|
||||
| test.cpp:308:5:308:6 | xs | test.cpp:308:5:308:11 | access to array |
|
||||
| test.cpp:308:5:308:11 | access to array | test.cpp:308:5:308:29 | Store: ... = ... |
|
||||
#select
|
||||
| test.cpp:6:14:6:15 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:6:14:6:15 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size |
|
||||
| test.cpp:8:14:8:21 | Load: * ... | test.cpp:4:15:4:20 | call to malloc | test.cpp:8:14:8:21 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@ + 1. | test.cpp:4:15:4:20 | call to malloc | call to malloc | test.cpp:5:19:5:22 | size | size |
|
||||
@@ -672,3 +676,4 @@ edges
|
||||
| test.cpp:254:9:254:16 | Store: ... = ... | test.cpp:248:24:248:30 | call to realloc | test.cpp:254:9:254:16 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:248:24:248:30 | call to realloc | call to realloc | test.cpp:254:11:254:11 | i | i |
|
||||
| test.cpp:264:13:264:14 | Load: * ... | test.cpp:260:13:260:24 | new[] | test.cpp:264:13:264:14 | Load: * ... | This read might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:260:13:260:24 | new[] | new[] | test.cpp:261:19:261:21 | len | len |
|
||||
| test.cpp:274:5:274:10 | Store: ... = ... | test.cpp:270:13:270:24 | new[] | test.cpp:274:5:274:10 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:270:13:270:24 | new[] | new[] | test.cpp:271:19:271:21 | len | len |
|
||||
| test.cpp:308:5:308:29 | Store: ... = ... | test.cpp:304:15:304:26 | new[] | test.cpp:308:5:308:29 | Store: ... = ... | This write might be out of bounds, as the pointer might be equal to $@ + $@. | test.cpp:304:15:304:26 | new[] | new[] | test.cpp:308:8:308:10 | ... + ... | ... + ... |
|
||||
|
||||
@@ -294,3 +294,17 @@ void test20(unsigned len)
|
||||
*x = 0; // GOOD
|
||||
}
|
||||
}
|
||||
|
||||
void* test21_get(int n);
|
||||
|
||||
void test21() {
|
||||
int n = 0;
|
||||
while (test21_get(n)) n+=2;
|
||||
|
||||
void** xs = new void*[n];
|
||||
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
xs[i] = test21_get(i);
|
||||
xs[i+1] = test21_get(i+1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,8 @@ postWithInFlow
|
||||
| test.cpp:681:3:681:3 | s [post update] | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:689:3:689:3 | s [post update] | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:690:3:690:3 | s [post update] | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:694:4:694:6 | buf [inner post update] | PostUpdateNode should not be the target of local flow. |
|
||||
| test.cpp:704:23:704:25 | buf [inner post update] | PostUpdateNode should not be the target of local flow. |
|
||||
viableImplInCallContextTooLarge
|
||||
uniqueParameterNodeAtPosition
|
||||
uniqueParameterNodePosition
|
||||
|
||||
@@ -690,3 +690,16 @@ void test_static_local_9() {
|
||||
s = 0;
|
||||
}
|
||||
|
||||
void increment_buf(int** buf) { // $ ast-def=buf ir-def=*buf ir-def=**buf
|
||||
*buf += 10;
|
||||
sink(buf); // $ SPURIOUS: ast,ir // should only be flow to the indirect argument, but there's also flow to the non-indirect argument
|
||||
}
|
||||
|
||||
void call_increment_buf(int** buf) { // $ ast-def=buf
|
||||
increment_buf(buf);
|
||||
}
|
||||
|
||||
void test_conflation_regression(int* source) { // $ ast-def=source
|
||||
int* buf = source;
|
||||
call_increment_buf(&buf);
|
||||
}
|
||||
@@ -49,3 +49,13 @@
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* f3_get(int n);
|
||||
|
||||
void f3() {
|
||||
int n = 0;
|
||||
while (f3_get(n)) n+=2;
|
||||
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
range(i); // $ range=>=0 SPURIOUS: range="<=call to f3_get-1" range="<=call to f3_get-2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,5 +72,5 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
|
||||
|
||||
override predicate reverseReadExclude(Node n) { n.asExpr() = any(AwaitExpr ae).getExpr() }
|
||||
|
||||
override predicate identityLocalStepExclude(Node n) { n.getLocation().getFile().fromLibrary() }
|
||||
override predicate identityLocalStepExclude(Node n) { none() }
|
||||
}
|
||||
|
||||
@@ -335,7 +335,8 @@ module LocalFlow {
|
||||
exists(ControlFlow::BasicBlock bb, int i |
|
||||
SsaImpl::lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
|
||||
def.definesAt(_, bb, i, _) and
|
||||
def = getSsaDefinitionExt(nodeFrom)
|
||||
def = getSsaDefinitionExt(nodeFrom) and
|
||||
nodeFrom != next
|
||||
)
|
||||
}
|
||||
|
||||
@@ -414,7 +415,8 @@ module LocalFlow {
|
||||
) {
|
||||
exists(CIL::BasicBlock bb, int i | CilSsaImpl::lastRefBeforeRedefExt(def, bb, i, next) |
|
||||
def.definesAt(_, bb, i, _) and
|
||||
def = nodeFrom.(CilSsaDefinitionExtNode).getDefinition()
|
||||
def = nodeFrom.(CilSsaDefinitionExtNode).getDefinition() and
|
||||
def != next
|
||||
or
|
||||
nodeFrom = TCilExprNode(bb.getNode(i).(CIL::ReadAccess))
|
||||
)
|
||||
@@ -440,7 +442,8 @@ module LocalFlow {
|
||||
exists(CIL::ReadAccess readFrom, CIL::ReadAccess readTo |
|
||||
CilSsaImpl::hasAdjacentReadsExt(def, readFrom, readTo) and
|
||||
nodeTo = TCilExprNode(readTo) and
|
||||
nodeFrom = TCilExprNode(readFrom)
|
||||
nodeFrom = TCilExprNode(readFrom) and
|
||||
nodeFrom != nodeTo
|
||||
)
|
||||
or
|
||||
// Flow into phi (read) node
|
||||
@@ -483,7 +486,8 @@ module LocalFlow {
|
||||
or
|
||||
hasNodePath(any(LocalExprStepConfiguration x), nodeFrom, nodeTo)
|
||||
or
|
||||
ThisFlow::adjacentThisRefs(nodeFrom, nodeTo)
|
||||
ThisFlow::adjacentThisRefs(nodeFrom, nodeTo) and
|
||||
nodeFrom != nodeTo
|
||||
or
|
||||
ThisFlow::adjacentThisRefs(nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
|
||||
or
|
||||
@@ -541,7 +545,8 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
|
||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _) and
|
||||
not LocalFlow::usesInstanceField(def)
|
||||
not LocalFlow::usesInstanceField(def) and
|
||||
nodeFrom != nodeTo
|
||||
)
|
||||
or
|
||||
// Flow into phi (read)/uncertain SSA definition node from read
|
||||
@@ -880,7 +885,8 @@ private module Cached {
|
||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||
or
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||
nodeFrom != nodeTo
|
||||
or
|
||||
exists(SsaImpl::DefinitionExt def |
|
||||
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo) and
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
identityLocalStep
|
||||
| test.cs:17:41:17:44 | this access | Node steps to itself |
|
||||
| test.cs:34:41:34:44 | this access | Node steps to itself |
|
||||
| test.cs:52:41:52:44 | this access | Node steps to itself |
|
||||
| test.cs:67:41:67:44 | this access | Node steps to itself |
|
||||
| test.cs:77:22:77:24 | this access | Node steps to itself |
|
||||
| test.cs:90:41:90:44 | this access | Node steps to itself |
|
||||
@@ -1,2 +0,0 @@
|
||||
identityLocalStep
|
||||
| Conditions.cs:133:17:133:22 | [Field1 (line 129): false] this access | Node steps to itself |
|
||||
@@ -1,2 +0,0 @@
|
||||
identityLocalStep
|
||||
| Splitting.cs:133:21:133:29 | [b (line 123): false] this access | Node steps to itself |
|
||||
@@ -1,7 +0,0 @@
|
||||
identityLocalStep
|
||||
| SplittingStressTest.cs:172:16:172:16 | SSA phi read(b29) | Node steps to itself |
|
||||
| SplittingStressTest.cs:179:13:183:13 | [b1 (line 170): false] SSA phi read(b1) | Node steps to itself |
|
||||
| SplittingStressTest.cs:184:13:188:13 | [b2 (line 170): false] SSA phi read(b2) | Node steps to itself |
|
||||
| SplittingStressTest.cs:189:13:193:13 | [b3 (line 170): false] SSA phi read(b3) | Node steps to itself |
|
||||
| SplittingStressTest.cs:194:13:198:13 | [b4 (line 170): false] SSA phi read(b4) | Node steps to itself |
|
||||
| SplittingStressTest.cs:199:13:203:13 | [b5 (line 170): false] SSA phi read(b5) | Node steps to itself |
|
||||
@@ -1,2 +0,0 @@
|
||||
identityLocalStep
|
||||
| Test.cs:80:37:80:42 | this access | Node steps to itself |
|
||||
@@ -1,2 +0,0 @@
|
||||
identityLocalStep
|
||||
| GlobalDataFlow.cs:573:9:576:9 | SSA phi read(f) | Node steps to itself |
|
||||
@@ -1,3 +0,0 @@
|
||||
identityLocalStep
|
||||
| DefUse.cs:80:37:80:42 | this access | Node steps to itself |
|
||||
| Properties.cs:65:24:65:31 | this access | Node steps to itself |
|
||||
@@ -1,4 +0,0 @@
|
||||
identityLocalStep
|
||||
| D.cs:320:17:320:25 | this access | Node steps to itself |
|
||||
| E.cs:123:21:123:24 | SSA phi read(x) | Node steps to itself |
|
||||
| E.cs:123:21:123:24 | SSA phi(i) | Node steps to itself |
|
||||
@@ -1,2 +0,0 @@
|
||||
identityLocalStep
|
||||
| ZipSlip.cs:13:13:45:13 | SSA phi read(destDirectory) | Node steps to itself |
|
||||
@@ -176,7 +176,11 @@ A QL module definition has the following syntax:
|
||||
|
||||
::
|
||||
|
||||
module ::= annotation* "module" modulename "{" moduleBody "}"
|
||||
module ::= annotation* "module" modulename parameters? implements? "{" moduleBody "}"
|
||||
|
||||
parameters ::= "<" signatureExpr simpleId ("," signatureExpr simpleId)* ">"
|
||||
|
||||
implements ::= "implements" moduleSignatureExpr ("," moduleSignatureExpr)*
|
||||
|
||||
moduleBody ::= (import | predicate | class | module | alias | select)*
|
||||
|
||||
@@ -208,12 +212,15 @@ An import directive refers to a module identifier:
|
||||
|
||||
::
|
||||
|
||||
import ::= annotations "import" importModuleId ("as" modulename)?
|
||||
import ::= annotations "import" importModuleExpr ("as" modulename)?
|
||||
|
||||
qualId ::= simpleId | qualId "." simpleId
|
||||
|
||||
importModuleId ::= qualId
|
||||
| importModuleId "::" simpleId
|
||||
importModuleExpr ::= qualId | importModuleExpr "::" simpleId arguments?
|
||||
|
||||
arguments ::= "<" argument ("," argument)* ">"
|
||||
|
||||
argument ::= moduleExpr | type | predicateRef "/" int
|
||||
|
||||
An import directive may optionally name the imported module using an ``as`` declaration. If a name is defined, then the import directive adds to the declared module environment of the current module a mapping from the name to the declaration of the imported module. Otherwise, the current module *directly imports* the imported module.
|
||||
|
||||
@@ -280,9 +287,9 @@ With the exception of class domain types and character types (which cannot be re
|
||||
|
||||
::
|
||||
|
||||
type ::= (moduleId "::")? classname | dbasetype | "boolean" | "date" | "float" | "int" | "string"
|
||||
type ::= (moduleExpr "::")? classname | dbasetype | "boolean" | "date" | "float" | "int" | "string"
|
||||
|
||||
moduleId ::= simpleId | moduleId "::" simpleId
|
||||
moduleExpr ::= simpleId arguments? | moduleExpr "::" simpleId arguments?
|
||||
|
||||
A type reference is resolved to a type as follows:
|
||||
|
||||
@@ -597,7 +604,7 @@ Identifiers are used in following syntactic constructs:
|
||||
modulename ::= simpleId
|
||||
classname ::= upperId
|
||||
dbasetype ::= atLowerId
|
||||
predicateRef ::= (moduleId "::")? literalId
|
||||
predicateRef ::= (moduleExpr "::")? literalId
|
||||
predicateName ::= lowerId
|
||||
varname ::= lowerId
|
||||
literalId ::= lowerId | atLowerId
|
||||
@@ -1615,7 +1622,7 @@ Aliases define new names for existing QL entities.
|
||||
|
||||
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
|
||||
| qldoc? annotations "class" classname "=" type ";"
|
||||
| qldoc? annotations "module" modulename "=" moduleId ";"
|
||||
| qldoc? annotations "module" modulename "=" moduleExpr ";"
|
||||
|
||||
|
||||
An alias introduces a binding from the new name to the entity referred to by the right-hand side in the current module's declared predicate, type, or module environment respectively.
|
||||
@@ -2064,16 +2071,23 @@ The complete grammar for QL is as follows:
|
||||
|
||||
ql ::= qldoc? moduleBody
|
||||
|
||||
module ::= annotation* "module" modulename "{" moduleBody "}"
|
||||
module ::= annotation* "module" modulename parameters? implements? "{" moduleBody "}"
|
||||
|
||||
parameters ::= "<" signatureExpr simpleId ("," signatureExpr simpleId)* ">"
|
||||
|
||||
implements ::= "implements" moduleSignatureExpr ("," moduleSignatureExpr)*
|
||||
|
||||
moduleBody ::= (import | predicate | class | module | alias | select)*
|
||||
|
||||
import ::= annotations "import" importModuleId ("as" modulename)?
|
||||
import ::= annotations "import" importModuleExpr ("as" modulename)?
|
||||
|
||||
qualId ::= simpleId | qualId "." simpleId
|
||||
|
||||
importModuleId ::= qualId
|
||||
| importModuleId "::" simpleId
|
||||
importModuleExpr ::= qualId | importModuleExpr "::" simpleId arguments?
|
||||
|
||||
arguments ::= "<" argument ("," argument)* ">"
|
||||
|
||||
argument ::= moduleExpr | type | predicateRef "/" int
|
||||
|
||||
select ::= ("from" var_decls)? ("where" formula)? "select" as_exprs ("order" "by" orderbys)?
|
||||
|
||||
@@ -2120,15 +2134,19 @@ The complete grammar for QL is as follows:
|
||||
|
||||
field ::= qldoc? annotations var_decl ";"
|
||||
|
||||
moduleId ::= simpleId | moduleId "::" simpleId
|
||||
moduleExpr ::= simpleId arguments? | moduleExpr "::" simpleId arguments?
|
||||
|
||||
type ::= (moduleId "::")? classname | dbasetype | "boolean" | "date" | "float" | "int" | "string"
|
||||
moduleSignatureExpr ::= (moduleExpr "::")? upperId arguments?
|
||||
|
||||
signatureExpr : (moduleExpr "::")? simpleId ("/" Integer | arguments)?;
|
||||
|
||||
type ::= (moduleExpr "::")? classname | dbasetype | "boolean" | "date" | "float" | "int" | "string"
|
||||
|
||||
exprs ::= expr ("," expr)*
|
||||
|
||||
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
|
||||
| qldoc? annotations "class" classname "=" type ";"
|
||||
| qldoc? annotations "module" modulename "=" moduleId ";"
|
||||
| qldoc? annotations "module" modulename "=" moduleExpr ";"
|
||||
|
||||
var_decls ::= (var_decl ("," var_decl)*)?
|
||||
|
||||
@@ -2249,7 +2267,7 @@ The complete grammar for QL is as follows:
|
||||
|
||||
dbasetype ::= atLowerId
|
||||
|
||||
predicateRef ::= (moduleId "::")? literalId
|
||||
predicateRef ::= (moduleExpr "::")? literalId
|
||||
|
||||
predicateName ::= lowerId
|
||||
|
||||
|
||||
@@ -113,9 +113,9 @@ ql/lib/go.dbscheme.stats: ql/lib/go.dbscheme build/stats/src.stamp extractor
|
||||
|
||||
test: all build/testdb/check-upgrade-path
|
||||
codeql test run -j0 ql/test --search-path build/codeql-extractor-go --consistency-queries ql/test/consistency --compilation-cache=$(cache)
|
||||
# use GOOS=linux because GOOS=darwin GOARCH=386 is no longer supported
|
||||
# use GOOS=linux because GOOS=darwin GOARCH=386 is no longer supported
|
||||
env GOOS=linux GOARCH=386 codeql$(EXE) test run -j0 ql/test/query-tests/Security/CWE-681 --search-path build/codeql-extractor-go --consistency-queries ql/test/consistency --compilation-cache=$(cache)
|
||||
cd extractor; go test -mod=vendor ./... | grep -vF "[no test files]"
|
||||
cd extractor; go test -mod=vendor ./...
|
||||
bash extractor-smoke-test/test.sh || (echo "Extractor smoke test FAILED"; exit 1)
|
||||
|
||||
.PHONY: build/testdb/check-upgrade-path
|
||||
|
||||
@@ -36,45 +36,37 @@ func TestParseGoVersion(t *testing.T) {
|
||||
|
||||
func TestGetVersionToInstall(t *testing.T) {
|
||||
tests := map[versionInfo]string{
|
||||
// checkForUnsupportedVersions()
|
||||
|
||||
// go.mod version below minGoVersion
|
||||
{"0.0", true, "1.20.3", true}: "",
|
||||
{"0.0", true, "9999.0", true}: "",
|
||||
{"0.0", true, "1.2.2", true}: "",
|
||||
{"0.0", true, "", false}: "",
|
||||
// go.mod version above maxGoVersion
|
||||
{"9999.0", true, "1.20.3", true}: "",
|
||||
{"9999.0", true, "9999.0.1", true}: "",
|
||||
{"9999.0", true, "1.1", true}: "",
|
||||
{"9999.0", true, "", false}: "",
|
||||
// Go installation found with version below minGoVersion
|
||||
{"1.20", true, "1.2.2", true}: "1.20",
|
||||
{"1.11", true, "1.2.2", true}: "1.11",
|
||||
{"", false, "1.2.2", true}: maxGoVersion,
|
||||
// Go installation found with version above maxGoVersion
|
||||
{"1.20", true, "9999.0.1", true}: "1.20",
|
||||
{"1.11", true, "9999.0.1", true}: "1.11",
|
||||
{"", false, "9999.0.1", true}: maxGoVersion,
|
||||
|
||||
// checkForVersionsNotFound()
|
||||
|
||||
// Go installation not found, go.mod version in supported range
|
||||
{"1.20", true, "", false}: "1.20",
|
||||
{"1.11", true, "", false}: "1.11",
|
||||
// Go installation not found, go.mod not found
|
||||
// getVersionWhenGoModVersionNotFound()
|
||||
{"", false, "", false}: maxGoVersion,
|
||||
// Go installation found with version in supported range, go.mod not found
|
||||
{"", false, "1.2.2", true}: maxGoVersion,
|
||||
{"", false, "9999.0.1", true}: maxGoVersion,
|
||||
{"", false, "1.11.13", true}: "",
|
||||
{"", false, "1.20.3", true}: "",
|
||||
|
||||
// compareVersions()
|
||||
// getVersionWhenGoModVersionTooHigh()
|
||||
{"9999.0", true, "", false}: maxGoVersion,
|
||||
{"9999.0", true, "9999.0.1", true}: "",
|
||||
{"9999.0", true, "1.1", true}: maxGoVersion,
|
||||
{"9999.0", true, minGoVersion, false}: maxGoVersion,
|
||||
{"9999.0", true, maxGoVersion, true}: "",
|
||||
|
||||
// Go installation found with version in supported range, go.mod version in supported range and go.mod version > go installation version
|
||||
// getVersionWhenGoModVersionTooLow()
|
||||
{"0.0", true, "", false}: minGoVersion,
|
||||
{"0.0", true, "9999.0", true}: minGoVersion,
|
||||
{"0.0", true, "1.2.2", true}: minGoVersion,
|
||||
{"0.0", true, "1.20.3", true}: "",
|
||||
|
||||
// getVersionWhenGoModVersionSupported()
|
||||
{"1.20", true, "", false}: "1.20",
|
||||
{"1.11", true, "", false}: "1.11",
|
||||
{"1.20", true, "1.2.2", true}: "1.20",
|
||||
{"1.11", true, "1.2.2", true}: "1.11",
|
||||
{"1.20", true, "9999.0.1", true}: "1.20",
|
||||
{"1.11", true, "9999.0.1", true}: "1.11",
|
||||
// go.mod version > go installation version
|
||||
{"1.20", true, "1.11.13", true}: "1.20",
|
||||
{"1.20", true, "1.12", true}: "1.20",
|
||||
// Go installation found with version in supported range, go.mod version in supported range and go.mod version <= go installation version
|
||||
// (Note comparisons ignore the patch version)
|
||||
// go.mod version <= go installation version (Note comparisons ignore the patch version)
|
||||
{"1.11", true, "1.20", true}: "",
|
||||
{"1.11", true, "1.20.3", true}: "",
|
||||
{"1.20", true, "1.20.3", true}: "",
|
||||
|
||||
@@ -78,7 +78,7 @@ class InlineFlowTest extends InlineExpectationsTest {
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "hasValueFlow" and
|
||||
exists(DataFlow::Node sink | getValueFlowConfig().hasFlowTo(sink) |
|
||||
exists(DataFlow::Node sink | this.getValueFlowConfig().hasFlowTo(sink) |
|
||||
sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(),
|
||||
location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and
|
||||
element = sink.toString() and
|
||||
@@ -87,7 +87,8 @@ class InlineFlowTest extends InlineExpectationsTest {
|
||||
or
|
||||
tag = "hasTaintFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||
getTaintFlowConfig().hasFlow(src, sink) and not getValueFlowConfig().hasFlow(src, sink)
|
||||
this.getTaintFlowConfig().hasFlow(src, sink) and
|
||||
not this.getValueFlowConfig().hasFlow(src, sink)
|
||||
|
|
||||
sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(),
|
||||
location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and
|
||||
|
||||
@@ -239,8 +239,6 @@ open class KotlinUsesExtractor(
|
||||
return UseClassInstanceResult(classTypeResult, extractClass)
|
||||
}
|
||||
|
||||
private fun isArray(t: IrSimpleType) = t.isBoxedArray || t.isPrimitiveArray()
|
||||
|
||||
private fun extractClassLaterIfExternal(c: IrClass) {
|
||||
if (isExternalDeclaration(c)) {
|
||||
extractExternalClassLater(c)
|
||||
@@ -551,6 +549,22 @@ open class KotlinUsesExtractor(
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
Kotlin arrays can be broken down as:
|
||||
|
||||
isArray(t)
|
||||
|- t.isBoxedArray
|
||||
| |- t.isArray() e.g. Array<Boolean>, Array<Boolean?>
|
||||
| |- t.isNullableArray() e.g. Array<Boolean>?, Array<Boolean?>?
|
||||
|- t.isPrimitiveArray() e.g. BooleanArray
|
||||
|
||||
For the corresponding Java types:
|
||||
Boxed arrays are represented as e.g. java.lang.Boolean[].
|
||||
Primitive arrays are represented as e.g. boolean[].
|
||||
*/
|
||||
|
||||
private fun isArray(t: IrType) = t.isBoxedArray || t.isPrimitiveArray()
|
||||
|
||||
data class ArrayInfo(val elementTypeResults: TypeResults,
|
||||
val componentTypeResults: TypeResults,
|
||||
val dimensions: Int)
|
||||
@@ -565,7 +579,7 @@ open class KotlinUsesExtractor(
|
||||
*/
|
||||
private fun useArrayType(t: IrType, isPrimitiveArray: Boolean): ArrayInfo {
|
||||
|
||||
if (!t.isBoxedArray && !t.isPrimitiveArray()) {
|
||||
if (!isArray(t)) {
|
||||
val nullableT = if (t.isPrimitiveType() && !isPrimitiveArray) t.makeNullable() else t
|
||||
val typeResults = useType(nullableT)
|
||||
return ArrayInfo(typeResults, typeResults, 0)
|
||||
@@ -1141,13 +1155,13 @@ open class KotlinUsesExtractor(
|
||||
}
|
||||
} else {
|
||||
t.classOrNull?.let { tCls ->
|
||||
if (t.isArray() || t.isNullableArray()) {
|
||||
if (t.isBoxedArray) {
|
||||
(t.arguments.singleOrNull() as? IrTypeProjection)?.let { elementTypeArg ->
|
||||
val elementType = elementTypeArg.type
|
||||
val replacedElementType = kClassToJavaClass(elementType)
|
||||
if (replacedElementType !== elementType) {
|
||||
val newArg = makeTypeProjection(replacedElementType, elementTypeArg.variance)
|
||||
return tCls.typeWithArguments(listOf(newArg)).codeQlWithHasQuestionMark(t.isNullableArray())
|
||||
return tCls.typeWithArguments(listOf(newArg)).codeQlWithHasQuestionMark(t.isNullable())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1578,7 +1592,7 @@ open class KotlinUsesExtractor(
|
||||
}
|
||||
|
||||
if (owner is IrClass) {
|
||||
if (t.isArray() || t.isNullableArray()) {
|
||||
if (t.isBoxedArray) {
|
||||
val elementType = t.getArrayElementType(pluginContext.irBuiltIns)
|
||||
val erasedElementType = erase(elementType)
|
||||
return owner.typeWith(erasedElementType).codeQlWithHasQuestionMark(t.isNullable())
|
||||
|
||||
@@ -8,6 +8,11 @@ if "JAVA_HOME_8_X64" in os.environ:
|
||||
sep = ";" if platform.system() == "Windows" else ":"
|
||||
os.environ["PATH"] = "".join([os.path.join(os.environ["JAVA_HOME"], "bin"), sep, os.environ["PATH"]])
|
||||
|
||||
# Ensure the autobuilder *doesn't* see Java 11 or 17, which it could switch to in order to build the project:
|
||||
for k in ["JAVA_HOME_11_X64", "JAVA_HOME_17_X64"]:
|
||||
if k in os.environ:
|
||||
del os.environ[k]
|
||||
|
||||
run_codeql_database_create([], lang="java", runFunction = runUnsuccessfully, db = None)
|
||||
|
||||
check_diagnostics()
|
||||
|
||||
@@ -27,8 +27,8 @@ deprecated class CamelToURI = CamelToUri;
|
||||
class CamelToBeanUri extends CamelToUri {
|
||||
CamelToBeanUri() {
|
||||
// A `<to>` element references a bean if the URI starts with "bean:", or there is no scheme.
|
||||
matches("bean:%") or
|
||||
not exists(indexOf(":"))
|
||||
this.matches("bean:%") or
|
||||
not exists(this.indexOf(":"))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,13 +38,13 @@ class CamelToBeanUri extends CamelToUri {
|
||||
* parameter parts are optional.
|
||||
*/
|
||||
string getBeanIdentifier() {
|
||||
if not exists(indexOf(":"))
|
||||
if not exists(this.indexOf(":"))
|
||||
then result = this
|
||||
else
|
||||
exists(int start | start = indexOf(":", 0, 0) + 1 |
|
||||
if not exists(indexOf("?"))
|
||||
then result = suffix(start)
|
||||
else result = substring(start, indexOf("?", 0, 0))
|
||||
exists(int start | start = this.indexOf(":", 0, 0) + 1 |
|
||||
if not exists(this.indexOf("?"))
|
||||
then result = this.suffix(start)
|
||||
else result = this.substring(start, this.indexOf("?", 0, 0))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -655,6 +655,11 @@ class XmlReader extends RefType {
|
||||
XmlReader() { this.hasQualifiedName("org.xml.sax", "XMLReader") }
|
||||
}
|
||||
|
||||
/** The class `org.xml.sax.InputSource`. */
|
||||
class InputSource extends Class {
|
||||
InputSource() { this.hasQualifiedName("org.xml.sax", "InputSource") }
|
||||
}
|
||||
|
||||
/** DEPRECATED: Alias for XmlReader */
|
||||
deprecated class XMLReader = XmlReader;
|
||||
|
||||
@@ -1164,22 +1169,34 @@ class XmlUnmarshal extends XmlParserCall {
|
||||
}
|
||||
|
||||
/* XPathExpression: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#xpathexpression */
|
||||
/** The class `javax.xml.xpath.XPathExpression`. */
|
||||
class XPathExpression extends RefType {
|
||||
/** The interface `javax.xml.xpath.XPathExpression`. */
|
||||
class XPathExpression extends Interface {
|
||||
XPathExpression() { this.hasQualifiedName("javax.xml.xpath", "XPathExpression") }
|
||||
}
|
||||
|
||||
/** A call to `XPathExpression.evaluate`. */
|
||||
/** The interface `java.xml.xpath.XPath`. */
|
||||
class XPath extends Interface {
|
||||
XPath() { this.hasQualifiedName("javax.xml.xpath", "XPath") }
|
||||
}
|
||||
|
||||
/** A call to the method `evaluate` of the classes `XPathExpression` or `XPath`. */
|
||||
class XPathEvaluate extends XmlParserCall {
|
||||
Argument sink;
|
||||
|
||||
XPathEvaluate() {
|
||||
exists(Method m |
|
||||
this.getMethod() = m and
|
||||
m.getDeclaringType() instanceof XPathExpression and
|
||||
m.hasName("evaluate")
|
||||
|
|
||||
m.getDeclaringType().getASourceSupertype*() instanceof XPathExpression and
|
||||
sink = this.getArgument(0)
|
||||
or
|
||||
m.getDeclaringType().getASourceSupertype*() instanceof XPath and
|
||||
sink = this.getArgument(1)
|
||||
)
|
||||
}
|
||||
|
||||
override Expr getSink() { result = this.getArgument(0) }
|
||||
override Expr getSink() { result = sink }
|
||||
|
||||
override predicate isSafe() { none() }
|
||||
}
|
||||
|
||||
4
java/ql/src/change-notes/2023-05-15-xpath-xxe-sink.md
Normal file
4
java/ql/src/change-notes/2023-05-15-xpath-xxe-sink.md
Normal file
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* The queries `java/xxe` and `java/xxe-local` now recognize the second argument of calls to `XPath.evaluate` as a sink.
|
||||
@@ -12,4 +12,4 @@ dependencies:
|
||||
codeql/util: ${workspace}
|
||||
dataExtensions:
|
||||
- Telemetry/ExtractorInformation.yml
|
||||
warnOmImplicitThis: true
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -73,7 +73,7 @@ class InlineFlowTest extends InlineExpectationsTest {
|
||||
|
||||
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||
tag = "hasValueFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink | hasValueFlow(src, sink) |
|
||||
exists(DataFlow::Node src, DataFlow::Node sink | this.hasValueFlow(src, sink) |
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
|
||||
@@ -81,7 +81,7 @@ class InlineFlowTest extends InlineExpectationsTest {
|
||||
or
|
||||
tag = "hasTaintFlow" and
|
||||
exists(DataFlow::Node src, DataFlow::Node sink |
|
||||
hasTaintFlow(src, sink) and not hasValueFlow(src, sink)
|
||||
this.hasTaintFlow(src, sink) and not this.hasValueFlow(src, sink)
|
||||
|
|
||||
sink.getLocation() = location and
|
||||
element = sink.toString() and
|
||||
|
||||
@@ -17,13 +17,28 @@ public class XPathExpressionTests {
|
||||
XPathFactory xFactory = XPathFactory.newInstance();
|
||||
XPath path = xFactory.newXPath();
|
||||
XPathExpression expr = path.compile("");
|
||||
expr.evaluate(builder.parse(sock.getInputStream())); //safe
|
||||
expr.evaluate(builder.parse(sock.getInputStream())); // safe
|
||||
}
|
||||
|
||||
public void unsafeExpressionTests(Socket sock) throws Exception {
|
||||
XPathFactory xFactory = XPathFactory.newInstance();
|
||||
XPath path = xFactory.newXPath();
|
||||
XPathExpression expr = path.compile("");
|
||||
expr.evaluate(new InputSource(sock.getInputStream())); //unsafe
|
||||
expr.evaluate(new InputSource(sock.getInputStream())); // unsafe
|
||||
}
|
||||
|
||||
public void safeXPathEvaluateTest(Socket sock) throws Exception {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
XPathFactory xFactory = XPathFactory.newInstance();
|
||||
XPath path = xFactory.newXPath();
|
||||
path.evaluate("", builder.parse(sock.getInputStream()));
|
||||
}
|
||||
|
||||
public void unsafeXPathEvaluateTest(Socket sock) throws Exception {
|
||||
XPathFactory xFactory = XPathFactory.newInstance();
|
||||
XPath path = xFactory.newXPath();
|
||||
path.evaluate("", new InputSource(sock.getInputStream())); // unsafe
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,8 @@ edges
|
||||
| XMLReaderTests.java:86:34:86:54 | getInputStream(...) : InputStream | XMLReaderTests.java:86:18:86:55 | new InputSource(...) |
|
||||
| XMLReaderTests.java:94:34:94:54 | getInputStream(...) : InputStream | XMLReaderTests.java:94:18:94:55 | new InputSource(...) |
|
||||
| XMLReaderTests.java:100:34:100:54 | getInputStream(...) : InputStream | XMLReaderTests.java:100:18:100:55 | new InputSource(...) |
|
||||
| XPathExpressionTests.java:27:37:27:57 | getInputStream(...) : InputStream | XPathExpressionTests.java:27:21:27:58 | new InputSource(...) |
|
||||
| XPathExpressionTests.java:27:35:27:55 | getInputStream(...) : InputStream | XPathExpressionTests.java:27:19:27:56 | new InputSource(...) |
|
||||
| XPathExpressionTests.java:42:39:42:59 | getInputStream(...) : InputStream | XPathExpressionTests.java:42:23:42:60 | new InputSource(...) |
|
||||
nodes
|
||||
| DocumentBuilderTests.java:14:19:14:39 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| DocumentBuilderTests.java:28:19:28:39 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
@@ -235,8 +236,10 @@ nodes
|
||||
| XMLReaderTests.java:94:34:94:54 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XMLReaderTests.java:100:18:100:55 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||
| XMLReaderTests.java:100:34:100:54 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XPathExpressionTests.java:27:21:27:58 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||
| XPathExpressionTests.java:27:37:27:57 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XPathExpressionTests.java:27:19:27:56 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||
| XPathExpressionTests.java:27:35:27:55 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XPathExpressionTests.java:42:23:42:60 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||
| XPathExpressionTests.java:42:39:42:59 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| XmlInputFactoryTests.java:9:35:9:55 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
| XmlInputFactoryTests.java:24:35:24:55 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||
@@ -336,7 +339,8 @@ subpaths
|
||||
| XMLReaderTests.java:86:18:86:55 | new InputSource(...) | XMLReaderTests.java:86:34:86:54 | getInputStream(...) : InputStream | XMLReaderTests.java:86:18:86:55 | new InputSource(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XMLReaderTests.java:86:34:86:54 | getInputStream(...) | user-provided value |
|
||||
| XMLReaderTests.java:94:18:94:55 | new InputSource(...) | XMLReaderTests.java:94:34:94:54 | getInputStream(...) : InputStream | XMLReaderTests.java:94:18:94:55 | new InputSource(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XMLReaderTests.java:94:34:94:54 | getInputStream(...) | user-provided value |
|
||||
| XMLReaderTests.java:100:18:100:55 | new InputSource(...) | XMLReaderTests.java:100:34:100:54 | getInputStream(...) : InputStream | XMLReaderTests.java:100:18:100:55 | new InputSource(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XMLReaderTests.java:100:34:100:54 | getInputStream(...) | user-provided value |
|
||||
| XPathExpressionTests.java:27:21:27:58 | new InputSource(...) | XPathExpressionTests.java:27:37:27:57 | getInputStream(...) : InputStream | XPathExpressionTests.java:27:21:27:58 | new InputSource(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XPathExpressionTests.java:27:37:27:57 | getInputStream(...) | user-provided value |
|
||||
| XPathExpressionTests.java:27:19:27:56 | new InputSource(...) | XPathExpressionTests.java:27:35:27:55 | getInputStream(...) : InputStream | XPathExpressionTests.java:27:19:27:56 | new InputSource(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XPathExpressionTests.java:27:35:27:55 | getInputStream(...) | user-provided value |
|
||||
| XPathExpressionTests.java:42:23:42:60 | new InputSource(...) | XPathExpressionTests.java:42:39:42:59 | getInputStream(...) : InputStream | XPathExpressionTests.java:42:23:42:60 | new InputSource(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XPathExpressionTests.java:42:39:42:59 | getInputStream(...) | user-provided value |
|
||||
| XmlInputFactoryTests.java:9:35:9:55 | getInputStream(...) | XmlInputFactoryTests.java:9:35:9:55 | getInputStream(...) | XmlInputFactoryTests.java:9:35:9:55 | getInputStream(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XmlInputFactoryTests.java:9:35:9:55 | getInputStream(...) | user-provided value |
|
||||
| XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | user-provided value |
|
||||
| XmlInputFactoryTests.java:24:35:24:55 | getInputStream(...) | XmlInputFactoryTests.java:24:35:24:55 | getInputStream(...) | XmlInputFactoryTests.java:24:35:24:55 | getInputStream(...) | XML parsing depends on a $@ without guarding against external entity expansion. | XmlInputFactoryTests.java:24:35:24:55 | getInputStream(...) | user-provided value |
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: minorAnalysis
|
||||
---
|
||||
* Improved the queries for injection vulnerabilities in GitHub Actions workflows (`js/actions/command-injection` and `js/actions/pull-request-target`) and the associated library `semmle.javascript.Actions`. These now support steps defined in composite actions, in addition to steps defined in Actions workflow files. It supports more potentially untrusted input values. Additionally to the shell injections it now also detects injections in `actions/github-script`. It also detects simple injections from user controlled `${{ env.name }}`. Additionally to the `yml` extension now it also supports workflows with the `yaml` extension.
|
||||
@@ -12,3 +12,4 @@ dependencies:
|
||||
codeql/yaml: ${workspace}
|
||||
dataExtensions:
|
||||
- semmle/javascript/frameworks/**/model.yml
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -10,16 +10,70 @@ import javascript
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
*/
|
||||
module Actions {
|
||||
/** A YAML node in a GitHub Actions workflow file. */
|
||||
/** A YAML node in a GitHub Actions workflow or a custom composite action file. */
|
||||
private class Node extends YamlNode {
|
||||
Node() {
|
||||
this.getLocation()
|
||||
.getFile()
|
||||
.getRelativePath()
|
||||
.regexpMatch("(^|.*/)\\.github/workflows/.*\\.yml$")
|
||||
exists(File f |
|
||||
f = this.getLocation().getFile() and
|
||||
(
|
||||
f.getRelativePath().regexpMatch("(^|.*/)\\.github/workflows/.*\\.ya?ml$")
|
||||
or
|
||||
f.getBaseName() = ["action.yml", "action.yaml"]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom composite action. This is a mapping at the top level of an Actions YAML action file.
|
||||
* See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions.
|
||||
*/
|
||||
class CompositeAction extends Node, YamlDocument, YamlMapping {
|
||||
CompositeAction() {
|
||||
this.getFile().getBaseName() = ["action.yml", "action.yaml"] and
|
||||
this.lookup("runs").(YamlMapping).lookup("using").(YamlScalar).getValue() = "composite"
|
||||
}
|
||||
|
||||
/** Gets the `runs` mapping. */
|
||||
Runs getRuns() { result = this.lookup("runs") }
|
||||
}
|
||||
|
||||
/**
|
||||
* An `runs` mapping in a custom composite action YAML.
|
||||
* See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs
|
||||
*/
|
||||
class Runs extends StepsContainer {
|
||||
CompositeAction action;
|
||||
|
||||
Runs() { action.lookup("runs") = this }
|
||||
|
||||
/** Gets the action that this `runs` mapping is in. */
|
||||
CompositeAction getAction() { result = action }
|
||||
|
||||
/** Gets the `using` mapping. */
|
||||
Using getUsing() { result = this.lookup("using") }
|
||||
}
|
||||
|
||||
/**
|
||||
* The parent class of the class that can contain `steps` mappings. (`Job` or `Runs` currently.)
|
||||
*/
|
||||
abstract class StepsContainer extends YamlNode, YamlMapping {
|
||||
/** Gets the sequence of `steps` within this YAML node. */
|
||||
YamlSequence getSteps() { result = this.lookup("steps") }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `using` mapping in a custom composite action YAML.
|
||||
*/
|
||||
class Using extends YamlNode, YamlScalar {
|
||||
Runs runs;
|
||||
|
||||
Using() { runs.lookup("using") = this }
|
||||
|
||||
/** Gets the `runs` mapping that this `using` mapping is in. */
|
||||
Runs getRuns() { result = runs }
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||
@@ -28,6 +82,9 @@ module Actions {
|
||||
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
|
||||
YamlMapping getJobs() { result = this.lookup("jobs") }
|
||||
|
||||
/** Gets the 'global' `env` mapping in this workflow. */
|
||||
WorkflowEnv getEnv() { result = this.lookup("env") }
|
||||
|
||||
/** Gets the name of the workflow. */
|
||||
string getName() { result = this.lookup("name").(YamlString).getValue() }
|
||||
|
||||
@@ -54,11 +111,44 @@ module Actions {
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
}
|
||||
|
||||
/** A common class for `env` in workflow, job or step. */
|
||||
abstract class Env extends YamlNode, YamlMapping { }
|
||||
|
||||
/** A workflow level `env` mapping. */
|
||||
class WorkflowEnv extends Env {
|
||||
Workflow workflow;
|
||||
|
||||
WorkflowEnv() { workflow.lookup("env") = this }
|
||||
|
||||
/** Gets the workflow this field belongs to. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
}
|
||||
|
||||
/** A job level `env` mapping. */
|
||||
class JobEnv extends Env {
|
||||
Job job;
|
||||
|
||||
JobEnv() { job.lookup("env") = this }
|
||||
|
||||
/** Gets the job this field belongs to. */
|
||||
Job getJob() { result = job }
|
||||
}
|
||||
|
||||
/** A step level `env` mapping. */
|
||||
class StepEnv extends Env {
|
||||
Step step;
|
||||
|
||||
StepEnv() { step.lookup("env") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
Step getStep() { result = step }
|
||||
}
|
||||
|
||||
/**
|
||||
* An Actions job within a workflow.
|
||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
|
||||
*/
|
||||
class Job extends YamlNode, YamlMapping {
|
||||
class Job extends StepsContainer {
|
||||
string jobId;
|
||||
Workflow workflow;
|
||||
|
||||
@@ -85,8 +175,8 @@ module Actions {
|
||||
/** Gets the step at the given index within this job. */
|
||||
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
||||
|
||||
/** Gets the sequence of `steps` within this job. */
|
||||
YamlSequence getSteps() { result = this.lookup("steps") }
|
||||
/** Gets the `env` mapping in this job. */
|
||||
JobEnv getEnv() { result = this.lookup("env") }
|
||||
|
||||
/** Gets the workflow this job belongs to. */
|
||||
Workflow getWorkflow() { result = workflow }
|
||||
@@ -130,15 +220,18 @@ module Actions {
|
||||
*/
|
||||
class Step extends YamlNode, YamlMapping {
|
||||
int index;
|
||||
Job job;
|
||||
StepsContainer parent;
|
||||
|
||||
Step() { this = job.getSteps().getElement(index) }
|
||||
Step() { this = parent.getSteps().getElement(index) }
|
||||
|
||||
/** Gets the 0-based position of this step within the sequence of `steps`. */
|
||||
int getIndex() { result = index }
|
||||
|
||||
/** Gets the job this step belongs to. */
|
||||
Job getJob() { result = job }
|
||||
/** Gets the `job` this step belongs to, if the step belongs to a `job` in a workflow. Has no result if the step belongs to `runs` in a custom composite action. */
|
||||
Job getJob() { result = parent }
|
||||
|
||||
/** Gets the `runs` this step belongs to, if the step belongs to a `runs` in a custom composite action. Has no result if the step belongs to a `job` in a workflow. */
|
||||
Runs getRuns() { result = parent }
|
||||
|
||||
/** Gets the value of the `uses` field in this step, if any. */
|
||||
Uses getUses() { result.getStep() = this }
|
||||
@@ -149,6 +242,9 @@ module Actions {
|
||||
/** Gets the value of the `if` field in this step, if any. */
|
||||
StepIf getIf() { result.getStep() = this }
|
||||
|
||||
/** Gets the value of the `env` field in this step, if any. */
|
||||
StepEnv getEnv() { result = this.lookup("env") }
|
||||
|
||||
/** Gets the ID of this step, if any. */
|
||||
string getId() { result = this.lookup("id").(YamlString).getValue() }
|
||||
}
|
||||
@@ -244,6 +340,25 @@ module Actions {
|
||||
With getWith() { result = with }
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `${{ e }}` is a GitHub Actions expression evaluated within this YAML string.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
|
||||
* Only finds simple expressions like `${{ github.event.comment.body }}`, where the expression contains only alphanumeric characters, underscores, dots, or dashes.
|
||||
* Does not identify more complicated expressions like `${{ fromJSON(env.time) }}`, or ${{ format('{{Hello {0}!}}', github.event.head_commit.author.name) }}
|
||||
*/
|
||||
string getASimpleReferenceExpression(YamlString node) {
|
||||
// We use `regexpFind` to obtain *all* matches of `${{...}}`,
|
||||
// not just the last (greedy match) or first (reluctant match).
|
||||
result =
|
||||
node.getValue()
|
||||
.regexpFind("\\$\\{\\{\\s*[A-Za-z0-9_\\[\\]\\*\\(\\)\\.\\-]+\\s*\\}\\}", _, _)
|
||||
.regexpCapture("\\$\\{\\{\\s*([A-Za-z0-9_\\[\\]\\*\\((\\)\\.\\-]+)\\s*\\}\\}", 1)
|
||||
}
|
||||
|
||||
/** Extracts the 'name' part from env.name */
|
||||
bindingset[name]
|
||||
string getEnvName(string name) { result = name.regexpCapture("env\\.([A-Za-z0-9_]+)", 1) }
|
||||
|
||||
/**
|
||||
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
|
||||
@@ -255,20 +370,5 @@ module Actions {
|
||||
|
||||
/** Gets the step that executes this `run` command. */
|
||||
Step getStep() { result = step }
|
||||
|
||||
/**
|
||||
* Holds if `${{ e }}` is a GitHub Actions expression evaluated within this `run` command.
|
||||
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
|
||||
* Only finds simple expressions like `${{ github.event.comment.body }}`, where the expression contains only alphanumeric characters, underscores, dots, or dashes.
|
||||
* Does not identify more complicated expressions like `${{ fromJSON(env.time) }}`, or ${{ format('{{Hello {0}!}}', github.event.head_commit.author.name) }}
|
||||
*/
|
||||
string getASimpleReferenceExpression() {
|
||||
// We use `regexpFind` to obtain *all* matches of `${{...}}`,
|
||||
// not just the last (greedy match) or first (reluctant match).
|
||||
result =
|
||||
this.getValue()
|
||||
.regexpFind("\\$\\{\\{\\s*[A-Za-z0-9_\\.\\-]+\\s*\\}\\}", _, _)
|
||||
.regexpCapture("\\$\\{\\{\\s*([A-Za-z0-9_\\.\\-]+)\\s*\\}\\}", 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,7 +507,7 @@ class DirectiveTargetName extends string {
|
||||
* `:` and `_` count as component delimiters.
|
||||
*/
|
||||
string getRawComponent(int i) {
|
||||
result = toLowerCase().regexpFind("(?<=^|[-:_])[a-zA-Z0-9]+(?=$|[-:_])", i, _)
|
||||
result = this.toLowerCase().regexpFind("(?<=^|[-:_])[a-zA-Z0-9]+(?=$|[-:_])", i, _)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var cp = require("child_process");
|
||||
|
||||
module.exports = function download(path, callback) {
|
||||
cp.exec("wget " + path, callback);
|
||||
cp.execSync("wget " + path, callback);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var cp = require("child_process");
|
||||
|
||||
module.exports = function download(path, callback) {
|
||||
cp.execFile("wget", [path], callback);
|
||||
cp.execFileSync("wget", [path], callback);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
</p>
|
||||
<p>
|
||||
Code injection in GitHub Actions may allow an attacker to
|
||||
exfiltrate the temporary GitHub repository authorization token.
|
||||
exfiltrate any secrets used in the workflow and
|
||||
the temporary GitHub repository authorization token.
|
||||
The token might have write access to the repository, allowing an attacker
|
||||
to use the token to make changes to the repository.
|
||||
</p>
|
||||
@@ -19,7 +20,8 @@
|
||||
<p>
|
||||
The best practice to avoid code injection vulnerabilities
|
||||
in GitHub workflows is to set the untrusted input value of the expression
|
||||
to an intermediate environment variable.
|
||||
to an intermediate environment variable and then use the environment variable
|
||||
using the native syntax of the shell/script interpreter (that is, not <i>${{ env.VAR }}</i>).
|
||||
</p>
|
||||
<p>
|
||||
It is also recommended to limit the permissions of any tokens used
|
||||
@@ -33,6 +35,12 @@
|
||||
</p>
|
||||
<sample src="examples/comment_issue_bad.yml" />
|
||||
|
||||
<p>
|
||||
The following example uses an environment variable, but
|
||||
<b>still allows the injection</b> because of the use of expression syntax:
|
||||
</p>
|
||||
<sample src="examples/comment_issue_bad_env.yml" />
|
||||
|
||||
<p>
|
||||
The following example uses shell syntax to read
|
||||
the environment variable and will prevent the attack:
|
||||
|
||||
@@ -15,6 +15,44 @@
|
||||
import javascript
|
||||
import semmle.javascript.Actions
|
||||
|
||||
/**
|
||||
* A `script:` field within an Actions `with:` specific to `actions/github-script` action.
|
||||
*
|
||||
* For example:
|
||||
* ```
|
||||
* uses: actions/github-script@v3
|
||||
* with:
|
||||
* script: console.log('${{ github.event.pull_request.head.sha }}')
|
||||
* ```
|
||||
*/
|
||||
class GitHubScript extends YamlNode, YamlString {
|
||||
GitHubScriptWith with;
|
||||
|
||||
GitHubScript() { with.lookup("script") = this }
|
||||
|
||||
/** Gets the `with` field this field belongs to. */
|
||||
GitHubScriptWith getWith() { result = with }
|
||||
}
|
||||
|
||||
/**
|
||||
* A step that uses `actions/github-script` action.
|
||||
*/
|
||||
class GitHubScriptStep extends Actions::Step {
|
||||
GitHubScriptStep() { this.getUses().getGitHubRepository() = "actions/github-script" }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `with:` field sibling to `uses: actions/github-script`.
|
||||
*/
|
||||
class GitHubScriptWith extends YamlNode, YamlMapping {
|
||||
GitHubScriptStep step;
|
||||
|
||||
GitHubScriptWith() { step.lookup("with") = this }
|
||||
|
||||
/** Gets the step this field belongs to. */
|
||||
GitHubScriptStep getStep() { result = step }
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledIssue(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*title\\b") or
|
||||
@@ -30,7 +68,10 @@ private predicate isExternalUserControlledPullRequest(string context) {
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*description\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*homepage\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b",
|
||||
"\\bgithub\\s*\\.\\s*head_ref\\b"
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
@@ -39,8 +80,7 @@ private predicate isExternalUserControlledPullRequest(string context) {
|
||||
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledReview(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review\\s*\\.\\s*body\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review_comment\\s*\\.\\s*body\\b")
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
@@ -51,7 +91,8 @@ private predicate isExternalUserControlledComment(string context) {
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledGollum(string context) {
|
||||
context
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*page_name\\b")
|
||||
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages\\[[0-9]+\\]\\s*\\.\\s*page_name\\b") or
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages\\[[0-9]+\\]\\s*\\.\\s*title\\b")
|
||||
}
|
||||
|
||||
bindingset[context]
|
||||
@@ -59,13 +100,16 @@ private predicate isExternalUserControlledCommit(string context) {
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*head_ref\\b"
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*committer\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*committer\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*author\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*author\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*committer\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits\\[[0-9]+\\]\\s*\\.\\s*committer\\s*\\.\\s*name\\b",
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
@@ -78,10 +122,117 @@ private predicate isExternalUserControlledDiscussion(string context) {
|
||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*body\\b")
|
||||
}
|
||||
|
||||
from Actions::Run run, string context, Actions::On on
|
||||
bindingset[context]
|
||||
private predicate isExternalUserControlledWorkflowRun(string context) {
|
||||
exists(string reg |
|
||||
reg =
|
||||
[
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_branch\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*display_title\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_repository\\b\\s*\\.\\s*description\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*message\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*author\\b\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*author\\b\\s*\\.\\s*name\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*committer\\b\\s*\\.\\s*email\\b",
|
||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*committer\\b\\s*\\.\\s*name\\b",
|
||||
]
|
||||
|
|
||||
context.regexpMatch(reg)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if environment name in the `injection` (in a form of `env.name`)
|
||||
* is tainted by the `context` (in a form of `github.event.xxx.xxx`).
|
||||
*/
|
||||
bindingset[injection]
|
||||
predicate isEnvInterpolationTainted(string injection, string context) {
|
||||
exists(Actions::Env env, string envName, YamlString envValue |
|
||||
envValue = env.lookup(envName) and
|
||||
Actions::getEnvName(injection) = envName and
|
||||
Actions::getASimpleReferenceExpression(envValue) = context
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `run` contains any expression interpolation `${{ e }}`.
|
||||
* Sets `context` to the initial untrusted value assignment in case of `${{ env... }}` interpolation
|
||||
*/
|
||||
predicate isRunInjectable(Actions::Run run, string injection, string context) {
|
||||
Actions::getASimpleReferenceExpression(run) = injection and
|
||||
(
|
||||
injection = context
|
||||
or
|
||||
isEnvInterpolationTainted(injection, context)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the `actions/github-script` contains any expression interpolation `${{ e }}`.
|
||||
* Sets `context` to the initial untrusted value assignment in case of `${{ env... }}` interpolation
|
||||
*/
|
||||
predicate isScriptInjectable(GitHubScript script, string injection, string context) {
|
||||
Actions::getASimpleReferenceExpression(script) = injection and
|
||||
(
|
||||
injection = context
|
||||
or
|
||||
isEnvInterpolationTainted(injection, context)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the composite action contains untrusted expression interpolation `${{ e }}`.
|
||||
*/
|
||||
YamlNode getInjectableCompositeActionNode(Actions::Runs runs, string injection, string context) {
|
||||
exists(Actions::Run run |
|
||||
isRunInjectable(run, injection, context) and
|
||||
result = run and
|
||||
run.getStep().getRuns() = runs
|
||||
)
|
||||
or
|
||||
exists(GitHubScript script |
|
||||
isScriptInjectable(script, injection, context) and
|
||||
result = script and
|
||||
script.getWith().getStep().getRuns() = runs
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if the workflow contains untrusted expression interpolation `${{ e }}`.
|
||||
*/
|
||||
YamlNode getInjectableWorkflowNode(Actions::On on, string injection, string context) {
|
||||
exists(Actions::Run run |
|
||||
isRunInjectable(run, injection, context) and
|
||||
result = run and
|
||||
run.getStep().getJob().getWorkflow().getOn() = on
|
||||
)
|
||||
or
|
||||
exists(GitHubScript script |
|
||||
isScriptInjectable(script, injection, context) and
|
||||
result = script and
|
||||
script.getWith().getStep().getJob().getWorkflow().getOn() = on
|
||||
)
|
||||
}
|
||||
|
||||
from YamlNode node, string injection, string context
|
||||
where
|
||||
run.getASimpleReferenceExpression() = context and
|
||||
run.getStep().getJob().getWorkflow().getOn() = on and
|
||||
exists(Actions::CompositeAction action, Actions::Runs runs |
|
||||
action.getRuns() = runs and
|
||||
node = getInjectableCompositeActionNode(runs, injection, context) and
|
||||
(
|
||||
isExternalUserControlledIssue(context) or
|
||||
isExternalUserControlledPullRequest(context) or
|
||||
isExternalUserControlledReview(context) or
|
||||
isExternalUserControlledComment(context) or
|
||||
isExternalUserControlledGollum(context) or
|
||||
isExternalUserControlledCommit(context) or
|
||||
isExternalUserControlledDiscussion(context) or
|
||||
isExternalUserControlledWorkflowRun(context)
|
||||
)
|
||||
)
|
||||
or
|
||||
exists(Actions::On on |
|
||||
node = getInjectableWorkflowNode(on, injection, context) and
|
||||
(
|
||||
exists(on.getNode("issues")) and
|
||||
isExternalUserControlledIssue(context)
|
||||
@@ -89,21 +240,31 @@ where
|
||||
exists(on.getNode("pull_request_target")) and
|
||||
isExternalUserControlledPullRequest(context)
|
||||
or
|
||||
(exists(on.getNode("pull_request_review_comment")) or exists(on.getNode("pull_request_review"))) and
|
||||
isExternalUserControlledReview(context)
|
||||
exists(on.getNode("pull_request_review")) and
|
||||
(isExternalUserControlledReview(context) or isExternalUserControlledPullRequest(context))
|
||||
or
|
||||
(exists(on.getNode("issue_comment")) or exists(on.getNode("pull_request_target"))) and
|
||||
isExternalUserControlledComment(context)
|
||||
exists(on.getNode("pull_request_review_comment")) and
|
||||
(isExternalUserControlledComment(context) or isExternalUserControlledPullRequest(context))
|
||||
or
|
||||
exists(on.getNode("issue_comment")) and
|
||||
(isExternalUserControlledComment(context) or isExternalUserControlledIssue(context))
|
||||
or
|
||||
exists(on.getNode("gollum")) and
|
||||
isExternalUserControlledGollum(context)
|
||||
or
|
||||
exists(on.getNode("pull_request_target")) and
|
||||
exists(on.getNode("push")) and
|
||||
isExternalUserControlledCommit(context)
|
||||
or
|
||||
(exists(on.getNode("discussion")) or exists(on.getNode("discussion_comment"))) and
|
||||
exists(on.getNode("discussion")) and
|
||||
isExternalUserControlledDiscussion(context)
|
||||
or
|
||||
exists(on.getNode("discussion_comment")) and
|
||||
(isExternalUserControlledDiscussion(context) or isExternalUserControlledComment(context))
|
||||
or
|
||||
exists(on.getNode("workflow_run")) and
|
||||
isExternalUserControlledWorkflowRun(context)
|
||||
)
|
||||
select run,
|
||||
"Potential injection from the " + context +
|
||||
" context, which may be controlled by an external user."
|
||||
)
|
||||
select node,
|
||||
"Potential injection from the ${{ " + injection +
|
||||
" }}, which may be controlled by an external user."
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
on: issue_comment
|
||||
|
||||
jobs:
|
||||
echo-body:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- env:
|
||||
BODY: ${{ github.event.issue.body }}
|
||||
run: |
|
||||
echo '${{ env.BODY }}'
|
||||
@@ -18,7 +18,7 @@
|
||||
<p>
|
||||
|
||||
This security check is, however, insufficient since an
|
||||
attacker can craft his cookie values to match those of any user. To
|
||||
attacker can craft their cookie values to match those of any user. To
|
||||
prevent this, the server can cryptographically sign the security
|
||||
critical cookie values:
|
||||
|
||||
|
||||
@@ -35,8 +35,8 @@
|
||||
<p>
|
||||
In the example below, the untrusted value <code>req.params.id</code> is used as the property name
|
||||
<code>req.session.todos[id]</code>. If a malicious user passes in the ID value <code>__proto__</code>,
|
||||
the variable <code>todo</code> will then refer to <code>Object.prototype</code>.
|
||||
Finally, the modification of <code>todo</code> then allows the attacker to inject arbitrary properties
|
||||
the variable <code>items</code> will then refer to <code>Object.prototype</code>.
|
||||
Finally, the modification of <code>items</code> then allows the attacker to inject arbitrary properties
|
||||
onto <code>Object.prototype</code>.
|
||||
</p>
|
||||
|
||||
@@ -49,6 +49,12 @@
|
||||
|
||||
<sample src="examples/PrototypePollutingAssignmentFixed.js"/>
|
||||
|
||||
<p>
|
||||
Another way to fix it is to prevent the <code>__proto__</code> property from being used as a key, as shown below:
|
||||
</p>
|
||||
|
||||
<sample src="examples/PrototypePollutingAssignmentFixed2.js"/>
|
||||
|
||||
</example>
|
||||
|
||||
<references>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
let express = require('express');
|
||||
let app = express()
|
||||
|
||||
app.put('/todos/:id', (req, res) => {
|
||||
let id = req.params.id;
|
||||
if (id === '__proto__' || id === 'constructor' || id === 'prototype') {
|
||||
res.end(403);
|
||||
return;
|
||||
}
|
||||
let items = req.session.todos[id];
|
||||
if (!items) {
|
||||
items = req.session.todos[id] = {};
|
||||
}
|
||||
items[req.query.name] = req.query.text;
|
||||
res.end(200);
|
||||
});
|
||||
@@ -17,7 +17,7 @@ import javascript
|
||||
import semmle.javascript.Actions
|
||||
|
||||
/**
|
||||
* An action step that doesn't contain `actor` or `label` check in `if:` or
|
||||
* An action step that doesn't contain `actor` check in `if:` or
|
||||
* the check requires manual analysis.
|
||||
*/
|
||||
class ProbableStep extends Actions::Step {
|
||||
@@ -29,25 +29,13 @@ class ProbableStep extends Actions::Step {
|
||||
// needs manual analysis if there is OR
|
||||
this.getIf().getValue().matches("%||%")
|
||||
or
|
||||
// labels can be assigned by owners only
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bcontains\\s*\\(\\s*github\\s*\\.\\s*event\\s*\\.\\s*(?:issue|pull_request)\\s*\\.\\s*labels\\b",
|
||||
_, _)
|
||||
) and
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*label\\s*\\.\\s*name\\s*==", _, _)
|
||||
) and
|
||||
// actor check means only the user is able to run it
|
||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An action job that doesn't contain `actor` or `label` check in `if:` or
|
||||
* An action job that doesn't contain `actor` check in `if:` or
|
||||
* the check requires manual analysis.
|
||||
*/
|
||||
class ProbableJob extends Actions::Job {
|
||||
@@ -59,46 +47,18 @@ class ProbableJob extends Actions::Job {
|
||||
// needs manual analysis if there is OR
|
||||
this.getIf().getValue().matches("%||%")
|
||||
or
|
||||
// labels can be assigned by owners only
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bcontains\\s*\\(\\s*github\\s*\\.\\s*event\\s*\\.\\s*(?:issue|pull_request)\\s*\\.\\s*labels\\b",
|
||||
_, _)
|
||||
) and
|
||||
not exists(
|
||||
this.getIf()
|
||||
.getValue()
|
||||
.regexpFind("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*label\\s*\\.\\s*name\\s*==", _, _)
|
||||
) and
|
||||
// actor check means only the user is able to run it
|
||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An action step that doesn't contain `actor` or `label` check in `if:` or
|
||||
* The `on: pull_request_target`.
|
||||
*/
|
||||
class ProbablePullRequestTarget extends Actions::On, YamlMappingLikeNode {
|
||||
ProbablePullRequestTarget() {
|
||||
exists(YamlNode prtNode |
|
||||
// The `on:` is triggered on `pull_request_target`
|
||||
this.getNode("pull_request_target") = prtNode and
|
||||
(
|
||||
// and either doesn't contain `types` filter
|
||||
not exists(prtNode.getAChild())
|
||||
or
|
||||
// or has the filter, that is something else than just [labeled]
|
||||
exists(YamlMappingLikeNode prt, YamlMappingLikeNode types |
|
||||
types = prt.getNode("types") and
|
||||
prtNode = prt and
|
||||
(
|
||||
not types.getElementCount() = 1 or
|
||||
not exists(types.getNode("labeled"))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
exists(this.getNode("pull_request_target"))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,3 +11,4 @@ dependencies:
|
||||
codeql/suite-helpers: ${workspace}
|
||||
codeql/typos: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
| .github/workflows/pull_request_target_if_job.yml:9:7:12:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_job.yml:16:7:19:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_job.yml:30:7:33:2 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_job.yml:36:7:38:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:9:7:14:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:14:7:19:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:24:7:29:4 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_if_step.yml:29:7:31:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_label_only.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_label_only_mapping.yml:11:7:13:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_labels_mapping.yml:13:7:15:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_labels_sequence.yml:10:7:12:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
| .github/workflows/pull_request_target_mapping.yml:8:7:10:54 | uses: a ... kout@v2 | Potential unsafe checkout of untrusted pull request on 'pull_request_target'. |
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
console.log('test')
|
||||
@@ -7,3 +7,4 @@ extractor: javascript
|
||||
tests: .
|
||||
dataExtensions:
|
||||
- library-tests/DataExtensions/*.model.yml
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -10,5 +10,19 @@ jobs:
|
||||
echo-chamber2:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: |
|
||||
echo '${{ github.event.comment.body }}'
|
||||
- run: echo '${{ github.event.comment.body }}'
|
||||
- run: echo '${{ github.event.issue.body }}'
|
||||
- run: echo '${{ github.event.issue.title }}'
|
||||
|
||||
echo-chamber3:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
script: console.log('${{ github.event.comment.body }}')
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
script: console.log('${{ github.event.issue.body }}')
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
script: console.log('${{ github.event.issue.title }}')
|
||||
@@ -0,0 +1,8 @@
|
||||
on: discussion
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.discussion.title }}'
|
||||
- run: echo '${{ github.event.discussion.body }}'
|
||||
@@ -0,0 +1,9 @@
|
||||
on: discussion_comment
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.discussion.title }}'
|
||||
- run: echo '${{ github.event.discussion.body }}'
|
||||
- run: echo '${{ github.event.comment.body }}'
|
||||
11
javascript/ql/test/query-tests/Security/CWE-094/ExpressionInjection/.github/workflows/gollum.yml
vendored
Normal file
11
javascript/ql/test/query-tests/Security/CWE-094/ExpressionInjection/.github/workflows/gollum.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
on: gollum
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.pages[1].title }}'
|
||||
- run: echo '${{ github.event.pages[11].title }}'
|
||||
- run: echo '${{ github.event.pages[0].page_name }}'
|
||||
- run: echo '${{ github.event.pages[2222].page_name }}'
|
||||
- run: echo '${{ toJSON(github.event.pages.*.title) }}' # safe
|
||||
@@ -0,0 +1,20 @@
|
||||
on: issues
|
||||
|
||||
env:
|
||||
global_env: ${{ github.event.issue.title }}
|
||||
test: test
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
env:
|
||||
job_env: ${{ github.event.issue.title }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.issue.title }}'
|
||||
- run: echo '${{ github.event.issue.body }}'
|
||||
- run: echo '${{ env.global_env }}'
|
||||
- run: echo '${{ env.test }}'
|
||||
- run: echo '${{ env.job_env }}'
|
||||
- run: echo '${{ env.step_env }}'
|
||||
env:
|
||||
step_env: ${{ github.event.issue.title }}
|
||||
@@ -0,0 +1,14 @@
|
||||
on: pull_request_review
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.pull_request.title }}'
|
||||
- run: echo '${{ github.event.pull_request.body }}'
|
||||
- run: echo '${{ github.event.pull_request.head.label }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.default_branch }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.description }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.homepage }}'
|
||||
- run: echo '${{ github.event.pull_request.head.ref }}'
|
||||
- run: echo '${{ github.event.review.body }}'
|
||||
@@ -0,0 +1,14 @@
|
||||
on: pull_request_review_comment
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.pull_request.title }}'
|
||||
- run: echo '${{ github.event.pull_request.body }}'
|
||||
- run: echo '${{ github.event.pull_request.head.label }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.default_branch }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.description }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.homepage }}'
|
||||
- run: echo '${{ github.event.pull_request.head.ref }}'
|
||||
- run: echo '${{ github.event.comment.body }}'
|
||||
@@ -0,0 +1,16 @@
|
||||
on: pull_request_target
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.issue.title }}' # not defined
|
||||
- run: echo '${{ github.event.issue.body }}' # not defined
|
||||
- run: echo '${{ github.event.pull_request.title }}'
|
||||
- run: echo '${{ github.event.pull_request.body }}'
|
||||
- run: echo '${{ github.event.pull_request.head.label }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.default_branch }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.description }}'
|
||||
- run: echo '${{ github.event.pull_request.head.repo.homepage }}'
|
||||
- run: echo '${{ github.event.pull_request.head.ref }}'
|
||||
- run: echo '${{ github.head_ref }}'
|
||||
16
javascript/ql/test/query-tests/Security/CWE-094/ExpressionInjection/.github/workflows/push.yml
vendored
Normal file
16
javascript/ql/test/query-tests/Security/CWE-094/ExpressionInjection/.github/workflows/push.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.commits[11].message }}'
|
||||
- run: echo '${{ github.event.commits[11].author.email }}'
|
||||
- run: echo '${{ github.event.commits[11].author.name }}'
|
||||
- run: echo '${{ github.event.head_commit.message }}'
|
||||
- run: echo '${{ github.event.head_commit.author.email }}'
|
||||
- run: echo '${{ github.event.head_commit.author.name }}'
|
||||
- run: echo '${{ github.event.head_commit.committer.email }}'
|
||||
- run: echo '${{ github.event.head_commit.committer.name }}'
|
||||
- run: echo '${{ github.event.commits[11].committer.email }}'
|
||||
- run: echo '${{ github.event.commits[11].committer.name }}'
|
||||
@@ -0,0 +1,16 @@
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: [test]
|
||||
|
||||
jobs:
|
||||
echo-chamber:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo '${{ github.event.workflow_run.display_title }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_commit.message }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_commit.author.email }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_commit.author.name }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_commit.committer.email }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_commit.committer.name }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_branch }}'
|
||||
- run: echo '${{ github.event.workflow_run.head_repository.description }}'
|
||||
@@ -1,3 +1,65 @@
|
||||
| .github/workflows/comment_issue.yml:7:12:8:48 | \| | Potential injection from the github.event.comment.body context, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:13:12:14:47 | \| | Potential injection from the github.event.comment.body context, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue_newline.yml:9:14:10:50 | \| | Potential injection from the github.event.comment.body context, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:7:12:8:48 | \| | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:13:12:13:50 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:14:12:14:48 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.issue.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:15:12:15:49 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.issue.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:22:17:22:63 | console ... dy }}') | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:25:17:25:61 | console ... dy }}') | Potential injection from the ${{ github.event.issue.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue.yml:28:17:28:62 | console ... le }}') | Potential injection from the ${{ github.event.issue.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/comment_issue_newline.yml:9:14:10:50 | \| | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/discussion.yml:7:12:7:54 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.discussion.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/discussion.yml:8:12:8:53 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.discussion.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/discussion_comment.yml:7:12:7:54 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.discussion.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/discussion_comment.yml:8:12:8:53 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.discussion.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/discussion_comment.yml:9:12:9:50 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/gollum.yml:7:12:7:52 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.pages[1].title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/gollum.yml:8:12:8:53 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.pages[11].title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/gollum.yml:9:12:9:56 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.pages[0].page_name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/gollum.yml:10:12:10:59 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.pages[2222].page_name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/issues.yaml:13:12:13:49 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.issue.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/issues.yaml:14:12:14:48 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.issue.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/issues.yaml:15:12:15:39 | echo '$ ... env }}' | Potential injection from the ${{ env.global_env }}, which may be controlled by an external user. |
|
||||
| .github/workflows/issues.yaml:17:12:17:36 | echo '$ ... env }}' | Potential injection from the ${{ env.job_env }}, which may be controlled by an external user. |
|
||||
| .github/workflows/issues.yaml:18:12:18:37 | echo '$ ... env }}' | Potential injection from the ${{ env.step_env }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:7:12:7:56 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.pull_request.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:8:12:8:55 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.pull_request.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:9:12:9:61 | echo '$ ... bel }}' | Potential injection from the ${{ github.event.pull_request.head.label }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:10:12:10:75 | echo '$ ... nch }}' | Potential injection from the ${{ github.event.pull_request.head.repo.default_branch }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:11:12:11:72 | echo '$ ... ion }}' | Potential injection from the ${{ github.event.pull_request.head.repo.description }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:12:12:12:69 | echo '$ ... age }}' | Potential injection from the ${{ github.event.pull_request.head.repo.homepage }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:13:12:13:59 | echo '$ ... ref }}' | Potential injection from the ${{ github.event.pull_request.head.ref }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review.yml:14:12:14:49 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.review.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:7:12:7:56 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.pull_request.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:8:12:8:55 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.pull_request.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:9:12:9:61 | echo '$ ... bel }}' | Potential injection from the ${{ github.event.pull_request.head.label }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:10:12:10:75 | echo '$ ... nch }}' | Potential injection from the ${{ github.event.pull_request.head.repo.default_branch }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:11:12:11:72 | echo '$ ... ion }}' | Potential injection from the ${{ github.event.pull_request.head.repo.description }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:12:12:12:69 | echo '$ ... age }}' | Potential injection from the ${{ github.event.pull_request.head.repo.homepage }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:13:12:13:59 | echo '$ ... ref }}' | Potential injection from the ${{ github.event.pull_request.head.ref }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_review_comment.yml:14:12:14:50 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:9:12:9:56 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.pull_request.title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:10:12:10:55 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.pull_request.body }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:11:12:11:61 | echo '$ ... bel }}' | Potential injection from the ${{ github.event.pull_request.head.label }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:12:12:12:75 | echo '$ ... nch }}' | Potential injection from the ${{ github.event.pull_request.head.repo.default_branch }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:13:12:13:72 | echo '$ ... ion }}' | Potential injection from the ${{ github.event.pull_request.head.repo.description }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:14:12:14:69 | echo '$ ... age }}' | Potential injection from the ${{ github.event.pull_request.head.repo.homepage }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:15:12:15:59 | echo '$ ... ref }}' | Potential injection from the ${{ github.event.pull_request.head.ref }}, which may be controlled by an external user. |
|
||||
| .github/workflows/pull_request_target.yml:16:12:16:40 | echo '$ ... ref }}' | Potential injection from the ${{ github.head_ref }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:7:12:7:57 | echo '$ ... age }}' | Potential injection from the ${{ github.event.commits[11].message }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:8:12:8:62 | echo '$ ... ail }}' | Potential injection from the ${{ github.event.commits[11].author.email }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:9:12:9:61 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.commits[11].author.name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:10:12:10:57 | echo '$ ... age }}' | Potential injection from the ${{ github.event.head_commit.message }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:11:12:11:62 | echo '$ ... ail }}' | Potential injection from the ${{ github.event.head_commit.author.email }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:12:12:12:61 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.head_commit.author.name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:13:12:13:65 | echo '$ ... ail }}' | Potential injection from the ${{ github.event.head_commit.committer.email }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:14:12:14:64 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.head_commit.committer.name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:15:12:15:65 | echo '$ ... ail }}' | Potential injection from the ${{ github.event.commits[11].committer.email }}, which may be controlled by an external user. |
|
||||
| .github/workflows/push.yml:16:12:16:64 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.commits[11].committer.name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:9:12:9:64 | echo '$ ... tle }}' | Potential injection from the ${{ github.event.workflow_run.display_title }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:10:12:10:70 | echo '$ ... age }}' | Potential injection from the ${{ github.event.workflow_run.head_commit.message }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:11:12:11:75 | echo '$ ... ail }}' | Potential injection from the ${{ github.event.workflow_run.head_commit.author.email }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:12:12:12:74 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.workflow_run.head_commit.author.name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:13:12:13:78 | echo '$ ... ail }}' | Potential injection from the ${{ github.event.workflow_run.head_commit.committer.email }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:14:12:14:77 | echo '$ ... ame }}' | Potential injection from the ${{ github.event.workflow_run.head_commit.committer.name }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:15:12:15:62 | echo '$ ... nch }}' | Potential injection from the ${{ github.event.workflow_run.head_branch }}, which may be controlled by an external user. |
|
||||
| .github/workflows/workflow_run.yml:16:12:16:78 | echo '$ ... ion }}' | Potential injection from the ${{ github.event.workflow_run.head_repository.description }}, which may be controlled by an external user. |
|
||||
| action1/action.yml:14:12:14:50 | echo '$ ... ody }}' | Potential injection from the ${{ github.event.comment.body }}, which may be controlled by an external user. |
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
name: 'test'
|
||||
description: 'test'
|
||||
branding:
|
||||
icon: 'test'
|
||||
color: 'test'
|
||||
inputs:
|
||||
test:
|
||||
description: test
|
||||
required: false
|
||||
default: 'test'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: echo '${{ github.event.comment.body }}'
|
||||
@@ -0,0 +1,17 @@
|
||||
name: 'Hello World'
|
||||
description: 'Greet someone and record the time'
|
||||
inputs:
|
||||
who-to-greet: # id of input
|
||||
description: 'Who to greet'
|
||||
required: true
|
||||
default: 'World'
|
||||
outputs:
|
||||
time: # id of output
|
||||
description: 'The time we greeted you'
|
||||
runs:
|
||||
using: 'docker'
|
||||
steps: # this is actually invalid, used to test we correctly identify composite actions
|
||||
- run: echo '${{ github.event.comment.body }}'
|
||||
image: 'Dockerfile'
|
||||
args:
|
||||
- ${{ inputs.who-to-greet }}
|
||||
@@ -1 +0,0 @@
|
||||
console.log('test')
|
||||
@@ -34,7 +34,7 @@ class RamlResource extends YamlMapping {
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RamlMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
result = this.lookup(verb)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import javascript
|
||||
|
||||
/** A RAML specification. */
|
||||
class RamlSpec extends YamlDocument, YamlMapping {
|
||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
||||
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||
}
|
||||
|
||||
from RamlSpec s
|
||||
|
||||
@@ -4,13 +4,13 @@ string httpVerb() { result = ["get", "put", "post", "delete"] }
|
||||
|
||||
/** A RAML specification. */
|
||||
class RamlSpec extends YamlDocument, YamlMapping {
|
||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
||||
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||
}
|
||||
|
||||
/** A RAML resource specification. */
|
||||
class RamlResource extends YamlMapping {
|
||||
RamlResource() {
|
||||
getDocument() instanceof RamlSpec and
|
||||
this.getDocument() instanceof RamlSpec and
|
||||
exists(YamlMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
@@ -30,14 +30,14 @@ class RamlResource extends YamlMapping {
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RamlMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
result = this.lookup(verb)
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML method specification. */
|
||||
class RamlMethod extends YamlValue {
|
||||
RamlMethod() {
|
||||
getDocument() instanceof RamlSpec and
|
||||
this.getDocument() instanceof RamlSpec and
|
||||
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,13 @@ string httpVerb() { result = ["get", "put", "post", "delete"] }
|
||||
|
||||
/** A RAML specification. */
|
||||
class RamlSpec extends YamlDocument, YamlMapping {
|
||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
||||
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||
}
|
||||
|
||||
/** A RAML resource specification. */
|
||||
class RamlResource extends YamlMapping {
|
||||
RamlResource() {
|
||||
getDocument() instanceof RamlSpec and
|
||||
this.getDocument() instanceof RamlSpec and
|
||||
exists(YamlMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
@@ -30,13 +30,13 @@ class RamlResource extends YamlMapping {
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RamlMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
result = this.lookup(verb)
|
||||
}
|
||||
}
|
||||
|
||||
class RamlMethod extends YamlValue {
|
||||
RamlMethod() {
|
||||
getDocument() instanceof RamlSpec and
|
||||
this.getDocument() instanceof RamlSpec and
|
||||
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,13 @@ string httpVerb() { result = ["get", "put", "post", "delete"] }
|
||||
|
||||
/** A RAML specification. */
|
||||
class RamlSpec extends YamlDocument, YamlMapping {
|
||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
||||
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||
}
|
||||
|
||||
/** A RAML resource specification. */
|
||||
class RamlResource extends YamlMapping {
|
||||
RamlResource() {
|
||||
getDocument() instanceof RamlSpec and
|
||||
this.getDocument() instanceof RamlSpec and
|
||||
exists(YamlMapping m, string name |
|
||||
this = m.lookup(name) and
|
||||
name.matches("/%")
|
||||
@@ -30,14 +30,14 @@ class RamlResource extends YamlMapping {
|
||||
/** Get the method for this resource with the given verb. */
|
||||
RamlMethod getMethod(string verb) {
|
||||
verb = httpVerb() and
|
||||
result = lookup(verb)
|
||||
result = this.lookup(verb)
|
||||
}
|
||||
}
|
||||
|
||||
/** A RAML method specification. */
|
||||
class RamlMethod extends YamlValue {
|
||||
RamlMethod() {
|
||||
getDocument() instanceof RamlSpec and
|
||||
this.getDocument() instanceof RamlSpec and
|
||||
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,27 @@ module SummaryComponent {
|
||||
/** Gets a summary component that represents a list element. */
|
||||
SummaryComponent listElement() { result = content(any(ListElementContent c)) }
|
||||
|
||||
/** Gets a summary component that represents a set element. */
|
||||
SummaryComponent setElement() { result = content(any(SetElementContent c)) }
|
||||
|
||||
/** Gets a summary component that represents a tuple element. */
|
||||
SummaryComponent tupleElement(int index) {
|
||||
exists(TupleElementContent c | c.getIndex() = index and result = content(c))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents a dictionary element. */
|
||||
SummaryComponent dictionaryElement(string key) {
|
||||
exists(DictionaryElementContent c | c.getKey() = key and result = content(c))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents a dictionary element at any key. */
|
||||
SummaryComponent dictionaryElementAny() { result = content(any(DictionaryElementAnyContent c)) }
|
||||
|
||||
/** Gets a summary component that represents an attribute element. */
|
||||
SummaryComponent attribute(string attr) {
|
||||
exists(AttributeContent c | c.getAttribute() = attr and result = content(c))
|
||||
}
|
||||
|
||||
/** Gets a summary component that represents the return value of a call. */
|
||||
SummaryComponent return() { result = SC::return(any(ReturnKind rk)) }
|
||||
}
|
||||
|
||||
@@ -105,6 +105,27 @@ predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string proven
|
||||
SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||
c = "ListElement" and
|
||||
result = FlowSummary::SummaryComponent::listElement()
|
||||
or
|
||||
c = "SetElement" and
|
||||
result = FlowSummary::SummaryComponent::setElement()
|
||||
or
|
||||
exists(int index |
|
||||
c.getAnArgument("TupleElement") = index.toString() and
|
||||
result = FlowSummary::SummaryComponent::tupleElement(index)
|
||||
)
|
||||
or
|
||||
exists(string key |
|
||||
c.getAnArgument("DictionaryElement") = key and
|
||||
result = FlowSummary::SummaryComponent::dictionaryElement(key)
|
||||
)
|
||||
or
|
||||
c = "DictionaryElementAny" and
|
||||
result = FlowSummary::SummaryComponent::dictionaryElementAny()
|
||||
or
|
||||
exists(string attr |
|
||||
c.getAnArgument("Attribute") = attr and
|
||||
result = FlowSummary::SummaryComponent::attribute(attr)
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets the textual representation of a summary component in the format used for flow summaries. */
|
||||
|
||||
@@ -613,8 +613,17 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
* also flow to `sink`.
|
||||
*/
|
||||
TypeTracker getACompatibleTypeTracker() {
|
||||
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
|
||||
hasCall = false or this.hasReturn() = false
|
||||
exists(boolean hasCall, OptionalTypeTrackerContent c |
|
||||
result = MkTypeTracker(hasCall, c) and
|
||||
(
|
||||
compatibleContents(c, content)
|
||||
or
|
||||
content = noContent() and c = content
|
||||
)
|
||||
|
|
||||
hasCall = false
|
||||
or
|
||||
this.hasReturn() = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
357
python/ql/test/experimental/dataflow/coverage/test_builtins.py
Normal file
357
python/ql/test/experimental/dataflow/coverage/test_builtins.py
Normal file
@@ -0,0 +1,357 @@
|
||||
# This tests some of the common built-in functions and methods.
|
||||
# We need a decent model of data flow through these in order to
|
||||
# analyse most programs.
|
||||
#
|
||||
# All functions starting with "test_" should run and execute `print("OK")` exactly once.
|
||||
# This can be checked by running validTest.py.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname((__file__))))
|
||||
from testlib import expects
|
||||
|
||||
# These are defined so that we can evaluate the test code.
|
||||
NONSOURCE = "not a source"
|
||||
SOURCE = "source"
|
||||
|
||||
def is_source(x):
|
||||
return x == "source" or x == b"source" or x == 42 or x == 42.0 or x == 42j
|
||||
|
||||
def SINK(x):
|
||||
if is_source(x):
|
||||
print("OK")
|
||||
else:
|
||||
print("Unexpected flow", x)
|
||||
|
||||
def SINK_F(x):
|
||||
if is_source(x):
|
||||
print("Unexpected flow", x)
|
||||
else:
|
||||
print("OK")
|
||||
|
||||
|
||||
# Actual tests
|
||||
|
||||
## Container constructors
|
||||
|
||||
### List
|
||||
|
||||
@expects(2)
|
||||
def test_list_from_list():
|
||||
l1 = [SOURCE, NONSOURCE]
|
||||
l2 = list(l1)
|
||||
SINK(l2[0]) #$ MISSING: flow="SOURCE, l:-2 -> l2[0]"
|
||||
SINK_F(l2[1]) # expecting FP due to imprecise flow
|
||||
|
||||
# -- skip list_from_string
|
||||
|
||||
@expects(2)
|
||||
def test_list_from_tuple():
|
||||
t = (SOURCE, NONSOURCE)
|
||||
l = list(t)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
SINK_F(l[1]) # expecting FP due to imprecise flow
|
||||
|
||||
def test_list_from_set():
|
||||
s = {SOURCE}
|
||||
l = list(s)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
|
||||
@expects(2)
|
||||
def test_list_from_dict():
|
||||
d = {SOURCE: 'v', NONSOURCE: 'v2'}
|
||||
l = list(d)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
SINK_F(l[1]) # expecting FP due to imprecise flow
|
||||
|
||||
### Tuple
|
||||
|
||||
@expects(2)
|
||||
def test_tuple_from_list():
|
||||
l = [SOURCE, NONSOURCE]
|
||||
t = tuple(l)
|
||||
SINK(t[0]) #$ MISSING: flow="SOURCE, l:-2 -> t[0]"
|
||||
SINK_F(t[1])
|
||||
|
||||
@expects(2)
|
||||
def test_tuple_from_tuple():
|
||||
t0 = (SOURCE, NONSOURCE)
|
||||
t = tuple(t0)
|
||||
SINK(t[0]) #$ MISSING: flow="SOURCE, l:-2 -> t[0]"
|
||||
SINK_F(t[1])
|
||||
|
||||
def test_tuple_from_set():
|
||||
s = {SOURCE}
|
||||
t = tuple(s)
|
||||
SINK(t[0]) #$ MISSING: flow="SOURCE, l:-2 -> t[0]"
|
||||
|
||||
@expects(2)
|
||||
def test_tuple_from_dict():
|
||||
d = {SOURCE: "v1", NONSOURCE: "v2"}
|
||||
t = tuple(d)
|
||||
SINK(t[0]) #$ MISSING: flow="SOURCE, l:-2 -> t[0]"
|
||||
SINK_F(t[1])
|
||||
|
||||
|
||||
### Set
|
||||
|
||||
def test_set_from_list():
|
||||
l = [SOURCE]
|
||||
s = set(l)
|
||||
v = s.pop()
|
||||
SINK(v) #$ MISSING: flow="SOURCE, l:-3 -> v"
|
||||
|
||||
def test_set_from_tuple():
|
||||
t = (SOURCE,)
|
||||
s = set(t)
|
||||
v = s.pop()
|
||||
SINK(v) #$ MISSING: flow="SOURCE, l:-3 -> v"
|
||||
|
||||
def test_set_from_set():
|
||||
s0 = {SOURCE}
|
||||
s = set(s0)
|
||||
v = s.pop()
|
||||
SINK(v) #$ MISSING: flow="SOURCE, l:-3 -> v"
|
||||
|
||||
def test_set_from_dict():
|
||||
d = {SOURCE: "val"}
|
||||
s = set(d)
|
||||
v = s.pop()
|
||||
SINK(v) #$ MISSING: flow="SOURCE, l:-3 -> v"
|
||||
|
||||
|
||||
### Dict
|
||||
|
||||
@expects(2)
|
||||
def test_dict_from_keyword():
|
||||
d = dict(k = SOURCE, k1 = NONSOURCE)
|
||||
SINK(d["k"]) #$ MISSING: flow="SOURCE, l:-1 -> d[k]"
|
||||
SINK_F(d["k1"])
|
||||
|
||||
@expects(2)
|
||||
def test_dict_from_list():
|
||||
d = dict([("k", SOURCE), ("k1", NONSOURCE)])
|
||||
SINK(d["k"]) #$ MISSING: flow="SOURCE, l:-1 -> d[k]"
|
||||
SINK_F(d["k1"])
|
||||
|
||||
@expects(2)
|
||||
def test_dict_from_dict():
|
||||
d1 = {'k': SOURCE, 'k1': NONSOURCE}
|
||||
d2 = dict(d1)
|
||||
SINK(d2["k"]) #$ MISSING: flow="SOURCE, l:-2 -> d[k]"
|
||||
SINK_F(d2["k1"])
|
||||
|
||||
## Container methods
|
||||
|
||||
### List
|
||||
|
||||
def test_list_pop():
|
||||
l = [SOURCE]
|
||||
v = l.pop()
|
||||
SINK(v) #$ flow="SOURCE, l:-2 -> v"
|
||||
|
||||
def test_list_pop_index():
|
||||
l = [SOURCE]
|
||||
v = l.pop(0)
|
||||
SINK(v) #$ MISSING: flow="SOURCE, l:-2 -> v"
|
||||
|
||||
def test_list_pop_index_imprecise():
|
||||
l = [SOURCE, NONSOURCE]
|
||||
v = l.pop(1)
|
||||
SINK_F(v)
|
||||
|
||||
@expects(2)
|
||||
def test_list_copy():
|
||||
l0 = [SOURCE, NONSOURCE]
|
||||
l = l0.copy()
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
SINK_F(l[1])
|
||||
|
||||
def test_list_append():
|
||||
l = [NONSOURCE]
|
||||
l.append(SOURCE)
|
||||
SINK(l[1]) #$ MISSING: flow="SOURCE, l:-1 -> l[1]"
|
||||
|
||||
### Set
|
||||
|
||||
def test_set_pop():
|
||||
s = {SOURCE}
|
||||
v = s.pop()
|
||||
SINK(v) #$ flow="SOURCE, l:-2 -> v"
|
||||
|
||||
def test_set_copy():
|
||||
s0 = {SOURCE}
|
||||
s = s0.copy()
|
||||
SINK(s.pop()) #$ MISSING: flow="SOURCE, l:-2 -> s.pop()"
|
||||
|
||||
def test_set_add():
|
||||
s = set([])
|
||||
s.add(SOURCE)
|
||||
SINK(s.pop()) #$ MISSING: flow="SOURCE, l:-2 -> s.pop()"
|
||||
|
||||
### Dict
|
||||
|
||||
def test_dict_keys():
|
||||
d = {SOURCE: "value"}
|
||||
keys = d.keys()
|
||||
key_list = list(keys)
|
||||
SINK(key_list[0]) #$ MISSING: flow="SOURCE, l:-3 -> key_list[0]"
|
||||
|
||||
def test_dict_values():
|
||||
d = {'k': SOURCE}
|
||||
vals = d.values()
|
||||
val_list = list(vals)
|
||||
SINK(val_list[0]) #$ MISSING: flow="SOURCE, l:-3 -> val_list[0]"
|
||||
|
||||
@expects(4)
|
||||
def test_dict_items():
|
||||
d = {'k': SOURCE, SOURCE: "value"}
|
||||
items = d.items()
|
||||
item_list = list(items)
|
||||
SINK_F(item_list[0][0]) # expecting FP due to imprecise flow
|
||||
SINK(item_list[0][1]) #$ MISSING: flow="SOURCE, l:-4 -> item_list[0][1]"
|
||||
SINK(item_list[1][0]) #$ MISSING: flow="SOURCE, l:-5 -> item_list[1][0]"
|
||||
SINK_F(item_list[1][1]) # expecting FP due to imprecise flow
|
||||
|
||||
@expects(3)
|
||||
def test_dict_pop():
|
||||
d = {'k': SOURCE}
|
||||
v = d.pop("k")
|
||||
SINK(v) #$ flow="SOURCE, l:-2 -> v"
|
||||
v1 = d.pop("k", NONSOURCE)
|
||||
SINK_F(v1) #$ SPURIOUS: flow="SOURCE, l:-4 -> v1"
|
||||
v2 = d.pop("non-existing", SOURCE)
|
||||
SINK(v2) #$ MISSING: flow="SOURCE, l:-1 -> v2"
|
||||
|
||||
@expects(2)
|
||||
def test_dict_get():
|
||||
d = {'k': SOURCE}
|
||||
v = d.get("k")
|
||||
SINK(v) #$ flow="SOURCE, l:-2 -> v"
|
||||
v1 = d.get("non-existing", SOURCE)
|
||||
SINK(v1) #$ MISSING: flow="SOURCE, l:-1 -> v1"
|
||||
|
||||
@expects(2)
|
||||
def test_dict_popitem():
|
||||
d = {'k': SOURCE}
|
||||
t = d.popitem() # could be any pair (before 3.7), but we only have one
|
||||
SINK_F(t[0])
|
||||
SINK(t[1]) #$ MISSING: flow="SOURCE, l:-3 -> t[1]"
|
||||
|
||||
@expects(2)
|
||||
def test_dict_copy():
|
||||
d = {'k': SOURCE, 'k1': NONSOURCE}
|
||||
d1 = d.copy()
|
||||
SINK(d1["k"]) #$ MISSING: flow="SOURCE, l:-2 -> d[k]"
|
||||
SINK_F(d1["k1"])
|
||||
|
||||
|
||||
## Functions on containers
|
||||
|
||||
### sorted
|
||||
|
||||
def test_sorted_list():
|
||||
l0 = [SOURCE]
|
||||
l = sorted(l0)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
|
||||
def test_sorted_tuple():
|
||||
t = (SOURCE,)
|
||||
l = sorted(t)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
|
||||
def test_sorted_set():
|
||||
s = {SOURCE}
|
||||
l = sorted(s)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
|
||||
def test_sorted_dict():
|
||||
d = {SOURCE: "val"}
|
||||
l = sorted(d)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-2 -> l[0]"
|
||||
|
||||
### reversed
|
||||
|
||||
@expects(2)
|
||||
def test_reversed_list():
|
||||
l0 = [SOURCE, NONSOURCE]
|
||||
r = reversed(l0)
|
||||
l = list(r)
|
||||
SINK_F(l[0])
|
||||
SINK(l[1]) #$ MISSING: flow="SOURCE, l:-4 -> l[1]"
|
||||
|
||||
@expects(2)
|
||||
def test_reversed_tuple():
|
||||
t = (SOURCE, NONSOURCE)
|
||||
r = reversed(t)
|
||||
l = list(r)
|
||||
SINK_F(l[0])
|
||||
SINK(l[1]) #$ MISSING: flow="SOURCE, l:-4 -> l[1]"
|
||||
|
||||
@expects(2)
|
||||
def test_reversed_dict():
|
||||
d = {SOURCE: "v1", NONSOURCE: "v2"}
|
||||
r = reversed(d)
|
||||
l = list(r)
|
||||
SINK_F(l[0])
|
||||
SINK(l[1]) #$ MISSING: flow="SOURCE, l:-4 -> l[1]"
|
||||
|
||||
### iter
|
||||
|
||||
def test_iter_list():
|
||||
l0 = [SOURCE]
|
||||
i = iter(l0)
|
||||
l = list(i)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-3 -> l[0]"
|
||||
|
||||
def test_iter_tuple():
|
||||
t = (SOURCE,)
|
||||
i = iter(t)
|
||||
l = list(i)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-3 -> l[0]"
|
||||
|
||||
def test_iter_set():
|
||||
t = {SOURCE}
|
||||
i = iter(t)
|
||||
l = list(i)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-3 -> l[0]"
|
||||
|
||||
def test_iter_dict():
|
||||
d = {SOURCE: "val"}
|
||||
i = iter(d)
|
||||
l = list(i)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-3 -> l[0]"
|
||||
|
||||
def test_iter_iter():
|
||||
# applying iter() to the result of iter() is basically a no-op
|
||||
l0 = [SOURCE]
|
||||
i = iter(iter(l0))
|
||||
l = list(i)
|
||||
SINK(l[0]) #$ MISSING: flow="SOURCE, l:-3 -> l[0]"
|
||||
|
||||
### next
|
||||
|
||||
def test_next_list():
|
||||
l = [SOURCE]
|
||||
i = iter(l)
|
||||
n = next(i)
|
||||
SINK(n) #$ MISSING: flow="SOURCE, l:-3 -> n"
|
||||
|
||||
def test_next_tuple():
|
||||
t = (SOURCE,)
|
||||
i = iter(t)
|
||||
n = next(i)
|
||||
SINK(n) #$ MISSING: flow="SOURCE, l:-3 -> n"
|
||||
|
||||
def test_next_set():
|
||||
s = {SOURCE}
|
||||
i = iter(s)
|
||||
n = next(i)
|
||||
SINK(n) #$ MISSING: flow="SOURCE, l:-3 -> n"
|
||||
|
||||
def test_next_dict():
|
||||
d = {SOURCE: "val"}
|
||||
i = iter(d)
|
||||
n = next(i)
|
||||
SINK(n) #$ MISSING: flow="SOURCE, l:-3 -> n"
|
||||
@@ -25,13 +25,17 @@ uniqueParameterNodePosition
|
||||
uniqueContentApprox
|
||||
identityLocalStep
|
||||
| test_async.py:48:9:48:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:56:10:56:21 | ControlFlowNode for tainted_list | Node steps to itself |
|
||||
| test_collections.py:63:9:63:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:65:9:65:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:79:9:79:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:81:9:81:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:64:10:64:21 | ControlFlowNode for tainted_list | Node steps to itself |
|
||||
| test_collections.py:71:9:71:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:73:9:73:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:88:10:88:21 | ControlFlowNode for tainted_list | Node steps to itself |
|
||||
| test_collections.py:89:10:89:23 | ControlFlowNode for TAINTED_STRING | Node steps to itself |
|
||||
| test_collections.py:97:9:97:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:99:9:99:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:112:9:112:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:114:9:114:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:116:9:116:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:213:9:213:15 | ControlFlowNode for my_dict | Node steps to itself |
|
||||
| test_collections.py:213:22:213:33 | ControlFlowNode for tainted_dict | Node steps to itself |
|
||||
| test_collections.py:147:9:147:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:149:9:149:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
| test_collections.py:246:9:246:15 | ControlFlowNode for my_dict | Node steps to itself |
|
||||
| test_collections.py:246:22:246:33 | ControlFlowNode for tainted_dict | Node steps to itself |
|
||||
| test_for.py:24:9:24:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||
|
||||
@@ -37,6 +37,14 @@ def test_construction():
|
||||
tuple(tainted_list), # $ tainted
|
||||
set(tainted_list), # $ tainted
|
||||
frozenset(tainted_list), # $ tainted
|
||||
dict(tainted_dict), # $ tainted
|
||||
dict(k = tainted_string)["k"], # $ MISSING: tainted
|
||||
dict(dict(k = tainted_string))["k"], # $ MISSING: tainted
|
||||
dict(["k", tainted_string]), # $ tainted
|
||||
)
|
||||
|
||||
ensure_not_tainted(
|
||||
dict(k = tainted_string)["k1"]
|
||||
)
|
||||
|
||||
|
||||
@@ -64,6 +72,31 @@ def test_access(x, y, z):
|
||||
for i in reversed(tainted_list):
|
||||
ensure_tainted(i) # $ tainted
|
||||
|
||||
def test_access_explicit(x, y, z):
|
||||
tainted_list = [TAINTED_STRING]
|
||||
|
||||
ensure_tainted(
|
||||
tainted_list[0], # $ tainted
|
||||
tainted_list[x], # $ tainted
|
||||
tainted_list[y:z], # $ tainted
|
||||
|
||||
sorted(tainted_list)[0], # $ tainted
|
||||
reversed(tainted_list)[0], # $ tainted
|
||||
iter(tainted_list), # $ tainted
|
||||
next(iter(tainted_list)), # $ tainted
|
||||
[i for i in tainted_list], # $ tainted
|
||||
[tainted_list for i in [1,2,3]], # $ MISSING: tainted
|
||||
[TAINTED_STRING for i in [1,2,3]], # $ tainted
|
||||
[tainted_list], # $ tainted
|
||||
)
|
||||
|
||||
a, b, c = tainted_list[0:3]
|
||||
ensure_tainted(a, b, c) # $ tainted
|
||||
|
||||
for h in tainted_list:
|
||||
ensure_tainted(h) # $ tainted
|
||||
for i in reversed(tainted_list):
|
||||
ensure_tainted(i) # $ tainted
|
||||
|
||||
def test_dict_access(x):
|
||||
tainted_dict = TAINTED_DICT
|
||||
|
||||
@@ -64,6 +64,7 @@ if __name__ == "__main__":
|
||||
check_tests_valid("coverage.test")
|
||||
check_tests_valid("coverage.argumentPassing")
|
||||
check_tests_valid("coverage.datamodel")
|
||||
check_tests_valid("coverage.test_builtins")
|
||||
check_tests_valid("coverage-py2.classes")
|
||||
check_tests_valid("coverage-py3.classes")
|
||||
check_tests_valid("variable-capture.in")
|
||||
|
||||
@@ -1,48 +1,21 @@
|
||||
use clap::Args;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use clap::Args;
|
||||
|
||||
use codeql_extractor::autobuilder;
|
||||
|
||||
#[derive(Args)]
|
||||
// The autobuilder takes no command-line options, but this may change in the future.
|
||||
pub struct Options {}
|
||||
|
||||
pub fn run(_: Options) -> std::io::Result<()> {
|
||||
let dist = env::var("CODEQL_DIST").expect("CODEQL_DIST not set");
|
||||
let db = env::var("CODEQL_EXTRACTOR_QL_WIP_DATABASE")
|
||||
let database = env::var("CODEQL_EXTRACTOR_QL_WIP_DATABASE")
|
||||
.expect("CODEQL_EXTRACTOR_QL_WIP_DATABASE not set");
|
||||
let codeql = if env::consts::OS == "windows" {
|
||||
"codeql.exe"
|
||||
} else {
|
||||
"codeql"
|
||||
};
|
||||
let codeql: PathBuf = [&dist, codeql].iter().collect();
|
||||
let mut cmd = Command::new(codeql);
|
||||
cmd.arg("database")
|
||||
.arg("index-files")
|
||||
.arg("--include-extension=.ql")
|
||||
.arg("--include-extension=.qll")
|
||||
.arg("--include-extension=.dbscheme")
|
||||
.arg("--include-extension=.json")
|
||||
.arg("--include-extension=.jsonc")
|
||||
.arg("--include-extension=.jsonl")
|
||||
.arg("--include=**/qlpack.yml")
|
||||
.arg("--include=deprecated.blame")
|
||||
.arg("--size-limit=10m")
|
||||
.arg("--language=ql")
|
||||
.arg("--working-dir=.")
|
||||
.arg(db);
|
||||
|
||||
for line in env::var("LGTM_INDEX_FILTERS")
|
||||
.unwrap_or_default()
|
||||
.split('\n')
|
||||
{
|
||||
if let Some(stripped) = line.strip_prefix("include:") {
|
||||
cmd.arg("--also-match=".to_owned() + stripped);
|
||||
} else if let Some(stripped) = line.strip_prefix("exclude:") {
|
||||
cmd.arg("--exclude=".to_owned() + stripped);
|
||||
}
|
||||
}
|
||||
let exit = &cmd.spawn()?.wait()?;
|
||||
std::process::exit(exit.code().unwrap_or(1))
|
||||
autobuilder::Autobuilder::new("ql", PathBuf::from(database))
|
||||
.include_extensions(&[".ql", ".qll", ".dbscheme", ".json", ".jsonc", ".jsonl"])
|
||||
.include_globs(&["**/qlpack.yml", "deprecated.blame"])
|
||||
.size_limit("10m")
|
||||
.run()
|
||||
}
|
||||
|
||||
@@ -25,13 +25,13 @@ class Location extends @location {
|
||||
int getEndColumn() { locations_default(this, _, _, _, _, result) }
|
||||
|
||||
/** Gets the number of lines covered by this location. */
|
||||
int getNumLines() { result = getEndLine() - getStartLine() + 1 }
|
||||
int getNumLines() { result = this.getEndLine() - this.getStartLine() + 1 }
|
||||
|
||||
/** Gets a textual representation of this element. */
|
||||
cached
|
||||
string toString() {
|
||||
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
||||
hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
|
||||
this.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
|
||||
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class DisjunctionChain extends Disjunction {
|
||||
Formula getOperand(int i) {
|
||||
result =
|
||||
rank[i + 1](Formula operand, Location l |
|
||||
operand = getAnOperand*() and
|
||||
operand = this.getAnOperand*() and
|
||||
not operand instanceof Disjunction and
|
||||
l = operand.getLocation()
|
||||
|
|
||||
@@ -33,16 +33,16 @@ class DisjunctionChain extends Disjunction {
|
||||
*/
|
||||
class EqualsLiteral extends ComparisonFormula {
|
||||
EqualsLiteral() {
|
||||
getOperator() = "=" and
|
||||
getAnOperand() instanceof Literal
|
||||
this.getOperator() = "=" and
|
||||
this.getAnOperand() instanceof Literal
|
||||
}
|
||||
|
||||
AstNode getOther() {
|
||||
result = getAnOperand() and
|
||||
result = this.getAnOperand() and
|
||||
not result instanceof Literal
|
||||
}
|
||||
|
||||
Literal getLiteral() { result = getAnOperand() }
|
||||
Literal getLiteral() { result = this.getAnOperand() }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,29 +60,33 @@ class DisjunctionEqualsLiteral extends DisjunctionChain {
|
||||
DisjunctionEqualsLiteral() {
|
||||
// VarAccess on the same variable
|
||||
exists(VarDef v |
|
||||
forex(Formula f | f = getOperand(_) |
|
||||
forex(Formula f | f = this.getOperand(_) |
|
||||
f.(EqualsLiteral).getAnOperand().(VarAccess).getDeclaration() = v
|
||||
) and
|
||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand() and
|
||||
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand() and
|
||||
firstOperand.(VarAccess).getDeclaration() = v
|
||||
)
|
||||
or
|
||||
// FieldAccess on the same variable
|
||||
exists(FieldDecl v |
|
||||
forex(Formula f | f = getOperand(_) |
|
||||
forex(Formula f | f = this.getOperand(_) |
|
||||
f.(EqualsLiteral).getAnOperand().(FieldAccess).getDeclaration() = v
|
||||
) and
|
||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand() and
|
||||
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand() and
|
||||
firstOperand.(FieldAccess).getDeclaration() = v
|
||||
)
|
||||
or
|
||||
// ThisAccess
|
||||
forex(Formula f | f = getOperand(_) | f.(EqualsLiteral).getAnOperand() instanceof ThisAccess) and
|
||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand().(ThisAccess)
|
||||
forex(Formula f | f = this.getOperand(_) |
|
||||
f.(EqualsLiteral).getAnOperand() instanceof ThisAccess
|
||||
) and
|
||||
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand().(ThisAccess)
|
||||
or
|
||||
// ResultAccess
|
||||
forex(Formula f | f = getOperand(_) | f.(EqualsLiteral).getAnOperand() instanceof ResultAccess) and
|
||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand().(ResultAccess)
|
||||
forex(Formula f | f = this.getOperand(_) |
|
||||
f.(EqualsLiteral).getAnOperand() instanceof ResultAccess
|
||||
) and
|
||||
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand().(ResultAccess)
|
||||
// (in principle something like GlobalValueNumbering could be used to generalize this)
|
||||
}
|
||||
|
||||
@@ -100,8 +104,8 @@ class DisjunctionEqualsLiteral extends DisjunctionChain {
|
||||
*/
|
||||
class CallLiteral extends Call {
|
||||
CallLiteral() {
|
||||
getNumberOfArguments() = 1 and
|
||||
getArgument(0) instanceof Literal
|
||||
this.getNumberOfArguments() = 1 and
|
||||
this.getArgument(0) instanceof Literal
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +122,7 @@ class DisjunctionPredicateLiteral extends DisjunctionChain {
|
||||
DisjunctionPredicateLiteral() {
|
||||
// Call to the same target
|
||||
exists(PredicateOrBuiltin target |
|
||||
forex(Formula f | f = getOperand(_) | f.(CallLiteral).getTarget() = target)
|
||||
forex(Formula f | f = this.getOperand(_) | f.(CallLiteral).getTarget() = target)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,4 @@ extractor: ql
|
||||
dependencies:
|
||||
codeql/typos: ${workspace}
|
||||
codeql/util: ${workspace}
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -7,7 +7,7 @@ query predicate test() { foo() }
|
||||
class Foo extends AstNode {
|
||||
predicate bar() { none() }
|
||||
|
||||
predicate baz() { bar() }
|
||||
predicate baz() { this.bar() }
|
||||
}
|
||||
|
||||
class Sub extends Foo {
|
||||
|
||||
@@ -5,7 +5,7 @@ getTarget
|
||||
| Bar.qll:30:12:30:32 | MemberCall | Bar.qll:19:7:19:18 | ClassPredicate getParameter |
|
||||
| Baz.qll:8:18:8:44 | MemberCall | Baz.qll:4:10:4:24 | ClassPredicate getImportedPath |
|
||||
| Foo.qll:5:26:5:30 | PredicateCall | Foo.qll:3:11:3:13 | ClasslessPredicate foo |
|
||||
| Foo.qll:10:21:10:25 | PredicateCall | Foo.qll:8:13:8:15 | ClassPredicate bar |
|
||||
| Foo.qll:10:21:10:30 | MemberCall | Foo.qll:8:13:8:15 | ClassPredicate bar |
|
||||
| Foo.qll:14:34:14:44 | MemberCall | Foo.qll:10:13:10:15 | ClassPredicate baz |
|
||||
| Foo.qll:17:27:17:42 | MemberCall | Foo.qll:8:13:8:15 | ClassPredicate bar |
|
||||
| Foo.qll:29:5:29:16 | PredicateCall | Foo.qll:20:13:20:20 | ClasslessPredicate myThing2 |
|
||||
|
||||
@@ -1,45 +1,22 @@
|
||||
use clap::Args;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::process::Command;
|
||||
|
||||
use clap::Args;
|
||||
|
||||
use codeql_extractor::autobuilder;
|
||||
|
||||
#[derive(Args)]
|
||||
// The autobuilder takes no command-line options, but this may change in the future.
|
||||
pub struct Options {}
|
||||
|
||||
pub fn run(_: Options) -> std::io::Result<()> {
|
||||
let dist = env::var("CODEQL_DIST").expect("CODEQL_DIST not set");
|
||||
let db = env::var("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE")
|
||||
let database = env::var("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE")
|
||||
.expect("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE not set");
|
||||
let codeql = if env::consts::OS == "windows" {
|
||||
"codeql.exe"
|
||||
} else {
|
||||
"codeql"
|
||||
};
|
||||
let codeql: PathBuf = [&dist, codeql].iter().collect();
|
||||
let mut cmd = Command::new(codeql);
|
||||
cmd.arg("database")
|
||||
.arg("index-files")
|
||||
.arg("--include-extension=.rb")
|
||||
.arg("--include-extension=.erb")
|
||||
.arg("--include-extension=.gemspec")
|
||||
.arg("--include=**/Gemfile")
|
||||
.arg("--exclude=**/.git")
|
||||
.arg("--size-limit=5m")
|
||||
.arg("--language=ruby")
|
||||
.arg("--working-dir=.")
|
||||
.arg(db);
|
||||
|
||||
for line in env::var("LGTM_INDEX_FILTERS")
|
||||
.unwrap_or_default()
|
||||
.split('\n')
|
||||
{
|
||||
if let Some(stripped) = line.strip_prefix("include:") {
|
||||
cmd.arg("--also-match=".to_owned() + stripped);
|
||||
} else if let Some(stripped) = line.strip_prefix("exclude:") {
|
||||
cmd.arg("--exclude=".to_owned() + stripped);
|
||||
}
|
||||
}
|
||||
let exit = &cmd.spawn()?.wait()?;
|
||||
std::process::exit(exit.code().unwrap_or(1))
|
||||
autobuilder::Autobuilder::new("ruby", PathBuf::from(database))
|
||||
.include_extensions(&[".rb", ".erb", ".gemspec"])
|
||||
.include_globs(&["**/Gemfile"])
|
||||
.exclude_globs(&["**/.git"])
|
||||
.size_limit("5m")
|
||||
.run()
|
||||
}
|
||||
|
||||
@@ -936,10 +936,10 @@ module ExprNodes {
|
||||
}
|
||||
|
||||
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
||||
class StringLiteralCfgNode extends ExprCfgNode {
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
class StringLiteralCfgNode extends StringlikeLiteralCfgNode {
|
||||
StringLiteralCfgNode() { e instanceof StringLiteral }
|
||||
|
||||
override StringLiteral e;
|
||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||
|
||||
final override StringLiteral getExpr() { result = super.getExpr() }
|
||||
}
|
||||
|
||||
@@ -112,6 +112,13 @@ private module Cached {
|
||||
)
|
||||
}
|
||||
|
||||
cached
|
||||
predicate summaryThroughStepTaint(
|
||||
DataFlow::Node arg, DataFlow::Node out, FlowSummaryImpl::Public::SummarizedCallable sc
|
||||
) {
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(arg, out, sc)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
|
||||
* (intra-procedural) step.
|
||||
@@ -122,7 +129,7 @@ private module Cached {
|
||||
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
|
||||
// Simple flow through library code is included in the exposed local
|
||||
// step relation, even though flow is technically inter-procedural
|
||||
FlowSummaryImpl::Private::Steps::summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
summaryThroughStepTaint(nodeFrom, nodeTo, _)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ private import codeql.ruby.typetracking.TypeTracker
|
||||
private import codeql.ruby.ApiGraphs
|
||||
private import codeql.ruby.Concepts
|
||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
||||
private import codeql.ruby.dataflow.internal.TaintTrackingPrivate as TaintTrackingPrivate
|
||||
private import codeql.ruby.TaintTracking
|
||||
private import codeql.ruby.frameworks.core.String
|
||||
|
||||
@@ -37,43 +38,6 @@ DataFlow::LocalSourceNode strStart() {
|
||||
/** Gets a dataflow node for a regular expression literal. */
|
||||
DataFlow::LocalSourceNode regStart() { result.asExpr().getExpr() instanceof Ast::RegExpLiteral }
|
||||
|
||||
/**
|
||||
* Holds if the analysis should track flow from `nodeFrom` to `nodeTo` on top of the ordinary type-tracking steps.
|
||||
* `nodeFrom` and `nodeTo` has type `fromType` and `toType` respectively.
|
||||
* The types are either "string" or "regexp".
|
||||
*/
|
||||
predicate step(
|
||||
DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo, string fromType, string toType
|
||||
) {
|
||||
fromType = toType and
|
||||
fromType = "string" and
|
||||
(
|
||||
// include taint flow through `String` summaries
|
||||
TaintTracking::localTaintStep(nodeFrom, nodeTo) and
|
||||
nodeFrom.(DataFlowPrivate::SummaryNode).getSummarizedCallable() instanceof
|
||||
String::SummarizedCallable
|
||||
or
|
||||
// string concatenations, and
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
|
||||
)
|
||||
or
|
||||
// string interpolations
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
)
|
||||
or
|
||||
fromType = "string" and
|
||||
toType = "reg" and
|
||||
exists(DataFlow::CallNode call |
|
||||
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
|
||||
nodeFrom = call.getArgument(0) and
|
||||
nodeTo = call
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node where string values that flow to the node are interpreted as regular expressions. */
|
||||
DataFlow::Node stringSink() {
|
||||
result instanceof RE::RegExpInterpretation::Range and
|
||||
@@ -91,28 +55,139 @@ DataFlow::Node stringSink() {
|
||||
/** Gets a node where regular expressions that flow to the node are used. */
|
||||
DataFlow::Node regSink() { result = any(RegexExecution exec).getRegex() }
|
||||
|
||||
/** Gets a node that is reachable by type-tracking from any string or regular expression. */
|
||||
DataFlow::LocalSourceNode forward(TypeTracker t) {
|
||||
t.start() and
|
||||
result = [strStart(), regStart()]
|
||||
or
|
||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
||||
or
|
||||
exists(TypeTracker t2 | t2 = t.continue() | step(forward(t2).getALocalUse(), result, _, _))
|
||||
private signature module TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start);
|
||||
|
||||
predicate end(DataFlow::Node n);
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is backwards reachable from any regular expression use,
|
||||
* where that use is reachable by type-tracking from any string or regular expression.
|
||||
* Provides a version of type tracking where we first prune for reachable nodes,
|
||||
* before doing the type tracking computation.
|
||||
*/
|
||||
DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
private module TypeTrack<TypeTrackInputSig Input> {
|
||||
private predicate additionalStep(
|
||||
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
Input::additionalStep(nodeFrom.getALocalUse(), nodeTo)
|
||||
}
|
||||
|
||||
/** Gets a node that is forwards reachable by type-tracking. */
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode forward(TypeTracker t) {
|
||||
result = Input::start(t, _)
|
||||
or
|
||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
||||
or
|
||||
exists(TypeTracker t2 | t2 = t.continue() | additionalStep(forward(t2), result))
|
||||
}
|
||||
|
||||
bindingset[result, tbt]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private DataFlow::LocalSourceNode forwardLateInline(TypeBackTracker tbt) {
|
||||
exists(TypeTracker tt |
|
||||
result = forward(tt) and
|
||||
tt = tbt.getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/** Gets a node that is backwards reachable by type-tracking. */
|
||||
pragma[nomagic]
|
||||
private DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
result = forwardLateInline(t) and
|
||||
(
|
||||
t.start() and
|
||||
result.flowsTo([stringSink(), regSink()]) and
|
||||
result = forward(TypeTracker::end())
|
||||
Input::end(result.getALocalUse())
|
||||
or
|
||||
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
||||
or
|
||||
exists(TypeBackTracker t2 | t2 = t.continue() | step(result.getALocalUse(), backwards(t2), _, _))
|
||||
exists(TypeBackTracker t2 | t2 = t.continue() | additionalStep(result, backwards(t2)))
|
||||
)
|
||||
}
|
||||
|
||||
bindingset[result, tt]
|
||||
pragma[inline_late]
|
||||
pragma[noopt]
|
||||
private DataFlow::LocalSourceNode backwardsInlineLate(TypeTracker tt) {
|
||||
exists(TypeBackTracker tbt |
|
||||
result = backwards(tbt) and
|
||||
tt = tbt.getACompatibleTypeTracker()
|
||||
)
|
||||
}
|
||||
|
||||
/** Holds if `n` is forwards and backwards reachable with type tracker `t`. */
|
||||
pragma[nomagic]
|
||||
private predicate reached(DataFlow::LocalSourceNode n, TypeTracker t) {
|
||||
n = forward(t) and
|
||||
n = backwardsInlineLate(t)
|
||||
}
|
||||
|
||||
pragma[nomagic]
|
||||
private TypeTracker stepReached(
|
||||
TypeTracker t, DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||
) {
|
||||
exists(StepSummary summary |
|
||||
StepSummary::step(nodeFrom, nodeTo, summary) and
|
||||
reached(nodeFrom, t) and
|
||||
reached(nodeTo, result) and
|
||||
result = t.append(summary)
|
||||
)
|
||||
or
|
||||
additionalStep(nodeFrom, nodeTo) and
|
||||
reached(nodeFrom, pragma[only_bind_into](t)) and
|
||||
reached(nodeTo, pragma[only_bind_into](t)) and
|
||||
result = t.continue()
|
||||
}
|
||||
|
||||
/** Gets a node that has been tracked from the start node `start`. */
|
||||
DataFlow::LocalSourceNode track(DataFlow::Node start, TypeTracker t) {
|
||||
t.start() and
|
||||
result = Input::start(t, start) and
|
||||
reached(result, t)
|
||||
or
|
||||
exists(TypeTracker t2 | t = stepReached(t2, track(start, t2), result))
|
||||
}
|
||||
}
|
||||
|
||||
/** Holds if `inputStr` is compiled to a regular expression that is returned at `call`. */
|
||||
pragma[nomagic]
|
||||
private predicate regFromString(DataFlow::LocalSourceNode inputStr, DataFlow::CallNode call) {
|
||||
exists(DataFlow::Node mid |
|
||||
inputStr.flowsTo(mid) and
|
||||
call = API::getTopLevelMember("Regexp").getAMethodCall(["compile", "new"]) and
|
||||
mid = call.getArgument(0)
|
||||
)
|
||||
}
|
||||
|
||||
private module StringTypeTrackInput implements TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
|
||||
start = strStart() and t.start() and result = start
|
||||
}
|
||||
|
||||
predicate end(DataFlow::Node n) {
|
||||
n = stringSink() or
|
||||
regFromString(n, _)
|
||||
}
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) {
|
||||
// include taint flow through `String` summaries
|
||||
TaintTrackingPrivate::summaryThroughStepTaint(nodeFrom, nodeTo,
|
||||
any(String::SummarizedCallable c))
|
||||
or
|
||||
// string concatenations, and
|
||||
exists(CfgNodes::ExprNodes::OperationCfgNode op |
|
||||
op = nodeTo.asExpr() and
|
||||
op.getAnOperand() = nodeFrom.asExpr() and
|
||||
op.getExpr().(Ast::BinaryOperation).getOperator() = "+"
|
||||
)
|
||||
or
|
||||
// string interpolations
|
||||
nodeFrom.asExpr() =
|
||||
nodeTo.asExpr().(CfgNodes::ExprNodes::StringlikeLiteralCfgNode).getAComponent()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,41 +195,34 @@ DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
||||
* This is used to figure out where `start` is evaluated as a regular expression against an input string,
|
||||
* or where `start` is compiled into a regular expression.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackStrings(DataFlow::Node start, TypeTracker t) {
|
||||
result = backwards(_) and
|
||||
(
|
||||
private predicate trackStrings = TypeTrack<StringTypeTrackInput>::track/2;
|
||||
|
||||
/** Holds if `strConst` flows to a regex compilation (tracked by `t`), where the resulting regular expression is stored in `reg`. */
|
||||
pragma[nomagic]
|
||||
private predicate regFromStringStart(DataFlow::Node strConst, TypeTracker t, DataFlow::CallNode reg) {
|
||||
regFromString(trackStrings(strConst, t), reg) and
|
||||
exists(t.continue())
|
||||
}
|
||||
|
||||
private module RegTypeTrackInput implements TypeTrackInputSig {
|
||||
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start) {
|
||||
start = regStart() and
|
||||
t.start() and
|
||||
start = result and
|
||||
result = strStart()
|
||||
result = start
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackStrings(start, t2).track(t2, t))
|
||||
or
|
||||
// an additional step from string to string
|
||||
exists(TypeTracker t2 | t2 = t.continue() |
|
||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "string")
|
||||
)
|
||||
)
|
||||
regFromStringStart(start, t, result)
|
||||
}
|
||||
|
||||
predicate end(DataFlow::Node n) { n = regSink() }
|
||||
|
||||
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) { none() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that has been tracked from the regular expression `start` to some node.
|
||||
* This is used to figure out where `start` is executed against an input string.
|
||||
*/
|
||||
private DataFlow::LocalSourceNode trackRegs(DataFlow::Node start, TypeTracker t) {
|
||||
result = backwards(_) and
|
||||
(
|
||||
t.start() and
|
||||
start = result and
|
||||
result = regStart()
|
||||
or
|
||||
exists(TypeTracker t2 | result = trackRegs(start, t2).track(t2, t))
|
||||
or
|
||||
// an additional step where a string is converted to a regular expression
|
||||
exists(TypeTracker t2 | t2 = t.continue() |
|
||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "reg")
|
||||
)
|
||||
)
|
||||
}
|
||||
private predicate trackRegs = TypeTrack<RegTypeTrackInput>::track/2;
|
||||
|
||||
/** Gets a node that references a regular expression. */
|
||||
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {
|
||||
|
||||
@@ -613,8 +613,17 @@ class TypeBackTracker extends TTypeBackTracker {
|
||||
* also flow to `sink`.
|
||||
*/
|
||||
TypeTracker getACompatibleTypeTracker() {
|
||||
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
|
||||
hasCall = false or this.hasReturn() = false
|
||||
exists(boolean hasCall, OptionalTypeTrackerContent c |
|
||||
result = MkTypeTracker(hasCall, c) and
|
||||
(
|
||||
compatibleContents(c, content)
|
||||
or
|
||||
content = noContent() and c = content
|
||||
)
|
||||
|
|
||||
hasCall = false
|
||||
or
|
||||
this.hasReturn() = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,3 +3,4 @@ version: 0.0.13-dev
|
||||
groups: shared
|
||||
library: true
|
||||
dependencies:
|
||||
warnOnImplicitThis: true
|
||||
|
||||
@@ -2,3 +2,4 @@ name: codeql/ssa
|
||||
version: 0.0.17-dev
|
||||
groups: shared
|
||||
library: true
|
||||
warnOnImplicitThis: true
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user