Skip to content

Commit 7e3adec

Browse files
author
Max Schaefer
authored
Merge pull request #135 from esben-semmle/js/pick-get-taint-steps
JS: model property projection calls (RC)
2 parents 0589be1 + 6ee8f71 commit 7e3adec

File tree

8 files changed

+237
-0
lines changed

8 files changed

+237
-0
lines changed

change-notes/1.18/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
- [deepmerge](https://github.com/KyleAMathews/deepmerge)
3232
- [defaults-deep](https://github.com/jonschlinkert/defaults-deep)
3333
- [defaults](https://github.com/tmpvar/defaults)
34+
- [dottie](https://github.com/mickhansen/dottie.js)
35+
- [dotty](https://github.com/deoxxa/dotty)
3436
- [ent](https://github.com/substack/node-ent)
3537
- [entities](https://github.com/fb55/node-entities)
3638
- [escape-goat](https://github.com/sindresorhus/escape-goat)

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ import semmle.javascript.frameworks.Logging
6363
import semmle.javascript.frameworks.HttpFrameworks
6464
import semmle.javascript.frameworks.NoSQL
6565
import semmle.javascript.frameworks.PkgCloud
66+
import semmle.javascript.frameworks.PropertyProjection
6667
import semmle.javascript.frameworks.React
6768
import semmle.javascript.frameworks.ReactNative
6869
import semmle.javascript.frameworks.Request
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/**
2+
* Provides classes for modelling property projection functions.
3+
*
4+
* Subclass `PropertyProjection` to refine the behavior of the analysis on existing property projections.
5+
* Subclass `CustomPropertyProjection` to introduce new kinds of property projections.
6+
*/
7+
8+
import javascript
9+
10+
/**
11+
* A property projection call such as `_.get(o, 'a.b')`, which is equivalent to `o.a.b`.
12+
*/
13+
abstract class CustomPropertyProjection extends DataFlow::CallNode {
14+
15+
/**
16+
* Gets the argument for the object to project properties from, such as `o` in `_.get(o, 'a.b')`.
17+
*/
18+
abstract DataFlow::Node getObject();
19+
20+
/**
21+
* Gets an argument that selects the properties to project, such as `'a.b'` in `_.get(o, 'a.b')`.
22+
*/
23+
abstract DataFlow::Node getASelector();
24+
25+
/**
26+
* Holds if this call returns the value of a single projected property, as opposed to an object that can contain multiple projected properties.
27+
*/
28+
abstract predicate isSingletonProjection();
29+
30+
}
31+
32+
/**
33+
* A property projection call such as `_.get(o, 'a.b')`, which is equivalent to `o.a.b`.
34+
*/
35+
class PropertyProjection extends DataFlow::CallNode {
36+
37+
CustomPropertyProjection custom;
38+
39+
PropertyProjection() { this = custom }
40+
41+
/**
42+
* Gets the argument for the object to project properties from, such as `o` in `_.get(o, 'a.b')`.
43+
*/
44+
DataFlow::Node getObject() { result = custom.getObject() }
45+
46+
/**
47+
* Gets an argument that selects the properties to project, such as `'a.b'` in `_.get(o, 'a.b')`.
48+
*/
49+
DataFlow::Node getASelector() { result = custom.getASelector() }
50+
51+
/**
52+
* Holds if this call returns the value of a single projected property, as opposed to an object that can contain multiple projected properties.
53+
*
54+
* Examples:
55+
* - This predicate holds for `_.get({a: 'b'}, 'a')`, which returns `'b'`,
56+
* - This predicate does not hold for `_.pick({a: 'b', c: 'd'}}, 'a')`, which returns `{a: 'b'}`,
57+
*/
58+
predicate isSingletonProjection() { custom.isSingletonProjection() }
59+
60+
}
61+
62+
/**
63+
* A simple model of common property projection functions.
64+
*/
65+
private class SimplePropertyProjection extends CustomPropertyProjection {
66+
67+
int objectIndex;
68+
int selectorIndex;
69+
boolean singleton;
70+
71+
SimplePropertyProjection() {
72+
exists (DataFlow::SourceNode callee |
73+
this = callee.getACall() |
74+
singleton = false and (
75+
(
76+
callee = LodashUnderscore::member("pick") and
77+
objectIndex = 0 and
78+
selectorIndex = [1..getNumArgument()]
79+
)
80+
or
81+
(
82+
callee = LodashUnderscore::member("pickBy") and
83+
objectIndex = 0 and
84+
selectorIndex = 1
85+
)
86+
or
87+
exists (string name |
88+
name = "pick" or
89+
name = "pickAll" or
90+
name = "pickBy" |
91+
callee = DataFlow::moduleMember("ramda", name) and
92+
objectIndex = 1 and
93+
selectorIndex = 0
94+
)
95+
or
96+
(
97+
callee = DataFlow::moduleMember("dotty", "search") and
98+
objectIndex = 0 and
99+
selectorIndex = 1
100+
)
101+
)
102+
or
103+
singleton = true and (
104+
(
105+
callee = LodashUnderscore::member("get") and
106+
objectIndex = 0 and
107+
selectorIndex = 1
108+
)
109+
or
110+
(
111+
callee = DataFlow::moduleMember("ramda", "path") and
112+
objectIndex = 1 and
113+
selectorIndex = 0
114+
)
115+
or
116+
(
117+
callee = DataFlow::moduleMember("dottie", "get") and
118+
objectIndex = 0 and
119+
selectorIndex = 1
120+
)
121+
or
122+
(
123+
callee = DataFlow::moduleMember("dotty", "get") and
124+
objectIndex = 0 and
125+
selectorIndex = 1
126+
)
127+
)
128+
)
129+
}
130+
131+
override DataFlow::Node getObject() { result = getArgument(objectIndex) }
132+
133+
override DataFlow::Node getASelector() { result = getArgument(selectorIndex) }
134+
135+
override predicate isSingletonProjection() { singleton = true }
136+
137+
}
138+
139+
/**
140+
* A taint step for a property projection.
141+
*/
142+
private class PropertyProjectionTaintStep extends TaintTracking::AdditionalTaintStep {
143+
144+
PropertyProjection projection;
145+
146+
PropertyProjectionTaintStep() {
147+
projection = this
148+
}
149+
150+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
151+
// reading from a tainted object yields a tainted result
152+
this = succ and
153+
pred = projection.getObject()
154+
}
155+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
| tst.js:25:10:25:15 | source |
2+
| tst.js:32:10:32:27 | _.pick(tainted, s) |
3+
| tst.js:33:10:33:26 | _.get(tainted, s) |
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import javascript
2+
3+
class ExampleConfiguration extends TaintTracking::Configuration {
4+
5+
ExampleConfiguration() { this = "ExampleConfiguration" }
6+
7+
override predicate isSource(DataFlow::Node source) {
8+
source.asExpr().(CallExpr).getCalleeName() = "SOURCE"
9+
}
10+
11+
override predicate isSink(DataFlow::Node sink) {
12+
exists (CallExpr callExpr |
13+
callExpr.getCalleeName() = "SINK" and
14+
DataFlow::valueNode(callExpr.getArgument(0)) = sink
15+
)
16+
}
17+
18+
}
19+
20+
from ExampleConfiguration cfg, DataFlow::Node source, DataFlow::Node sink
21+
where cfg.hasFlow(source, sink)
22+
select sink
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
| tst.js:6:1:6:17 | _.pick(o, s1, s2) | tst.js:6:8:6:8 | o | tst.js:6:11:6:12 | s1 | false |
2+
| tst.js:6:1:6:17 | _.pick(o, s1, s2) | tst.js:6:8:6:8 | o | tst.js:6:15:6:16 | s2 | false |
3+
| tst.js:7:1:7:14 | _.pickBy(o, s) | tst.js:7:10:7:10 | o | tst.js:7:13:7:13 | s | false |
4+
| tst.js:9:1:9:12 | R.pick(s, o) | tst.js:9:11:9:11 | o | tst.js:9:8:9:8 | s | false |
5+
| tst.js:10:1:10:14 | R.pickBy(s, o) | tst.js:10:13:10:13 | o | tst.js:10:10:10:10 | s | false |
6+
| tst.js:11:1:11:15 | R.pickAll(s, o) | tst.js:11:14:11:14 | o | tst.js:11:11:11:11 | s | false |
7+
| tst.js:13:1:13:11 | _.get(o, s) | tst.js:13:7:13:7 | o | tst.js:13:10:13:10 | s | true |
8+
| tst.js:15:1:15:12 | R.path(s, o) | tst.js:15:11:15:11 | o | tst.js:15:8:15:8 | s | true |
9+
| tst.js:17:1:17:16 | dottie.get(o, s) | tst.js:17:12:17:12 | o | tst.js:17:15:17:15 | s | true |
10+
| tst.js:19:1:19:15 | dotty.get(o, s) | tst.js:19:11:19:11 | o | tst.js:19:14:19:14 | s | true |
11+
| tst.js:20:1:20:18 | dotty.search(o, s) | tst.js:20:14:20:14 | o | tst.js:20:17:20:17 | s | false |
12+
| tst.js:27:10:27:30 | _.pick( ... ted, s) | tst.js:27:17:27:26 | notTainted | tst.js:27:29:27:29 | s | false |
13+
| tst.js:28:10:28:29 | _.get(notTainted, s) | tst.js:28:16:28:25 | notTainted | tst.js:28:28:28:28 | s | true |
14+
| tst.js:32:10:32:27 | _.pick(tainted, s) | tst.js:32:17:32:23 | tainted | tst.js:32:26:32:26 | s | false |
15+
| tst.js:33:10:33:26 | _.get(tainted, s) | tst.js:33:16:33:22 | tainted | tst.js:33:25:33:25 | s | true |
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import javascript
2+
3+
from PropertyProjection p, boolean singleton
4+
where if p.isSingletonProjection() then singleton = true else singleton = false
5+
select p, p.getObject(), p.getASelector(), singleton
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
var _ = require("lodash"),
2+
dotty = require("dotty"),
3+
dottie = require("dottie"),
4+
R = require("ramda");
5+
6+
_.pick(o, s1, s2);
7+
_.pickBy(o, s);
8+
9+
R.pick(s, o);
10+
R.pickBy(s, o);
11+
R.pickAll(s, o);
12+
13+
_.get(o, s);
14+
15+
R.path(s, o);
16+
17+
dottie.get(o, s);
18+
19+
dotty.get(o, s);
20+
dotty.search(o, s);
21+
22+
(function(){
23+
var source = SOURCE();
24+
25+
SINK(source);
26+
27+
SINK(_.pick(notTainted, s));
28+
SINK(_.get(notTainted, s));
29+
30+
var tainted = {};
31+
tainted[x] = source;
32+
SINK(_.pick(tainted, s));
33+
SINK(_.get(tainted, s));
34+
});

0 commit comments

Comments
 (0)