-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Ruby: Add case string comparison barrier guard #11114
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 all commits
4bc9096
25ceeaf
87944a3
0ab88c2
f1b63c4
ad7b5ae
a8b0d29
e25e192
62ea1f0
b16cecc
2b4217b
f24fa40
5deb16e
376d4e0
57f6894
6897fb4
2822c94
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,4 @@ | ||
| --- | ||
| category: minorAnalysis | ||
| --- | ||
| * String literals and arrays of string literals in case expression patterns are now recognised as barrier guards. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,22 +7,53 @@ private import codeql.ruby.controlflow.CfgNodes | |
| private import codeql.ruby.dataflow.SSA | ||
| private import codeql.ruby.ast.internal.Constant | ||
| private import codeql.ruby.InclusionTests | ||
| private import codeql.ruby.ast.internal.Literal | ||
|
|
||
| private predicate stringConstCompare(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) { | ||
| cached | ||
| private predicate stringConstCompare(CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch) { | ||
| exists(CfgNodes::ExprNodes::ComparisonOperationCfgNode c | | ||
| c = g and | ||
| c = guard and | ||
| exists(CfgNodes::ExprNodes::StringLiteralCfgNode strLitNode | | ||
| c.getExpr() instanceof EqExpr and branch = true | ||
| // Only consider strings without any interpolations | ||
| not strLitNode.getExpr().getComponent(_) instanceof StringInterpolationComponent and | ||
| c.getExpr() instanceof EqExpr and | ||
| branch = true | ||
| or | ||
| c.getExpr() instanceof CaseEqExpr and branch = true | ||
| or | ||
| c.getExpr() instanceof NEExpr and branch = false | ||
| | | ||
| c.getLeftOperand() = strLitNode and c.getRightOperand() = e | ||
| c.getLeftOperand() = strLitNode and c.getRightOperand() = testedNode | ||
| or | ||
| c.getLeftOperand() = e and c.getRightOperand() = strLitNode | ||
| c.getLeftOperand() = testedNode and c.getRightOperand() = strLitNode | ||
| ) | ||
| ) | ||
| or | ||
| stringConstCaseCompare(guard, testedNode, branch) | ||
| or | ||
| exists(CfgNodes::ExprNodes::BinaryOperationCfgNode g | | ||
| g = guard and | ||
| stringConstCompareOr(guard, branch) and | ||
| stringConstCompare(g.getLeftOperand(), testedNode, _) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Holds if `guard` is an `or` expression whose operands are string comparison guards. | ||
| * For example: | ||
| * | ||
| * ```rb | ||
| * x == "foo" or x == "bar" | ||
| * ``` | ||
| */ | ||
| private predicate stringConstCompareOr( | ||
|
||
| CfgNodes::ExprNodes::BinaryOperationCfgNode guard, boolean branch | ||
| ) { | ||
| guard.getExpr() instanceof LogicalOrExpr and | ||
| branch = true and | ||
| forall(CfgNode innerGuard | innerGuard = guard.getAnOperand() | | ||
| stringConstCompare(innerGuard, any(Ssa::Definition def).getARead(), branch) | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -72,10 +103,13 @@ deprecated class StringConstCompare extends DataFlow::BarrierGuard, | |
| } | ||
| } | ||
|
|
||
| private predicate stringConstArrayInclusionCall(CfgNodes::ExprCfgNode g, CfgNode e, boolean branch) { | ||
| cached | ||
| private predicate stringConstArrayInclusionCall( | ||
| CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch | ||
| ) { | ||
| exists(InclusionTest t | | ||
| t.asExpr() = g and | ||
| e = t.getContainedNode().asExpr() and | ||
| t.asExpr() = guard and | ||
| testedNode = t.getContainedNode().asExpr() and | ||
| branch = t.getPolarity() | ||
| | | ||
| exists(ExprNodes::ArrayLiteralCfgNode arr | | ||
|
|
@@ -132,3 +166,68 @@ deprecated class StringConstArrayInclusionCall extends DataFlow::BarrierGuard, | |
|
|
||
| override predicate checks(CfgNode expr, boolean branch) { expr = checkedNode and branch = true } | ||
| } | ||
|
|
||
| /** | ||
| * A validation of a value by comparing with a constant string via a `case` | ||
| * expression. For example: | ||
| * | ||
| * ```rb | ||
| * name = params[:user_name] | ||
| * case name | ||
| * when "alice" | ||
| * User.find_by("username = #{name}") | ||
| * when *["bob", "charlie"] | ||
| * User.find_by("username = #{name}") | ||
| * when "dave", "eve" # this is not yet recognised as a barrier guard | ||
| * User.find_by("username = #{name}") | ||
| * end | ||
| * ``` | ||
| */ | ||
| private predicate stringConstCaseCompare( | ||
| CfgNodes::AstCfgNode guard, CfgNode testedNode, boolean branch | ||
| ) { | ||
| branch = true and | ||
| exists(CfgNodes::ExprNodes::CaseExprCfgNode case | | ||
| case.getValue() = testedNode and | ||
|
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. Should we also handle the case where
Contributor
Author
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. Weird, I didn't know you could do this. This appears to already be covered by our existing |
||
| ( | ||
| guard = | ||
| any(CfgNodes::ExprNodes::WhenClauseCfgNode branchNode | | ||
| branchNode = case.getBranch(_) and | ||
| // For simplicity, consider patterns that contain only string literals or arrays of string literals | ||
| forall(ExprCfgNode pattern | pattern = branchNode.getPattern(_) | | ||
| // when "foo" | ||
| // when "foo", "bar" | ||
| pattern instanceof ExprNodes::StringLiteralCfgNode | ||
| or | ||
| pattern = | ||
| any(CfgNodes::ExprNodes::SplatExprCfgNode splat | | ||
| // when *["foo", "bar"] | ||
| forex(ExprCfgNode elem | | ||
| elem = splat.getOperand().(ExprNodes::ArrayLiteralCfgNode).getAnArgument() | ||
| | | ||
| elem instanceof ExprNodes::StringLiteralCfgNode | ||
| ) | ||
| or | ||
| // when *some_var | ||
| // when *SOME_CONST | ||
| exists(ExprNodes::ArrayLiteralCfgNode arr | | ||
| isArrayConstant(splat.getOperand(), arr) and | ||
| forall(ExprCfgNode elem | elem = arr.getAnArgument() | | ||
| elem instanceof ExprNodes::StringLiteralCfgNode | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
| ) | ||
| or | ||
| // in "foo" | ||
| exists( | ||
| CfgNodes::ExprNodes::InClauseCfgNode branchNode, ExprNodes::StringLiteralCfgNode pattern | ||
| | | ||
| branchNode = case.getBranch(_) and | ||
| pattern = branchNode.getPattern() and | ||
| guard = pattern | ||
| ) | ||
| ) | ||
| ) | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.