Skip to content

Commit 7b97fd0

Browse files
committed
JS: add query js/memory-exhaustion
1 parent 6d6f29e commit 7b97fd0

5 files changed

Lines changed: 535 additions & 0 deletions

File tree

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
8+
</overview>
9+
10+
<recommendation>
11+
12+
</recommendation>
13+
14+
<example>
15+
16+
</example>
17+
18+
<references>
19+
20+
</references>
21+
22+
</qhelp>
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/**
2+
* @name Memory exhaustion
3+
* @description Allocating objects with user-controlled sizes
4+
* can cause memory exhaustion.
5+
* @kind path-problem
6+
* @problem.severity warning
7+
* @id js/memory-exhaustion
8+
* @precision high
9+
* @tags security
10+
* external/cwe/cwe-770
11+
*/
12+
13+
import javascript
14+
import DataFlow::PathGraph
15+
private import semmle.javascript.dataflow.InferredTypes
16+
import semmle.javascript.security.dataflow.LoopBoundInjectionCustomizations
17+
18+
/**
19+
* A data flow source for memory exhaustion vulnerabilities.
20+
*/
21+
abstract class Source extends DataFlow::Node {
22+
/** Gets a flow label denoting the type of value for which this is a source. */
23+
DataFlow::FlowLabel getAFlowLabel() { result.isTaint() }
24+
}
25+
26+
/**
27+
* A data flow sink for memory exhaustion vulnerabilities.
28+
*/
29+
abstract class Sink extends DataFlow::Node {
30+
/** Gets a flow label denoting the type of value for which this is a sink. */
31+
DataFlow::FlowLabel getAFlowLabel() { result instanceof Label::Number }
32+
}
33+
34+
/**
35+
* A data flow sanitizer for memory exhaustion vulnerabilities.
36+
*/
37+
abstract class Sanitizer extends DataFlow::Node { }
38+
39+
/**
40+
* Provides data flow labels for memory exhaustion vulnerabilities.
41+
*/
42+
module Label {
43+
/**
44+
* A number data flow label.
45+
*/
46+
class Number extends DataFlow::FlowLabel {
47+
Number() { this = "number" }
48+
}
49+
}
50+
51+
/**
52+
* A data flow configuration for memory exhaustion vulnerabilities.
53+
*/
54+
class Configuration extends TaintTracking::Configuration {
55+
Configuration() { this = "MemoryExhaustion" }
56+
57+
override predicate isSource(DataFlow::Node source, DataFlow::FlowLabel label) {
58+
source.(Source).getAFlowLabel() = label
59+
}
60+
61+
override predicate isSink(DataFlow::Node sink, DataFlow::FlowLabel label) {
62+
sink.(Sink).getAFlowLabel() = label
63+
}
64+
65+
override predicate isAdditionalFlowStep(
66+
DataFlow::Node src, DataFlow::Node dst, DataFlow::FlowLabel srclabel,
67+
DataFlow::FlowLabel dstlabel
68+
) {
69+
exists(Expr dstExpr, Expr srcExpr | dstExpr = dst.asExpr() and srcExpr = src.asExpr() |
70+
// reuse taint steps
71+
super.isAdditionalFlowStep(src, dst) and
72+
(
73+
srclabel = dstlabel and
74+
not dstExpr instanceof AddExpr and
75+
not dst.(DataFlow::MethodCallNode).calls(src, "toString")
76+
or
77+
dstlabel.isTaint() and
78+
dst.(DataFlow::MethodCallNode).calls(src, "toString")
79+
or
80+
// this conversion step is probably covered below
81+
dstlabel instanceof Label::Number and dst.(AnalyzedNode).getTheType() = TTNumber()
82+
)
83+
or
84+
//
85+
// steps that introduce or preserve a number
86+
dstlabel instanceof Label::Number and
87+
(
88+
dst.(DataFlow::PropRead).accesses(src, ["length", "size"])
89+
or
90+
dstExpr.(BinaryExpr).getAnOperand() = srcExpr and
91+
not dstExpr instanceof AddExpr
92+
or
93+
dstExpr.(PlusExpr).getOperand() = srcExpr
94+
or
95+
exists(DataFlow::CallNode c |
96+
c = dst and
97+
src = c.getAnArgument()
98+
|
99+
c = DataFlow::globalVarRef("Math").getAPropertyRead().getACall() or
100+
c = DataFlow::globalVarRef(["Number", "parseInt", "parseFloat"]).getACall()
101+
)
102+
)
103+
or
104+
// optimistic propagation through plus if either operand is a number
105+
exists(Expr operand | dstExpr.(AddExpr).hasOperands(operand, srcExpr) |
106+
operand.analyze().getTheType() = TTNumber()
107+
or
108+
operand.flow().getALocalSource().(DataFlow::PropRead).getPropertyName() = "length"
109+
or
110+
srclabel instanceof Label::Number and
111+
// unless the result provably is a string
112+
not operand.analyze().getTheType() = TTString()
113+
)
114+
)
115+
}
116+
117+
override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode guard) {
118+
guard instanceof LoopBoundInjection::LengthCheckSanitizerGuard or
119+
guard instanceof UpperBoundsCheckSanitizerGuard or
120+
guard instanceof TypeTestGuard
121+
}
122+
}
123+
124+
/**
125+
* A sanitizer that blocks taint flow if the size of a number is limited.
126+
*/
127+
class UpperBoundsCheckSanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode,
128+
DataFlow::ValueNode {
129+
override RelationalComparison astNode;
130+
131+
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
132+
label instanceof Label::Number and
133+
(
134+
true = outcome and
135+
e = astNode.getLesserOperand()
136+
or
137+
false = outcome and
138+
e = astNode.getGreaterOperand()
139+
)
140+
}
141+
}
142+
143+
/**
144+
* A test of form `typeof x === "something"`, preventing `x` from being a number in some cases.
145+
*/
146+
private class TypeTestGuard extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode {
147+
override EqualityTest astNode;
148+
TypeofExpr typeof;
149+
boolean polarity;
150+
151+
TypeTestGuard() {
152+
astNode.getAnOperand() = typeof and
153+
(
154+
// typeof x === "number" sanitizes `x` when it evaluates to false
155+
astNode.getAnOperand().getStringValue() = "number" and
156+
polarity = astNode.getPolarity().booleanNot()
157+
or
158+
// typeof x === "string" sanitizes `x` when it evaluates to true
159+
astNode.getAnOperand().getStringValue() != "number" and
160+
polarity = astNode.getPolarity()
161+
)
162+
}
163+
164+
override predicate sanitizes(boolean outcome, Expr e, DataFlow::FlowLabel label) {
165+
polarity = outcome and
166+
e = typeof.getOperand() and
167+
label instanceof Label::Number
168+
}
169+
}
170+
171+
/** A source of remote user input, considered as a data flow source for memory exhaustion vulnerabilities. */
172+
class RemoteFlowSourceAsSource extends Source {
173+
RemoteFlowSourceAsSource() { this instanceof RemoteFlowSource }
174+
}
175+
176+
/**
177+
* A node that determines the size of a buffer, considered as a data flow sink for memory exhaustion vulnerabilities.
178+
*/
179+
class BufferSizeSink extends Sink {
180+
BufferSizeSink() {
181+
exists(DataFlow::SourceNode clazz, DataFlow::InvokeNode invk, int index |
182+
clazz = DataFlow::globalVarRef("Buffer") and this = invk.getArgument(index)
183+
|
184+
exists(string name |
185+
invk = clazz.getAMemberCall(name) and
186+
(
187+
name = "from" and index = 2
188+
or
189+
name = ["alloc", "allocUnsafe", "allocUnsafeSlow"] and index = 0
190+
)
191+
)
192+
or
193+
invk = clazz.getAnInvocation() and
194+
invk.getNumArgument() = 1 and
195+
index = 0
196+
or
197+
invk.getNumArgument() = 3 and index = 2
198+
)
199+
or
200+
this = DataFlow::globalVarRef("SlowBuffer").getAnInstantiation().getArgument(0)
201+
}
202+
}
203+
204+
/**
205+
* A node that determines the size of an array, considered as a data flow sink for memory exhaustion vulnerabilities.
206+
*/
207+
class DenseArraySizeSink extends Sink {
208+
DenseArraySizeSink() {
209+
// Arrays are sparse by default, so we must also look at how the array is used
210+
exists(DataFlow::ArrayConstructorInvokeNode instance |
211+
this = instance.getArgument(0) and
212+
instance.getNumArgument() = 1
213+
|
214+
exists(instance.getAMethodCall(["map", "fill", "join", "toString"])) or
215+
instance.flowsToExpr(any(AddExpr p).getAnOperand())
216+
)
217+
}
218+
}
219+
220+
/**
221+
* A node that determines the repetitions of a string, considered as a data flow sink for memory exhaustion vulnerabilities.
222+
*/
223+
class StringRepetitionSink extends Sink {
224+
StringRepetitionSink() {
225+
exists(DataFlow::MethodCallNode repeat |
226+
repeat.getMethodName() = "repeat" and
227+
this = repeat.getArgument(0)
228+
)
229+
}
230+
231+
override DataFlow::FlowLabel getAFlowLabel() { any() }
232+
}
233+
234+
from Configuration dataflow, DataFlow::PathNode source, DataFlow::PathNode sink
235+
where dataflow.hasFlowPath(source, sink)
236+
select sink, source, sink, "This allocates an object with a user-controlled size from $@.", source,
237+
"here"

0 commit comments

Comments
 (0)