Skip to content

Commit a1873cc

Browse files
committed
Ruby: IncompleteUrlSubstringSanitization.ql
1 parent c9fa1fb commit a1873cc

12 files changed

+245
-933
lines changed

ruby/ql/lib/codeql/ruby/InclusionTests.qll

Lines changed: 62 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,26 @@
22
* Contains classes for recognizing array and string inclusion tests.
33
*/
44

5-
private import javascript
5+
private import ruby
6+
private import codeql.ruby.DataFlow
7+
private import codeql.ruby.controlflow.CfgNodes
68

79
/**
810
* An expression that checks if an element is contained in an array
911
* or is a substring of another string.
1012
*
1113
* Examples:
1214
* ```
13-
* A.includes(B)
14-
* A.indexOf(B) !== -1
15-
* A.indexOf(B) >= 0
16-
* ~A.indexOf(B)
15+
* A.include?(B)
16+
* A.index(B) !== -1
17+
* A.index(B) >= 0
1718
* ```
1819
*/
1920
class InclusionTest extends DataFlow::Node instanceof InclusionTest::Range {
20-
/** Gets the `A` in `A.includes(B)`. */
21+
/** Gets the `A` in `A.include?(B)`. */
2122
DataFlow::Node getContainerNode() { result = super.getContainerNode() }
2223

23-
/** Gets the `B` in `A.includes(B)`. */
24+
/** Gets the `B` in `A.include?(B)`. */
2425
DataFlow::Node getContainedNode() { result = super.getContainedNode() }
2526

2627
/**
@@ -34,15 +35,15 @@ class InclusionTest extends DataFlow::Node instanceof InclusionTest::Range {
3435

3536
module InclusionTest {
3637
/**
37-
* A expression that is equivalent to `A.includes(B)` or `!A.includes(B)`.
38+
* A expression that is equivalent to `A.include?(B)` or `!A.include?(B)`.
3839
*
39-
* Note that this also includes calls to the array method named `includes`.
40+
* Note that this also includes calls to the array method named `include?`.
4041
*/
4142
abstract class Range extends DataFlow::Node {
42-
/** Gets the `A` in `A.includes(B)`. */
43+
/** Gets the `A` in `A.include?(B)`. */
4344
abstract DataFlow::Node getContainerNode();
4445

45-
/** Gets the `B` in `A.includes(B)`. */
46+
/** Gets the `B` in `A.include?(B)`. */
4647
abstract DataFlow::Node getContainedNode();
4748

4849
/**
@@ -55,47 +56,13 @@ module InclusionTest {
5556
}
5657

5758
/**
58-
* A call to a utility function (`callee`) that performs an InclusionTest (`inner`).
59+
* A call to a method named `include?`, assumed to refer to `String.include?`
60+
* or `Array.include?`.
5961
*/
60-
private class IndirectInclusionTest extends Range, DataFlow::CallNode {
61-
InclusionTest inner;
62-
Function callee;
63-
64-
IndirectInclusionTest() {
65-
inner.getEnclosingExpr() = unique(Expr ret | ret = callee.getAReturnedExpr()) and
66-
callee = unique(Function f | f = this.getACallee()) and
67-
not this.isImprecise() and
68-
inner.getContainedNode().getALocalSource() = DataFlow::parameterNode(callee.getAParameter()) and
69-
inner.getContainerNode().getALocalSource() = DataFlow::parameterNode(callee.getAParameter())
70-
}
71-
72-
override DataFlow::Node getContainerNode() {
73-
exists(int arg |
74-
inner.getContainerNode().getALocalSource() =
75-
DataFlow::parameterNode(callee.getParameter(arg)) and
76-
result = this.getArgument(arg)
77-
)
78-
}
79-
80-
override DataFlow::Node getContainedNode() {
81-
exists(int arg |
82-
inner.getContainedNode().getALocalSource() =
83-
DataFlow::parameterNode(callee.getParameter(arg)) and
84-
result = this.getArgument(arg)
85-
)
86-
}
87-
88-
override boolean getPolarity() { result = inner.getPolarity() }
89-
}
90-
91-
/**
92-
* A call to a method named `includes`, assumed to refer to `String.prototype.includes`
93-
* or `Array.prototype.includes`.
94-
*/
95-
private class Includes_Native extends Range, DataFlow::MethodCallNode {
62+
private class Includes_Native extends Range, DataFlow::CallNode {
9663
Includes_Native() {
97-
this.getMethodName() = "includes" and
98-
this.getNumArgument() = 1
64+
this.getMethodName() = "include?" and
65+
count(this.getArgument(_)) = 1
9966
}
10067

10168
override DataFlow::Node getContainerNode() { result = this.getReceiver() }
@@ -104,101 +71,60 @@ module InclusionTest {
10471
}
10572

10673
/**
107-
* A call to `_.includes` or similar, assumed to operate on strings.
74+
* A check of form `A.index(B) != -1`, `A.index(B) >= 0`, or similar.
10875
*/
109-
private class Includes_Library extends Range, DataFlow::CallNode {
110-
Includes_Library() {
111-
exists(string name |
112-
this = LodashUnderscore::member(name).getACall() and
113-
(name = "includes" or name = "include" or name = "contains")
114-
or
115-
this = Closure::moduleImport("goog.string." + name).getACall() and
116-
(name = "contains" or name = "caseInsensitiveContains")
117-
)
118-
}
119-
120-
override DataFlow::Node getContainerNode() { result = this.getArgument(0) }
121-
122-
override DataFlow::Node getContainedNode() { result = this.getArgument(1) }
123-
}
124-
125-
/**
126-
* A check of form `A.indexOf(B) !== -1` or similar.
127-
*/
128-
private class Includes_IndexOfEquals extends Range, DataFlow::ValueNode {
129-
MethodCallExpr indexOf;
130-
override EqualityTest astNode;
131-
132-
Includes_IndexOfEquals() {
133-
exists(Expr index | astNode.hasOperands(indexOf, index) |
134-
// one operand is of the form `whitelist.indexOf(x)`
135-
indexOf.getMethodName() = "indexOf" and
136-
// and the other one is -1
137-
index.getIntValue() = -1
138-
)
139-
}
140-
141-
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver().flow() }
142-
143-
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0).flow() }
144-
145-
override boolean getPolarity() { result = astNode.getPolarity().booleanNot() }
146-
}
147-
148-
/**
149-
* A check of form `A.indexOf(B) >= 0` or similar.
150-
*/
151-
private class Includes_IndexOfRelational extends Range, DataFlow::ValueNode {
152-
MethodCallExpr indexOf;
153-
override RelationalComparison astNode;
154-
boolean polarity;
155-
156-
Includes_IndexOfRelational() {
157-
exists(Expr lesser, Expr greater |
158-
astNode.getLesserOperand() = lesser and
159-
astNode.getGreaterOperand() = greater and
160-
indexOf.getMethodName() = "indexOf" and
161-
indexOf.getNumArgument() = 1
76+
private class Includes_IndexOfComparison extends Range, DataFlow::Node {
77+
private DataFlow::CallNode indexOf;
78+
private boolean polarity;
79+
80+
Includes_IndexOfComparison() {
81+
exists(ExprCfgNode index, ExprNodes::ComparisonOperationCfgNode comparison, int value |
82+
indexOf.asExpr() = comparison.getAnOperand() and
83+
index = comparison.getAnOperand() and
84+
this.asExpr() = comparison and
85+
// one operand is of the form `whitelist.index(x)`
86+
indexOf.getMethodName() = "index" and
87+
// and the other one is 0 or -1
88+
value = index.getConstantValue().getInt() and
89+
value = [0, -1]
16290
|
163-
polarity = true and
164-
greater = indexOf and
165-
(
166-
lesser.getIntValue() = 0 and astNode.isInclusive()
167-
or
168-
lesser.getIntValue() = -1 and not astNode.isInclusive()
169-
)
91+
value = -1 and polarity = false and comparison.getExpr() instanceof CaseEqExpr
17092
or
171-
polarity = false and
172-
lesser = indexOf and
173-
(
174-
greater.getIntValue() = -1 and astNode.isInclusive()
175-
or
176-
greater.getIntValue() = 0 and not astNode.isInclusive()
93+
value = -1 and polarity = false and comparison.getExpr() instanceof EqExpr
94+
or
95+
value = -1 and polarity = true and comparison.getExpr() instanceof NEExpr
96+
or
97+
value = 0 and polarity = false and comparison.getExpr() instanceof NEExpr
98+
or
99+
exists(RelationalOperation op | op = comparison.getExpr() |
100+
exists(Expr lesser, Expr greater |
101+
op.getLesserOperand() = lesser and
102+
op.getGreaterOperand() = greater
103+
|
104+
polarity = true and
105+
greater = indexOf.asExpr().getExpr() and
106+
(
107+
value = 0 and op.isInclusive()
108+
or
109+
value = -1 and not op.isInclusive()
110+
)
111+
or
112+
polarity = false and
113+
lesser = indexOf.asExpr().getExpr() and
114+
(
115+
value = -1 and op.isInclusive()
116+
or
117+
value = 0 and not op.isInclusive()
118+
)
119+
)
177120
)
178121
)
179122
}
180123

181-
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver().flow() }
124+
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver() }
182125

183-
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0).flow() }
126+
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0) }
184127

185128
override boolean getPolarity() { result = polarity }
186129
}
187-
188-
/**
189-
* An expression of form `~A.indexOf(B)` which, when coerced to a boolean, is equivalent to `A.includes(B)`.
190-
*/
191-
private class Includes_IndexOfBitwise extends Range, DataFlow::ValueNode {
192-
MethodCallExpr indexOf;
193-
override BitNotExpr astNode;
194-
195-
Includes_IndexOfBitwise() {
196-
astNode.getOperand() = indexOf and
197-
indexOf.getMethodName() = "indexOf"
198-
}
199-
200-
override DataFlow::Node getContainerNode() { result = indexOf.getReceiver().flow() }
201-
202-
override DataFlow::Node getContainedNode() { result = indexOf.getArgument(0).flow() }
203-
}
204130
}

0 commit comments

Comments
 (0)