Skip to content

Commit bca941d

Browse files
author
Max Schaefer
authored
Merge pull request #765 from asger-semmle/class-receiver-propagation
JS: support flow out of "this" in constructor call
2 parents 65337ef + a1c7f32 commit bca941d

File tree

8 files changed

+95
-20
lines changed

8 files changed

+95
-20
lines changed

change-notes/1.20/analysis-javascript.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
- server-side code, for example [hapi](https://hapijs.com/)
99
* File classification has been improved to recognize additional generated files, for example files from [HTML Tidy](html-tidy.org).
1010

11-
* The taint tracking library now recognizes flow through persistent storage and callbacks in certain cases. This may give more results for the security queries.
11+
* The taint tracking library now recognizes flow through persistent storage, class fields, and callbacks in certain cases. This may give more results for the security queries.
1212

1313
## New queries
1414

@@ -41,3 +41,4 @@
4141
* `DataFlow::SourceNode` is no longer an abstract class; to add new source nodes, extend `DataFlow::SourceNode::Range` instead.
4242
* Subclasses of `DataFlow::PropRead` are no longer automatically made source nodes; you now need to additionally define a corresponding subclass of `DataFlow::SourceNode::Range` to achieve this.
4343
* The deprecated libraries `semmle.javascript.DataFlow` and `semmle.javascript.dataflow.CallGraph` have been removed; they are both superseded by `semmle.javascript.dataflow.DataFlow`.
44+
* The predicate `DataFlow::returnedPropWrite` was intended for internal use only and is no longer available.

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -606,22 +606,19 @@ private predicate storeStep(
606606
basicStoreStep(pred, succ, prop) and
607607
summary = PathSummary::level()
608608
or
609-
exists(Function f, DataFlow::Node mid, DataFlow::Node base |
610-
// `f` stores its parameter `pred` in property `prop` of a value that it returns,
609+
exists(Function f, DataFlow::Node mid |
610+
// `f` stores its parameter `pred` in property `prop` of a value that flows back to the caller,
611611
// and `succ` is an invocation of `f`
612612
reachableFromInput(f, succ, pred, mid, cfg, summary) and
613-
returnedPropWrite(f, base, prop, mid)
613+
(
614+
returnedPropWrite(f, _, prop, mid)
615+
or
616+
succ instanceof DataFlow::NewNode and
617+
receiverPropWrite(f, prop, mid)
618+
)
614619
)
615620
}
616621

617-
/**
618-
* Holds if `f` may return `base`, which has a write of property `prop` with right-hand side `rhs`.
619-
*/
620-
predicate returnedPropWrite(Function f, DataFlow::SourceNode base, string prop, DataFlow::Node rhs) {
621-
base.hasPropertyWrite(prop, rhs) and
622-
base.flowsToExpr(f.getAReturnedExpr())
623-
}
624-
625622
/**
626623
* Holds if `rhs` is the right-hand side of a write to property `prop`, and `nd` is reachable
627624
* from the base of that write under configuration `cfg` (possibly through callees) along a

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -802,6 +802,13 @@ module DataFlow {
802802
*/
803803
predicate thisNode(DataFlow::Node node, StmtContainer container) { node = TThisNode(container) }
804804

805+
/**
806+
* Gets the node representing the receiver of the given function, or `this` in the given top-level.
807+
*
808+
* Has no result if `container` is an arrow function.
809+
*/
810+
DataFlow::ThisNode thisNode(StmtContainer container) { result = TThisNode(container) }
811+
805812
/**
806813
* A classification of flows that are not modeled, or only modeled incompletely, by
807814
* `DataFlowNode`:

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,12 +167,16 @@ private module NodeTracking {
167167
basicStoreStep(pred, succ, prop) and
168168
summary = PathSummary::level()
169169
or
170-
exists(Function f, DataFlow::Node mid, DataFlow::SourceNode base |
171-
// `f` stores its parameter `pred` in property `prop` of a value that it returns,
170+
exists(Function f, DataFlow::Node mid |
171+
// `f` stores its parameter `pred` in property `prop` of a value that flows back to the caller,
172172
// and `succ` is an invocation of `f`
173173
reachableFromInput(f, succ, pred, mid, summary) and
174-
base.hasPropertyWrite(prop, mid) and
175-
base.flowsToExpr(f.getAReturnedExpr())
174+
(
175+
returnedPropWrite(f, _, prop, mid)
176+
or
177+
succ instanceof DataFlow::NewNode and
178+
receiverPropWrite(f, prop, mid)
179+
)
176180
)
177181
}
178182

javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ predicate callStep(DataFlow::Node pred, DataFlow::Node succ) {
115115

116116
/**
117117
* Holds if there is a flow step from `pred` to `succ` through returning
118-
* from a function call.
118+
* from a function call or the receiver flowing out of a constructor call.
119119
*/
120120
predicate returnStep(DataFlow::Node pred, DataFlow::Node succ) {
121-
exists(Function f |
122-
returnExpr(f, pred, _) and
123-
calls(succ, f)
121+
exists(Function f | calls(succ, f) |
122+
returnExpr(f, pred, _)
123+
or
124+
succ instanceof DataFlow::NewNode and
125+
DataFlow::thisNode(pred, f)
124126
)
125127
}
126128

@@ -264,6 +266,21 @@ predicate callback(DataFlow::Node arg, DataFlow::SourceNode cb) {
264266
)
265267
}
266268

269+
/**
270+
* Holds if `f` may return `base`, which has a write of property `prop` with right-hand side `rhs`.
271+
*/
272+
predicate returnedPropWrite(Function f, DataFlow::SourceNode base, string prop, DataFlow::Node rhs) {
273+
base.hasPropertyWrite(prop, rhs) and
274+
base.flowsToExpr(f.getAReturnedExpr())
275+
}
276+
277+
/**
278+
* Holds if `f` may assign `rhs` to `this.prop`.
279+
*/
280+
predicate receiverPropWrite(Function f, string prop, DataFlow::Node rhs) {
281+
DataFlow::thisNode(f).hasPropertyWrite(prop, rhs)
282+
}
283+
267284
/**
268285
* A utility class that is equivalent to `boolean` but does not require type joining.
269286
*/
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import * as dummy from 'dummy';
2+
3+
class C {
4+
constructor() {
5+
this.field = new Object();
6+
}
7+
}
8+
9+
function test() {
10+
let x = new C().field;
11+
foo(x);
12+
}

javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
| advanced-callgraph.js:2:13:2:20 | source() | advanced-callgraph.js:6:22:6:22 | v |
2+
| constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:18:8:18:14 | c.taint |
3+
| constructor-calls.js:4:18:4:25 | source() | constructor-calls.js:22:8:22:19 | c_safe.taint |
4+
| constructor-calls.js:10:16:10:23 | source() | constructor-calls.js:26:8:26:14 | d.taint |
5+
| constructor-calls.js:10:16:10:23 | source() | constructor-calls.js:30:8:30:19 | d_safe.taint |
6+
| constructor-calls.js:14:15:14:22 | source() | constructor-calls.js:17:8:17:14 | c.param |
7+
| constructor-calls.js:14:15:14:22 | source() | constructor-calls.js:25:8:25:14 | d.param |
28
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x |
39
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y |
410
| partialCalls.js:4:17:4:24 | source() | partialCalls.js:30:14:30:20 | x.value |
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
class EcmaClass {
2+
constructor(param) {
3+
this.param = param;
4+
this.taint = source();
5+
}
6+
}
7+
8+
function JsClass(param) {
9+
this.param = param;
10+
this.taint = source();
11+
}
12+
13+
function test() {
14+
let taint = source();
15+
16+
let c = new EcmaClass(taint);
17+
sink(c.param); // NOT OK
18+
sink(c.taint); // NOT OK
19+
20+
let c_safe = new EcmaClass("safe");
21+
sink(c_safe.param); // OK
22+
sink(c_safe.taint); // NOT OK
23+
24+
let d = new JsClass(taint);
25+
sink(d.param); // NOT OK
26+
sink(d.taint); // NOT OK
27+
28+
let d_safe = new JsClass("safe");
29+
sink(d_safe.param); // OK
30+
sink(d_safe.taint); // NOT OK
31+
}

0 commit comments

Comments
 (0)