Skip to content

Commit 69ca103

Browse files
author
Max Schaefer
authored
Merge pull request #115 from esben-semmle/js/composed-function-taint
JS: model composed functions
2 parents 7e18426 + dc72788 commit 69ca103

File tree

6 files changed

+211
-0
lines changed

6 files changed

+211
-0
lines changed

change-notes/1.18/analysis-javascript.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* Support for popular libraries has been improved. Consequently, queries may produce more results on code bases that use the following libraries:
2222
- [bluebird](https://bluebirdjs.com)
2323
- [browserid-crypto](https://github.com/mozilla/browserid-crypto)
24+
- [compose-function](https://github.com/stoeffel/compose-function)
2425
- [cookie-parser](https://github.com/expressjs/cookie-parser)
2526
- [cookie-session](https://github.com/expressjs/cookie-session)
2627
- [crypto-js](https://github.com/https://github.com/brix/crypto-js)
@@ -52,6 +53,7 @@
5253
- [json-parse-better-errors](https://github.com/zkat/json-parse-better-errors)
5354
- [json-parse-safe](https://github.com/joaquimserafim/json-parse-safe)
5455
- [json-safe-parse](https://github.com/bahamas10/node-json-safe-parse)
56+
- [just-compose](https://github.com/angus-c/just)
5557
- [just-extend](https://github.com/angus-c/just)
5658
- [lodash](https://lodash.com)
5759
- [merge-deep](https://github.com/jonschlinkert/merge-deep)

javascript/ql/src/javascript.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import semmle.javascript.frameworks.AngularJS
5353
import semmle.javascript.frameworks.AWS
5454
import semmle.javascript.frameworks.Azure
5555
import semmle.javascript.frameworks.Babel
56+
import semmle.javascript.frameworks.ComposedFunctions
5657
import semmle.javascript.frameworks.Credentials
5758
import semmle.javascript.frameworks.CryptoLibraries
5859
import semmle.javascript.frameworks.DigitalOcean
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Provides classes for reasoning about composed functions.
3+
*/
4+
import javascript
5+
6+
/**
7+
* A function composed from a collection of functions.
8+
*/
9+
private class ComposedFunction extends DataFlow::CallNode {
10+
11+
ComposedFunction() {
12+
exists (string name |
13+
name = "just-compose" or
14+
name = "compose-function" |
15+
this = DataFlow::moduleImport(name).getACall()
16+
) or
17+
this = LodashUnderscore::member("flow").getACall()
18+
}
19+
20+
/**
21+
* Gets the ith function in this composition.
22+
*/
23+
DataFlow::FunctionNode getFunction(int i) {
24+
result.flowsTo(getArgument(i))
25+
}
26+
27+
}
28+
29+
/**
30+
* A taint step for a composed function.
31+
*/
32+
private class ComposedFunctionTaintStep extends TaintTracking::AdditionalTaintStep {
33+
34+
ComposedFunction composed;
35+
36+
DataFlow::CallNode call;
37+
38+
ComposedFunctionTaintStep() {
39+
call = composed.getACall() and
40+
this = call
41+
}
42+
43+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
44+
exists (int fnIndex, DataFlow::FunctionNode fn |
45+
fn = composed.getFunction(fnIndex) |
46+
// flow out of the composed call
47+
fnIndex = composed.getNumArgument() - 1 and
48+
pred = fn.getAReturn() and
49+
succ = this
50+
or
51+
if fnIndex = 0 then
52+
// flow into the first composed function
53+
exists (int callArgIndex |
54+
pred = call.getArgument(callArgIndex) and
55+
succ = fn.getParameter(callArgIndex)
56+
)
57+
else
58+
// flow through the composed functions
59+
exists (DataFlow::FunctionNode predFn |
60+
predFn = composed.getFunction(fnIndex - 1) |
61+
pred = predFn.getAReturn() and
62+
succ = fn.getParameter(0)
63+
)
64+
)
65+
}
66+
67+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
| tst.js:10:10:10:15 | source |
2+
| tst.js:15:10:15:13 | f1() |
3+
| tst.js:20:10:20:23 | compose1(f2)() |
4+
| tst.js:28:10:28:27 | compose1(f3, f4)() |
5+
| tst.js:33:10:33:28 | compose1(o.f, f5)() |
6+
| tst.js:41:10:41:27 | compose1(f6, f7)() |
7+
| tst.js:49:10:49:33 | compose ... source) |
8+
| tst.js:61:10:61:40 | compose ... source) |
9+
| tst.js:66:10:66:30 | compose ... source) |
10+
| tst.js:89:10:89:31 | f18(und ... source) |
11+
| tst.js:94:10:94:24 | compose2(f19)() |
12+
| tst.js:99:10:99:24 | compose3(f20)() |
13+
| tst.js:104:10:104:24 | compose4(f21)() |
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: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import compose1 from 'just-compose';
2+
import compose2 from 'compose-function';
3+
import compose3 from 'lodash.flow';
4+
import _ from 'lodash';
5+
var compose4 = _.flow;
6+
7+
(function(){
8+
var source = SOURCE();
9+
10+
SINK(source);
11+
12+
function f1(){
13+
return source;
14+
}
15+
SINK(f1());
16+
17+
function f2(){
18+
return source;
19+
}
20+
SINK(compose1(f2)());
21+
22+
function f3(){
23+
24+
}
25+
function f4(){
26+
return source;
27+
}
28+
SINK(compose1(f3, f4)());
29+
30+
function f5(){
31+
return source;
32+
}
33+
SINK(compose1(o.f, f5)());
34+
35+
function f6(){
36+
return source;
37+
}
38+
function f7(x){
39+
return x;
40+
}
41+
SINK(compose1(f6, f7)());
42+
43+
function f8(x){
44+
return x;
45+
}
46+
function f9(x){
47+
return x;
48+
}
49+
SINK(compose1(f8, f9)(source));
50+
51+
52+
function f10(x){
53+
return x;
54+
}
55+
function f11(x){
56+
return x;
57+
}
58+
function f12(x){
59+
return x;
60+
}
61+
SINK(compose1(f10, f11, f12)(source));
62+
63+
function f13(x){
64+
return x + 'foo' ;
65+
}
66+
SINK(compose1(f13)(source));
67+
68+
function f14(){
69+
return undefined;
70+
}
71+
SINK(f14()); // NO FLOW
72+
73+
function f15(){
74+
return source;
75+
}
76+
function f16(){
77+
return undefined;
78+
}
79+
SINK(compose1(f15, f16)()); // NO FLOW
80+
81+
function f17(x, y){
82+
return y;
83+
}
84+
SINK(f17(source)); // NO FLOW
85+
86+
function f18(x, y){
87+
return y;
88+
}
89+
SINK(f18(undefined, source));
90+
91+
function f19(){
92+
return source;
93+
}
94+
SINK(compose2(f19)());
95+
96+
function f20(){
97+
return source;
98+
}
99+
SINK(compose3(f20)());
100+
101+
function f21(){
102+
return source;
103+
}
104+
SINK(compose4(f21)());
105+
106+
})();

0 commit comments

Comments
 (0)