Merge pull request #7568 from aibaars/ruby-pattern-matching-taint

Ruby: taint steps for pattern matches
This commit is contained in:
Arthur Baars
2022-01-26 10:27:47 +01:00
committed by GitHub
16 changed files with 533 additions and 54 deletions

View File

@@ -97,7 +97,10 @@ private class TPattern =
* ```
*/
class CasePattern extends AstNode, TPattern {
CasePattern() { casePattern(toGenerated(this)) }
CasePattern() {
casePattern(toGenerated(this)) or
synthChild(any(HashPattern p), _, this)
}
}
/**
@@ -207,7 +210,7 @@ class FindPattern extends CasePattern, TFindPattern {
CasePattern getAnElement() { result = this.getElement(_) }
/**
* Gets the variable for the prefix of this list pattern, if any. For example `init` in:
* Gets the variable for the prefix of this find pattern, if any. For example `init` in:
* ```rb
* in List[*init, "a", Integer => x, *tail]
* ```
@@ -217,7 +220,7 @@ class FindPattern extends CasePattern, TFindPattern {
}
/**
* Gets the variable for the suffix of this list pattern, if any. For example `tail` in:
* Gets the variable for the suffix of this find pattern, if any. For example `tail` in:
* ```rb
* in List[*init, "a", Integer => x, *tail]
* ```
@@ -267,7 +270,10 @@ class HashPattern extends CasePattern, THashPattern {
StringlikeLiteral getKey(int n) { toGenerated(result) = this.keyValuePair(n).getKey() }
/** Gets the value of the `n`th pair. */
CasePattern getValue(int n) { toGenerated(result) = this.keyValuePair(n).getValue() }
CasePattern getValue(int n) {
toGenerated(result) = this.keyValuePair(n).getValue() or
synthChild(this, n, result)
}
/** Gets the value for a given key name. */
CasePattern getValueByKey(string key) {
@@ -383,6 +389,7 @@ class ParenthesizedPattern extends CasePattern, TParenthesizedPattern {
ParenthesizedPattern() { this = TParenthesizedPattern(g) }
/** Gets the underlying pattern. */
final CasePattern getPattern() { toGenerated(result) = g.getChild() }
final override string getAPrimaryQlClass() { result = "ParenthesizedPattern" }

View File

@@ -115,6 +115,8 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
implicitWriteAccess(toGenerated(this))
or
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
or
this = any(HashPattern p).getValue(_)
}
final override string toString() { result = VariableAccessImpl.super.toString() }

View File

@@ -7,6 +7,7 @@ private import codeql.ruby.ast.internal.Expr
private import codeql.ruby.ast.internal.Variable
private import codeql.ruby.ast.internal.Pattern
private import codeql.ruby.ast.internal.Scope
private import codeql.ruby.ast.internal.TreeSitter
private import codeql.ruby.AST
/** A synthesized AST node kind. */
@@ -921,3 +922,35 @@ private module ForLoopDesugar {
}
}
}
/**
* ```rb
* { a: }
* ```
* desugars to,
* ```rb
* { a: a }
* ```
*/
private module ImplicitHashValueSynthesis {
private Ruby::AstNode keyWithoutValue(HashPattern parent, int i) {
exists(Ruby::KeywordPattern pair |
result = pair.getKey() and
result = toGenerated(parent.getKey(i)) and
not exists(pair.getValue())
)
}
private class ImplicitHashValueSynthesis extends Synthesis {
final override predicate child(AstNode parent, int i, Child child) {
exists(TVariableReal variable |
access(keyWithoutValue(parent, i), variable) and
child = SynthChild(LocalVariableAccessRealKind(variable))
)
}
final override predicate location(AstNode n, Location l) {
exists(HashPattern p, int i | n = p.getValue(i) and l = keyWithoutValue(p, i).getLocation())
}
}
}

View File

@@ -39,6 +39,8 @@ predicate implicitAssignmentNode(Ruby::AstNode n) {
or
n = any(Ruby::HashPattern parent).getChild(_).(Ruby::HashSplatParameter).getName()
or
n = any(Ruby::KeywordPattern parent | not exists(parent.getValue())).getKey()
or
n = any(Ruby::ExceptionVariable ev).getChild()
or
n = any(Ruby::For for).getPattern()
@@ -104,11 +106,23 @@ private predicate scopeDefinesParameterVariable(
)
}
pragma[nomagic]
private string variableNameInScope(Ruby::AstNode i, Scope::Range scope) {
scope = scopeOf(i) and
(
result = i.(Ruby::Identifier).getValue()
or
exists(Ruby::KeywordPattern p | i = p.getKey() and not exists(p.getValue()) |
result = i.(Ruby::String).getChild(0).(Ruby::StringContent).getValue() or
result = i.(Ruby::HashKeySymbol).getValue()
)
)
}
/** Holds if `name` is assigned in `scope` at `i`. */
private predicate scopeAssigns(Scope::Range scope, string name, Ruby::Identifier i) {
private predicate scopeAssigns(Scope::Range scope, string name, Ruby::AstNode i) {
(explicitAssignmentNode(i, _) or implicitAssignmentNode(i)) and
name = i.getValue() and
scope = scopeOf(i)
name = variableNameInScope(i, scope)
}
cached
@@ -132,11 +146,11 @@ private module Cached {
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
)
} or
TLocalVariableReal(Scope::Range scope, string name, Ruby::Identifier i) {
TLocalVariableReal(Scope::Range scope, string name, Ruby::AstNode i) {
scopeDefinesParameterVariable(scope, name, i)
or
i =
min(Ruby::Identifier other |
min(Ruby::AstNode other |
scopeAssigns(scope, name, other)
|
other order by other.getLocation().getStartLine(), other.getLocation().getStartColumn()
@@ -295,13 +309,18 @@ private module Cached {
i = any(Ruby::WhileModifier x).getBody()
}
pragma[nomagic]
private predicate hasScopeAndName(VariableReal variable, Scope::Range scope, string name) {
variable.getNameImpl() = name and
scope = variable.getDeclaringScopeImpl()
}
cached
predicate access(Ruby::Identifier access, VariableReal variable) {
exists(string name |
variable.getNameImpl() = name and
name = access.getValue()
predicate access(Ruby::AstNode access, VariableReal variable) {
exists(string name, Scope::Range scope |
pragma[only_bind_into](name) = variableNameInScope(access, scope)
|
variable.getDeclaringScopeImpl() = scopeOf(access) and
hasScopeAndName(variable, scope, name) and
not access.getLocation().strictlyBefore(variable.getLocationImpl()) and
// In case of overlapping parameter names, later parameters should not
// be considered accesses to the first parameter
@@ -310,15 +329,15 @@ private module Cached {
else any()
or
exists(Scope::Range declScope |
variable.getDeclaringScopeImpl() = declScope and
inherits(scopeOf(access), name, declScope)
hasScopeAndName(variable, declScope, pragma[only_bind_into](name)) and
inherits(scope, name, declScope)
)
)
}
private class Access extends Ruby::Token {
Access() {
access(this, _) or
access(this.(Ruby::Identifier), _) or
this instanceof Ruby::GlobalVariable or
this instanceof Ruby::InstanceVariable or
this instanceof Ruby::ClassVariable or
@@ -371,7 +390,7 @@ private predicate inherits(Scope::Range scope, string name, Scope::Range outer)
(
scopeDefinesParameterVariable(outer, name, _)
or
exists(Ruby::Identifier i |
exists(Ruby::AstNode i |
scopeAssigns(outer, name, i) and
i.getLocation().strictlyBefore(scope.getLocation())
)
@@ -420,7 +439,7 @@ private class VariableRealAdapter extends VariableImpl, TVariableReal instanceof
class LocalVariableReal extends VariableReal, TLocalVariableReal {
private Scope::Range scope;
private string name;
private Ruby::Identifier i;
private Ruby::AstNode i;
LocalVariableReal() { this = TLocalVariableReal(scope, name, i) }

View File

@@ -66,16 +66,16 @@ class ExitNode extends CfgNode, TExitNode {
*/
class AstCfgNode extends CfgNode, TElementNode {
private Splits splits;
private AstNode n;
AstNode e;
AstCfgNode() { this = TElementNode(_, n, splits) }
AstCfgNode() { this = TElementNode(_, e, splits) }
final override AstNode getNode() { result = n }
final override AstNode getNode() { result = e }
override Location getLocation() { result = n.getLocation() }
override Location getLocation() { result = e.getLocation() }
final override string toString() {
exists(string s | s = n.toString() |
exists(string s | s = e.toString() |
result = "[" + this.getSplitsString() + "] " + s
or
not exists(this.getSplitsString()) and result = s
@@ -94,7 +94,7 @@ class AstCfgNode extends CfgNode, TElementNode {
/** A control-flow node that wraps an AST expression. */
class ExprCfgNode extends AstCfgNode {
Expr e;
override Expr e;
ExprCfgNode() { e = this.getNode() }
@@ -159,7 +159,7 @@ private AstNode desugar(AstNode n) {
/**
* A class for mapping parent-child AST nodes to parent-child CFG nodes.
*/
abstract private class ExprChildMapping extends Expr {
abstract private class ChildMapping extends AstNode {
/**
* Holds if `child` is a (possibly nested) child of this expression
* for which we would like to find a matching CFG child.
@@ -167,17 +167,7 @@ abstract private class ExprChildMapping extends Expr {
abstract predicate relevantChild(AstNode child);
pragma[nomagic]
private predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) {
this.relevantChild(child) and
cfn = this.getAControlFlowNode() and
bb.getANode() = cfn
or
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = child
)
}
abstract predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb);
/**
* Holds if there is a control-flow path from `cfn` to `cfnChild`, where `cfn`
@@ -193,6 +183,44 @@ abstract private class ExprChildMapping extends Expr {
}
}
/**
* A class for mapping parent-child AST nodes to parent-child CFG nodes.
*/
abstract private class ExprChildMapping extends Expr, ChildMapping {
pragma[nomagic]
override predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) {
this.relevantChild(child) and
cfn = this.getAControlFlowNode() and
bb.getANode() = cfn
or
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getAPredecessor() and
not mid.getANode().getNode() = child
)
}
}
/**
* A class for mapping parent-child AST nodes to parent-child CFG nodes.
*/
abstract private class NonExprChildMapping extends ChildMapping {
NonExprChildMapping() { not this instanceof Expr }
pragma[nomagic]
override predicate reachesBasicBlock(AstNode child, CfgNode cfn, BasicBlock bb) {
this.relevantChild(child) and
cfn.getNode() = this and
bb.getANode() = cfn
or
exists(BasicBlock mid |
this.reachesBasicBlock(child, cfn, mid) and
bb = mid.getASuccessor() and
not mid.getANode().getNode() = child
)
}
}
/** Provides classes for control-flow nodes that wrap AST expressions. */
module ExprNodes {
private class LiteralChildMapping extends ExprChildMapping, Literal {
@@ -475,10 +503,6 @@ module ExprNodes {
final ExprCfgNode getBlock() { e.hasCfgChild(e.(MethodCall).getBlock(), this, result) }
}
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {
override predicate relevantChild(AstNode e) { e = this.getValue() }
}
/** A control-flow node that wraps a `MethodCall` AST expression. */
class MethodCallCfgNode extends CallCfgNode {
MethodCallCfgNode() { super.getExpr() instanceof MethodCall }
@@ -486,6 +510,10 @@ module ExprNodes {
override MethodCall getExpr() { result = super.getExpr() }
}
private class CaseExprChildMapping extends ExprChildMapping, CaseExpr {
override predicate relevantChild(AstNode e) { e = this.getValue() or e = this.getABranch() }
}
/** A control-flow node that wraps a `CaseExpr` AST expression. */
class CaseExprCfgNode extends ExprCfgNode {
override CaseExprChildMapping e;
@@ -494,6 +522,169 @@ module ExprNodes {
/** Gets the expression being compared, if any. */
final ExprCfgNode getValue() { e.hasCfgChild(e.getValue(), this, result) }
/**
* Gets the `n`th branch of this case expression, either a `when` clause, an `in` clause, or an `else` branch.
*/
final AstCfgNode getBranch(int n) { e.hasCfgChild(e.getBranch(n), this, result) }
}
private class InClauseChildMapping extends NonExprChildMapping, InClause {
override predicate relevantChild(AstNode e) {
e = this.getPattern() or
e = this.getCondition() or
e = this.getBody()
}
}
/** A control-flow node that wraps an `InClause` AST expression. */
class InClauseCfgNode extends AstCfgNode {
override InClauseChildMapping e;
/** Gets the pattern in this `in`-clause. */
final AstCfgNode getPattern() { e.hasCfgChild(e.getPattern(), this, result) }
/** Gets the pattern guard condition in this `in` clause, if any. */
final ExprCfgNode getCondition() { e.hasCfgChild(e.getCondition(), this, result) }
/** Gets the body of this `in`-clause. */
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
}
private class WhenClauseChildMapping extends NonExprChildMapping, WhenClause {
override predicate relevantChild(AstNode e) { e = this.getBody() }
}
/** A control-flow node that wraps a `WhenClause` AST expression. */
class WhenClauseCfgNode extends AstCfgNode {
override WhenClauseChildMapping e;
/** Gets the body of this `when`-clause. */
final ExprCfgNode getBody() { e.hasCfgChild(e.getBody(), this, result) }
}
/** A control-flow node that wraps a `CasePattern`. */
class CasePatternCfgNode extends AstCfgNode {
override CasePattern e;
}
private class ArrayPatternChildMapping extends NonExprChildMapping, ArrayPattern {
override predicate relevantChild(AstNode e) {
e = this.getPrefixElement(_) or
e = this.getSuffixElement(_) or
e = this.getRestVariableAccess()
}
}
/** A control-flow node that wraps an `ArrayPattern` node. */
class ArrayPatternCfgNode extends CasePatternCfgNode {
override ArrayPatternChildMapping e;
/** Gets the `n`th element of this list pattern's prefix. */
final CasePatternCfgNode getPrefixElement(int n) {
e.hasCfgChild(e.getPrefixElement(n), this, result)
}
/** Gets the `n`th element of this list pattern's suffix. */
final CasePatternCfgNode getSuffixElement(int n) {
e.hasCfgChild(e.getSuffixElement(n), this, result)
}
/** Gets the variable of the rest token, if any. */
final VariableWriteAccessCfgNode getRestVariableAccess() {
e.hasCfgChild(e.getRestVariableAccess(), this, result)
}
}
private class FindPatternChildMapping extends NonExprChildMapping, FindPattern {
override predicate relevantChild(AstNode e) {
e = this.getElement(_) or
e = this.getPrefixVariableAccess() or
e = this.getSuffixVariableAccess()
}
}
/** A control-flow node that wraps a `FindPattern` node. */
class FindPatternCfgNode extends CasePatternCfgNode {
override FindPatternChildMapping e;
/** Gets the `n`th element of this find pattern. */
final CasePatternCfgNode getElement(int n) { e.hasCfgChild(e.getElement(n), this, result) }
/** Gets the variable for the prefix of this find pattern, if any. */
final VariableWriteAccessCfgNode getPrefixVariableAccess() {
e.hasCfgChild(e.getPrefixVariableAccess(), this, result)
}
/** Gets the variable for the suffix of this find pattern, if any. */
final VariableWriteAccessCfgNode getSuffixVariableAccess() {
e.hasCfgChild(e.getSuffixVariableAccess(), this, result)
}
}
private class HashPatternChildMapping extends NonExprChildMapping, HashPattern {
override predicate relevantChild(AstNode e) {
e = this.getValue(_) or
e = this.getRestVariableAccess()
}
}
/** A control-flow node that wraps a `HashPattern` node. */
class HashPatternCfgNode extends CasePatternCfgNode {
override HashPatternChildMapping e;
/** Gets the value of the `n`th pair. */
final CasePatternCfgNode getValue(int n) { e.hasCfgChild(e.getValue(n), this, result) }
/** Gets the variable of the keyword rest token, if any. */
final VariableWriteAccessCfgNode getRestVariableAccess() {
e.hasCfgChild(e.getRestVariableAccess(), this, result)
}
}
private class AlternativePatternChildMapping extends NonExprChildMapping, AlternativePattern {
override predicate relevantChild(AstNode e) { e = this.getAnAlternative() }
}
/** A control-flow node that wraps an `AlternativePattern` node. */
class AlternativePatternCfgNode extends CasePatternCfgNode {
override AlternativePatternChildMapping e;
/** Gets the `n`th alternative. */
final CasePatternCfgNode getAlternative(int n) {
e.hasCfgChild(e.getAlternative(n), this, result)
}
}
private class AsPatternChildMapping extends NonExprChildMapping, AsPattern {
override predicate relevantChild(AstNode e) {
e = this.getPattern() or e = this.getVariableAccess()
}
}
/** A control-flow node that wraps an `AsPattern` node. */
class AsPatternCfgNode extends CasePatternCfgNode {
override AsPatternChildMapping e;
/** Gets the underlying pattern. */
final CasePatternCfgNode getPattern() { e.hasCfgChild(e.getPattern(), this, result) }
/** Gets the variable access for this pattern. */
final VariableWriteAccessCfgNode getVariableAccess() {
e.hasCfgChild(e.getVariableAccess(), this, result)
}
}
private class ParenthesizedPatternChildMapping extends NonExprChildMapping, ParenthesizedPattern {
override predicate relevantChild(AstNode e) { e = this.getPattern() }
}
/** A control-flow node that wraps a `ParenthesizedPattern` node. */
class ParenthesizedPatternCfgNode extends CasePatternCfgNode {
override ParenthesizedPatternChildMapping e;
/** Gets the underlying pattern. */
final CasePatternCfgNode getPattern() { e.hasCfgChild(e.getPattern(), this, result) }
}
private class ConditionalExprChildMapping extends ExprChildMapping, ConditionalExpr {
@@ -593,6 +784,13 @@ module ExprNodes {
final override VariableReadAccess getExpr() { result = ExprCfgNode.super.getExpr() }
}
/** A control-flow node that wraps a `VariableWriteAccess` AST expression. */
class VariableWriteAccessCfgNode extends ExprCfgNode {
override VariableWriteAccess e;
final override VariableWriteAccess getExpr() { result = ExprCfgNode.super.getExpr() }
}
/** A control-flow node that wraps a `InstanceVariableWriteAccess` AST expression. */
class InstanceVariableWriteAccessCfgNode extends ExprCfgNode {
override InstanceVariableWriteAccess e;

View File

@@ -711,7 +711,9 @@ module Trees {
last(this.getPattern(), last, c) and
c.(MatchingCompletion).getValue() = false
or
last(this.getVariableAccess(), last, c)
last(this.getVariableAccess(), last, any(SimpleCompletion x)) and
c.(MatchingCompletion).getValue() = true and
not c instanceof NestedCompletion
}
final override predicate succ(AstNode pred, AstNode succ, Completion c) {

View File

@@ -126,15 +126,14 @@ module LocalFlow {
or
nodeFrom.asExpr() = nodeTo.asExpr().(CfgNodes::ExprNodes::ConditionalExprCfgNode).getBranch(_)
or
exists(CfgNode n, Stmt stmt, CaseExpr c |
c = nodeTo.asExpr().getExpr() and
n = nodeFrom.asExpr() and
n = nodeTo.asExpr().getAPredecessor() and
stmt = n.getNode()
exists(CfgNodes::AstCfgNode branch |
branch = nodeTo.asExpr().(CfgNodes::ExprNodes::CaseExprCfgNode).getBranch(_)
|
stmt = c.getElseBranch() or
stmt = c.getABranch().(InClause).getBody() or
stmt = c.getABranch().(WhenClause).getBody()
nodeFrom.asExpr() = branch.(CfgNodes::ExprNodes::InClauseCfgNode).getBody()
or
nodeFrom.asExpr() = branch.(CfgNodes::ExprNodes::WhenClauseCfgNode).getBody()
or
nodeFrom.asExpr() = branch
)
or
exists(CfgNodes::ExprCfgNode exprTo, ReturningStatementNode n |

View File

@@ -24,12 +24,62 @@ predicate defaultTaintSanitizerGuard(DataFlow::BarrierGuard guard) { none() }
bindingset[node]
predicate defaultImplicitTaintRead(DataFlow::Node node, DataFlow::Content c) { none() }
private CfgNodes::ExprNodes::VariableWriteAccessCfgNode variablesInPattern(
CfgNodes::ExprNodes::CasePatternCfgNode p
) {
result = p
or
p =
any(CfgNodes::ExprNodes::AsPatternCfgNode ap |
result = variablesInPattern(ap.getPattern()) or
result = ap.getVariableAccess()
)
or
p =
any(CfgNodes::ExprNodes::ParenthesizedPatternCfgNode pp |
result = variablesInPattern(pp.getPattern())
)
or
p =
any(CfgNodes::ExprNodes::AlternativePatternCfgNode ap |
result = variablesInPattern(ap.getAlternative(_))
)
or
p =
any(CfgNodes::ExprNodes::ArrayPatternCfgNode ap |
result = variablesInPattern(ap.getPrefixElement(_)) or
result = variablesInPattern(ap.getSuffixElement(_)) or
result = ap.getRestVariableAccess()
)
or
p =
any(CfgNodes::ExprNodes::FindPatternCfgNode fp |
result = variablesInPattern(fp.getElement(_)) or
result = fp.getPrefixVariableAccess() or
result = fp.getSuffixVariableAccess()
)
or
p =
any(CfgNodes::ExprNodes::HashPatternCfgNode hp |
result = variablesInPattern(hp.getValue(_)) or
result = hp.getRestVariableAccess()
)
}
/**
* Holds if the additional step from `nodeFrom` to `nodeTo` should be included
* in all global taint flow configurations.
*/
cached
predicate defaultAdditionalTaintStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// value of `case` expression into variables in patterns
exists(CfgNodes::ExprNodes::CaseExprCfgNode case, CfgNodes::ExprNodes::InClauseCfgNode clause |
nodeFrom.asExpr() = case.getValue() and
clause = case.getBranch(_) and
nodeTo.(SsaDefinitionNode).getDefinition().getControlFlowNode() =
variablesInPattern(clause.getPattern())
)
or
// operation involving `nodeFrom`
exists(CfgNodes::ExprNodes::OperationCfgNode op |
op = nodeTo.asExpr() and