Skip to content

Commit cc76782

Browse files
author
Dave Bartolomeo
committed
Merge remote-tracking branch 'upstream/master' into codeql-c-analysis/34-Bad-Overlap
2 parents bebf89f + ce0b72f commit cc76782

File tree

17 files changed

+412
-25
lines changed

17 files changed

+412
-25
lines changed

change-notes/1.24/analysis-javascript.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
| Use of password hash with insufficient computational effort (`js/insufficient-password-hash`) | Fewer false positive results | This query now recognizes additional cases that do not require secure hashing. |
8383
| Useless regular-expression character escape (`js/useless-regexp-character-escape`) | Fewer false positive results | This query now distinguishes escapes in strings and regular expression literals. |
8484
| Identical operands (`js/redundant-operation`) | Fewer results | This query now recognizes cases where the operands change a value using ++/-- expressions. |
85+
| Superfluous trailing arguments (`js/superfluous-trailing-arguments`) | Fewer results | This query now recognizes cases where a function uses the `Function.arguments` value to process a variable number of parameters. |
8586

8687
## Changes to libraries
8788

java/ql/src/semmle/code/java/Member.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,13 +481,13 @@ class GetterMethod extends Method {
481481

482482
/**
483483
* A finalizer method, with name `finalize`,
484-
* return type `void` and modifier `protected`.
484+
* return type `void` and no parameters.
485485
*/
486486
class FinalizeMethod extends Method {
487487
FinalizeMethod() {
488488
this.hasName("finalize") and
489489
this.getReturnType().hasName("void") and
490-
this.isProtected()
490+
this.hasNoParameters()
491491
}
492492
}
493493

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Provides classes for working with [SockJS](http://sockjs.org).
3+
*/
4+
5+
import javascript
6+
7+
/**
8+
* A model of the `SockJS` websocket data handler (https://sockjs.org).
9+
*/
10+
module SockJS {
11+
/**
12+
* Access to user-controlled data object received from websocket
13+
* For example:
14+
* ```
15+
* server.on('connection', function(conn) {
16+
* conn.on('data', function(message) {
17+
* ...
18+
* });
19+
* });
20+
* ```
21+
*/
22+
class SourceFromSocketJS extends RemoteFlowSource {
23+
SourceFromSocketJS() {
24+
exists(
25+
DataFlow::CallNode createServer, DataFlow::CallNode connNode,
26+
DataFlow::CallNode dataHandlerNode
27+
|
28+
createServer = appCreation() and
29+
connNode = createServer.getAMethodCall("on") and
30+
connNode.getArgument(0).getStringValue() = "connection" and
31+
dataHandlerNode = connNode.getCallback(1).getParameter(0).getAMethodCall("on") and
32+
dataHandlerNode.getArgument(0).getStringValue() = "data" and
33+
this = dataHandlerNode.getCallback(1).getParameter(0)
34+
)
35+
}
36+
37+
override string getSourceType() { result = "input from SockJS WebSocket" }
38+
}
39+
40+
/**
41+
* Gets a new SockJS server.
42+
*/
43+
private DataFlow::CallNode appCreation() {
44+
result = DataFlow::moduleImport("sockjs").getAMemberCall("createServer")
45+
}
46+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const express = require('express');
2+
const http = require('http');
3+
const sockjs = require('sockjs');
4+
5+
const app = express();
6+
const server = http.createServer(app);
7+
const sockjs_echo = sockjs.createServer({});
8+
sockjs_echo.on('connection', function(conn) {
9+
conn.on('data', function(message) {
10+
var data = JSON.parse(message);
11+
conn.write(JSON.stringify(eval(data.test)));
12+
});
13+
});
14+
15+
sockjs_echo.installHandlers(server, {prefix:'/echo'});
16+
server.listen(9090, '127.0.0.1');

javascript/ql/src/semmle/javascript/Functions.qll

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,14 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine
117117
ArgumentsVariable getArgumentsVariable() { result.getFunction() = this }
118118

119119
/** Holds if the body of this function refers to the function's `arguments` variable. */
120-
predicate usesArgumentsObject() { exists(getArgumentsVariable().getAnAccess()) }
120+
predicate usesArgumentsObject() {
121+
exists(getArgumentsVariable().getAnAccess())
122+
or
123+
exists(PropAccess read |
124+
read.getBase() = getVariable().getAnAccess() and
125+
read.getPropertyName() = "arguments"
126+
)
127+
}
121128

122129
/**
123130
* Holds if this function declares a parameter or local variable named `arguments`.

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

Lines changed: 2 additions & 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
@@ -184,6 +185,7 @@ module PromiseTypeTracking {
184185
* Gets the result from a single step through a promise, from `pred` with tracker `t2` to `result` with tracker `t`.
185186
* 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.
186187
*/
188+
pragma[inline]
187189
DataFlow::SourceNode promiseStep(
188190
DataFlow::SourceNode pred, DataFlow::TypeTracker t, DataFlow::TypeTracker t2
189191
) {

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

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -958,18 +958,31 @@ private predicate reachableFromStoreBase(
958958
s2.getEndLabel())
959959
)
960960
or
961-
exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary |
962-
reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and
963-
(
964-
flowStep(mid, cfg, nd, newSummary)
965-
or
966-
isAdditionalLoadStoreStep(mid, nd, prop, cfg) and
967-
newSummary = PathSummary::level()
968-
) and
961+
exists(PathSummary oldSummary, PathSummary newSummary |
962+
reachableFromStoreBaseStep(prop, rhs, nd, cfg, oldSummary, newSummary) and
969963
summary = oldSummary.appendValuePreserving(newSummary)
970964
)
971965
}
972966

967+
/**
968+
* Holds if `rhs` is the right-hand side of a write to property `prop`, and `nd` is reachable
969+
* from the base of that write under configuration `cfg` (possibly through callees) along a
970+
* path whose last step is summarized by `newSummary`, and the previous steps are summarized
971+
* by `oldSummary`.
972+
*/
973+
pragma[noinline]
974+
private predicate reachableFromStoreBaseStep(
975+
string prop, DataFlow::Node rhs, DataFlow::Node nd, DataFlow::Configuration cfg,
976+
PathSummary oldSummary, PathSummary newSummary
977+
) {
978+
exists(DataFlow::Node mid | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) |
979+
flowStep(mid, cfg, nd, newSummary)
980+
or
981+
isAdditionalLoadStoreStep(mid, nd, prop, cfg) and
982+
newSummary = PathSummary::level()
983+
)
984+
}
985+
973986
/**
974987
* Holds if the value of `pred` is written to a property of some base object, and that base
975988
* object may flow into the base of property read `succ` under configuration `cfg` along
@@ -981,13 +994,29 @@ pragma[noinline]
981994
private predicate flowThroughProperty(
982995
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary summary
983996
) {
984-
exists(string prop, DataFlow::Node base, PathSummary oldSummary, PathSummary newSummary |
985-
reachableFromStoreBase(prop, pred, base, cfg, oldSummary) and
986-
loadStep(base, succ, prop, cfg, newSummary) and
997+
exists(PathSummary oldSummary, PathSummary newSummary |
998+
storeToLoad(pred, succ, cfg, oldSummary, newSummary) and
987999
summary = oldSummary.append(newSummary)
9881000
)
9891001
}
9901002

1003+
/**
1004+
* Holds if the value of `pred` is written to a property of some base object, and that base
1005+
* object may flow into the base of property read `succ` under configuration `cfg` along
1006+
* a path whose last step is summarized by `newSummary`, and the previous steps are summarized
1007+
* by `oldSummary`.
1008+
*/
1009+
pragma[noinline]
1010+
private predicate storeToLoad(
1011+
DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg, PathSummary oldSummary,
1012+
PathSummary newSummary
1013+
) {
1014+
exists(string prop, DataFlow::Node base |
1015+
reachableFromStoreBase(prop, pred, base, cfg, oldSummary) and
1016+
loadStep(base, succ, prop, cfg, newSummary)
1017+
)
1018+
}
1019+
9911020
/**
9921021
* Holds if `arg` and `cb` are passed as arguments to a function which in turn
9931022
* invokes `cb`, passing `arg` as its `i`th argument.

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,12 @@ class InvokeNode extends DataFlow::SourceNode {
157157
* `name` is set to `result`.
158158
*/
159159
DataFlow::ValueNode getOptionArgument(int i, string name) {
160-
exists(ObjectLiteralNode obj |
161-
obj.flowsTo(getArgument(i)) and
162-
obj.hasPropertyWrite(name, result)
163-
)
160+
getOptionsArgument(i).hasPropertyWrite(name, result)
164161
}
165162

163+
pragma[noinline]
164+
private ObjectLiteralNode getOptionsArgument(int i) { result.flowsTo(getArgument(i)) }
165+
166166
/** Gets an abstract value representing possible callees of this call site. */
167167
final AbstractValue getACalleeValue() { result = getCalleeNode().analyze().getAValue() }
168168

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 |

0 commit comments

Comments
 (0)