Skip to content

Commit 2f3d516

Browse files
asger-semmleasgerf
authored andcommitted
JS: Track flow into ES accessors
1 parent 4f46908 commit 2f3d516

File tree

6 files changed

+47
-7
lines changed

6 files changed

+47
-7
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,13 @@ module DataFlow {
531531
predicate isPrivateField() {
532532
getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label
533533
}
534+
535+
/**
536+
* Gets an accessor (`get` or `set` method) that may be invoked by this property reference.
537+
*/
538+
final DataFlow::FunctionNode getAnAccessorCallee() {
539+
result = CallGraph::getAnAccessorCallee(this)
540+
}
534541
}
535542

536543
/**

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,18 @@ module CallGraph {
162162
)
163163
)
164164
}
165+
166+
/**
167+
* Gets a getter or setter invoked as a result of the given property access.
168+
*/
169+
cached
170+
DataFlow::FunctionNode getAnAccessorCallee(DataFlow::PropRef ref) {
171+
exists(DataFlow::ClassNode cls, string name |
172+
ref = cls.getAnInstanceReference().getAPropertyRead(name) and
173+
result = cls.getInstanceMember(name, DataFlow::MemberKind::getter())
174+
or
175+
ref = cls.getAnInstanceReference().getAPropertyWrite(name) and
176+
result = cls.getInstanceMember(name, DataFlow::MemberKind::setter())
177+
)
178+
}
165179
}

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ predicate shouldTrackProperties(AbstractValue obj) {
2525
*/
2626
pragma[noinline]
2727
predicate returnExpr(Function f, DataFlow::Node source, DataFlow::Node sink) {
28-
sink.asExpr() = f.getAReturnedExpr() and source = sink
28+
sink.asExpr() = f.getAReturnedExpr() and source = sink and
29+
not f = any(SetterMethodDeclaration decl).getBody()
2930
}
3031

3132
/**
@@ -120,7 +121,11 @@ private module CachedSteps {
120121
* Holds if `invk` may invoke `f`.
121122
*/
122123
cached
123-
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
124+
predicate calls(DataFlow::SourceNode invk, Function f) {
125+
f = invk.(DataFlow::InvokeNode).getACallee(0)
126+
or
127+
f = invk.(DataFlow::PropRef).getAnAccessorCallee().getFunction()
128+
}
124129

125130
private predicate callsBoundInternal(
126131
DataFlow::InvokeNode invk, Function f, int boundArgs, boolean contextDependent
@@ -177,11 +182,11 @@ private module CachedSteps {
177182
*/
178183
cached
179184
predicate argumentPassing(
180-
DataFlow::InvokeNode invk, DataFlow::ValueNode arg, Function f, DataFlow::SourceNode parm
185+
DataFlow::SourceNode invk, DataFlow::ValueNode arg, Function f, DataFlow::SourceNode parm
181186
) {
182187
calls(invk, f) and
183188
(
184-
exists(int i | arg = invk.getArgument(i) |
189+
exists(int i | arg = invk.(DataFlow::InvokeNode).getArgument(i) |
185190
exists(Parameter p |
186191
f.getParameter(i) = p and
187192
not p.isRestParameter() and
@@ -193,6 +198,12 @@ private module CachedSteps {
193198
or
194199
arg = invk.(DataFlow::CallNode).getReceiver() and
195200
parm = DataFlow::thisNode(f)
201+
or
202+
arg = invk.(DataFlow::PropRef).getBase() and
203+
parm = DataFlow::thisNode(f)
204+
or
205+
arg = invk.(DataFlow::PropWrite).getRhs() and
206+
parm = DataFlow::parameterNode(f.getParameter(0))
196207
)
197208
or
198209
exists(DataFlow::Node callback, int i, Parameter p |
@@ -213,7 +224,7 @@ private module CachedSteps {
213224
callsBound(invk, f, boundArgs) and
214225
f.getParameter(boundArgs + i) = p and
215226
not p.isRestParameter() and
216-
arg = invk.getArgument(i) and
227+
arg = invk.(DataFlow::InvokeNode).getArgument(i) and
217228
parm = DataFlow::parameterNode(p)
218229
)
219230
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ typeInferenceMismatch
6666
| exceptions.js:144:9:144:16 | source() | exceptions.js:132:8:132:27 | returnThrownSource() |
6767
| exceptions.js:150:13:150:20 | source() | exceptions.js:153:10:153:10 | e |
6868
| exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e |
69+
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:9:10:9:18 | new C().x |
70+
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:13:18:13:20 | c.x |
71+
| getters-and-setters.js:27:15:27:22 | source() | getters-and-setters.js:23:18:23:18 | v |
72+
| getters-and-setters.js:47:23:47:30 | source() | getters-and-setters.js:45:14:45:16 | c.x |
6973
| importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text |
7074
| indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x |
7175
| json-stringify.js:2:16:2:23 | source() | json-stringify.js:5:8:5:29 | JSON.st ... source) |

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@
4141
| exceptions.js:144:9:144:16 | source() | exceptions.js:132:8:132:27 | returnThrownSource() |
4242
| exceptions.js:150:13:150:20 | source() | exceptions.js:153:10:153:10 | e |
4343
| exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e |
44+
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:9:10:9:18 | new C().x |
45+
| getters-and-setters.js:6:20:6:27 | source() | getters-and-setters.js:13:18:13:20 | c.x |
46+
| getters-and-setters.js:27:15:27:22 | source() | getters-and-setters.js:23:18:23:18 | v |
47+
| getters-and-setters.js:47:23:47:30 | source() | getters-and-setters.js:45:14:45:16 | c.x |
4448
| indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x |
4549
| indexOf.js:4:11:4:18 | source() | indexOf.js:13:10:13:10 | x |
4650
| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x |

javascript/ql/test/library-tests/TaintTracking/getters-and-setters.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ function testFlowThroughGetter() {
5050
function getX(c) {
5151
return c.x;
5252
}
53-
sink(getX(new C(source()))); // NOT OK
54-
sink(getX(new C(source()))); // NOT OK
53+
sink(getX(new C(source()))); // NOT OK - but not flagged
54+
getX(null);
5555
}

0 commit comments

Comments
 (0)