Skip to content

Commit 948ebe4

Browse files
authored
Merge pull request github#7568 from aibaars/ruby-pattern-matching-taint
Ruby: taint steps for pattern matches
2 parents b7690e5 + 67962cb commit 948ebe4

File tree

16 files changed

+533
-54
lines changed

16 files changed

+533
-54
lines changed

ruby/ql/lib/codeql/ruby/ast/Pattern.qll

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,10 @@ private class TPattern =
9797
* ```
9898
*/
9999
class CasePattern extends AstNode, TPattern {
100-
CasePattern() { casePattern(toGenerated(this)) }
100+
CasePattern() {
101+
casePattern(toGenerated(this)) or
102+
synthChild(any(HashPattern p), _, this)
103+
}
101104
}
102105

103106
/**
@@ -207,7 +210,7 @@ class FindPattern extends CasePattern, TFindPattern {
207210
CasePattern getAnElement() { result = this.getElement(_) }
208211

209212
/**
210-
* Gets the variable for the prefix of this list pattern, if any. For example `init` in:
213+
* Gets the variable for the prefix of this find pattern, if any. For example `init` in:
211214
* ```rb
212215
* in List[*init, "a", Integer => x, *tail]
213216
* ```
@@ -217,7 +220,7 @@ class FindPattern extends CasePattern, TFindPattern {
217220
}
218221

219222
/**
220-
* Gets the variable for the suffix of this list pattern, if any. For example `tail` in:
223+
* Gets the variable for the suffix of this find pattern, if any. For example `tail` in:
221224
* ```rb
222225
* in List[*init, "a", Integer => x, *tail]
223226
* ```
@@ -267,7 +270,10 @@ class HashPattern extends CasePattern, THashPattern {
267270
StringlikeLiteral getKey(int n) { toGenerated(result) = this.keyValuePair(n).getKey() }
268271

269272
/** Gets the value of the `n`th pair. */
270-
CasePattern getValue(int n) { toGenerated(result) = this.keyValuePair(n).getValue() }
273+
CasePattern getValue(int n) {
274+
toGenerated(result) = this.keyValuePair(n).getValue() or
275+
synthChild(this, n, result)
276+
}
271277

272278
/** Gets the value for a given key name. */
273279
CasePattern getValueByKey(string key) {
@@ -383,6 +389,7 @@ class ParenthesizedPattern extends CasePattern, TParenthesizedPattern {
383389

384390
ParenthesizedPattern() { this = TParenthesizedPattern(g) }
385391

392+
/** Gets the underlying pattern. */
386393
final CasePattern getPattern() { toGenerated(result) = g.getChild() }
387394

388395
final override string getAPrimaryQlClass() { result = "ParenthesizedPattern" }

ruby/ql/lib/codeql/ruby/ast/Variable.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
115115
implicitWriteAccess(toGenerated(this))
116116
or
117117
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
118+
or
119+
this = any(HashPattern p).getValue(_)
118120
}
119121

120122
final override string toString() { result = VariableAccessImpl.super.toString() }

ruby/ql/lib/codeql/ruby/ast/internal/Synthesis.qll

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ private import codeql.ruby.ast.internal.Expr
77
private import codeql.ruby.ast.internal.Variable
88
private import codeql.ruby.ast.internal.Pattern
99
private import codeql.ruby.ast.internal.Scope
10+
private import codeql.ruby.ast.internal.TreeSitter
1011
private import codeql.ruby.AST
1112

1213
/** A synthesized AST node kind. */
@@ -921,3 +922,35 @@ private module ForLoopDesugar {
921922
}
922923
}
923924
}
925+
926+
/**
927+
* ```rb
928+
* { a: }
929+
* ```
930+
* desugars to,
931+
* ```rb
932+
* { a: a }
933+
* ```
934+
*/
935+
private module ImplicitHashValueSynthesis {
936+
private Ruby::AstNode keyWithoutValue(HashPattern parent, int i) {
937+
exists(Ruby::KeywordPattern pair |
938+
result = pair.getKey() and
939+
result = toGenerated(parent.getKey(i)) and
940+
not exists(pair.getValue())
941+
)
942+
}
943+
944+
private class ImplicitHashValueSynthesis extends Synthesis {
945+
final override predicate child(AstNode parent, int i, Child child) {
946+
exists(TVariableReal variable |
947+
access(keyWithoutValue(parent, i), variable) and
948+
child = SynthChild(LocalVariableAccessRealKind(variable))
949+
)
950+
}
951+
952+
final override predicate location(AstNode n, Location l) {
953+
exists(HashPattern p, int i | n = p.getValue(i) and l = keyWithoutValue(p, i).getLocation())
954+
}
955+
}
956+
}

ruby/ql/lib/codeql/ruby/ast/internal/Variable.qll

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ predicate implicitAssignmentNode(Ruby::AstNode n) {
3939
or
4040
n = any(Ruby::HashPattern parent).getChild(_).(Ruby::HashSplatParameter).getName()
4141
or
42+
n = any(Ruby::KeywordPattern parent | not exists(parent.getValue())).getKey()
43+
or
4244
n = any(Ruby::ExceptionVariable ev).getChild()
4345
or
4446
n = any(Ruby::For for).getPattern()
@@ -104,11 +106,23 @@ private predicate scopeDefinesParameterVariable(
104106
)
105107
}
106108

109+
pragma[nomagic]
110+
private string variableNameInScope(Ruby::AstNode i, Scope::Range scope) {
111+
scope = scopeOf(i) and
112+
(
113+
result = i.(Ruby::Identifier).getValue()
114+
or
115+
exists(Ruby::KeywordPattern p | i = p.getKey() and not exists(p.getValue()) |
116+
result = i.(Ruby::String).getChild(0).(Ruby::StringContent).getValue() or
117+
result = i.(Ruby::HashKeySymbol).getValue()
118+
)
119+
)
120+
}
121+
107122
/** Holds if `name` is assigned in `scope` at `i`. */
108-
private predicate scopeAssigns(Scope::Range scope, string name, Ruby::Identifier i) {
123+
private predicate scopeAssigns(Scope::Range scope, string name, Ruby::AstNode i) {
109124
(explicitAssignmentNode(i, _) or implicitAssignmentNode(i)) and
110-
name = i.getValue() and
111-
scope = scopeOf(i)
125+
name = variableNameInScope(i, scope)
112126
}
113127

114128
cached
@@ -132,11 +146,11 @@ private module Cached {
132146
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
133147
)
134148
} or
135-
TLocalVariableReal(Scope::Range scope, string name, Ruby::Identifier i) {
149+
TLocalVariableReal(Scope::Range scope, string name, Ruby::AstNode i) {
136150
scopeDefinesParameterVariable(scope, name, i)
137151
or
138152
i =
139-
min(Ruby::Identifier other |
153+
min(Ruby::AstNode other |
140154
scopeAssigns(scope, name, other)
141155
|
142156
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
@@ -295,13 +309,18 @@ private module Cached {
295309
i = any(Ruby::WhileModifier x).getBody()
296310
}
297311

312+
pragma[nomagic]
313+
private predicate hasScopeAndName(VariableReal variable, Scope::Range scope, string name) {
314+
variable.getNameImpl() = name and
315+
scope = variable.getDeclaringScopeImpl()
316+
}
317+
298318
cached
299-
predicate access(Ruby::Identifier access, VariableReal variable) {
300-
exists(string name |
301-
variable.getNameImpl() = name and
302-
name = access.getValue()
319+
predicate access(Ruby::AstNode access, VariableReal variable) {
320+
exists(string name, Scope::Range scope |
321+
pragma[only_bind_into](name) = variableNameInScope(access, scope)
303322
|
304-
variable.getDeclaringScopeImpl() = scopeOf(access) and
323+
hasScopeAndName(variable, scope, name) and
305324
not access.getLocation().strictlyBefore(variable.getLocationImpl()) and
306325
// In case of overlapping parameter names, later parameters should not
307326
// be considered accesses to the first parameter
@@ -310,15 +329,15 @@ private module Cached {
310329
else any()
311330
or
312331
exists(Scope::Range declScope |
313-
variable.getDeclaringScopeImpl() = declScope and
314-
inherits(scopeOf(access), name, declScope)
332+
hasScopeAndName(variable, declScope, pragma[only_bind_into](name)) and
333+
inherits(scope, name, declScope)
315334
)
316335
)
317336
}
318337

319338
private class Access extends Ruby::Token {
320339
Access() {
321-
access(this, _) or
340+
access(this.(Ruby::Identifier), _) or
322341
this instanceof Ruby::GlobalVariable or
323342
this instanceof Ruby::InstanceVariable or
324343
this instanceof Ruby::ClassVariable or
@@ -371,7 +390,7 @@ private predicate inherits(Scope::Range scope, string name, Scope::Range outer)
371390
(
372391
scopeDefinesParameterVariable(outer, name, _)
373392
or
374-
exists(Ruby::Identifier i |
393+
exists(Ruby::AstNode i |
375394
scopeAssigns(outer, name, i) and
376395
i.getLocation().strictlyBefore(scope.getLocation())
377396
)
@@ -420,7 +439,7 @@ private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof
420439
class LocalVariableReal extends VariableReal, TLocalVariableReal {
421440
private Scope::Range scope;
422441
private string name;
423-
private Ruby::Identifier i;
442+
private Ruby::AstNode i;
424443

425444
LocalVariableReal() { this = TLocalVariableReal(scope, name, i) }
426445

0 commit comments

Comments
 (0)