-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Shared: Add DataFlow::DeduplicatePathGraph #14350
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
cba7b98
8efdc2d
0eb543e
5aa1242
815581d
f9c0ba3
7363888
afdbf2c
889100a
0edb306
f2968f4
950ae44
8340841
e34fbc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import java.util.function.Function; | ||
|
|
||
| class A { | ||
| String source() { return ""; } | ||
|
|
||
| void sink(String s) { } | ||
|
|
||
| String propagateState(String s, String state) { | ||
| return ""; | ||
| } | ||
|
|
||
| void foo() { | ||
| Function<String, String> lambda = Math.random() > 0.5 | ||
| ? x -> propagateState(x, "A") | ||
| : x -> propagateState(x, "B"); | ||
|
|
||
| String step0 = source(); | ||
| String step1 = lambda.apply(step0); | ||
| String step2 = lambda.apply(step1); | ||
|
|
||
| sink(step2); | ||
| } | ||
|
|
||
| void bar() { | ||
| Function<String, String> lambda = | ||
| (x -> Math.random() > 0.5 ? propagateState(x, "A") : propagateState(x, "B")); | ||
|
|
||
| String step0 = source(); | ||
| String step1 = lambda.apply(step0); | ||
| String step2 = lambda.apply(step1); | ||
|
|
||
| sink(step2); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| nodes | ||
| | A.java:14:9:14:9 | x : String | semmle.label | x : String | | ||
| | A.java:14:14:14:35 | propagateState(...) : String | semmle.label | propagateState(...) : String | | ||
| | A.java:14:29:14:29 | x : String | semmle.label | x : String | | ||
| | A.java:15:9:15:9 | x : String | semmle.label | x : String | | ||
| | A.java:15:14:15:35 | propagateState(...) : String | semmle.label | propagateState(...) : String | | ||
| | A.java:15:29:15:29 | x : String | semmle.label | x : String | | ||
| | A.java:17:20:17:27 | source(...) : String | semmle.label | source(...) : String | | ||
| | A.java:18:20:18:38 | apply(...) : String | semmle.label | apply(...) : String | | ||
| | A.java:18:20:18:38 | apply(...) : String | semmle.label | apply(...) : String | | ||
| | A.java:18:33:18:37 | step0 : String | semmle.label | step0 : String | | ||
| | A.java:19:20:19:38 | apply(...) : String | semmle.label | apply(...) : String | | ||
| | A.java:19:33:19:37 | step1 : String | semmle.label | step1 : String | | ||
| | A.java:19:33:19:37 | step1 : String | semmle.label | step1 : String | | ||
| | A.java:21:10:21:14 | step2 | semmle.label | step2 | | ||
| | A.java:26:8:26:8 | x : String | semmle.label | x : String | | ||
| | A.java:26:8:26:8 | x : String | semmle.label | x : String | | ||
| | A.java:26:13:26:81 | ...?...:... : String | semmle.label | ...?...:... : String | | ||
| | A.java:26:13:26:81 | ...?...:... : String | semmle.label | ...?...:... : String | | ||
| | A.java:26:35:26:56 | propagateState(...) : String | semmle.label | propagateState(...) : String | | ||
| | A.java:26:50:26:50 | x : String | semmle.label | x : String | | ||
| | A.java:26:60:26:81 | propagateState(...) : String | semmle.label | propagateState(...) : String | | ||
| | A.java:26:75:26:75 | x : String | semmle.label | x : String | | ||
| | A.java:28:20:28:27 | source(...) : String | semmle.label | source(...) : String | | ||
| | A.java:29:20:29:38 | apply(...) : String | semmle.label | apply(...) : String | | ||
| | A.java:29:20:29:38 | apply(...) : String | semmle.label | apply(...) : String | | ||
| | A.java:29:33:29:37 | step0 : String | semmle.label | step0 : String | | ||
| | A.java:30:20:30:38 | apply(...) : String | semmle.label | apply(...) : String | | ||
| | A.java:30:33:30:37 | step1 : String | semmle.label | step1 : String | | ||
| | A.java:30:33:30:37 | step1 : String | semmle.label | step1 : String | | ||
| | A.java:32:10:32:14 | step2 | semmle.label | step2 | | ||
| edges | ||
| | A.java:14:9:14:9 | x : String | A.java:14:29:14:29 | x : String | provenance | | | ||
| | A.java:14:29:14:29 | x : String | A.java:14:14:14:35 | propagateState(...) : String | provenance | Config | | ||
| | A.java:15:9:15:9 | x : String | A.java:15:29:15:29 | x : String | provenance | | | ||
| | A.java:15:29:15:29 | x : String | A.java:15:14:15:35 | propagateState(...) : String | provenance | Config | | ||
| | A.java:17:20:17:27 | source(...) : String | A.java:18:33:18:37 | step0 : String | provenance | | | ||
| | A.java:18:20:18:38 | apply(...) : String | A.java:19:33:19:37 | step1 : String | provenance | | | ||
| | A.java:18:20:18:38 | apply(...) : String | A.java:19:33:19:37 | step1 : String | provenance | | | ||
| | A.java:18:33:18:37 | step0 : String | A.java:14:9:14:9 | x : String | provenance | | | ||
| | A.java:18:33:18:37 | step0 : String | A.java:15:9:15:9 | x : String | provenance | | | ||
| | A.java:18:33:18:37 | step0 : String | A.java:18:20:18:38 | apply(...) : String | provenance | Config | | ||
| | A.java:18:33:18:37 | step0 : String | A.java:18:20:18:38 | apply(...) : String | provenance | Config | | ||
| | A.java:19:20:19:38 | apply(...) : String | A.java:21:10:21:14 | step2 | provenance | | | ||
| | A.java:19:33:19:37 | step1 : String | A.java:14:9:14:9 | x : String | provenance | | | ||
| | A.java:19:33:19:37 | step1 : String | A.java:15:9:15:9 | x : String | provenance | | | ||
| | A.java:19:33:19:37 | step1 : String | A.java:19:20:19:38 | apply(...) : String | provenance | Config | | ||
| | A.java:19:33:19:37 | step1 : String | A.java:19:20:19:38 | apply(...) : String | provenance | Config | | ||
| | A.java:26:8:26:8 | x : String | A.java:26:50:26:50 | x : String | provenance | | | ||
| | A.java:26:8:26:8 | x : String | A.java:26:75:26:75 | x : String | provenance | | | ||
| | A.java:26:35:26:56 | propagateState(...) : String | A.java:26:13:26:81 | ...?...:... : String | provenance | | | ||
| | A.java:26:50:26:50 | x : String | A.java:26:35:26:56 | propagateState(...) : String | provenance | Config | | ||
| | A.java:26:60:26:81 | propagateState(...) : String | A.java:26:13:26:81 | ...?...:... : String | provenance | | | ||
| | A.java:26:75:26:75 | x : String | A.java:26:60:26:81 | propagateState(...) : String | provenance | Config | | ||
| | A.java:28:20:28:27 | source(...) : String | A.java:29:33:29:37 | step0 : String | provenance | | | ||
| | A.java:29:20:29:38 | apply(...) : String | A.java:30:33:30:37 | step1 : String | provenance | | | ||
| | A.java:29:20:29:38 | apply(...) : String | A.java:30:33:30:37 | step1 : String | provenance | | | ||
| | A.java:29:33:29:37 | step0 : String | A.java:26:8:26:8 | x : String | provenance | | | ||
| | A.java:29:33:29:37 | step0 : String | A.java:26:8:26:8 | x : String | provenance | | | ||
| | A.java:29:33:29:37 | step0 : String | A.java:29:20:29:38 | apply(...) : String | provenance | Config | | ||
| | A.java:29:33:29:37 | step0 : String | A.java:29:20:29:38 | apply(...) : String | provenance | Config | | ||
| | A.java:30:20:30:38 | apply(...) : String | A.java:32:10:32:14 | step2 | provenance | | | ||
| | A.java:30:33:30:37 | step1 : String | A.java:26:8:26:8 | x : String | provenance | | | ||
| | A.java:30:33:30:37 | step1 : String | A.java:26:8:26:8 | x : String | provenance | | | ||
| | A.java:30:33:30:37 | step1 : String | A.java:30:20:30:38 | apply(...) : String | provenance | Config | | ||
| | A.java:30:33:30:37 | step1 : String | A.java:30:20:30:38 | apply(...) : String | provenance | Config | | ||
| subpaths | ||
| | A.java:18:33:18:37 | step0 : String | A.java:14:9:14:9 | x : String | A.java:14:14:14:35 | propagateState(...) : String | A.java:18:20:18:38 | apply(...) : String | | ||
| | A.java:18:33:18:37 | step0 : String | A.java:15:9:15:9 | x : String | A.java:15:14:15:35 | propagateState(...) : String | A.java:18:20:18:38 | apply(...) : String | | ||
| | A.java:19:33:19:37 | step1 : String | A.java:14:9:14:9 | x : String | A.java:14:14:14:35 | propagateState(...) : String | A.java:19:20:19:38 | apply(...) : String | | ||
| | A.java:19:33:19:37 | step1 : String | A.java:15:9:15:9 | x : String | A.java:15:14:15:35 | propagateState(...) : String | A.java:19:20:19:38 | apply(...) : String | | ||
| | A.java:29:33:29:37 | step0 : String | A.java:26:8:26:8 | x : String | A.java:26:13:26:81 | ...?...:... : String | A.java:29:20:29:38 | apply(...) : String | | ||
| | A.java:29:33:29:37 | step0 : String | A.java:26:8:26:8 | x : String | A.java:26:13:26:81 | ...?...:... : String | A.java:29:20:29:38 | apply(...) : String | | ||
| | A.java:30:33:30:37 | step1 : String | A.java:26:8:26:8 | x : String | A.java:26:13:26:81 | ...?...:... : String | A.java:30:20:30:38 | apply(...) : String | | ||
| | A.java:30:33:30:37 | step1 : String | A.java:26:8:26:8 | x : String | A.java:26:13:26:81 | ...?...:... : String | A.java:30:20:30:38 | apply(...) : String | | ||
| spuriousFlow | ||
| #select | ||
| | A.java:17:20:17:27 | source(...) : String | A.java:17:20:17:27 | source(...) : String | A.java:21:10:21:14 | step2 | Flow | | ||
| | A.java:28:20:28:27 | source(...) : String | A.java:28:20:28:27 | source(...) : String | A.java:32:10:32:14 | step2 | Flow | |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| /** | ||
| * @kind path-problem | ||
| */ | ||
|
|
||
| import java | ||
| import semmle.code.java.dataflow.DataFlow | ||
| import DataFlow | ||
|
|
||
| MethodCall propagateCall(string state) { | ||
| result.getMethod().getName() = "propagateState" and | ||
| state = result.getArgument(1).(StringLiteral).getValue() | ||
| } | ||
|
|
||
| module TestConfig implements StateConfigSig { | ||
| class FlowState = string; | ||
|
|
||
| predicate isSource(Node n, FlowState state) { | ||
| n.asExpr().(MethodCall).getMethod().getName() = "source" and state = ["A", "B"] | ||
| } | ||
|
|
||
| predicate isSink(Node n, FlowState state) { | ||
| n.asExpr() = any(MethodCall acc | acc.getMethod().getName() = "sink").getAnArgument() and | ||
| state = ["A", "B"] | ||
| } | ||
|
|
||
| predicate isAdditionalFlowStep(Node node1, FlowState state1, Node node2, FlowState state2) { | ||
| exists(MethodCall call | | ||
| call = propagateCall(state1) and | ||
| state2 = state1 and | ||
| node1.asExpr() = call.getArgument(0) and | ||
| node2.asExpr() = call | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| module TestFlow = DataFlow::GlobalWithState<TestConfig>; | ||
|
|
||
| module Graph = DataFlow::DeduplicatePathGraph<TestFlow::PathNode, TestFlow::PathGraph>; | ||
|
|
||
| /** | ||
| * Holds if `node` is reachable from a call to `propagateState` with the given `state` argument. | ||
| * `call` indicates if a call step was taken (i.e. into a subpath). | ||
| * | ||
| * We use this to check if one `propagateState` can flow out of another, which is not allowed. | ||
| */ | ||
| predicate reachableFromPropagate(Graph::PathNode node, string state, boolean call) { | ||
| node.getNode().asExpr() = propagateCall(state) and call = false | ||
| or | ||
| exists(Graph::PathNode prev | reachableFromPropagate(prev, state, call) | | ||
| Graph::edges(prev, node, _, _) | ||
| or | ||
| Graph::subpaths(prev, _, _, node) // arg -> out | ||
|
||
| ) | ||
| or | ||
| exists(Graph::PathNode prev | | ||
| reachableFromPropagate(prev, state, _) and | ||
| Graph::subpaths(prev, node, _, _) and // arg -> parameter | ||
| call = true | ||
| ) | ||
| or | ||
| exists(Graph::PathNode prev | | ||
| reachableFromPropagate(prev, state, call) and | ||
| Graph::subpaths(_, _, prev, node) and // return -> out | ||
| call = false | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Holds if `node` is the return value of a `propagateState` call that appears to be reachable | ||
| * with a different state than the one propagated by the call, indicating spurious flow resulting from | ||
| * merging path nodes. | ||
| */ | ||
| query predicate spuriousFlow(Graph::PathNode node, string state1, string state2) { | ||
| reachableFromPropagate(node, state1, _) and | ||
| node.getNode().asExpr() = propagateCall(state2) and | ||
| state1 != state2 | ||
| } | ||
|
|
||
| import Graph::PathGraph | ||
|
|
||
| from Graph::PathNode source, Graph::PathNode sink | ||
| where TestFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode()) | ||
| select source, source, sink, "Flow" | ||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,20 +16,9 @@ | |||||||||
|
|
||||||||||
| private import codeql.ruby.AST | ||||||||||
| private import codeql.ruby.security.CodeInjectionQuery | ||||||||||
| import CodeInjectionFlow::PathGraph | ||||||||||
| import DataFlow::DeduplicatePathGraph<CodeInjectionFlow::PathNode, CodeInjectionFlow::PathGraph> | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about not re-exporting the dedup'ed pathgraph, and instead export the boilerplate-translated
Suggested change
|
||||||||||
|
|
||||||||||
| from CodeInjectionFlow::PathNode source, CodeInjectionFlow::PathNode sink, Source sourceNode | ||||||||||
| where | ||||||||||
| CodeInjectionFlow::flowPath(source, sink) and | ||||||||||
| sourceNode = source.getNode() and | ||||||||||
| // removing duplications of the same path, but different flow-labels. | ||||||||||
| sink = | ||||||||||
| min(CodeInjectionFlow::PathNode otherSink | | ||||||||||
| CodeInjectionFlow::flowPath(any(CodeInjectionFlow::PathNode s | s.getNode() = sourceNode), | ||||||||||
| otherSink) and | ||||||||||
| otherSink.getNode() = sink.getNode() | ||||||||||
| | | ||||||||||
| otherSink order by otherSink.getState().getStringRepresentation() | ||||||||||
| ) | ||||||||||
| select sink.getNode(), source, sink, "This code execution depends on a $@.", sourceNode, | ||||||||||
| from PathNode source, PathNode sink | ||||||||||
| where CodeInjectionFlow::flowPath(source.getAnOriginalPathNode(), sink.getAnOriginalPathNode()) | ||||||||||
|
Comment on lines
+21
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. .. and
Suggested change
|
||||||||||
| select sink.getNode(), source, sink, "This code execution depends on a $@.", source.getNode(), | ||||||||||
| "user-provided value" | ||||||||||
Check warningCode scanning / CodeQL Consistent alert message Warning
The rb/code-injection query does not have the same alert message as cs, js.
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you need to amend this case slightly if you want the call bit to have meaning: