Skip to content

Commit 3aefb7f

Browse files
authored
Merge pull request github#3613 from erik-krogh/Reassigned
Approved by asgerf
2 parents b015c73 + e4fe236 commit 3aefb7f

File tree

13 files changed

+1049
-12
lines changed

13 files changed

+1049
-12
lines changed

javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,4 +419,135 @@ module AccessPath {
419419
or
420420
result = node.getALocalSource()
421421
}
422+
423+
/**
424+
* A module for reasoning dominating reads and writes to access-paths.
425+
*/
426+
module DominatingPaths {
427+
/**
428+
* A classification of acccess paths into reads and writes.
429+
*/
430+
cached
431+
private newtype AccessPathKind =
432+
AccessPathRead() or
433+
AccessPathWrite()
434+
435+
/**
436+
* Gets the `ranking`th access-path with `root` and `path` within `bb`.
437+
* And the access-path has type `type`.
438+
*
439+
* Only has a result if there exists both a read and write of the access-path within `bb`.
440+
*/
441+
private ControlFlowNode rankedAccessPath(
442+
ReachableBasicBlock bb, Root root, string path, int ranking, AccessPathKind type
443+
) {
444+
result =
445+
rank[ranking](ControlFlowNode ref |
446+
ref = getAccessTo(root, path, _) and
447+
ref.getBasicBlock() = bb and
448+
// Prunes the accesses where there does not exists a read and write within the same basicblock.
449+
// This could be more precise, but doing it like this avoids massive joins.
450+
hasRead(bb) and
451+
hasWrite(bb)
452+
|
453+
ref order by any(int i | ref = bb.getNode(i))
454+
) and
455+
result = getAccessTo(root, path, type)
456+
}
457+
458+
/**
459+
* Holds if there exists an access-path read inside the basic-block `bb`.
460+
*
461+
* INTERNAL: This predicate is only meant to be used inside `rankedAccessPath`.
462+
*/
463+
pragma[noinline]
464+
private predicate hasRead(ReachableBasicBlock bb) {
465+
bb = getAccessTo(_, _, AccessPathRead()).getBasicBlock()
466+
}
467+
468+
/**
469+
* Holds if there exists an access-path write inside the basic-block `bb`.
470+
*
471+
* INTERNAL: This predicate is only meant to be used inside `rankedAccessPath`.
472+
*/
473+
pragma[noinline]
474+
private predicate hasWrite(ReachableBasicBlock bb) {
475+
bb = getAccessTo(_, _, AccessPathRead()).getBasicBlock()
476+
}
477+
478+
/**
479+
* Gets a `ControlFlowNode` for an access to `path` from `root` with type `type`.
480+
*
481+
* This predicate uses both the AccessPath API, and the SourceNode API.
482+
* This ensures that we have basic support for access-paths with ambiguous roots.
483+
*
484+
* There is only a result if both a read and a write of the access-path exists.
485+
*/
486+
pragma[nomagic]
487+
private ControlFlowNode getAccessTo(Root root, string path, AccessPathKind type) {
488+
exists(getAReadNode(root, path)) and
489+
exists(getAWriteNode(root, path)) and
490+
(
491+
type = AccessPathRead() and
492+
result = getAReadNode(root, path)
493+
or
494+
type = AccessPathWrite() and
495+
result = getAWriteNode(root, path)
496+
)
497+
}
498+
499+
/**
500+
* Gets a `ControlFlowNode` for a read to `path` from `root`.
501+
*
502+
* Only used within `getAccessTo`.
503+
*/
504+
private ControlFlowNode getAReadNode(Root root, string path) {
505+
exists(DataFlow::PropRead read | read.asExpr() = result |
506+
path = fromReference(read, root) or
507+
read = root.getAPropertyRead(path)
508+
)
509+
}
510+
511+
/**
512+
* Gets a `ControlFlowNode` for a write to `path` from `root`.
513+
*
514+
* Only used within `getAccessTo`.
515+
*/
516+
private ControlFlowNode getAWriteNode(Root root, string path) {
517+
result = root.getAPropertyWrite(path).getWriteNode()
518+
or
519+
exists(DataFlow::PropWrite write | path = fromRhs(write.getRhs(), root) |
520+
result = write.getWriteNode()
521+
)
522+
}
523+
524+
/**
525+
* Gets a basic-block where the access path defined by `root` and `path` is written to.
526+
* And a read to the same access path exists.
527+
*/
528+
private ReachableBasicBlock getAWriteBlock(Root root, string path) {
529+
result = getAccessTo(root, path, AccessPathWrite()).getBasicBlock() and
530+
exists(getAccessTo(root, path, AccessPathRead())) // helps performance
531+
}
532+
533+
/**
534+
* EXPERIMENTAL. This API may change in the future.
535+
*
536+
* Holds for `read` if there exists a previous write to the same access-path that dominates this read.
537+
*/
538+
cached
539+
predicate hasDominatingWrite(DataFlow::PropRead read) {
540+
// within the same basic block.
541+
exists(ReachableBasicBlock bb, Root root, string path, int ranking |
542+
read.asExpr() = rankedAccessPath(bb, root, path, ranking, AccessPathRead()) and
543+
exists(rankedAccessPath(bb, root, path, any(int prev | prev < ranking), AccessPathWrite()))
544+
)
545+
or
546+
// across basic blocks.
547+
exists(Root root, string path |
548+
read.asExpr() = getAccessTo(root, path, AccessPathRead()) and
549+
getAWriteBlock(root, path).strictlyDominates(read.getBasicBlock())
550+
)
551+
}
552+
}
422553
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,8 @@ module TaintTracking {
260260
not any(PromiseAllCreation call).getArrayNode() = succ
261261
or
262262
// reading from a tainted object yields a tainted result
263-
succ.(DataFlow::PropRead).getBase() = pred
263+
succ.(DataFlow::PropRead).getBase() = pred and
264+
not AccessPath::DominatingPaths::hasDominatingWrite(succ)
264265
or
265266
// iterating over a tainted iterator taints the loop variable
266267
exists(ForOfStmt fos |

javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -649,7 +649,8 @@ module TaintedPath {
649649
exists(DataFlow::PropRead read | read = dst |
650650
src = read.getBase() and
651651
read.getPropertyName() != "length" and
652-
srclabel = dstlabel
652+
srclabel = dstlabel and
653+
not AccessPath::DominatingPaths::hasDominatingWrite(read)
653654
)
654655
or
655656
// string method calls of interest

javascript/ql/test/library-tests/GlobalAccessPaths/GlobalAccessPaths.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ test_getAReferenceTo
6161
| test.js:39:14:39:16 | foo | foo |
6262
| test.js:39:14:39:20 | foo.bar | foo.bar |
6363
| test.js:40:3:40:10 | lazyInit | foo.bar |
64+
| test.js:44:13:44:18 | Object | Object |
65+
| test.js:44:13:44:25 | Object.create | Object.create |
66+
| test.js:50:7:50:12 | random | random |
67+
| test.js:56:7:56:12 | random | random |
6468
test_getAnAssignmentTo
6569
| other_ns.js:4:9:4:16 | NS \|\| {} | NS |
6670
| other_ns.js:6:12:6:13 | {} | Conflict |
@@ -71,9 +75,15 @@ test_getAnAssignmentTo
7175
| test.js:30:1:30:28 | functio ... on() {} | globalFunction |
7276
| test.js:32:1:35:1 | functio ... .baz'\\n} | destruct |
7377
| test.js:37:1:41:1 | functio ... Init;\\n} | lazy |
78+
| test.js:43:1:64:1 | functio ... // no\\n} | dominatingWrite |
7479
test_assignedUnique
7580
| GlobalClass |
7681
| destruct |
82+
| dominatingWrite |
7783
| f |
7884
| globalFunction |
7985
| lazy |
86+
hasDominatingWrite
87+
| test.js:48:3:48:11 | obj.prop1 |
88+
| test.js:57:5:57:13 | obj.prop3 |
89+
| test.js:62:29:62:37 | obj.prop5 |

javascript/ql/test/library-tests/GlobalAccessPaths/GlobalAccessPaths.ql

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ query string test_getAnAssignmentTo(DataFlow::Node node) {
99
}
1010

1111
query string test_assignedUnique() { AccessPath::isAssignedInUniqueFile(result) }
12+
13+
query DataFlow::PropRead hasDominatingWrite() {
14+
AccessPath::DominatingPaths::hasDominatingWrite(result)
15+
}

javascript/ql/test/library-tests/GlobalAccessPaths/test.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,26 @@ function lazy() {
3939
lazyInit = foo.bar; // 'foo.bar'
4040
lazyInit;
4141
}
42+
43+
function dominatingWrite() {
44+
var obj = Object.create();
45+
46+
obj.prop1; // no
47+
obj.prop1 = "foo";
48+
obj.prop1; // yes
49+
50+
if (random()) {
51+
obj.prop2 = "foo";
52+
}
53+
obj.prop2; // no
54+
55+
obj.prop3 = "foo";
56+
if (random()) {
57+
obj.prop3; // yes
58+
}
59+
60+
obj.prop4 = obj.prop4; // no
61+
62+
var foo = (obj.prop5 = 2, obj.prop5); // yes
63+
var bar = (obj.prop6, obj.prop6 = 3); // no
64+
}

0 commit comments

Comments
 (0)