mirror of
https://github.com/github/codeql.git
synced 2025-12-22 11:46:32 +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 isBarrier2(DataFlow::Node node, FlowState2 state) { none() }
|
||||||
|
|
||||||
|
predicate isBarrierOut2(DataFlow::Node node) {
|
||||||
|
node = any(DataFlow::SsaPhiNode phi).getAnInput(true)
|
||||||
|
}
|
||||||
|
|
||||||
predicate isAdditionalFlowStep1(
|
predicate isAdditionalFlowStep1(
|
||||||
DataFlow::Node node1, FlowState1 state1, DataFlow::Node node2, FlowState1 state2
|
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: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: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: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
|
nodes
|
||||||
| test.cpp:16:11:16:21 | mk_string_t indirection [string] | semmle.label | mk_string_t indirection [string] |
|
| 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 | ... = ... |
|
| 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: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: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: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
|
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] |
|
| 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
|
#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: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: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: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: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 |
|
| 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()) {
|
while(unknown()) {
|
||||||
++size;
|
++size;
|
||||||
}
|
}
|
||||||
memset(buffer, 0, size); // BAD
|
memset(buffer, 0, size); // BAD [NOT DETECTED]
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_string(string_t* p_str, char* buffer) {
|
void set_string(string_t* p_str, char* buffer) {
|
||||||
@@ -248,4 +248,12 @@ void* my_alloc(unsigned size);
|
|||||||
void foo(unsigned size) {
|
void foo(unsigned size) {
|
||||||
int* p = (int*)my_alloc(size); // BAD
|
int* p = (int*)my_alloc(size); // BAD
|
||||||
memset(p, 0, size + 1);
|
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: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:291:14:291:15 | xs |
|
||||||
| test.cpp:290:13:290:24 | new[] | test.cpp:292:30:292:30 | x |
|
| 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
|
#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: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 |
|
| 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: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: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: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 | ... + ... | ... + ... |
|
||||||
|
|||||||
@@ -293,4 +293,18 @@ void test20(unsigned len)
|
|||||||
{
|
{
|
||||||
*x = 0; // GOOD
|
*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: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: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: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
|
viableImplInCallContextTooLarge
|
||||||
uniqueParameterNodeAtPosition
|
uniqueParameterNodeAtPosition
|
||||||
uniqueParameterNodePosition
|
uniqueParameterNodePosition
|
||||||
|
|||||||
@@ -690,3 +690,16 @@ void test_static_local_9() {
|
|||||||
s = 0;
|
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;
|
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 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 |
|
exists(ControlFlow::BasicBlock bb, int i |
|
||||||
SsaImpl::lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
|
SsaImpl::lastRefBeforeRedefExt(def, bb, i, next.getDefinitionExt()) and
|
||||||
def.definesAt(_, bb, i, _) 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) |
|
exists(CIL::BasicBlock bb, int i | CilSsaImpl::lastRefBeforeRedefExt(def, bb, i, next) |
|
||||||
def.definesAt(_, bb, i, _) and
|
def.definesAt(_, bb, i, _) and
|
||||||
def = nodeFrom.(CilSsaDefinitionExtNode).getDefinition()
|
def = nodeFrom.(CilSsaDefinitionExtNode).getDefinition() and
|
||||||
|
def != next
|
||||||
or
|
or
|
||||||
nodeFrom = TCilExprNode(bb.getNode(i).(CIL::ReadAccess))
|
nodeFrom = TCilExprNode(bb.getNode(i).(CIL::ReadAccess))
|
||||||
)
|
)
|
||||||
@@ -440,7 +442,8 @@ module LocalFlow {
|
|||||||
exists(CIL::ReadAccess readFrom, CIL::ReadAccess readTo |
|
exists(CIL::ReadAccess readFrom, CIL::ReadAccess readTo |
|
||||||
CilSsaImpl::hasAdjacentReadsExt(def, readFrom, readTo) and
|
CilSsaImpl::hasAdjacentReadsExt(def, readFrom, readTo) and
|
||||||
nodeTo = TCilExprNode(readTo) and
|
nodeTo = TCilExprNode(readTo) and
|
||||||
nodeFrom = TCilExprNode(readFrom)
|
nodeFrom = TCilExprNode(readFrom) and
|
||||||
|
nodeFrom != nodeTo
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
// Flow into phi (read) node
|
// Flow into phi (read) node
|
||||||
@@ -483,7 +486,8 @@ module LocalFlow {
|
|||||||
or
|
or
|
||||||
hasNodePath(any(LocalExprStepConfiguration x), nodeFrom, nodeTo)
|
hasNodePath(any(LocalExprStepConfiguration x), nodeFrom, nodeTo)
|
||||||
or
|
or
|
||||||
ThisFlow::adjacentThisRefs(nodeFrom, nodeTo)
|
ThisFlow::adjacentThisRefs(nodeFrom, nodeTo) and
|
||||||
|
nodeFrom != nodeTo
|
||||||
or
|
or
|
||||||
ThisFlow::adjacentThisRefs(nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
|
ThisFlow::adjacentThisRefs(nodeFrom.(PostUpdateNode).getPreUpdateNode(), nodeTo)
|
||||||
or
|
or
|
||||||
@@ -541,7 +545,8 @@ predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo) {
|
|||||||
exists(SsaImpl::DefinitionExt def |
|
exists(SsaImpl::DefinitionExt def |
|
||||||
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
|
LocalFlow::localSsaFlowStepUseUse(def, nodeFrom, nodeTo) and
|
||||||
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _) and
|
not FlowSummaryImpl::Private::Steps::prohibitsUseUseFlow(nodeFrom, _) and
|
||||||
not LocalFlow::usesInstanceField(def)
|
not LocalFlow::usesInstanceField(def) and
|
||||||
|
nodeFrom != nodeTo
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
// Flow into phi (read)/uncertain SSA definition node from read
|
// Flow into phi (read)/uncertain SSA definition node from read
|
||||||
@@ -880,7 +885,8 @@ private module Cached {
|
|||||||
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
predicate localFlowStepImpl(Node nodeFrom, Node nodeTo) {
|
||||||
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
LocalFlow::localFlowStepCommon(nodeFrom, nodeTo)
|
||||||
or
|
or
|
||||||
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo)
|
LocalFlow::localSsaFlowStepUseUse(_, nodeFrom, nodeTo) and
|
||||||
|
nodeFrom != nodeTo
|
||||||
or
|
or
|
||||||
exists(SsaImpl::DefinitionExt def |
|
exists(SsaImpl::DefinitionExt def |
|
||||||
LocalFlow::localSsaFlowStep(def, nodeFrom, nodeTo) and
|
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)*
|
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
|
qualId ::= simpleId | qualId "." simpleId
|
||||||
|
|
||||||
importModuleId ::= qualId
|
importModuleExpr ::= qualId | importModuleExpr "::" simpleId arguments?
|
||||||
| importModuleId "::" simpleId
|
|
||||||
|
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.
|
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:
|
A type reference is resolved to a type as follows:
|
||||||
|
|
||||||
@@ -597,7 +604,7 @@ Identifiers are used in following syntactic constructs:
|
|||||||
modulename ::= simpleId
|
modulename ::= simpleId
|
||||||
classname ::= upperId
|
classname ::= upperId
|
||||||
dbasetype ::= atLowerId
|
dbasetype ::= atLowerId
|
||||||
predicateRef ::= (moduleId "::")? literalId
|
predicateRef ::= (moduleExpr "::")? literalId
|
||||||
predicateName ::= lowerId
|
predicateName ::= lowerId
|
||||||
varname ::= lowerId
|
varname ::= lowerId
|
||||||
literalId ::= lowerId | atLowerId
|
literalId ::= lowerId | atLowerId
|
||||||
@@ -1615,7 +1622,7 @@ Aliases define new names for existing QL entities.
|
|||||||
|
|
||||||
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
|
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
|
||||||
| qldoc? annotations "class" classname "=" type ";"
|
| 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.
|
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
|
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)*
|
moduleBody ::= (import | predicate | class | module | alias | select)*
|
||||||
|
|
||||||
import ::= annotations "import" importModuleId ("as" modulename)?
|
import ::= annotations "import" importModuleExpr ("as" modulename)?
|
||||||
|
|
||||||
qualId ::= simpleId | qualId "." simpleId
|
qualId ::= simpleId | qualId "." simpleId
|
||||||
|
|
||||||
importModuleId ::= qualId
|
importModuleExpr ::= qualId | importModuleExpr "::" simpleId arguments?
|
||||||
| importModuleId "::" simpleId
|
|
||||||
|
arguments ::= "<" argument ("," argument)* ">"
|
||||||
|
|
||||||
|
argument ::= moduleExpr | type | predicateRef "/" int
|
||||||
|
|
||||||
select ::= ("from" var_decls)? ("where" formula)? "select" as_exprs ("order" "by" orderbys)?
|
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 ";"
|
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)*
|
exprs ::= expr ("," expr)*
|
||||||
|
|
||||||
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
|
alias ::= qldoc? annotations "predicate" literalId "=" predicateRef "/" int ";"
|
||||||
| qldoc? annotations "class" classname "=" type ";"
|
| qldoc? annotations "class" classname "=" type ";"
|
||||||
| qldoc? annotations "module" modulename "=" moduleId ";"
|
| qldoc? annotations "module" modulename "=" moduleExpr ";"
|
||||||
|
|
||||||
var_decls ::= (var_decl ("," var_decl)*)?
|
var_decls ::= (var_decl ("," var_decl)*)?
|
||||||
|
|
||||||
@@ -2249,7 +2267,7 @@ The complete grammar for QL is as follows:
|
|||||||
|
|
||||||
dbasetype ::= atLowerId
|
dbasetype ::= atLowerId
|
||||||
|
|
||||||
predicateRef ::= (moduleId "::")? literalId
|
predicateRef ::= (moduleExpr "::")? literalId
|
||||||
|
|
||||||
predicateName ::= lowerId
|
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
|
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)
|
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)
|
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)
|
bash extractor-smoke-test/test.sh || (echo "Extractor smoke test FAILED"; exit 1)
|
||||||
|
|
||||||
.PHONY: build/testdb/check-upgrade-path
|
.PHONY: build/testdb/check-upgrade-path
|
||||||
|
|||||||
@@ -36,45 +36,37 @@ func TestParseGoVersion(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetVersionToInstall(t *testing.T) {
|
func TestGetVersionToInstall(t *testing.T) {
|
||||||
tests := map[versionInfo]string{
|
tests := map[versionInfo]string{
|
||||||
// checkForUnsupportedVersions()
|
// getVersionWhenGoModVersionNotFound()
|
||||||
|
{"", false, "", false}: maxGoVersion,
|
||||||
// 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,
|
{"", false, "1.2.2", true}: maxGoVersion,
|
||||||
// Go installation found with version above maxGoVersion
|
{"", false, "9999.0.1", true}: maxGoVersion,
|
||||||
|
{"", false, "1.11.13", true}: "",
|
||||||
|
{"", false, "1.20.3", true}: "",
|
||||||
|
|
||||||
|
// 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}: "",
|
||||||
|
|
||||||
|
// 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.20", true, "9999.0.1", true}: "1.20",
|
||||||
{"1.11", true, "9999.0.1", true}: "1.11",
|
{"1.11", true, "9999.0.1", true}: "1.11",
|
||||||
{"", false, "9999.0.1", true}: maxGoVersion,
|
// go.mod version > go installation version
|
||||||
|
|
||||||
// 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
|
|
||||||
{"", false, "", false}: maxGoVersion,
|
|
||||||
// Go installation found with version in supported range, go.mod not found
|
|
||||||
{"", false, "1.11.13", true}: "",
|
|
||||||
{"", false, "1.20.3", true}: "",
|
|
||||||
|
|
||||||
// compareVersions()
|
|
||||||
|
|
||||||
// Go installation found with version in supported range, go.mod version in supported range and go.mod version > go installation version
|
|
||||||
{"1.20", true, "1.11.13", true}: "1.20",
|
{"1.20", true, "1.11.13", true}: "1.20",
|
||||||
{"1.20", true, "1.12", 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
|
// go.mod version <= go installation version (Note comparisons ignore the patch version)
|
||||||
// (Note comparisons ignore the patch version)
|
|
||||||
{"1.11", true, "1.20", true}: "",
|
{"1.11", true, "1.20", true}: "",
|
||||||
{"1.11", true, "1.20.3", true}: "",
|
{"1.11", true, "1.20.3", true}: "",
|
||||||
{"1.20", 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) {
|
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||||
tag = "hasValueFlow" and
|
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(),
|
sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(),
|
||||||
location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and
|
location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and
|
||||||
element = sink.toString() and
|
element = sink.toString() and
|
||||||
@@ -87,7 +87,8 @@ class InlineFlowTest extends InlineExpectationsTest {
|
|||||||
or
|
or
|
||||||
tag = "hasTaintFlow" and
|
tag = "hasTaintFlow" and
|
||||||
exists(DataFlow::Node src, DataFlow::Node sink |
|
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(),
|
sink.hasLocationInfo(location.getFile().getAbsolutePath(), location.getStartLine(),
|
||||||
location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and
|
location.getStartColumn(), location.getEndLine(), location.getEndColumn()) and
|
||||||
|
|||||||
@@ -239,8 +239,6 @@ open class KotlinUsesExtractor(
|
|||||||
return UseClassInstanceResult(classTypeResult, extractClass)
|
return UseClassInstanceResult(classTypeResult, extractClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isArray(t: IrSimpleType) = t.isBoxedArray || t.isPrimitiveArray()
|
|
||||||
|
|
||||||
private fun extractClassLaterIfExternal(c: IrClass) {
|
private fun extractClassLaterIfExternal(c: IrClass) {
|
||||||
if (isExternalDeclaration(c)) {
|
if (isExternalDeclaration(c)) {
|
||||||
extractExternalClassLater(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,
|
data class ArrayInfo(val elementTypeResults: TypeResults,
|
||||||
val componentTypeResults: TypeResults,
|
val componentTypeResults: TypeResults,
|
||||||
val dimensions: Int)
|
val dimensions: Int)
|
||||||
@@ -565,7 +579,7 @@ open class KotlinUsesExtractor(
|
|||||||
*/
|
*/
|
||||||
private fun useArrayType(t: IrType, isPrimitiveArray: Boolean): ArrayInfo {
|
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 nullableT = if (t.isPrimitiveType() && !isPrimitiveArray) t.makeNullable() else t
|
||||||
val typeResults = useType(nullableT)
|
val typeResults = useType(nullableT)
|
||||||
return ArrayInfo(typeResults, typeResults, 0)
|
return ArrayInfo(typeResults, typeResults, 0)
|
||||||
@@ -1141,13 +1155,13 @@ open class KotlinUsesExtractor(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
t.classOrNull?.let { tCls ->
|
t.classOrNull?.let { tCls ->
|
||||||
if (t.isArray() || t.isNullableArray()) {
|
if (t.isBoxedArray) {
|
||||||
(t.arguments.singleOrNull() as? IrTypeProjection)?.let { elementTypeArg ->
|
(t.arguments.singleOrNull() as? IrTypeProjection)?.let { elementTypeArg ->
|
||||||
val elementType = elementTypeArg.type
|
val elementType = elementTypeArg.type
|
||||||
val replacedElementType = kClassToJavaClass(elementType)
|
val replacedElementType = kClassToJavaClass(elementType)
|
||||||
if (replacedElementType !== elementType) {
|
if (replacedElementType !== elementType) {
|
||||||
val newArg = makeTypeProjection(replacedElementType, elementTypeArg.variance)
|
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 (owner is IrClass) {
|
||||||
if (t.isArray() || t.isNullableArray()) {
|
if (t.isBoxedArray) {
|
||||||
val elementType = t.getArrayElementType(pluginContext.irBuiltIns)
|
val elementType = t.getArrayElementType(pluginContext.irBuiltIns)
|
||||||
val erasedElementType = erase(elementType)
|
val erasedElementType = erase(elementType)
|
||||||
return owner.typeWith(erasedElementType).codeQlWithHasQuestionMark(t.isNullable())
|
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 ":"
|
sep = ";" if platform.system() == "Windows" else ":"
|
||||||
os.environ["PATH"] = "".join([os.path.join(os.environ["JAVA_HOME"], "bin"), sep, os.environ["PATH"]])
|
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)
|
run_codeql_database_create([], lang="java", runFunction = runUnsuccessfully, db = None)
|
||||||
|
|
||||||
check_diagnostics()
|
check_diagnostics()
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ deprecated class CamelToURI = CamelToUri;
|
|||||||
class CamelToBeanUri extends CamelToUri {
|
class CamelToBeanUri extends CamelToUri {
|
||||||
CamelToBeanUri() {
|
CamelToBeanUri() {
|
||||||
// A `<to>` element references a bean if the URI starts with "bean:", or there is no scheme.
|
// A `<to>` element references a bean if the URI starts with "bean:", or there is no scheme.
|
||||||
matches("bean:%") or
|
this.matches("bean:%") or
|
||||||
not exists(indexOf(":"))
|
not exists(this.indexOf(":"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,13 +38,13 @@ class CamelToBeanUri extends CamelToUri {
|
|||||||
* parameter parts are optional.
|
* parameter parts are optional.
|
||||||
*/
|
*/
|
||||||
string getBeanIdentifier() {
|
string getBeanIdentifier() {
|
||||||
if not exists(indexOf(":"))
|
if not exists(this.indexOf(":"))
|
||||||
then result = this
|
then result = this
|
||||||
else
|
else
|
||||||
exists(int start | start = indexOf(":", 0, 0) + 1 |
|
exists(int start | start = this.indexOf(":", 0, 0) + 1 |
|
||||||
if not exists(indexOf("?"))
|
if not exists(this.indexOf("?"))
|
||||||
then result = suffix(start)
|
then result = this.suffix(start)
|
||||||
else result = substring(start, indexOf("?", 0, 0))
|
else result = this.substring(start, this.indexOf("?", 0, 0))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -655,6 +655,11 @@ class XmlReader extends RefType {
|
|||||||
XmlReader() { this.hasQualifiedName("org.xml.sax", "XMLReader") }
|
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: Alias for XmlReader */
|
||||||
deprecated class XMLReader = 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 */
|
/* XPathExpression: https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#xpathexpression */
|
||||||
/** The class `javax.xml.xpath.XPathExpression`. */
|
/** The interface `javax.xml.xpath.XPathExpression`. */
|
||||||
class XPathExpression extends RefType {
|
class XPathExpression extends Interface {
|
||||||
XPathExpression() { this.hasQualifiedName("javax.xml.xpath", "XPathExpression") }
|
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 {
|
class XPathEvaluate extends XmlParserCall {
|
||||||
|
Argument sink;
|
||||||
|
|
||||||
XPathEvaluate() {
|
XPathEvaluate() {
|
||||||
exists(Method m |
|
exists(Method m |
|
||||||
this.getMethod() = m and
|
this.getMethod() = m and
|
||||||
m.getDeclaringType() instanceof XPathExpression and
|
|
||||||
m.hasName("evaluate")
|
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() }
|
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}
|
codeql/util: ${workspace}
|
||||||
dataExtensions:
|
dataExtensions:
|
||||||
- Telemetry/ExtractorInformation.yml
|
- 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) {
|
override predicate hasActualResult(Location location, string element, string tag, string value) {
|
||||||
tag = "hasValueFlow" and
|
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
|
sink.getLocation() = location and
|
||||||
element = sink.toString() and
|
element = sink.toString() and
|
||||||
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
|
if exists(getSourceArgString(src)) then value = getSourceArgString(src) else value = ""
|
||||||
@@ -81,7 +81,7 @@ class InlineFlowTest extends InlineExpectationsTest {
|
|||||||
or
|
or
|
||||||
tag = "hasTaintFlow" and
|
tag = "hasTaintFlow" and
|
||||||
exists(DataFlow::Node src, DataFlow::Node sink |
|
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
|
sink.getLocation() = location and
|
||||||
element = sink.toString() and
|
element = sink.toString() and
|
||||||
|
|||||||
@@ -12,18 +12,33 @@ public class XPathExpressionTests {
|
|||||||
|
|
||||||
public void safeXPathExpression(Socket sock) throws Exception {
|
public void safeXPathExpression(Socket sock) throws Exception {
|
||||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||||
XPathFactory xFactory = XPathFactory.newInstance();
|
XPathFactory xFactory = XPathFactory.newInstance();
|
||||||
XPath path = xFactory.newXPath();
|
XPath path = xFactory.newXPath();
|
||||||
XPathExpression expr = path.compile("");
|
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 {
|
public void unsafeExpressionTests(Socket sock) throws Exception {
|
||||||
XPathFactory xFactory = XPathFactory.newInstance();
|
XPathFactory xFactory = XPathFactory.newInstance();
|
||||||
XPath path = xFactory.newXPath();
|
XPath path = xFactory.newXPath();
|
||||||
XPathExpression expr = path.compile("");
|
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: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: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(...) |
|
| 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
|
nodes
|
||||||
| DocumentBuilderTests.java:14:19:14:39 | getInputStream(...) | semmle.label | getInputStream(...) |
|
| DocumentBuilderTests.java:14:19:14:39 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||||
| DocumentBuilderTests.java:28:19:28: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: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:18:100:55 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||||
| XMLReaderTests.java:100:34:100:54 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
| 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:19:27:56 | new InputSource(...) | semmle.label | new InputSource(...) |
|
||||||
| XPathExpressionTests.java:27:37:27:57 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
| 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:9:35:9:55 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||||
| XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | semmle.label | getInputStream(...) |
|
| XmlInputFactoryTests.java:10:34:10:54 | getInputStream(...) | semmle.label | getInputStream(...) |
|
||||||
| XmlInputFactoryTests.java:24:35:24:55 | 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: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: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 |
|
| 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: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: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 |
|
| 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}
|
codeql/yaml: ${workspace}
|
||||||
dataExtensions:
|
dataExtensions:
|
||||||
- semmle/javascript/frameworks/**/model.yml
|
- 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.
|
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
|
||||||
*/
|
*/
|
||||||
module 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 {
|
private class Node extends YamlNode {
|
||||||
Node() {
|
Node() {
|
||||||
this.getLocation()
|
exists(File f |
|
||||||
.getFile()
|
f = this.getLocation().getFile() and
|
||||||
.getRelativePath()
|
(
|
||||||
.regexpMatch("(^|.*/)\\.github/workflows/.*\\.yml$")
|
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.
|
* 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.
|
* 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. */
|
/** Gets the `jobs` mapping from job IDs to job definitions in this workflow. */
|
||||||
YamlMapping getJobs() { result = this.lookup("jobs") }
|
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. */
|
/** Gets the name of the workflow. */
|
||||||
string getName() { result = this.lookup("name").(YamlString).getValue() }
|
string getName() { result = this.lookup("name").(YamlString).getValue() }
|
||||||
|
|
||||||
@@ -54,11 +111,44 @@ module Actions {
|
|||||||
Workflow getWorkflow() { result = workflow }
|
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.
|
* An Actions job within a workflow.
|
||||||
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
|
* 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;
|
string jobId;
|
||||||
Workflow workflow;
|
Workflow workflow;
|
||||||
|
|
||||||
@@ -85,8 +175,8 @@ module Actions {
|
|||||||
/** Gets the step at the given index within this job. */
|
/** Gets the step at the given index within this job. */
|
||||||
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
Step getStep(int index) { result.getJob() = this and result.getIndex() = index }
|
||||||
|
|
||||||
/** Gets the sequence of `steps` within this job. */
|
/** Gets the `env` mapping in this job. */
|
||||||
YamlSequence getSteps() { result = this.lookup("steps") }
|
JobEnv getEnv() { result = this.lookup("env") }
|
||||||
|
|
||||||
/** Gets the workflow this job belongs to. */
|
/** Gets the workflow this job belongs to. */
|
||||||
Workflow getWorkflow() { result = workflow }
|
Workflow getWorkflow() { result = workflow }
|
||||||
@@ -130,15 +220,18 @@ module Actions {
|
|||||||
*/
|
*/
|
||||||
class Step extends YamlNode, YamlMapping {
|
class Step extends YamlNode, YamlMapping {
|
||||||
int index;
|
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`. */
|
/** Gets the 0-based position of this step within the sequence of `steps`. */
|
||||||
int getIndex() { result = index }
|
int getIndex() { result = index }
|
||||||
|
|
||||||
/** Gets the job this step belongs to. */
|
/** 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 = job }
|
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. */
|
/** Gets the value of the `uses` field in this step, if any. */
|
||||||
Uses getUses() { result.getStep() = this }
|
Uses getUses() { result.getStep() = this }
|
||||||
@@ -149,6 +242,9 @@ module Actions {
|
|||||||
/** Gets the value of the `if` field in this step, if any. */
|
/** Gets the value of the `if` field in this step, if any. */
|
||||||
StepIf getIf() { result.getStep() = this }
|
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. */
|
/** Gets the ID of this step, if any. */
|
||||||
string getId() { result = this.lookup("id").(YamlString).getValue() }
|
string getId() { result = this.lookup("id").(YamlString).getValue() }
|
||||||
}
|
}
|
||||||
@@ -244,6 +340,25 @@ module Actions {
|
|||||||
With getWith() { result = with }
|
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.
|
* 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.
|
* 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. */
|
/** Gets the step that executes this `run` command. */
|
||||||
Step getStep() { result = step }
|
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.
|
* `:` and `_` count as component delimiters.
|
||||||
*/
|
*/
|
||||||
string getRawComponent(int i) {
|
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");
|
var cp = require("child_process");
|
||||||
|
|
||||||
module.exports = function download(path, callback) {
|
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");
|
var cp = require("child_process");
|
||||||
|
|
||||||
module.exports = function download(path, callback) {
|
module.exports = function download(path, callback) {
|
||||||
cp.execFile("wget", [path], callback);
|
cp.execFileSync("wget", [path], callback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
code injection in contexts like <i>run:</i> or <i>script:</i>.
|
code injection in contexts like <i>run:</i> or <i>script:</i>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Code injection in GitHub Actions may allow an attacker to
|
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
|
The token might have write access to the repository, allowing an attacker
|
||||||
to use the token to make changes to the repository.
|
to use the token to make changes to the repository.
|
||||||
</p>
|
</p>
|
||||||
</overview>
|
</overview>
|
||||||
|
|
||||||
@@ -19,7 +20,8 @@
|
|||||||
<p>
|
<p>
|
||||||
The best practice to avoid code injection vulnerabilities
|
The best practice to avoid code injection vulnerabilities
|
||||||
in GitHub workflows is to set the untrusted input value of the expression
|
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>
|
||||||
<p>
|
<p>
|
||||||
It is also recommended to limit the permissions of any tokens used
|
It is also recommended to limit the permissions of any tokens used
|
||||||
@@ -33,6 +35,12 @@
|
|||||||
</p>
|
</p>
|
||||||
<sample src="examples/comment_issue_bad.yml" />
|
<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>
|
<p>
|
||||||
The following example uses shell syntax to read
|
The following example uses shell syntax to read
|
||||||
the environment variable and will prevent the attack:
|
the environment variable and will prevent the attack:
|
||||||
|
|||||||
@@ -15,6 +15,44 @@
|
|||||||
import javascript
|
import javascript
|
||||||
import semmle.javascript.Actions
|
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]
|
bindingset[context]
|
||||||
private predicate isExternalUserControlledIssue(string context) {
|
private predicate isExternalUserControlledIssue(string context) {
|
||||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*title\\b") or
|
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*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*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*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*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b",
|
||||||
|
"\\bgithub\\s*\\.\\s*head_ref\\b"
|
||||||
]
|
]
|
||||||
|
|
|
|
||||||
context.regexpMatch(reg)
|
context.regexpMatch(reg)
|
||||||
@@ -39,8 +80,7 @@ private predicate isExternalUserControlledPullRequest(string context) {
|
|||||||
|
|
||||||
bindingset[context]
|
bindingset[context]
|
||||||
private predicate isExternalUserControlledReview(string 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\\s*\\.\\s*body\\b")
|
||||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review_comment\\s*\\.\\s*body\\b")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bindingset[context]
|
bindingset[context]
|
||||||
@@ -51,7 +91,8 @@ private predicate isExternalUserControlledComment(string context) {
|
|||||||
bindingset[context]
|
bindingset[context]
|
||||||
private predicate isExternalUserControlledGollum(string context) {
|
private predicate isExternalUserControlledGollum(string context) {
|
||||||
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]
|
bindingset[context]
|
||||||
@@ -59,13 +100,16 @@ private predicate isExternalUserControlledCommit(string context) {
|
|||||||
exists(string reg |
|
exists(string reg |
|
||||||
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*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*email\\b",
|
||||||
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\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*head_commit\\s*\\.\\s*committer\\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*event\\s*\\.\\s*head_commit\\s*\\.\\s*committer\\s*\\.\\s*name\\b",
|
||||||
"\\bgithub\\s*\\.\\s*head_ref\\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)
|
context.regexpMatch(reg)
|
||||||
@@ -78,32 +122,149 @@ private predicate isExternalUserControlledDiscussion(string context) {
|
|||||||
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*body\\b")
|
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*discussion\\s*\\.\\s*body\\b")
|
||||||
}
|
}
|
||||||
|
|
||||||
from Actions::Run run, string context, Actions::On on
|
bindingset[context]
|
||||||
where
|
private predicate isExternalUserControlledWorkflowRun(string context) {
|
||||||
run.getASimpleReferenceExpression() = context and
|
exists(string reg |
|
||||||
run.getStep().getJob().getWorkflow().getOn() = on and
|
reg =
|
||||||
(
|
[
|
||||||
exists(on.getNode("issues")) and
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_branch\\b",
|
||||||
isExternalUserControlledIssue(context)
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*display_title\\b",
|
||||||
or
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_repository\\b\\s*\\.\\s*description\\b",
|
||||||
exists(on.getNode("pull_request_target")) and
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*message\\b",
|
||||||
isExternalUserControlledPullRequest(context)
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*author\\b\\s*\\.\\s*email\\b",
|
||||||
or
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*author\\b\\s*\\.\\s*name\\b",
|
||||||
(exists(on.getNode("pull_request_review_comment")) or exists(on.getNode("pull_request_review"))) and
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*committer\\b\\s*\\.\\s*email\\b",
|
||||||
isExternalUserControlledReview(context)
|
"\\bgithub\\s*\\.\\s*event\\s*\\.\\s*workflow_run\\s*\\.\\s*head_commit\\b\\s*\\.\\s*committer\\b\\s*\\.\\s*name\\b",
|
||||||
or
|
]
|
||||||
(exists(on.getNode("issue_comment")) or exists(on.getNode("pull_request_target"))) and
|
|
|
||||||
isExternalUserControlledComment(context)
|
context.regexpMatch(reg)
|
||||||
or
|
|
||||||
exists(on.getNode("gollum")) and
|
|
||||||
isExternalUserControlledGollum(context)
|
|
||||||
or
|
|
||||||
exists(on.getNode("pull_request_target")) and
|
|
||||||
isExternalUserControlledCommit(context)
|
|
||||||
or
|
|
||||||
(exists(on.getNode("discussion")) or exists(on.getNode("discussion_comment"))) and
|
|
||||||
isExternalUserControlledDiscussion(context)
|
|
||||||
)
|
)
|
||||||
select run,
|
}
|
||||||
"Potential injection from the " + context +
|
|
||||||
" context, which may be controlled by an external user."
|
/**
|
||||||
|
* 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
|
||||||
|
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)
|
||||||
|
or
|
||||||
|
exists(on.getNode("pull_request_target")) and
|
||||||
|
isExternalUserControlledPullRequest(context)
|
||||||
|
or
|
||||||
|
exists(on.getNode("pull_request_review")) and
|
||||||
|
(isExternalUserControlledReview(context) or isExternalUserControlledPullRequest(context))
|
||||||
|
or
|
||||||
|
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("push")) and
|
||||||
|
isExternalUserControlledCommit(context)
|
||||||
|
or
|
||||||
|
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 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>
|
<p>
|
||||||
|
|
||||||
This security check is, however, insufficient since an
|
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
|
prevent this, the server can cryptographically sign the security
|
||||||
critical cookie values:
|
critical cookie values:
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@
|
|||||||
<p>
|
<p>
|
||||||
In the example below, the untrusted value <code>req.params.id</code> is used as the property name
|
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>,
|
<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>.
|
the variable <code>items</code> will then refer to <code>Object.prototype</code>.
|
||||||
Finally, the modification of <code>todo</code> then allows the attacker to inject arbitrary properties
|
Finally, the modification of <code>items</code> then allows the attacker to inject arbitrary properties
|
||||||
onto <code>Object.prototype</code>.
|
onto <code>Object.prototype</code>.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -48,6 +48,12 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<sample src="examples/PrototypePollutingAssignmentFixed.js"/>
|
<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>
|
</example>
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
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.
|
* the check requires manual analysis.
|
||||||
*/
|
*/
|
||||||
class ProbableStep extends Actions::Step {
|
class ProbableStep extends Actions::Step {
|
||||||
@@ -29,25 +29,13 @@ class ProbableStep extends Actions::Step {
|
|||||||
// needs manual analysis if there is OR
|
// needs manual analysis if there is OR
|
||||||
this.getIf().getValue().matches("%||%")
|
this.getIf().getValue().matches("%||%")
|
||||||
or
|
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
|
// actor check means only the user is able to run it
|
||||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
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.
|
* the check requires manual analysis.
|
||||||
*/
|
*/
|
||||||
class ProbableJob extends Actions::Job {
|
class ProbableJob extends Actions::Job {
|
||||||
@@ -59,46 +47,18 @@ class ProbableJob extends Actions::Job {
|
|||||||
// needs manual analysis if there is OR
|
// needs manual analysis if there is OR
|
||||||
this.getIf().getValue().matches("%||%")
|
this.getIf().getValue().matches("%||%")
|
||||||
or
|
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
|
// actor check means only the user is able to run it
|
||||||
not exists(this.getIf().getValue().regexpFind("\\bgithub\\s*\\.\\s*actor\\s*==", _, _))
|
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 {
|
class ProbablePullRequestTarget extends Actions::On, YamlMappingLikeNode {
|
||||||
ProbablePullRequestTarget() {
|
ProbablePullRequestTarget() {
|
||||||
exists(YamlNode prtNode |
|
// The `on:` is triggered on `pull_request_target`
|
||||||
// The `on:` is triggered on `pull_request_target`
|
exists(this.getNode("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"))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,3 +11,4 @@ dependencies:
|
|||||||
codeql/suite-helpers: ${workspace}
|
codeql/suite-helpers: ${workspace}
|
||||||
codeql/typos: ${workspace}
|
codeql/typos: ${workspace}
|
||||||
codeql/util: ${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: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_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: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_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_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_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'. |
|
| .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: .
|
tests: .
|
||||||
dataExtensions:
|
dataExtensions:
|
||||||
- library-tests/DataExtensions/*.model.yml
|
- library-tests/DataExtensions/*.model.yml
|
||||||
|
warnOnImplicitThis: true
|
||||||
|
|||||||
@@ -10,5 +10,19 @@ jobs:
|
|||||||
echo-chamber2:
|
echo-chamber2:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: echo '${{ github.event.comment.body }}'
|
||||||
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: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:14:47 | \| | Potential injection from the github.event.comment.body context, 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_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: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. */
|
/** Get the method for this resource with the given verb. */
|
||||||
RamlMethod getMethod(string verb) {
|
RamlMethod getMethod(string verb) {
|
||||||
verb = httpVerb() and
|
verb = httpVerb() and
|
||||||
result = lookup(verb)
|
result = this.lookup(verb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import javascript
|
|||||||
|
|
||||||
/** A RAML specification. */
|
/** A RAML specification. */
|
||||||
class RamlSpec extends YamlDocument, YamlMapping {
|
class RamlSpec extends YamlDocument, YamlMapping {
|
||||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||||
}
|
}
|
||||||
|
|
||||||
from RamlSpec s
|
from RamlSpec s
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ string httpVerb() { result = ["get", "put", "post", "delete"] }
|
|||||||
|
|
||||||
/** A RAML specification. */
|
/** A RAML specification. */
|
||||||
class RamlSpec extends YamlDocument, YamlMapping {
|
class RamlSpec extends YamlDocument, YamlMapping {
|
||||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A RAML resource specification. */
|
/** A RAML resource specification. */
|
||||||
class RamlResource extends YamlMapping {
|
class RamlResource extends YamlMapping {
|
||||||
RamlResource() {
|
RamlResource() {
|
||||||
getDocument() instanceof RamlSpec and
|
this.getDocument() instanceof RamlSpec and
|
||||||
exists(YamlMapping m, string name |
|
exists(YamlMapping m, string name |
|
||||||
this = m.lookup(name) and
|
this = m.lookup(name) and
|
||||||
name.matches("/%")
|
name.matches("/%")
|
||||||
@@ -30,14 +30,14 @@ class RamlResource extends YamlMapping {
|
|||||||
/** Get the method for this resource with the given verb. */
|
/** Get the method for this resource with the given verb. */
|
||||||
RamlMethod getMethod(string verb) {
|
RamlMethod getMethod(string verb) {
|
||||||
verb = httpVerb() and
|
verb = httpVerb() and
|
||||||
result = lookup(verb)
|
result = this.lookup(verb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A RAML method specification. */
|
/** A RAML method specification. */
|
||||||
class RamlMethod extends YamlValue {
|
class RamlMethod extends YamlValue {
|
||||||
RamlMethod() {
|
RamlMethod() {
|
||||||
getDocument() instanceof RamlSpec and
|
this.getDocument() instanceof RamlSpec and
|
||||||
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ string httpVerb() { result = ["get", "put", "post", "delete"] }
|
|||||||
|
|
||||||
/** A RAML specification. */
|
/** A RAML specification. */
|
||||||
class RamlSpec extends YamlDocument, YamlMapping {
|
class RamlSpec extends YamlDocument, YamlMapping {
|
||||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A RAML resource specification. */
|
/** A RAML resource specification. */
|
||||||
class RamlResource extends YamlMapping {
|
class RamlResource extends YamlMapping {
|
||||||
RamlResource() {
|
RamlResource() {
|
||||||
getDocument() instanceof RamlSpec and
|
this.getDocument() instanceof RamlSpec and
|
||||||
exists(YamlMapping m, string name |
|
exists(YamlMapping m, string name |
|
||||||
this = m.lookup(name) and
|
this = m.lookup(name) and
|
||||||
name.matches("/%")
|
name.matches("/%")
|
||||||
@@ -30,13 +30,13 @@ class RamlResource extends YamlMapping {
|
|||||||
/** Get the method for this resource with the given verb. */
|
/** Get the method for this resource with the given verb. */
|
||||||
RamlMethod getMethod(string verb) {
|
RamlMethod getMethod(string verb) {
|
||||||
verb = httpVerb() and
|
verb = httpVerb() and
|
||||||
result = lookup(verb)
|
result = this.lookup(verb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RamlMethod extends YamlValue {
|
class RamlMethod extends YamlValue {
|
||||||
RamlMethod() {
|
RamlMethod() {
|
||||||
getDocument() instanceof RamlSpec and
|
this.getDocument() instanceof RamlSpec and
|
||||||
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ string httpVerb() { result = ["get", "put", "post", "delete"] }
|
|||||||
|
|
||||||
/** A RAML specification. */
|
/** A RAML specification. */
|
||||||
class RamlSpec extends YamlDocument, YamlMapping {
|
class RamlSpec extends YamlDocument, YamlMapping {
|
||||||
RamlSpec() { getLocation().getFile().getExtension() = "raml" }
|
RamlSpec() { this.getLocation().getFile().getExtension() = "raml" }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A RAML resource specification. */
|
/** A RAML resource specification. */
|
||||||
class RamlResource extends YamlMapping {
|
class RamlResource extends YamlMapping {
|
||||||
RamlResource() {
|
RamlResource() {
|
||||||
getDocument() instanceof RamlSpec and
|
this.getDocument() instanceof RamlSpec and
|
||||||
exists(YamlMapping m, string name |
|
exists(YamlMapping m, string name |
|
||||||
this = m.lookup(name) and
|
this = m.lookup(name) and
|
||||||
name.matches("/%")
|
name.matches("/%")
|
||||||
@@ -30,14 +30,14 @@ class RamlResource extends YamlMapping {
|
|||||||
/** Get the method for this resource with the given verb. */
|
/** Get the method for this resource with the given verb. */
|
||||||
RamlMethod getMethod(string verb) {
|
RamlMethod getMethod(string verb) {
|
||||||
verb = httpVerb() and
|
verb = httpVerb() and
|
||||||
result = lookup(verb)
|
result = this.lookup(verb)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A RAML method specification. */
|
/** A RAML method specification. */
|
||||||
class RamlMethod extends YamlValue {
|
class RamlMethod extends YamlValue {
|
||||||
RamlMethod() {
|
RamlMethod() {
|
||||||
getDocument() instanceof RamlSpec and
|
this.getDocument() instanceof RamlSpec and
|
||||||
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
exists(YamlMapping obj | this = obj.lookup(httpVerb()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,27 @@ module SummaryComponent {
|
|||||||
/** Gets a summary component that represents a list element. */
|
/** Gets a summary component that represents a list element. */
|
||||||
SummaryComponent listElement() { result = content(any(ListElementContent c)) }
|
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. */
|
/** Gets a summary component that represents the return value of a call. */
|
||||||
SummaryComponent return() { result = SC::return(any(ReturnKind rk)) }
|
SummaryComponent return() { result = SC::return(any(ReturnKind rk)) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,27 @@ predicate neutralSummaryElement(FlowSummary::SummarizedCallable c, string proven
|
|||||||
SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
SummaryComponent interpretComponentSpecific(AccessPathToken c) {
|
||||||
c = "ListElement" and
|
c = "ListElement" and
|
||||||
result = FlowSummary::SummaryComponent::listElement()
|
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. */
|
/** 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`.
|
* also flow to `sink`.
|
||||||
*/
|
*/
|
||||||
TypeTracker getACompatibleTypeTracker() {
|
TypeTracker getACompatibleTypeTracker() {
|
||||||
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
|
exists(boolean hasCall, OptionalTypeTrackerContent c |
|
||||||
hasCall = false or this.hasReturn() = false
|
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
|
uniqueContentApprox
|
||||||
identityLocalStep
|
identityLocalStep
|
||||||
| test_async.py:48:9:48:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
| 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:64:10:64: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:71:9:71: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:73:9:73: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:88:10:88:21 | ControlFlowNode for tainted_list | Node steps to itself |
|
||||||
| test_collections.py:81:9:81:22 | ControlFlowNode for ensure_tainted | 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: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:147:9:147: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:149:9:149:22 | ControlFlowNode for ensure_tainted | Node steps to itself |
|
||||||
| test_collections.py:213:22:213:33 | ControlFlowNode for tainted_dict | 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 |
|
| 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
|
tuple(tainted_list), # $ tainted
|
||||||
set(tainted_list), # $ tainted
|
set(tainted_list), # $ tainted
|
||||||
frozenset(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):
|
for i in reversed(tainted_list):
|
||||||
ensure_tainted(i) # $ tainted
|
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):
|
def test_dict_access(x):
|
||||||
tainted_dict = TAINTED_DICT
|
tainted_dict = TAINTED_DICT
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ if __name__ == "__main__":
|
|||||||
check_tests_valid("coverage.test")
|
check_tests_valid("coverage.test")
|
||||||
check_tests_valid("coverage.argumentPassing")
|
check_tests_valid("coverage.argumentPassing")
|
||||||
check_tests_valid("coverage.datamodel")
|
check_tests_valid("coverage.datamodel")
|
||||||
|
check_tests_valid("coverage.test_builtins")
|
||||||
check_tests_valid("coverage-py2.classes")
|
check_tests_valid("coverage-py2.classes")
|
||||||
check_tests_valid("coverage-py3.classes")
|
check_tests_valid("coverage-py3.classes")
|
||||||
check_tests_valid("variable-capture.in")
|
check_tests_valid("variable-capture.in")
|
||||||
|
|||||||
@@ -1,48 +1,21 @@
|
|||||||
use clap::Args;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
use codeql_extractor::autobuilder;
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
// The autobuilder takes no command-line options, but this may change in the future.
|
// The autobuilder takes no command-line options, but this may change in the future.
|
||||||
pub struct Options {}
|
pub struct Options {}
|
||||||
|
|
||||||
pub fn run(_: Options) -> std::io::Result<()> {
|
pub fn run(_: Options) -> std::io::Result<()> {
|
||||||
let dist = env::var("CODEQL_DIST").expect("CODEQL_DIST not set");
|
let database = env::var("CODEQL_EXTRACTOR_QL_WIP_DATABASE")
|
||||||
let db = env::var("CODEQL_EXTRACTOR_QL_WIP_DATABASE")
|
|
||||||
.expect("CODEQL_EXTRACTOR_QL_WIP_DATABASE not set");
|
.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")
|
autobuilder::Autobuilder::new("ql", PathBuf::from(database))
|
||||||
.unwrap_or_default()
|
.include_extensions(&[".ql", ".qll", ".dbscheme", ".json", ".jsonc", ".jsonl"])
|
||||||
.split('\n')
|
.include_globs(&["**/qlpack.yml", "deprecated.blame"])
|
||||||
{
|
.size_limit("10m")
|
||||||
if let Some(stripped) = line.strip_prefix("include:") {
|
.run()
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ class Location extends @location {
|
|||||||
int getEndColumn() { locations_default(this, _, _, _, _, result) }
|
int getEndColumn() { locations_default(this, _, _, _, _, result) }
|
||||||
|
|
||||||
/** Gets the number of lines covered by this location. */
|
/** 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. */
|
/** Gets a textual representation of this element. */
|
||||||
cached
|
cached
|
||||||
string toString() {
|
string toString() {
|
||||||
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
|
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
|
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class DisjunctionChain extends Disjunction {
|
|||||||
Formula getOperand(int i) {
|
Formula getOperand(int i) {
|
||||||
result =
|
result =
|
||||||
rank[i + 1](Formula operand, Location l |
|
rank[i + 1](Formula operand, Location l |
|
||||||
operand = getAnOperand*() and
|
operand = this.getAnOperand*() and
|
||||||
not operand instanceof Disjunction and
|
not operand instanceof Disjunction and
|
||||||
l = operand.getLocation()
|
l = operand.getLocation()
|
||||||
|
|
|
|
||||||
@@ -33,16 +33,16 @@ class DisjunctionChain extends Disjunction {
|
|||||||
*/
|
*/
|
||||||
class EqualsLiteral extends ComparisonFormula {
|
class EqualsLiteral extends ComparisonFormula {
|
||||||
EqualsLiteral() {
|
EqualsLiteral() {
|
||||||
getOperator() = "=" and
|
this.getOperator() = "=" and
|
||||||
getAnOperand() instanceof Literal
|
this.getAnOperand() instanceof Literal
|
||||||
}
|
}
|
||||||
|
|
||||||
AstNode getOther() {
|
AstNode getOther() {
|
||||||
result = getAnOperand() and
|
result = this.getAnOperand() and
|
||||||
not result instanceof Literal
|
not result instanceof Literal
|
||||||
}
|
}
|
||||||
|
|
||||||
Literal getLiteral() { result = getAnOperand() }
|
Literal getLiteral() { result = this.getAnOperand() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,29 +60,33 @@ class DisjunctionEqualsLiteral extends DisjunctionChain {
|
|||||||
DisjunctionEqualsLiteral() {
|
DisjunctionEqualsLiteral() {
|
||||||
// VarAccess on the same variable
|
// VarAccess on the same variable
|
||||||
exists(VarDef v |
|
exists(VarDef v |
|
||||||
forex(Formula f | f = getOperand(_) |
|
forex(Formula f | f = this.getOperand(_) |
|
||||||
f.(EqualsLiteral).getAnOperand().(VarAccess).getDeclaration() = v
|
f.(EqualsLiteral).getAnOperand().(VarAccess).getDeclaration() = v
|
||||||
) and
|
) and
|
||||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand() and
|
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand() and
|
||||||
firstOperand.(VarAccess).getDeclaration() = v
|
firstOperand.(VarAccess).getDeclaration() = v
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
// FieldAccess on the same variable
|
// FieldAccess on the same variable
|
||||||
exists(FieldDecl v |
|
exists(FieldDecl v |
|
||||||
forex(Formula f | f = getOperand(_) |
|
forex(Formula f | f = this.getOperand(_) |
|
||||||
f.(EqualsLiteral).getAnOperand().(FieldAccess).getDeclaration() = v
|
f.(EqualsLiteral).getAnOperand().(FieldAccess).getDeclaration() = v
|
||||||
) and
|
) and
|
||||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand() and
|
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand() and
|
||||||
firstOperand.(FieldAccess).getDeclaration() = v
|
firstOperand.(FieldAccess).getDeclaration() = v
|
||||||
)
|
)
|
||||||
or
|
or
|
||||||
// ThisAccess
|
// ThisAccess
|
||||||
forex(Formula f | f = getOperand(_) | f.(EqualsLiteral).getAnOperand() instanceof ThisAccess) and
|
forex(Formula f | f = this.getOperand(_) |
|
||||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand().(ThisAccess)
|
f.(EqualsLiteral).getAnOperand() instanceof ThisAccess
|
||||||
|
) and
|
||||||
|
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand().(ThisAccess)
|
||||||
or
|
or
|
||||||
// ResultAccess
|
// ResultAccess
|
||||||
forex(Formula f | f = getOperand(_) | f.(EqualsLiteral).getAnOperand() instanceof ResultAccess) and
|
forex(Formula f | f = this.getOperand(_) |
|
||||||
firstOperand = getOperand(0).(EqualsLiteral).getAnOperand().(ResultAccess)
|
f.(EqualsLiteral).getAnOperand() instanceof ResultAccess
|
||||||
|
) and
|
||||||
|
firstOperand = this.getOperand(0).(EqualsLiteral).getAnOperand().(ResultAccess)
|
||||||
// (in principle something like GlobalValueNumbering could be used to generalize this)
|
// (in principle something like GlobalValueNumbering could be used to generalize this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,8 +104,8 @@ class DisjunctionEqualsLiteral extends DisjunctionChain {
|
|||||||
*/
|
*/
|
||||||
class CallLiteral extends Call {
|
class CallLiteral extends Call {
|
||||||
CallLiteral() {
|
CallLiteral() {
|
||||||
getNumberOfArguments() = 1 and
|
this.getNumberOfArguments() = 1 and
|
||||||
getArgument(0) instanceof Literal
|
this.getArgument(0) instanceof Literal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +122,7 @@ class DisjunctionPredicateLiteral extends DisjunctionChain {
|
|||||||
DisjunctionPredicateLiteral() {
|
DisjunctionPredicateLiteral() {
|
||||||
// Call to the same target
|
// Call to the same target
|
||||||
exists(PredicateOrBuiltin 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:
|
dependencies:
|
||||||
codeql/typos: ${workspace}
|
codeql/typos: ${workspace}
|
||||||
codeql/util: ${workspace}
|
codeql/util: ${workspace}
|
||||||
|
warnOnImplicitThis: true
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ query predicate test() { foo() }
|
|||||||
class Foo extends AstNode {
|
class Foo extends AstNode {
|
||||||
predicate bar() { none() }
|
predicate bar() { none() }
|
||||||
|
|
||||||
predicate baz() { bar() }
|
predicate baz() { this.bar() }
|
||||||
}
|
}
|
||||||
|
|
||||||
class Sub extends Foo {
|
class Sub extends Foo {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ getTarget
|
|||||||
| Bar.qll:30:12:30:32 | MemberCall | Bar.qll:19:7:19:18 | ClassPredicate getParameter |
|
| 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 |
|
| 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: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: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: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 |
|
| 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::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
|
||||||
|
use clap::Args;
|
||||||
|
|
||||||
|
use codeql_extractor::autobuilder;
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
// The autobuilder takes no command-line options, but this may change in the future.
|
// The autobuilder takes no command-line options, but this may change in the future.
|
||||||
pub struct Options {}
|
pub struct Options {}
|
||||||
|
|
||||||
pub fn run(_: Options) -> std::io::Result<()> {
|
pub fn run(_: Options) -> std::io::Result<()> {
|
||||||
let dist = env::var("CODEQL_DIST").expect("CODEQL_DIST not set");
|
let database = env::var("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE")
|
||||||
let db = env::var("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE")
|
|
||||||
.expect("CODEQL_EXTRACTOR_RUBY_WIP_DATABASE not set");
|
.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")
|
autobuilder::Autobuilder::new("ruby", PathBuf::from(database))
|
||||||
.unwrap_or_default()
|
.include_extensions(&[".rb", ".erb", ".gemspec"])
|
||||||
.split('\n')
|
.include_globs(&["**/Gemfile"])
|
||||||
{
|
.exclude_globs(&["**/.git"])
|
||||||
if let Some(stripped) = line.strip_prefix("include:") {
|
.size_limit("5m")
|
||||||
cmd.arg("--also-match=".to_owned() + stripped);
|
.run()
|
||||||
} 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))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -936,10 +936,10 @@ module ExprNodes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
/** A control-flow node that wraps a `StringLiteral` AST expression. */
|
||||||
class StringLiteralCfgNode extends ExprCfgNode {
|
class StringLiteralCfgNode extends StringlikeLiteralCfgNode {
|
||||||
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
StringLiteralCfgNode() { e instanceof StringLiteral }
|
||||||
|
|
||||||
override StringLiteral e;
|
override string getAPrimaryQlClass() { result = "StringLiteralCfgNode" }
|
||||||
|
|
||||||
final override StringLiteral getExpr() { result = super.getExpr() }
|
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
|
* Holds if taint propagates from `nodeFrom` to `nodeTo` in exactly one local
|
||||||
* (intra-procedural) step.
|
* (intra-procedural) step.
|
||||||
@@ -122,7 +129,7 @@ private module Cached {
|
|||||||
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
|
defaultAdditionalTaintStep(nodeFrom, nodeTo) or
|
||||||
// Simple flow through library code is included in the exposed local
|
// Simple flow through library code is included in the exposed local
|
||||||
// step relation, even though flow is technically inter-procedural
|
// 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.ApiGraphs
|
||||||
private import codeql.ruby.Concepts
|
private import codeql.ruby.Concepts
|
||||||
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
|
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.TaintTracking
|
||||||
private import codeql.ruby.frameworks.core.String
|
private import codeql.ruby.frameworks.core.String
|
||||||
|
|
||||||
@@ -37,43 +38,6 @@ DataFlow::LocalSourceNode strStart() {
|
|||||||
/** Gets a dataflow node for a regular expression literal. */
|
/** Gets a dataflow node for a regular expression literal. */
|
||||||
DataFlow::LocalSourceNode regStart() { result.asExpr().getExpr() instanceof Ast::RegExpLiteral }
|
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. */
|
/** Gets a node where string values that flow to the node are interpreted as regular expressions. */
|
||||||
DataFlow::Node stringSink() {
|
DataFlow::Node stringSink() {
|
||||||
result instanceof RE::RegExpInterpretation::Range and
|
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. */
|
/** Gets a node where regular expressions that flow to the node are used. */
|
||||||
DataFlow::Node regSink() { result = any(RegexExecution exec).getRegex() }
|
DataFlow::Node regSink() { result = any(RegexExecution exec).getRegex() }
|
||||||
|
|
||||||
/** Gets a node that is reachable by type-tracking from any string or regular expression. */
|
private signature module TypeTrackInputSig {
|
||||||
DataFlow::LocalSourceNode forward(TypeTracker t) {
|
DataFlow::LocalSourceNode start(TypeTracker t, DataFlow::Node start);
|
||||||
t.start() and
|
|
||||||
result = [strStart(), regStart()]
|
predicate end(DataFlow::Node n);
|
||||||
or
|
|
||||||
exists(TypeTracker t2 | result = forward(t2).track(t2, t))
|
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo);
|
||||||
or
|
|
||||||
exists(TypeTracker t2 | t2 = t.continue() | step(forward(t2).getALocalUse(), result, _, _))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a node that is backwards reachable from any regular expression use,
|
* Provides a version of type tracking where we first prune for reachable nodes,
|
||||||
* where that use is reachable by type-tracking from any string or regular expression.
|
* before doing the type tracking computation.
|
||||||
*/
|
*/
|
||||||
DataFlow::LocalSourceNode backwards(TypeBackTracker t) {
|
private module TypeTrack<TypeTrackInputSig Input> {
|
||||||
t.start() and
|
private predicate additionalStep(
|
||||||
result.flowsTo([stringSink(), regSink()]) and
|
DataFlow::LocalSourceNode nodeFrom, DataFlow::LocalSourceNode nodeTo
|
||||||
result = forward(TypeTracker::end())
|
) {
|
||||||
or
|
Input::additionalStep(nodeFrom.getALocalUse(), nodeTo)
|
||||||
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
}
|
||||||
or
|
|
||||||
exists(TypeBackTracker t2 | t2 = t.continue() | step(result.getALocalUse(), backwards(t2), _, _))
|
/** 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
|
||||||
|
Input::end(result.getALocalUse())
|
||||||
|
or
|
||||||
|
exists(TypeBackTracker t2 | result = backwards(t2).backtrack(t2, t))
|
||||||
|
or
|
||||||
|
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,
|
* 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.
|
* or where `start` is compiled into a regular expression.
|
||||||
*/
|
*/
|
||||||
private DataFlow::LocalSourceNode trackStrings(DataFlow::Node start, TypeTracker t) {
|
private predicate trackStrings = TypeTrack<StringTypeTrackInput>::track/2;
|
||||||
result = backwards(_) and
|
|
||||||
(
|
/** 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
|
t.start() and
|
||||||
start = result and
|
result = start
|
||||||
result = strStart()
|
|
||||||
or
|
or
|
||||||
exists(TypeTracker t2 | result = trackStrings(start, t2).track(t2, t))
|
regFromStringStart(start, t, result)
|
||||||
or
|
}
|
||||||
// an additional step from string to string
|
|
||||||
exists(TypeTracker t2 | t2 = t.continue() |
|
predicate end(DataFlow::Node n) { n = regSink() }
|
||||||
step(trackStrings(start, t2).getALocalUse(), result, "string", "string")
|
|
||||||
)
|
predicate additionalStep(DataFlow::Node nodeFrom, DataFlow::LocalSourceNode nodeTo) { none() }
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a node that has been tracked from the regular expression `start` to some node.
|
* 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.
|
* This is used to figure out where `start` is executed against an input string.
|
||||||
*/
|
*/
|
||||||
private DataFlow::LocalSourceNode trackRegs(DataFlow::Node start, TypeTracker t) {
|
private predicate trackRegs = TypeTrack<RegTypeTrackInput>::track/2;
|
||||||
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")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Gets a node that references a regular expression. */
|
/** Gets a node that references a regular expression. */
|
||||||
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {
|
private DataFlow::LocalSourceNode trackRegexpType(TypeTracker t) {
|
||||||
|
|||||||
@@ -613,8 +613,17 @@ class TypeBackTracker extends TTypeBackTracker {
|
|||||||
* also flow to `sink`.
|
* also flow to `sink`.
|
||||||
*/
|
*/
|
||||||
TypeTracker getACompatibleTypeTracker() {
|
TypeTracker getACompatibleTypeTracker() {
|
||||||
exists(boolean hasCall | result = MkTypeTracker(hasCall, content) |
|
exists(boolean hasCall, OptionalTypeTrackerContent c |
|
||||||
hasCall = false or this.hasReturn() = false
|
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
|
groups: shared
|
||||||
library: true
|
library: true
|
||||||
dependencies:
|
dependencies:
|
||||||
|
warnOnImplicitThis: true
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ name: codeql/ssa
|
|||||||
version: 0.0.17-dev
|
version: 0.0.17-dev
|
||||||
groups: shared
|
groups: shared
|
||||||
library: true
|
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