Skip to content

Commit 6c2842b

Browse files
authored
Merge pull request github#2919 from asger-semmle/js/property-barriers
JS: Make sanitizers no longer block taint inside an object
2 parents 9eee16b + 4f42675 commit 6c2842b

19 files changed

+1014
-835
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@
66

77
* Alert suppression can now be done with single-line block comments (`/* ... */`) as well as line comments (`// ...`).
88

9-
* Imports with the `.js` extension can now be resolved to a TypeScript file,
9+
* Resolution of imports has improved, leading to more results from the security queries:
10+
- Imports with the `.js` extension can now be resolved to a TypeScript file,
1011
when the import refers to a file generated by TypeScript.
12+
- Imports that rely on path-mappings from a `tsconfig.json` file can now be resolved.
13+
- Export declarations of the form `export * as ns from "x"` are now analyzed more precisely.
1114

12-
* Imports that rely on path-mappings from a `tsconfig.json` file can now be resolved.
15+
* The analysis of sanitizers has improved, leading to more accurate results from the security queries.
16+
In particular:
17+
- Sanitizer guards now act across function boundaries in more cases.
18+
- Sanitizers can now better distinguish between a tainted value and an object _containing_ a tainted value.
1319

14-
* Export declarations of the form `export * as ns from "x"` are now analyzed more precisely.
15-
16-
* The analysis of sanitizer guards has improved, leading to fewer false-positive results from the security queries.
17-
18-
* The call graph construction has been improved, leading to more results from the security queries:
20+
* Call graph construction has been improved, leading to more results from the security queries:
1921
- Calls can now be resolved to indirectly-defined class members in more cases.
2022
- Calls through partial invocations such as `.bind` can now be resolved in more cases.
2123

@@ -85,3 +87,8 @@
8587

8688
* The predicates `RegExpTerm.getSuccessor` and `RegExpTerm.getPredecessor` have been changed to reflect textual, not operational, matching order. This only makes a difference in lookbehind assertions, which are operationally matched backwards. Previously, `getSuccessor` would mimick this, so in an assertion `(?<=ab)` the term `b` would be considered the predecessor, not the successor, of `a`. Textually, however, `a` is still matched before `b`, and this is the order we now follow.
8789
* An extensible model of the `EventEmitter` pattern has been implemented.
90+
* Taint-tracking configurations now interact differently with the `data` flow label, which may affect queries
91+
that combine taint-tracking and flow labels.
92+
- Sources added by the 1-argument `isSource` predicate are associated with the `taint` label now, instead of the `data` label.
93+
- Sanitizers now only block the `taint` label. As a result, sanitizers no longer block the flow of tainted values wrapped inside a property of an object.
94+
To retain the old behavior, instead use a barrier, or block the `data` flow label using a labeled sanitizer.

javascript/ql/src/semmle/javascript/dataflow/Configuration.qll

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ abstract class Configuration extends string {
9898
*/
9999
predicate isSource(DataFlow::Node source) { none() }
100100

101+
/**
102+
* Gets the flow label to associate with sources added by the 1-argument `isSource` predicate.
103+
*
104+
* For taint-tracking configurations, this defaults to `taint` and for other data-flow configurations
105+
* it defaults to `data`.
106+
*
107+
* Overriding this predicate is rarely needed, and overriding the 2-argument `isSource` predicate
108+
* should be preferred when possible.
109+
*/
110+
FlowLabel getDefaultSourceLabel() { result = FlowLabel::data() }
111+
101112
/**
102113
* Holds if `source` is a source of flow labeled with `lbl` that is relevant
103114
* for this configuration.
@@ -256,9 +267,11 @@ abstract class Configuration extends string {
256267
/**
257268
* A label describing the kind of information tracked by a flow configuration.
258269
*
259-
* There are two standard labels "data" and "taint", the former describing values
260-
* that directly originate from a flow source, the latter values that are derived
261-
* from a flow source via one or more transformations (such as string operations).
270+
* There are two standard labels "data" and "taint".
271+
* - "data" only propagates along value-preserving data flow, such as assignments
272+
* and parameter-passing, and is the default flow source for a `DataFlow::Configuration`.
273+
* - "taint" additionally permits flow through transformations such as string operations,
274+
* and is the default flow source for a `TaintTracking::Configuration`.
262275
*/
263276
abstract class FlowLabel extends string {
264277
bindingset[this]
@@ -666,7 +679,7 @@ private predicate exploratoryFlowStep(
666679
*/
667680
private predicate isSource(DataFlow::Node nd, DataFlow::Configuration cfg, FlowLabel lbl) {
668681
(cfg.isSource(nd) or nd.(AdditionalSource).isSourceFor(cfg)) and
669-
lbl = FlowLabel::data()
682+
lbl = cfg.getDefaultSourceLabel()
670683
or
671684
nd.(AdditionalSource).isSourceFor(cfg, lbl)
672685
or

javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,16 @@ module TaintTracking {
4848
// overridden to provide taint-tracking specific qldoc
4949
override predicate isSink(DataFlow::Node sink) { super.isSink(sink) }
5050

51-
/** Holds if the intermediate node `node` is a taint sanitizer. */
51+
/**
52+
* Holds if the intermediate node `node` is a taint sanitizer, that is,
53+
* tainted values can not flow into or out of `node`.
54+
*
55+
* Note that this only blocks flow through nodes that operate directly on the tainted value.
56+
* An object _containing_ a tainted value in a property can still flow into and out of `node`.
57+
* To block such objects, override `isBarrier` or use a labeled sanitizer to block the `data` flow label.
58+
*
59+
* For operations that _check_ if a value is tainted or safe, use `isSanitizerGuard` instead.
60+
*/
5261
predicate isSanitizer(DataFlow::Node node) { none() }
5362

5463
/**
@@ -84,25 +93,35 @@ module TaintTracking {
8493
* For example, if `guard` is the comparison expression in
8594
* `if(x == 'some-constant'){ ... x ... }`, it could sanitize flow of
8695
* `x` into the "then" branch.
96+
*
97+
* Node that this only handles checks that operate directly on the tainted value.
98+
* Objects that _contain_ a tainted value in a property may still flow across the check.
99+
* To block such objects, use a labeled sanitizer guard to block the `data` label.
87100
*/
88101
predicate isSanitizerGuard(SanitizerGuardNode guard) { none() }
89102

90-
final override predicate isBarrier(DataFlow::Node node) {
91-
super.isBarrier(node) or
92-
isSanitizer(node) or
93-
node instanceof DataFlow::VarAccessBarrier
103+
override predicate isLabeledBarrier(DataFlow::Node node, DataFlow::FlowLabel lbl) {
104+
super.isLabeledBarrier(node, lbl)
105+
or
106+
isSanitizer(node) and lbl.isTaint()
94107
}
95108

96-
final override predicate isBarrierEdge(DataFlow::Node source, DataFlow::Node sink) {
97-
super.isBarrierEdge(source, sink) or
98-
isSanitizerEdge(source, sink)
109+
override predicate isBarrier(DataFlow::Node node) {
110+
super.isBarrier(node)
111+
or
112+
// For variable accesses we block both the data and taint label, as a falsy value
113+
// can't be an object, and thus can't have any tainted properties.
114+
node instanceof DataFlow::VarAccessBarrier
99115
}
100116

101117
final override predicate isBarrierEdge(
102118
DataFlow::Node source, DataFlow::Node sink, DataFlow::FlowLabel lbl
103119
) {
104-
super.isBarrierEdge(source, sink, lbl) or
120+
super.isBarrierEdge(source, sink, lbl)
121+
or
105122
isSanitizerEdge(source, sink, lbl)
123+
or
124+
isSanitizerEdge(source, sink) and lbl.isTaint()
106125
}
107126

108127
final override predicate isBarrierGuard(DataFlow::BarrierGuardNode guard) {
@@ -127,6 +146,8 @@ module TaintTracking {
127146
) {
128147
isAdditionalFlowStep(pred, succ) and valuePreserving = false
129148
}
149+
150+
override DataFlow::FlowLabel getDefaultSourceLabel() { result.isTaint() }
130151
}
131152

132153
/**
@@ -157,7 +178,7 @@ module TaintTracking {
157178
* them.
158179
*/
159180
abstract class SanitizerGuardNode extends DataFlow::BarrierGuardNode {
160-
override predicate blocks(boolean outcome, Expr e) { sanitizes(outcome, e) }
181+
override predicate blocks(boolean outcome, Expr e) { none() }
161182

162183
/**
163184
* Holds if this node sanitizes expression `e`, provided it evaluates
@@ -166,6 +187,8 @@ module TaintTracking {
166187
abstract predicate sanitizes(boolean outcome, Expr e);
167188

168189
override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
190+
sanitizes(outcome, e) and label.isTaint()
191+
or
169192
sanitizes(outcome, e, label)
170193
}
171194

@@ -180,10 +203,6 @@ module TaintTracking {
180203
* A sanitizer guard node that only blocks specific flow labels.
181204
*/
182205
abstract class LabeledSanitizerGuardNode extends SanitizerGuardNode, DataFlow::BarrierGuardNode {
183-
final override predicate blocks(boolean outcome, Expr e, DataFlow::FlowLabel label) {
184-
sanitizes(outcome, e, label)
185-
}
186-
187206
override predicate sanitizes(boolean outcome, Expr e) { none() }
188207
}
189208

javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module CleartextLogging {
2323
* A data flow sink for clear-text logging of sensitive information.
2424
*/
2525
abstract class Sink extends DataFlow::Node {
26-
DataFlow::FlowLabel getLabel() { result.isDataOrTaint() }
26+
DataFlow::FlowLabel getLabel() { result.isTaint() }
2727
}
2828

2929
/**
@@ -127,7 +127,7 @@ module CleartextLogging {
127127

128128
override string describe() { result = "an access to " + name }
129129

130-
override DataFlow::FlowLabel getLabel() { result.isData() }
130+
override DataFlow::FlowLabel getLabel() { result.isTaint() }
131131
}
132132

133133
/** An access to a variable or property that might contain a password. */
@@ -153,7 +153,7 @@ module CleartextLogging {
153153

154154
override string describe() { result = "an access to " + name }
155155

156-
override DataFlow::FlowLabel getLabel() { result.isData() }
156+
override DataFlow::FlowLabel getLabel() { result.isTaint() }
157157
}
158158

159159
/** A call that might return a password. */
@@ -167,7 +167,7 @@ module CleartextLogging {
167167

168168
override string describe() { result = "a call to " + name }
169169

170-
override DataFlow::FlowLabel getLabel() { result.isData() }
170+
override DataFlow::FlowLabel getLabel() { result.isTaint() }
171171
}
172172

173173
/** An access to the sensitive object `process.env`. */
@@ -177,7 +177,7 @@ module CleartextLogging {
177177
override string describe() { result = "process environment" }
178178

179179
override DataFlow::FlowLabel getLabel() {
180-
result.isData() or
180+
result.isTaint() or
181181
result instanceof PartiallySensitiveMap
182182
}
183183
}

javascript/ql/src/semmle/javascript/security/dataflow/PrototypePollutionCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ module PrototypePollution {
7474
private class RemoteFlowAsSource extends Source {
7575
RemoteFlowAsSource() { this instanceof RemoteFlowSource }
7676

77-
override DataFlow::FlowLabel getAFlowLabel() { result.isData() }
77+
override DataFlow::FlowLabel getAFlowLabel() { result.isTaint() }
7878
}
7979

8080
/**

javascript/ql/src/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccess.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ module UnsafeDynamicMethodAccess {
5353
hasUnsafeMethods(read.getBase().getALocalSource()) and
5454
src = read.getPropertyNameExpr().flow() and
5555
dst = read and
56-
(srclabel = data() or srclabel = taint()) and
56+
srclabel.isTaint() and
5757
dstlabel = unsafeFunction()
5858
)
5959
or
@@ -62,7 +62,7 @@ module UnsafeDynamicMethodAccess {
6262
not PropertyInjection::isPrototypeLessObject(proj.getObject().getALocalSource()) and
6363
src = proj.getASelector() and
6464
dst = proj and
65-
(srclabel = data() or srclabel = taint()) and
65+
srclabel.isTaint() and
6666
dstlabel = unsafeFunction()
6767
)
6868
}

javascript/ql/src/semmle/javascript/security/dataflow/UnsafeDynamicMethodAccessCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module UnsafeDynamicMethodAccess {
1919
/**
2020
* Gets the flow label relevant for this source.
2121
*/
22-
DataFlow::FlowLabel getFlowLabel() { result = data() }
22+
DataFlow::FlowLabel getFlowLabel() { result = taint() }
2323
}
2424

2525
/**

javascript/ql/src/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCall.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ module UnvalidatedDynamicMethodCall {
4040
exists(DataFlow::PropRead read |
4141
src = read.getPropertyNameExpr().flow() and
4242
dst = read and
43-
(srclabel = data() or srclabel = taint()) and
43+
srclabel.isTaint() and
4444
(
4545
dstlabel instanceof MaybeNonFunction
4646
or

javascript/ql/src/semmle/javascript/security/dataflow/UnvalidatedDynamicMethodCallCustomizations.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ module UnvalidatedDynamicMethodCall {
1919
/**
2020
* Gets the flow label relevant for this source.
2121
*/
22-
DataFlow::FlowLabel getFlowLabel() { result = data() }
22+
DataFlow::FlowLabel getFlowLabel() { result = taint() }
2323
}
2424

2525
/**
Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,45 @@
1-
| tst.js:6:14:6:14 | v | ExampleConfiguration |
2-
| tst.js:14:14:14:14 | v | ExampleConfiguration |
3-
| tst.js:24:14:24:14 | v | ExampleConfiguration |
4-
| tst.js:36:14:36:14 | v | ExampleConfiguration |
5-
| tst.js:50:14:50:14 | v | ExampleConfiguration |
6-
| tst.js:56:14:56:14 | v | ExampleConfiguration |
7-
| tst.js:60:14:60:14 | v | ExampleConfiguration |
8-
| tst.js:74:14:74:14 | v | ExampleConfiguration |
9-
| tst.js:80:14:80:14 | v | ExampleConfiguration |
10-
| tst.js:84:14:84:14 | v | ExampleConfiguration |
11-
| tst.js:96:14:96:14 | v | ExampleConfiguration |
12-
| tst.js:108:14:108:14 | v | ExampleConfiguration |
13-
| tst.js:120:14:120:14 | v | ExampleConfiguration |
14-
| tst.js:132:14:132:14 | v | ExampleConfiguration |
15-
| tst.js:134:14:134:16 | v.p | ExampleConfiguration |
16-
| tst.js:136:14:136:18 | v.p.q | ExampleConfiguration |
17-
| tst.js:148:9:148:27 | v | ExampleConfiguration |
18-
| tst.js:149:14:149:14 | v | ExampleConfiguration |
19-
| tst.js:154:9:154:27 | v | ExampleConfiguration |
20-
| tst.js:157:14:157:14 | v | ExampleConfiguration |
21-
| tst.js:160:9:160:30 | v | ExampleConfiguration |
22-
| tst.js:160:35:160:56 | v | ExampleConfiguration |
23-
| tst.js:167:14:167:14 | v | ExampleConfiguration |
24-
| tst.js:176:18:176:18 | v | ExampleConfiguration |
25-
| tst.js:185:14:185:14 | v | ExampleConfiguration |
26-
| tst.js:193:14:193:14 | v | ExampleConfiguration |
27-
| tst.js:205:14:205:14 | v | ExampleConfiguration |
28-
| tst.js:209:14:209:14 | v | ExampleConfiguration |
29-
| tst.js:217:14:217:14 | v | ExampleConfiguration |
30-
| tst.js:221:14:221:14 | v | ExampleConfiguration |
31-
| tst.js:229:14:229:14 | v | ExampleConfiguration |
32-
| tst.js:237:14:237:14 | v | ExampleConfiguration |
33-
| tst.js:241:14:241:14 | v | ExampleConfiguration |
34-
| tst.js:255:14:255:14 | v | ExampleConfiguration |
35-
| tst.js:265:14:265:14 | v | ExampleConfiguration |
36-
| tst.js:284:14:284:14 | v | ExampleConfiguration |
37-
| tst.js:331:14:331:14 | v | ExampleConfiguration |
38-
| tst.js:350:14:350:14 | v | ExampleConfiguration |
39-
| tst.js:356:16:356:27 | x10 | ExampleConfiguration |
40-
| tst.js:356:32:356:34 | x10 | ExampleConfiguration |
41-
| tst.js:361:14:361:14 | v | ExampleConfiguration |
42-
| tst.js:371:14:371:16 | o.p | ExampleConfiguration |
43-
| tst.js:378:14:378:17 | o[p] | ExampleConfiguration |
1+
isBarrier
2+
isLabeledBarrier
3+
| ExampleConfiguration | tst.js:6:14:6:14 | v | taint |
4+
| ExampleConfiguration | tst.js:14:14:14:14 | v | taint |
5+
| ExampleConfiguration | tst.js:24:14:24:14 | v | taint |
6+
| ExampleConfiguration | tst.js:36:14:36:14 | v | taint |
7+
| ExampleConfiguration | tst.js:50:14:50:14 | v | taint |
8+
| ExampleConfiguration | tst.js:56:14:56:14 | v | taint |
9+
| ExampleConfiguration | tst.js:60:14:60:14 | v | taint |
10+
| ExampleConfiguration | tst.js:74:14:74:14 | v | taint |
11+
| ExampleConfiguration | tst.js:80:14:80:14 | v | taint |
12+
| ExampleConfiguration | tst.js:84:14:84:14 | v | taint |
13+
| ExampleConfiguration | tst.js:96:14:96:14 | v | taint |
14+
| ExampleConfiguration | tst.js:108:14:108:14 | v | taint |
15+
| ExampleConfiguration | tst.js:120:14:120:14 | v | taint |
16+
| ExampleConfiguration | tst.js:132:14:132:14 | v | taint |
17+
| ExampleConfiguration | tst.js:134:14:134:16 | v.p | taint |
18+
| ExampleConfiguration | tst.js:136:14:136:18 | v.p.q | taint |
19+
| ExampleConfiguration | tst.js:148:9:148:27 | v | taint |
20+
| ExampleConfiguration | tst.js:149:14:149:14 | v | taint |
21+
| ExampleConfiguration | tst.js:154:9:154:27 | v | taint |
22+
| ExampleConfiguration | tst.js:157:14:157:14 | v | taint |
23+
| ExampleConfiguration | tst.js:160:9:160:30 | v | taint |
24+
| ExampleConfiguration | tst.js:160:35:160:56 | v | taint |
25+
| ExampleConfiguration | tst.js:167:14:167:14 | v | taint |
26+
| ExampleConfiguration | tst.js:176:18:176:18 | v | taint |
27+
| ExampleConfiguration | tst.js:185:14:185:14 | v | taint |
28+
| ExampleConfiguration | tst.js:193:14:193:14 | v | taint |
29+
| ExampleConfiguration | tst.js:205:14:205:14 | v | taint |
30+
| ExampleConfiguration | tst.js:209:14:209:14 | v | taint |
31+
| ExampleConfiguration | tst.js:217:14:217:14 | v | taint |
32+
| ExampleConfiguration | tst.js:221:14:221:14 | v | taint |
33+
| ExampleConfiguration | tst.js:229:14:229:14 | v | taint |
34+
| ExampleConfiguration | tst.js:237:14:237:14 | v | taint |
35+
| ExampleConfiguration | tst.js:241:14:241:14 | v | taint |
36+
| ExampleConfiguration | tst.js:255:14:255:14 | v | taint |
37+
| ExampleConfiguration | tst.js:265:14:265:14 | v | taint |
38+
| ExampleConfiguration | tst.js:284:14:284:14 | v | taint |
39+
| ExampleConfiguration | tst.js:331:14:331:14 | v | taint |
40+
| ExampleConfiguration | tst.js:350:14:350:14 | v | taint |
41+
| ExampleConfiguration | tst.js:356:16:356:27 | x10 | taint |
42+
| ExampleConfiguration | tst.js:356:32:356:34 | x10 | taint |
43+
| ExampleConfiguration | tst.js:361:14:361:14 | v | taint |
44+
| ExampleConfiguration | tst.js:371:14:371:16 | o.p | taint |
45+
| ExampleConfiguration | tst.js:378:14:378:17 | o[p] | taint |

0 commit comments

Comments
 (0)