Skip to content

Commit ce0b72f

Browse files
authored
Merge pull request github#3093 from erik-krogh/MorePathSinks
Approved by asgerf
2 parents fe00d1c + 36981f3 commit ce0b72f

File tree

6 files changed

+235
-5
lines changed

6 files changed

+235
-5
lines changed

javascript/ql/src/semmle/javascript/Promises.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ module PromiseTypeTracking {
167167
* Gets the result from a single step through a promise, from `pred` to `result` summarized by `summary`.
168168
* This can be loading a resolved value from a promise, storing a value in a promise, or copying a resolved value from one promise to another.
169169
*/
170+
pragma[inline]
170171
DataFlow::SourceNode promiseStep(DataFlow::SourceNode pred, StepSummary summary) {
171172
exists(PromiseFlowStep step, string field | field = Promises::valueProp() |
172173
summary = LoadStep(field) and

javascript/ql/src/semmle/javascript/frameworks/Files.qll

Lines changed: 149 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,18 @@ private class RecursiveReadDir extends FileSystemAccess, FileNameProducer, DataF
201201

202202
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
203203

204-
override DataFlow::Node getAFileName() { result = getCallback([1 .. 2]).getParameter(1) }
204+
override DataFlow::Node getAFileName() { result = trackFileSource(DataFlow::TypeTracker::end()) }
205+
206+
private DataFlow::SourceNode trackFileSource(DataFlow::TypeTracker t) {
207+
t.start() and result = getCallback([1 .. 2]).getParameter(1)
208+
or
209+
t.startInPromise() and not exists(getCallback([1 .. 2])) and result = this
210+
or
211+
// Tracking out of a promise
212+
exists(DataFlow::TypeTracker t2 |
213+
result = PromiseTypeTracking::promiseStep(trackFileSource(t2), t, t2)
214+
)
215+
}
205216
}
206217

207218
/**
@@ -220,10 +231,24 @@ private module JSONFile {
220231

221232
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
222233

223-
override DataFlow::Node getADataNode() {
224-
this.getCalleeName() = "readFile" and result = getCallback([1 .. 2]).getParameter(1)
234+
override DataFlow::Node getADataNode() { result = trackRead(DataFlow::TypeTracker::end()) }
235+
236+
private DataFlow::SourceNode trackRead(DataFlow::TypeTracker t) {
237+
this.getCalleeName() = "readFile" and
238+
(
239+
t.start() and result = getCallback([1 .. 2]).getParameter(1)
240+
or
241+
t.startInPromise() and not exists(getCallback([1 .. 2])) and result = this
242+
)
225243
or
226-
this.getCalleeName() = "readFileSync" and result = this
244+
t.start() and
245+
this.getCalleeName() = "readFileSync" and
246+
result = this
247+
or
248+
// Tracking out of a promise
249+
exists(DataFlow::TypeTracker t2 |
250+
result = PromiseTypeTracking::promiseStep(trackRead(t2), t, t2)
251+
)
227252
}
228253
}
229254

@@ -243,6 +268,122 @@ private module JSONFile {
243268
}
244269
}
245270

271+
/**
272+
* A call to the library `load-json-file`.
273+
*/
274+
private class LoadJsonFile extends FileSystemReadAccess, DataFlow::CallNode {
275+
LoadJsonFile() {
276+
this = DataFlow::moduleImport("load-json-file").getACall()
277+
or
278+
this = DataFlow::moduleMember("load-json-file", "sync").getACall()
279+
}
280+
281+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
282+
283+
override DataFlow::Node getADataNode() { result = trackRead(DataFlow::TypeTracker::end()) }
284+
285+
private DataFlow::SourceNode trackRead(DataFlow::TypeTracker t) {
286+
this.getCalleeName() = "sync" and t.start() and result = this
287+
or
288+
not this.getCalleeName() = "sync" and t.startInPromise() and result = this
289+
or
290+
// Tracking out of a promise
291+
exists(DataFlow::TypeTracker t2 |
292+
result = PromiseTypeTracking::promiseStep(trackRead(t2), t, t2)
293+
)
294+
}
295+
}
296+
297+
/**
298+
* A call to the library `write-json-file`.
299+
*/
300+
private class WriteJsonFile extends FileSystemWriteAccess, DataFlow::CallNode {
301+
WriteJsonFile() {
302+
this = DataFlow::moduleImport("write-json-file").getACall()
303+
or
304+
this = DataFlow::moduleMember("write-json-file", "sync").getACall()
305+
}
306+
307+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
308+
309+
override DataFlow::Node getADataNode() { result = getArgument(1) }
310+
}
311+
312+
/**
313+
* A call to the library `walkdir`.
314+
*/
315+
private class WalkDir extends FileNameProducer, FileSystemAccess, DataFlow::CallNode {
316+
WalkDir() {
317+
this = DataFlow::moduleImport("walkdir").getACall()
318+
or
319+
this = DataFlow::moduleMember("walkdir", "sync").getACall()
320+
or
321+
this = DataFlow::moduleMember("walkdir", "async").getACall()
322+
}
323+
324+
override DataFlow::Node getAPathArgument() { result = getArgument(0) }
325+
326+
override DataFlow::Node getAFileName() { result = trackFileSource(DataFlow::TypeTracker::end()) }
327+
328+
private DataFlow::SourceNode trackFileSource(DataFlow::TypeTracker t) {
329+
not this.getCalleeName() = any(string s | s = "sync" or s = "async") and
330+
t.start() and
331+
(
332+
result = getCallback(getNumArgument() - 1).getParameter(0)
333+
or
334+
result = getAMethodCall(EventEmitter::on()).getCallback(1).getParameter(0)
335+
)
336+
or
337+
t.start() and this.getCalleeName() = "sync" and result = this
338+
or
339+
t.startInPromise() and this.getCalleeName() = "async" and result = this
340+
or
341+
// Tracking out of a promise
342+
exists(DataFlow::TypeTracker t2 |
343+
result = PromiseTypeTracking::promiseStep(trackFileSource(t2), t, t2)
344+
)
345+
}
346+
}
347+
348+
/**
349+
* A call to the library `globule`.
350+
*/
351+
private class Globule extends FileNameProducer, FileSystemAccess, DataFlow::CallNode {
352+
Globule() {
353+
this = DataFlow::moduleMember("globule", "find").getACall()
354+
or
355+
this = DataFlow::moduleMember("globule", "match").getACall()
356+
or
357+
this = DataFlow::moduleMember("globule", "isMatch").getACall()
358+
or
359+
this = DataFlow::moduleMember("globule", "mapping").getACall()
360+
or
361+
this = DataFlow::moduleMember("globule", "findMapping").getACall()
362+
}
363+
364+
override DataFlow::Node getAPathArgument() {
365+
(this.getCalleeName() = "match" or this.getCalleeName() = "isMatch") and
366+
result = getArgument(1)
367+
or
368+
this.getCalleeName() = "mapping" and
369+
(
370+
result = getAnArgument() and not exists(result.getALocalSource().getAPropertyWrite("src"))
371+
or
372+
result = getAnArgument().getALocalSource().getAPropertyWrite("src").getRhs()
373+
)
374+
}
375+
376+
override DataFlow::Node getAFileName() {
377+
result = this and
378+
(
379+
this.getCalleeName() = "find" or
380+
this.getCalleeName() = "match" or
381+
this.getCalleeName() = "findMapping" or
382+
this.getCalleeName() = "mapping"
383+
)
384+
}
385+
}
386+
246387
/**
247388
* A file system access made by a NodeJS library.
248389
* This class models multiple NodeJS libraries that access files.
@@ -257,6 +398,10 @@ private class LibraryAccess extends FileSystemAccess, DataFlow::InvokeNode {
257398
or
258399
this = DataFlow::moduleImport("rimraf").getACall()
259400
or
401+
this = DataFlow::moduleImport("readdirp").getACall()
402+
or
403+
this = DataFlow::moduleImport("walker").getACall()
404+
or
260405
this =
261406
DataFlow::moduleMember("node-dir",
262407
any(string s |

javascript/ql/test/library-tests/frameworks/Concepts/FileAccess.expected

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,34 @@ getPathArgument
1616
| file-access.js:47:1:47:36 | vfs.des ... true }) | file-access.js:47:10:47:13 | './' |
1717
| file-access.js:51:1:51:36 | ncp("fr ... rr) {}) | file-access.js:51:5:51:10 | "from" |
1818
| file-access.js:51:1:51:36 | ncp("fr ... rr) {}) | file-access.js:51:13:51:16 | "to" |
19+
| file-access.js:56:23:56:46 | loadJso ... .json') | file-access.js:56:36:56:45 | 'foo.json' |
20+
| file-access.js:57:14:57:42 | loadJso ... .json') | file-access.js:57:32:57:41 | 'foo.json' |
21+
| file-access.js:62:5:62:42 | writeJs ... true}) | file-access.js:62:19:62:28 | 'bar.json' |
22+
| file-access.js:63:2:63:60 | writeJs ... : " "}) | file-access.js:63:21:63:30 | 'bar.json' |
23+
| file-access.js:67:1:67:35 | readdir ... *.js'}) | file-access.js:67:10:67:12 | '.' |
24+
| file-access.js:70:1:72:2 | recursi ... es);\\n}) | file-access.js:70:11:70:29 | "directory/to/read" |
25+
| file-access.js:73:1:73:30 | recursi ... /read") | file-access.js:73:11:73:29 | "directory/to/read" |
26+
| file-access.js:75:1:75:29 | jsonfil ... .json') | file-access.js:75:19:75:28 | 'baz.json' |
27+
| file-access.js:79:2:81:3 | walk('. ... h);\\n\\t}) | file-access.js:79:7:79:11 | '../' |
28+
| file-access.js:82:16:82:26 | walk('../') | file-access.js:82:21:82:25 | '../' |
29+
| file-access.js:84:2:86:3 | walk.sy ... h);\\n\\t}) | file-access.js:84:12:84:16 | '../' |
30+
| file-access.js:87:14:87:29 | walk.sync('../') | file-access.js:87:24:87:28 | '../' |
31+
| file-access.js:88:21:88:37 | walk.async('../') | file-access.js:88:32:88:36 | '../' |
32+
| file-access.js:92:1:92:15 | walker('/etc/') | file-access.js:92:8:92:14 | '/etc/' |
33+
| tst-file-names.js:43:15:43:50 | globule ... o.js"]) | tst-file-names.js:43:40:43:49 | ["foo.js"] |
34+
| tst-file-names.js:44:12:44:49 | globule ... o.js"]) | tst-file-names.js:44:39:44:48 | ["foo.js"] |
35+
| tst-file-names.js:46:12:46:51 | globule ... .js"]}) | tst-file-names.js:46:34:46:49 | ["a.js", "b.js"] |
36+
| tst-file-names.js:47:12:47:52 | globule ... b.js"]) | tst-file-names.js:47:28:47:51 | ["foo/a ... /b.js"] |
1937
getReadNode
2038
| file-access.js:25:1:25:59 | jsonfil ... bj) {}) | file-access.js:25:52:25:54 | obj |
2139
| file-access.js:26:1:26:39 | jsonfil ... .json') | file-access.js:26:1:26:39 | jsonfil ... .json') |
40+
| file-access.js:56:23:56:46 | loadJso ... .json') | file-access.js:56:17:56:46 | await l ... .json') |
41+
| file-access.js:57:14:57:42 | loadJso ... .json') | file-access.js:57:14:57:42 | loadJso ... .json') |
42+
| file-access.js:75:1:75:29 | jsonfil ... .json') | file-access.js:75:36:75:38 | obj |
2243
getWriteNode
2344
| file-access.js:15:1:15:60 | writeFi ... rr) {}) | file-access.js:15:31:15:36 | 'Data' |
2445
| file-access.js:18:1:18:59 | writeFi ... tions]) | file-access.js:18:37:18:47 | "More data" |
2546
| file-access.js:28:1:28:60 | jsonfil ... rr) {}) | file-access.js:28:38:28:40 | obj |
2647
| file-access.js:29:1:29:45 | jsonfil ... ', obj) | file-access.js:29:42:29:44 | obj |
48+
| file-access.js:62:5:62:42 | writeJs ... true}) | file-access.js:62:31:62:41 | {bar: true} |
49+
| file-access.js:63:2:63:60 | writeJs ... : " "}) | file-access.js:63:33:63:44 | {bar: false} |

javascript/ql/test/library-tests/frameworks/Concepts/FileNameSource.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
| file-access.js:22:39:22:43 | files |
2+
| file-access.js:70:47:70:51 | files |
3+
| file-access.js:73:37:73:42 | files2 |
4+
| file-access.js:79:23:79:26 | path |
5+
| file-access.js:83:30:83:37 | filename |
6+
| file-access.js:84:2:86:3 | walk.sy ... h);\\n\\t}) |
7+
| file-access.js:87:14:87:29 | walk.sync('../') |
8+
| file-access.js:88:15:88:37 | await w ... ('../') |
29
| tst-file-names.js:7:1:7:10 | walkSync() |
310
| tst-file-names.js:9:35:9:44 | stats.name |
411
| tst-file-names.js:11:1:11:12 | glob.sync(_) |
@@ -15,3 +22,8 @@
1522
| tst-file-names.js:34:15:34:29 | await globby(_) |
1623
| tst-file-names.js:36:16:36:38 | await f ... sync(_) |
1724
| tst-file-names.js:38:16:38:57 | await f ... => {}) |
25+
| tst-file-names.js:42:17:42:39 | globule ... /*.js') |
26+
| tst-file-names.js:43:15:43:50 | globule ... o.js"]) |
27+
| tst-file-names.js:45:12:45:42 | globule ... /*.js") |
28+
| tst-file-names.js:46:12:46:51 | globule ... .js"]}) |
29+
| tst-file-names.js:47:12:47:52 | globule ... b.js"]) |

javascript/ql/test/library-tests/frameworks/Concepts/file-access.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,45 @@ vfs.dest('./', { sourcemaps: true });
4848

4949

5050
var ncp = require('ncp').ncp;
51-
ncp("from", "to", function (err) {});
51+
ncp("from", "to", function (err) {});
52+
53+
54+
const loadJsonFile = require('load-json-file');
55+
(async () => {
56+
console.log(await loadJsonFile('foo.json'));
57+
console.log(loadJsonFile.sync('foo.json'));
58+
})();
59+
60+
const writeJsonFile = require('write-json-file');
61+
(async () => {
62+
writeJsonFile('bar.json', {bar: true});
63+
writeJsonFile.sync('bar.json', {bar: false}, {indent: " "})
64+
})();
65+
66+
var readdirp = require("readdirp");
67+
readdirp('.', {fileFilter: '*.js'}).on('data', (entry) => { /* stream and promise api not modelled yet */ })
68+
69+
var recursive = require("recursive-readdir");
70+
recursive("directory/to/read", function (err, files) {
71+
console.log(files);
72+
});
73+
recursive("directory/to/read").then(files2 => console.log(files2));
74+
75+
jsonfile.readFile('baz.json').then(obj => console.log(obj))
76+
77+
(async function () {
78+
var walk = require('walkdir');
79+
walk('../', function(path, stat) {
80+
console.log('found: ', path);
81+
});
82+
var emitter = walk('../');
83+
emitter.on('file', function(filename, stat) { });
84+
walk.sync('../', function(path, stat) {
85+
console.log('found sync:', path);
86+
});
87+
var paths = walk.sync('../');
88+
let result = await walk.async('../')
89+
})();
90+
91+
var walker = require("walker");
92+
walker('/etc/').filterDir(() => {}).on('entry', () => {}); // only file access modelled.

javascript/ql/test/library-tests/frameworks/Concepts/tst-file-names.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,11 @@ async function foo() {
3737

3838
var files2 = await fastGlob.async(_).catch((wat) => {});
3939
}
40+
41+
var globule = require('globule');
42+
var filepaths = globule.find('**/*.js');
43+
var matches = globule.match('**/*.js', ["foo.js"])
44+
var bool = globule.isMatch('**/*.js', ["foo.js"])
45+
var map1 = globule.findMapping("foo/*.js")
46+
var map2 = globule.mapping({src: ["a.js", "b.js"]})
47+
var map3 = globule.mapping(["foo/a.js", "foo/b.js"])

0 commit comments

Comments
 (0)