Merge pull request #9287 from aibaars/instance-variable-flow-2

Ruby: flow through getters/setters
This commit is contained in:
Arthur Baars
2022-05-27 10:49:20 +02:00
committed by GitHub
8 changed files with 166 additions and 3 deletions

View File

@@ -145,6 +145,21 @@ class SetterMethodCall extends MethodCall, TMethodCallSynth {
SetterMethodCall() { this = TMethodCallSynth(_, _, _, true, _) }
final override string getAPrimaryQlClass() { result = "SetterMethodCall" }
/**
* Gets the name of the method being called without the trailing `=`. For example, in the following
* two statements the target name is `value`:
* ```rb
* foo.value=(1)
* foo.value = 1
* ```
*/
final string getTargetName() {
exists(string methodName |
methodName = this.getMethodName() and
result = methodName.prefix(methodName.length() - 1)
)
}
}
/**

View File

@@ -7,6 +7,7 @@ private import DataFlowDispatch
private import SsaImpl as SsaImpl
private import FlowSummaryImpl as FlowSummaryImpl
private import FlowSummaryImplSpecific as FlowSummaryImplSpecific
private import codeql.ruby.frameworks.data.ModelsAsData
/** Gets the callable in which this node occurs. */
DataFlowCallable nodeGetEnclosingCallable(NodeImpl n) { result = n.getEnclosingCallable() }
@@ -358,7 +359,22 @@ private module Cached {
TUnknownElementContent() or
TKnownPairValueContent(ConstantValue cv) or
TUnknownPairValueContent() or
TFieldContent(string name) { name = any(InstanceVariable v).getName() }
TFieldContent(string name) {
name = any(InstanceVariable v).getName()
or
name = "@" + any(SetterMethodCall c).getTargetName()
or
// The following equation unfortunately leads to a non-monotonic recursion error:
// name = any(AccessPathToken a).getAnArgument("Field")
// Therefore, we use the following instead to extract the field names from the
// external model data. This, unfortunately, does not included any field names used
// in models defined in QL code.
exists(string input, string output |
ModelOutput::relevantSummaryModel(_, _, _, input, output, _)
|
name = [input, output].regexpFind("(?<=(^|\\.)Field\\[)[^\\]]+(?=\\])", _, _).trim()
)
}
/**
* Holds if `e` is an `ExprNode` that may be returned by a call to `c`.
@@ -880,6 +896,16 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
)
).getReceiver()
or
// Attribute assignment, `receiver.property = value`
node2.(PostUpdateNode).getPreUpdateNode().asExpr() =
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
node1.asExpr() = call.getArgument(0) and
call.getNumberOfArguments() = 1 and
c.isSingleton(any(Content::FieldContent ct |
ct.getName() = "@" + call.getExpr().(SetterMethodCall).getTargetName()
))
).getReceiver()
or
FlowSummaryImpl::Private::Steps::summaryStoreStep(node1, c, node2)
or
// Needed for pairs passed into method calls where the key is not a symbol,
@@ -923,6 +949,19 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
))
)
or
// Attribute read, `receiver.field`. Note that we do not check whether
// the `field` method is really an attribute reader. This is probably fine
// because the read step has only effect if there exists a matching store step
// (instance variable assignment or setter method call).
node2.asExpr() =
any(CfgNodes::ExprNodes::MethodCallCfgNode call |
node1.asExpr() = call.getReceiver() and
call.getNumberOfArguments() = 0 and
c.isSingleton(any(Content::FieldContent ct |
ct.getName() = "@" + call.getExpr().getMethodName()
))
)
or
FlowSummaryImpl::Private::Steps::summaryReadStep(node1, c, node2)
}

View File

@@ -101,6 +101,9 @@ SummaryComponent interpretComponentSpecific(AccessPathToken c) {
or
result = interpretElementArg(c.getAnArgument("Element"))
or
result =
FlowSummary::SummaryComponent::content(TSingletonContent(TFieldContent(c.getAnArgument("Field"))))
or
exists(ContentSet cs |
FlowSummary::SummaryComponent::content(cs) = interpretElementArg(c.getAnArgument("WithElement")) and
result = FlowSummary::SummaryComponent::withContent(cs)

View File

@@ -33,6 +33,34 @@ edges
| instance_variables.rb:20:15:20:23 | call to source : | instance_variables.rb:20:1:20:3 | [post] bar [@field] : |
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : |
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:21:6:21:18 | call to inc_field |
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | instance_variables.rb:25:6:25:9 | foo1 [@field] : |
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | instance_variables.rb:25:6:25:9 | foo1 [@field] : |
| instance_variables.rb:24:14:24:23 | call to source : | instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : |
| instance_variables.rb:24:14:24:23 | call to source : | instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : |
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | instance_variables.rb:25:6:25:15 | call to field |
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | instance_variables.rb:25:6:25:15 | call to field |
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | instance_variables.rb:29:6:29:9 | foo2 [@field] : |
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | instance_variables.rb:29:6:29:9 | foo2 [@field] : |
| instance_variables.rb:28:14:28:23 | call to source : | instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : |
| instance_variables.rb:28:14:28:23 | call to source : | instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:29:6:29:19 | call to get_field |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:29:6:29:19 | call to get_field |
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | instance_variables.rb:33:6:33:9 | foo3 [@field] : |
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | instance_variables.rb:33:6:33:9 | foo3 [@field] : |
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : |
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : |
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | instance_variables.rb:33:6:33:15 | call to field |
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | instance_variables.rb:33:6:33:15 | call to field |
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | instance_variables.rb:37:6:37:9 | foo4 [@other] : |
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | instance_variables.rb:37:6:37:9 | foo4 [@other] : |
| instance_variables.rb:36:14:36:23 | call to source : | instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : |
| instance_variables.rb:36:14:36:23 | call to source : | instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : |
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | instance_variables.rb:37:6:37:15 | call to other |
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | instance_variables.rb:37:6:37:15 | call to other |
nodes
| instance_variables.rb:2:19:2:19 | x : | semmle.label | x : |
| instance_variables.rb:2:19:2:19 | x : | semmle.label | x : |
@@ -70,6 +98,38 @@ nodes
| instance_variables.rb:20:15:20:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:21:6:21:8 | bar [@field] : | semmle.label | bar [@field] : |
| instance_variables.rb:21:6:21:18 | call to inc_field | semmle.label | call to inc_field |
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | semmle.label | [post] foo1 [@field] : |
| instance_variables.rb:24:1:24:4 | [post] foo1 [@field] : | semmle.label | [post] foo1 [@field] : |
| instance_variables.rb:24:14:24:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:24:14:24:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | semmle.label | foo1 [@field] : |
| instance_variables.rb:25:6:25:9 | foo1 [@field] : | semmle.label | foo1 [@field] : |
| instance_variables.rb:25:6:25:15 | call to field | semmle.label | call to field |
| instance_variables.rb:25:6:25:15 | call to field | semmle.label | call to field |
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | semmle.label | [post] foo2 [@field] : |
| instance_variables.rb:28:1:28:4 | [post] foo2 [@field] : | semmle.label | [post] foo2 [@field] : |
| instance_variables.rb:28:14:28:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:28:14:28:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | semmle.label | foo2 [@field] : |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | semmle.label | foo2 [@field] : |
| instance_variables.rb:29:6:29:19 | call to get_field | semmle.label | call to get_field |
| instance_variables.rb:29:6:29:19 | call to get_field | semmle.label | call to get_field |
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | semmle.label | [post] foo3 [@field] : |
| instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : | semmle.label | [post] foo3 [@field] : |
| instance_variables.rb:32:16:32:25 | call to source : | semmle.label | call to source : |
| instance_variables.rb:32:16:32:25 | call to source : | semmle.label | call to source : |
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | semmle.label | foo3 [@field] : |
| instance_variables.rb:33:6:33:9 | foo3 [@field] : | semmle.label | foo3 [@field] : |
| instance_variables.rb:33:6:33:15 | call to field | semmle.label | call to field |
| instance_variables.rb:33:6:33:15 | call to field | semmle.label | call to field |
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | semmle.label | [post] foo4 [@other] : |
| instance_variables.rb:36:1:36:4 | [post] foo4 [@other] : | semmle.label | [post] foo4 [@other] : |
| instance_variables.rb:36:14:36:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:36:14:36:23 | call to source : | semmle.label | call to source : |
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | semmle.label | foo4 [@other] : |
| instance_variables.rb:37:6:37:9 | foo4 [@other] : | semmle.label | foo4 [@other] : |
| instance_variables.rb:37:6:37:15 | call to other | semmle.label | call to other |
| instance_variables.rb:37:6:37:15 | call to other | semmle.label | call to other |
subpaths
| instance_variables.rb:16:15:16:24 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:16:1:16:3 | [post] foo [@field] : |
| instance_variables.rb:16:15:16:24 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:16:1:16:3 | [post] foo [@field] : |
@@ -78,7 +138,15 @@ subpaths
| instance_variables.rb:20:15:20:23 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:20:1:20:3 | [post] bar [@field] : |
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : | instance_variables.rb:21:6:21:18 | call to inc_field |
| instance_variables.rb:21:6:21:8 | bar [@field] : | instance_variables.rb:8:5:10:7 | self in inc_field [@field] : | instance_variables.rb:9:9:9:14 | [post] self [@field] : | instance_variables.rb:21:6:21:18 | call to inc_field |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : | instance_variables.rb:6:9:6:21 | return : | instance_variables.rb:29:6:29:19 | call to get_field |
| instance_variables.rb:29:6:29:9 | foo2 [@field] : | instance_variables.rb:5:5:7:7 | self in get_field [@field] : | instance_variables.rb:6:9:6:21 | return : | instance_variables.rb:29:6:29:19 | call to get_field |
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
| instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:2:19:2:19 | x : | instance_variables.rb:3:9:3:14 | [post] self [@field] : | instance_variables.rb:32:1:32:4 | [post] foo3 [@field] : |
#select
| instance_variables.rb:12:10:12:13 | @foo | instance_variables.rb:11:12:11:22 | call to source : | instance_variables.rb:12:10:12:13 | @foo | $@ | instance_variables.rb:11:12:11:22 | call to source : | call to source : |
| instance_variables.rb:17:6:17:18 | call to get_field | instance_variables.rb:16:15:16:24 | call to source : | instance_variables.rb:17:6:17:18 | call to get_field | $@ | instance_variables.rb:16:15:16:24 | call to source : | call to source : |
| instance_variables.rb:21:6:21:18 | call to inc_field | instance_variables.rb:20:15:20:23 | call to source : | instance_variables.rb:21:6:21:18 | call to inc_field | $@ | instance_variables.rb:20:15:20:23 | call to source : | call to source : |
| instance_variables.rb:25:6:25:15 | call to field | instance_variables.rb:24:14:24:23 | call to source : | instance_variables.rb:25:6:25:15 | call to field | $@ | instance_variables.rb:24:14:24:23 | call to source : | call to source : |
| instance_variables.rb:29:6:29:19 | call to get_field | instance_variables.rb:28:14:28:23 | call to source : | instance_variables.rb:29:6:29:19 | call to get_field | $@ | instance_variables.rb:28:14:28:23 | call to source : | call to source : |
| instance_variables.rb:33:6:33:15 | call to field | instance_variables.rb:32:16:32:25 | call to source : | instance_variables.rb:33:6:33:15 | call to field | $@ | instance_variables.rb:32:16:32:25 | call to source : | call to source : |
| instance_variables.rb:37:6:37:15 | call to other | instance_variables.rb:36:14:36:23 | call to source : | instance_variables.rb:37:6:37:15 | call to other | $@ | instance_variables.rb:36:14:36:23 | call to source : | call to source : |

View File

@@ -18,4 +18,20 @@ sink(foo.get_field) # $ hasValueFlow=42
bar = Foo.new
bar.set_field(source(5))
sink(bar.inc_field) # $ hasTaintFlow=5
sink(bar.inc_field) # $ hasTaintFlow=5
foo1 = Foo.new
foo1.field = source(20)
sink(foo1.field) # $ hasValueFlow=20
foo2 = Foo.new
foo2.field = source(21)
sink(foo2.get_field) # $ hasValueFlow=21
foo3 = Foo.new
foo3.set_field(source(22))
sink(foo3.field) # $ hasValueFlow=22
foo4 = "hello"
foo4.other = source(23)
sink(foo4.other) # $ hasValueFlow=23

View File

@@ -86,6 +86,12 @@ edges
| summaries.rb:82:1:82:1 | a [element 2] : | summaries.rb:82:1:82:1 | [post] a [element 2] : |
| summaries.rb:85:6:85:6 | a [element 2] : | summaries.rb:85:6:85:9 | ...[...] |
| summaries.rb:85:6:85:6 | a [element 2] : | summaries.rb:85:6:85:9 | ...[...] |
| summaries.rb:88:1:88:1 | [post] x [@value] : | summaries.rb:89:6:89:6 | x [@value] : |
| summaries.rb:88:1:88:1 | [post] x [@value] : | summaries.rb:89:6:89:6 | x [@value] : |
| summaries.rb:88:13:88:26 | call to source : | summaries.rb:88:1:88:1 | [post] x [@value] : |
| summaries.rb:88:13:88:26 | call to source : | summaries.rb:88:1:88:1 | [post] x [@value] : |
| summaries.rb:89:6:89:6 | x [@value] : | summaries.rb:89:6:89:16 | call to get_value |
| summaries.rb:89:6:89:6 | x [@value] : | summaries.rb:89:6:89:16 | call to get_value |
nodes
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
| summaries.rb:1:11:1:36 | call to identity : | semmle.label | call to identity : |
@@ -183,6 +189,14 @@ nodes
| summaries.rb:85:6:85:6 | a [element 2] : | semmle.label | a [element 2] : |
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:85:6:85:9 | ...[...] | semmle.label | ...[...] |
| summaries.rb:88:1:88:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
| summaries.rb:88:1:88:1 | [post] x [@value] : | semmle.label | [post] x [@value] : |
| summaries.rb:88:13:88:26 | call to source : | semmle.label | call to source : |
| summaries.rb:88:13:88:26 | call to source : | semmle.label | call to source : |
| summaries.rb:89:6:89:6 | x [@value] : | semmle.label | x [@value] : |
| summaries.rb:89:6:89:6 | x [@value] : | semmle.label | x [@value] : |
| summaries.rb:89:6:89:16 | call to get_value | semmle.label | call to get_value |
| summaries.rb:89:6:89:16 | call to get_value | semmle.label | call to get_value |
subpaths
invalidSpecComponent
#select
@@ -227,6 +241,8 @@ invalidSpecComponent
| summaries.rb:80:6:80:9 | ...[...] | summaries.rb:74:15:74:29 | call to source : | summaries.rb:80:6:80:9 | ...[...] | $@ | summaries.rb:74:15:74:29 | call to source : | call to source : |
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:74:32:74:46 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:74:32:74:46 | call to source : | call to source : |
| summaries.rb:85:6:85:9 | ...[...] | summaries.rb:74:32:74:46 | call to source : | summaries.rb:85:6:85:9 | ...[...] | $@ | summaries.rb:74:32:74:46 | call to source : | call to source : |
| summaries.rb:89:6:89:16 | call to get_value | summaries.rb:88:13:88:26 | call to source : | summaries.rb:89:6:89:16 | call to get_value | $@ | summaries.rb:88:13:88:26 | call to source : | call to source : |
| summaries.rb:89:6:89:16 | call to get_value | summaries.rb:88:13:88:26 | call to source : | summaries.rb:89:6:89:16 | call to get_value | $@ | summaries.rb:88:13:88:26 | call to source : | call to source : |
warning
| CSV type row should have 5 columns but has 2: test;TooFewColumns |
| CSV type row should have 5 columns but has 8: test;TooManyColumns;;;Member[Foo].Instance;too;many;columns |

View File

@@ -66,6 +66,8 @@ private class StepsFromModel extends ModelInput::SummaryModelCsv {
override predicate row(string row) {
row =
[
";any;Method[set_value];Argument[0];Argument[self].Field[@value];value",
";any;Method[get_value];Argument[self].Field[@value];ReturnValue;value",
";;Member[Foo].Method[firstArg];Argument[0];ReturnValue;taint",
";;Member[Foo].Method[secondArg];Argument[1];ReturnValue;taint",
";;Member[Foo].Method[onlyWithoutBlock].WithoutBlock;Argument[0];ReturnValue;taint",

View File

@@ -82,4 +82,8 @@ sink(b[2])
a.withoutElementOne()
sink(a[0])
sink(a[1])
sink(a[2]) # $ hasValueFlow=elem2
sink(a[2]) # $ hasValueFlow=elem2
x = Foo.new
x.set_value(source("attr"))
sink(x.get_value) # $ hasValueFlow=attr