Ruby: generate type-tracking steps from simple summary specs

This commit is contained in:
Asger F
2022-08-30 12:56:28 +02:00
parent f1b99e867c
commit cd9cddf45a
7 changed files with 140 additions and 4 deletions

View File

@@ -373,7 +373,7 @@ private module Cached {
n instanceof SynthReturnNode
or
// Needed for stores in type tracking
TypeTrackerSpecific::basicStoreStep(_, n, _)
TypeTrackerSpecific::postUpdateStoreStep(_, n, _)
}
cached

View File

@@ -1,11 +1,15 @@
private import codeql.ruby.AST as Ast
private import codeql.ruby.CFG as Cfg
private import Cfg::CfgNodes
private import codeql.ruby.dataflow.FlowSummary
private import codeql.ruby.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
private import codeql.ruby.dataflow.internal.DataFlowPublic as DataFlowPublic
private import codeql.ruby.dataflow.internal.DataFlowPrivate as DataFlowPrivate
private import codeql.ruby.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import codeql.ruby.dataflow.internal.SsaImpl as SsaImpl
private import codeql.ruby.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import codeql.ruby.dataflow.internal.FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.dataflow.internal.AccessPathSyntax
class Node = DataFlowPublic::Node;
@@ -169,6 +173,27 @@ predicate returnStep(Node nodeFrom, Node nodeTo) {
* called.
*/
predicate basicStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) {
postUpdateStoreStep(nodeFrom, nodeTo, content)
or
exists(
DataFlowPublic::CallNode call, SummaryComponent input, DataFlowPublic::ContentSet contents,
SummaryComponent output
|
summarizableCall(call.asExpr().getExpr(), //
SummaryComponentStack::singleton(input),
SummaryComponentStack::push(SummaryComponent::content(contents),
SummaryComponentStack::singleton(output))) and
nodeFrom = evaluateSummaryComponentLocal(call, input) and
nodeTo = evaluateSummaryComponentLocal(call, output) and
content.asContent() = contents.getAStoreContent()
)
}
/**
* A `content`-store step from `nodeFrom -> nodeTo` where the destination node is a post-update
* node that should be treated as a local source node.
*/
predicate postUpdateStoreStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content) {
// TODO: support SetterMethodCall inside TuplePattern
exists(ExprNodes::MethodCallCfgNode call |
content = MkAttribute(getSetterCallAttributeName(call.getExpr())) and
@@ -205,6 +230,19 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content)
nodeFrom.asExpr() = call.getReceiver() and
nodeTo.asExpr() = call
)
or
exists(
DataFlowPublic::CallNode call, SummaryComponent input, DataFlowPublic::ContentSet contents,
SummaryComponent output
|
summarizableCall(call.asExpr().getExpr(), //
SummaryComponentStack::push(SummaryComponent::content(contents),
SummaryComponentStack::singleton(input)), //
SummaryComponentStack::singleton(output)) and
nodeFrom = evaluateSummaryComponentLocal(call, input) and
nodeTo = evaluateSummaryComponentLocal(call, output) and
content.asContent() = contents.getAReadContent()
)
}
/**
@@ -213,3 +251,43 @@ predicate basicLoadStep(Node nodeFrom, Node nodeTo, TypeTrackerContent content)
class Boolean extends boolean {
Boolean() { this = true or this = false }
}
/** Holds if `call` has a summary consisting of the given `input`/`output` pair. */
private predicate summarizableCall(
MethodCall call, SummaryComponentStack input, SummaryComponentStack output
) {
exists(SummarizedCallable callable |
call = callable.getACallSimple() and
callable.propagatesFlow(input, output, true)
)
}
/**
* Gets a data flow node corresponding an argument or return value of `call`,
* as specified by `component`.
*/
bindingset[call, component]
private DataFlowPublic::Node evaluateSummaryComponentLocal(
DataFlowPublic::CallNode call, SummaryComponent component
) {
exists(DataFlowDispatch::ParameterPosition pos | component = SummaryComponent::argument(pos) |
exists(int i |
pos.isPositional(i) and
result = call.getPositionalArgument(i)
)
or
exists(string name |
pos.isKeyword(name) and
result = call.getKeywordArgument(name)
)
or
pos.isBlock() and
result = call.getBlock()
or
pos.isSelf() and
result = call.getReceiver()
)
or
component = SummaryComponent::return() and
result = call
}

View File

@@ -4,3 +4,5 @@ classMethodCalls
instanceMethodCalls
| test1.rb:61:1:61:12 | Use getMember("M1").getMember("C1").getMethod("new").getReturn().getMethod("m").getReturn() |
| test1.rb:62:1:62:12 | Use getMember("M2").getMember("C3").getMethod("new").getReturn().getMethod("m").getReturn() |
flowThroughArray
| test1.rb:73:1:73:10 | call to m |

View File

@@ -2,6 +2,8 @@
* Tests of the public API of API Graphs
*/
import ruby
import codeql.ruby.DataFlow
import codeql.ruby.ApiGraphs
query predicate classMethodCalls(API::Node node) {
@@ -11,3 +13,8 @@ query predicate classMethodCalls(API::Node node) {
query predicate instanceMethodCalls(API::Node node) {
node = API::getTopLevelMember("M1").getMember("C1").getInstance().getReturn("m")
}
query predicate flowThroughArray(DataFlow::Node node) {
node =
API::getTopLevelMember("A").getMember("B").getMember("C").getMethod("m").getReturn().asSource()
}

View File

@@ -68,3 +68,6 @@ def userDefinedFunction(x, y)
x.customEntryPointCall(y) #$ call=entryPoint("CustomEntryPointCall") use=entryPoint("CustomEntryPointCall").getReturn() rhs=entryPoint("CustomEntryPointCall").getParameter(0)
x.customEntryPointUse(y) #$ use=entryPoint("CustomEntryPointUse")
end
array = [A::B::C] #$ use=getMember("Array").getMethod("[]").getReturn()
array[0].m #$ use=getMember("A").getMember("B").getMember("C").getMethod("m").getReturn()

View File

@@ -55,12 +55,12 @@ track
| type_tracker.rb:14:5:14:13 | call to field= | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps | type_tracker.rb:2:16:2:18 | val |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self (field) |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker with call steps with content attribute field | type_tracker.rb:7:5:9:7 | self in field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:5:14:13 | call to field= |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:14:17:14:23 | "hello" |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps | type_tracker.rb:15:10:15:18 | call to field |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content field | type_tracker.rb:14:5:14:7 | [post] var |
| type_tracker.rb:14:17:14:23 | "hello" | type tracker without call steps with content attribute field | type_tracker.rb:14:5:14:7 | [post] var |
| type_tracker.rb:14:17:14:23 | __synth__0 | type tracker without call steps | type_tracker.rb:14:17:14:23 | __synth__0 |
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:12:1:16:3 | return return in m |
| type_tracker.rb:15:5:15:18 | call to puts | type tracker without call steps | type_tracker.rb:15:5:15:18 | call to puts |
@@ -147,6 +147,25 @@ track
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker with call steps | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type tracker without call steps | type_tracker.rb:32:26:32:26 | 8 |
| type_tracker.rb:34:1:37:3 | &block | type tracker without call steps | type_tracker.rb:34:1:37:3 | &block |
| type_tracker.rb:34:1:37:3 | return return in throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | return return in throughArray |
| type_tracker.rb:34:1:37:3 | self in throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | self in throughArray |
| type_tracker.rb:34:1:37:3 | throughArray | type tracker without call steps | type_tracker.rb:34:1:37:3 | throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker with call steps with content element 0 | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:1:37:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type tracker without call steps with content element 0 | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:35:5:35:7 | tmp | type tracker without call steps | type_tracker.rb:35:5:35:7 | tmp |
| type_tracker.rb:35:11:35:15 | Array | type tracker without call steps | type_tracker.rb:35:11:35:15 | Array |
| type_tracker.rb:35:11:35:15 | call to [] | type tracker without call steps | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:36:5:36:10 | ...[...] | type tracker without call steps | type_tracker.rb:34:1:37:3 | return return in throughArray |
| type_tracker.rb:36:5:36:10 | ...[...] | type tracker without call steps | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:36:9:36:9 | 0 | type tracker without call steps | type_tracker.rb:36:9:36:9 | 0 |
trackEnd
| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:1:1:10:3 | self (type_tracker.rb) |
| type_tracker.rb:1:1:10:3 | self (type_tracker.rb) | type_tracker.rb:18:1:21:3 | self (positional) |
@@ -358,3 +377,25 @@ trackEnd
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:25:13:25:14 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:26:10:26:11 | p1 |
| type_tracker.rb:32:26:32:26 | 8 | type_tracker.rb:32:26:32:26 | 8 |
| type_tracker.rb:34:1:37:3 | &block | type_tracker.rb:34:1:37:3 | &block |
| type_tracker.rb:34:1:37:3 | return return in throughArray | type_tracker.rb:34:1:37:3 | return return in throughArray |
| type_tracker.rb:34:1:37:3 | self in throughArray | type_tracker.rb:34:1:37:3 | self in throughArray |
| type_tracker.rb:34:1:37:3 | throughArray | type_tracker.rb:34:1:37:3 | throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:1:37:3 | return return in throughArray |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:34:18:34:20 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:35:12:35:14 | obj |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:34:18:34:20 | obj | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:35:5:35:7 | tmp | type_tracker.rb:35:5:35:7 | tmp |
| type_tracker.rb:35:11:35:15 | Array | type_tracker.rb:35:11:35:15 | Array |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:5:35:15 | ... = ... |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:35:11:35:15 | call to [] |
| type_tracker.rb:35:11:35:15 | call to [] | type_tracker.rb:36:5:36:7 | tmp |
| type_tracker.rb:36:5:36:10 | ...[...] | type_tracker.rb:34:1:37:3 | return return in throughArray |
| type_tracker.rb:36:5:36:10 | ...[...] | type_tracker.rb:36:5:36:10 | ...[...] |
| type_tracker.rb:36:9:36:9 | 0 | type_tracker.rb:36:9:36:9 | 0 |

View File

@@ -30,3 +30,8 @@ end
keyword(p1: 3, p2: 4)
keyword(p2: 5, p1: 6)
keyword(:p2 => 7, :p1 => 8)
def throughArray(obj)
tmp = [obj]
tmp[0]
end