mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
JS: introduce CapturedSource
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Provides classes for the nodes that the dataflow library can reason about soundly.
|
||||
*/
|
||||
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* Holds if the dataflow library can not track flow through `escape` due to `cause`.
|
||||
*/
|
||||
private predicate isEscape(DataFlow::Node escape, string cause) {
|
||||
escape = any(DataFlow::InvokeNode invk).getAnArgument() and cause = "argument"
|
||||
or
|
||||
escape = any(DataFlow::FunctionNode fun).getAReturn() and cause = "return"
|
||||
or
|
||||
escape = any(ThrowStmt t).getExpr().flow() and cause = "throw"
|
||||
or
|
||||
escape = any(DataFlow::GlobalVariable v).getAnAssignedExpr().flow() and cause = "global"
|
||||
or
|
||||
escape = any(DataFlow::PropWrite write).getRhs() and cause = "heap"
|
||||
or
|
||||
escape = any(ExportDeclaration e).getSourceNode(_) and cause = "export"
|
||||
or
|
||||
any(WithStmt with).mayAffect(escape.asExpr()) and cause = "heap"
|
||||
}
|
||||
|
||||
private DataFlow::Node getAnEscape() {
|
||||
isEscape(result, _)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `n` can flow to a `this`-variable.
|
||||
*/
|
||||
private predicate exposedAsReceiver(DataFlow::SourceNode n) {
|
||||
// pragmatic limitation: guarantee for object literals only
|
||||
not n instanceof DataFlow::ObjectLiteralNode
|
||||
or
|
||||
exists(AbstractValue v | n.getAPropertyWrite().getRhs().analyze().getALocalValue() = v |
|
||||
v.isIndefinite(_) or
|
||||
exists(ThisExpr dis | dis.getBinder() = v.(AbstractCallable).getFunction())
|
||||
)
|
||||
or
|
||||
n.flowsToExpr(any(FunctionBindExpr bind).getObject())
|
||||
or
|
||||
// technically, the builtin prototypes could have a `this`-using function through which this node escapes, but we ignore that here
|
||||
// (we also ignore `o['__' + 'proto__"] = ...`)
|
||||
exists(n.getAPropertyWrite("__proto__"))
|
||||
or
|
||||
// could check the assigned value of all affected variables, but it is unlikely to matter in practice
|
||||
exists(WithStmt with | n.flowsToExpr(with.getExpr()))
|
||||
}
|
||||
|
||||
/**
|
||||
* A source for which the flow is entirely captured by the dataflow library.
|
||||
* All uses of the node is represented by `this.flowsTo(_)` and friends.
|
||||
*/
|
||||
class CapturedSource extends DataFlow::SourceNode {
|
||||
CapturedSource() {
|
||||
// pragmatic limitation: object literals only
|
||||
this instanceof DataFlow::ObjectLiteralNode and
|
||||
not flowsTo(getAnEscape()) and
|
||||
not exposedAsReceiver(this)
|
||||
}
|
||||
|
||||
predicate hasOwnProperty(string name) {
|
||||
// the property is defined in the initializer,
|
||||
any(DataFlow::PropWrite write).writes(this, name, _) and
|
||||
// and it is never deleted
|
||||
not exists(DeleteExpr del, DataFlow::PropRef ref |
|
||||
del.getOperand().flow() = ref and
|
||||
flowsTo(ref.getBase()) and
|
||||
(ref.getPropertyName() = name or not exists(ref.getPropertyName()))
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} |
|
||||
| method-calls.js:11:23:11:24 | {} |
|
||||
| method-calls.js:16:11:16:12 | {} |
|
||||
| method-calls.js:23:11:23:17 | {m: f1} |
|
||||
| method-calls.js:27:11:27:17 | {m: f2} |
|
||||
| method-calls.js:32:12:32:18 | {m: f3} |
|
||||
| method-calls.js:36:15:36:21 | {m: f2} |
|
||||
| method-calls.js:42:16:42:28 | {m: () => 42} |
|
||||
| method-calls.js:46:17:46:29 | {m: () => 42} |
|
||||
| method-calls.js:50:16:50:28 | {m: () => 42} |
|
||||
| method-calls.js:53:16:53:28 | {m: () => 42} |
|
||||
| tst.js:3:18:3:19 | {} |
|
||||
| tst.js:4:18:4:36 | { f: function(){} } |
|
||||
| tst.js:5:18:5:34 | { [unknown]: 42 } |
|
||||
| tst.js:38:18:38:26 | { p: 42 } |
|
||||
| tst.js:42:18:42:33 | { p: 42, q: 42 } |
|
||||
| tst.js:52:23:52:24 | {} |
|
||||
| tst.js:56:20:56:35 | { p: 42, p: 42 } |
|
||||
| tst.js:59:20:59:28 | { p: 42 } |
|
||||
| tst.js:63:20:63:28 | { p: 42 } |
|
||||
| tst.js:68:19:68:27 | { p: 42 } |
|
||||
| tst.js:73:18:73:20 | { } |
|
||||
| tst.js:76:18:76:26 | { p: 42 } |
|
||||
| tst.js:77:18:77:28 | { p: true } |
|
||||
| tst.js:82:20:82:21 | {} |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.CapturedNodes
|
||||
|
||||
select any(CapturedSource n)
|
||||
@@ -0,0 +1,22 @@
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m1 |
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m2 |
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m3 |
|
||||
| method-calls.js:2:11:7:2 | {\\n\\t\\tm1: ... }; }\\n\\t} | m4 |
|
||||
| method-calls.js:23:11:23:17 | {m: f1} | m |
|
||||
| method-calls.js:27:11:27:17 | {m: f2} | m |
|
||||
| method-calls.js:32:12:32:18 | {m: f3} | m |
|
||||
| method-calls.js:36:15:36:21 | {m: f2} | m |
|
||||
| method-calls.js:42:16:42:28 | {m: () => 42} | m |
|
||||
| method-calls.js:46:17:46:29 | {m: () => 42} | m |
|
||||
| method-calls.js:50:16:50:28 | {m: () => 42} | m |
|
||||
| method-calls.js:53:16:53:28 | {m: () => 42} | m |
|
||||
| tst.js:4:18:4:36 | { f: function(){} } | f |
|
||||
| tst.js:38:18:38:26 | { p: 42 } | p |
|
||||
| tst.js:42:18:42:33 | { p: 42, q: 42 } | p |
|
||||
| tst.js:42:18:42:33 | { p: 42, q: 42 } | q |
|
||||
| tst.js:56:20:56:35 | { p: 42, p: 42 } | p |
|
||||
| tst.js:59:20:59:28 | { p: 42 } | p |
|
||||
| tst.js:63:20:63:28 | { p: 42 } | p |
|
||||
| tst.js:68:19:68:27 | { p: 42 } | p |
|
||||
| tst.js:76:18:76:26 | { p: 42 } | p |
|
||||
| tst.js:77:18:77:28 | { p: true } | p |
|
||||
@@ -0,0 +1,6 @@
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.CapturedNodes
|
||||
|
||||
from CapturedSource src, string name
|
||||
where src.hasOwnProperty(name)
|
||||
select src, name
|
||||
@@ -0,0 +1,12 @@
|
||||
| method-calls.js:8:2:8:8 | o1.m1() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:9:2:9:8 | o1.m2() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:12:2:12:7 | o.m3() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:19:2:19:7 | o2.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:23:11:23:21 | {m: f1}.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:28:11:28:16 | o2.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:32:11:32:23 | ({m: f3}).m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:37:11:37:16 | o4.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:43:12:43:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:47:12:47:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:51:12:51:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
| method-calls.js:54:12:54:16 | o.m() | boolean, class, date, function, null, number, object, regular expression,string or undefined |
|
||||
@@ -0,0 +1,4 @@
|
||||
import javascript
|
||||
|
||||
from DataFlow::MethodCallNode call
|
||||
select call, call.analyze().ppTypes()
|
||||
@@ -0,0 +1,4 @@
|
||||
| method-calls.js:23:11:23:21 | {m: f1}.m() | method-calls.js:24:2:24:3 | v1 | file://:0:0:0:0 | indefinite value (call) |
|
||||
| method-calls.js:28:11:28:16 | o2.m() | method-calls.js:29:2:29:3 | v2 | file://:0:0:0:0 | indefinite value (call) |
|
||||
| method-calls.js:32:11:32:23 | ({m: f3}).m() | method-calls.js:33:2:33:3 | v3 | file://:0:0:0:0 | indefinite value (call) |
|
||||
| method-calls.js:37:11:37:16 | o4.m() | method-calls.js:38:2:38:3 | v4 | file://:0:0:0:0 | indefinite value (call) |
|
||||
@@ -0,0 +1,5 @@
|
||||
import javascript
|
||||
|
||||
from DataFlow::MethodCallNode call, DataFlow::Node use
|
||||
where call.flowsTo(use) and use != call and not exists(use.getASuccessor())
|
||||
select call, use, use.analyze().getAValue()
|
||||
@@ -0,0 +1,57 @@
|
||||
(function() {
|
||||
var o1 = {
|
||||
m1: function(){ return {}; },
|
||||
m2: function(){ return {}; },
|
||||
m3: function(){ return {}; },
|
||||
m4: function(){ return {}; }
|
||||
};
|
||||
o1.m1();
|
||||
o1.m2();
|
||||
unknown(o1.m2);
|
||||
var o = unknown? o1: {};
|
||||
o.m3(); // NOT supported
|
||||
var m4 = o.m4;
|
||||
m4();
|
||||
|
||||
var o2 = {};
|
||||
o2.m = function() { return {}; };
|
||||
o2[unknown] = function() { return true; }; // could be __proto__
|
||||
o2.m();
|
||||
});
|
||||
(function(){
|
||||
function f1(){return {};}
|
||||
var v1 = {m: f1}.m();
|
||||
v1 === true;
|
||||
|
||||
function f2(){return {};}
|
||||
var o2 = {m: f2};
|
||||
var v2 = o2.m();
|
||||
v2 === true;
|
||||
|
||||
function f3(){return {};}
|
||||
var v3 = ({m: f3}).m();
|
||||
v3 === true;
|
||||
|
||||
function f4(){return {};}
|
||||
var { o4 } = {m: f2};
|
||||
var v4 = o4.m();
|
||||
v4 === true;
|
||||
});
|
||||
|
||||
(function(){
|
||||
(function(o = {m: () => 42}){
|
||||
var v1 = o.m();
|
||||
})(unknown);
|
||||
|
||||
function f(o = {m: () => 42}){
|
||||
var v2 = o.m();
|
||||
};
|
||||
f(unknown);
|
||||
(function(o = {m: () => 42}){
|
||||
var v3 = o.m();
|
||||
})({m: unknown});
|
||||
(function(o = {m: () => 42}){
|
||||
var v4 = o.m();
|
||||
})({m: () => true});
|
||||
|
||||
});
|
||||
93
javascript/ql/test/library-tests/CapturedNodes/tst.js
Normal file
93
javascript/ql/test/library-tests/CapturedNodes/tst.js
Normal file
@@ -0,0 +1,93 @@
|
||||
(function capturedSource(){
|
||||
|
||||
let captured1 = {};
|
||||
let captured2 = { f: function(){} };
|
||||
let captured3 = { [unknown]: 42 };
|
||||
|
||||
unknown({});
|
||||
|
||||
function known(){}
|
||||
known({});
|
||||
|
||||
function known_escaping(e){unknown(e)}
|
||||
known_escaping({});
|
||||
|
||||
(function(){return {}});
|
||||
|
||||
(function(){throw {}});
|
||||
|
||||
global = {};
|
||||
|
||||
this.p = {};
|
||||
|
||||
let local_in_with;
|
||||
with (unknown) {
|
||||
local_in_with = {};
|
||||
}
|
||||
|
||||
with({}){}
|
||||
|
||||
({ m: function(){ this; } });
|
||||
({ m: unknown });
|
||||
let indirectlyUnknown = unknown? unknown: function(){};
|
||||
({ m: indirectlyUnknown });
|
||||
});
|
||||
|
||||
(function capturedProperty(){
|
||||
|
||||
let captured1 = { p: 42 };
|
||||
captured1.p;
|
||||
captured1.p;
|
||||
|
||||
let captured2 = { p: 42, q: 42 };
|
||||
captured2.p;
|
||||
captured2.p;
|
||||
captured2.q = 42;
|
||||
captured2 = 42;
|
||||
|
||||
let nonObject = function(){}
|
||||
nonObject.p = 42;
|
||||
nonObject.p;
|
||||
|
||||
let nonInitializer = {};
|
||||
nonInitializer.p = 42;
|
||||
nonInitializer.p;
|
||||
|
||||
let overridden1 = { p: 42, p: 42 };
|
||||
overridden1.p;
|
||||
|
||||
let overridden2 = { p: 42 };
|
||||
overridden2.p = 42;
|
||||
overridden2.p;
|
||||
|
||||
let overridden3 = { p: 42 };
|
||||
overridden3[x] = 42;
|
||||
overridden3.p;
|
||||
|
||||
function f(o) {
|
||||
let captured3 = { p: 42 };
|
||||
o = o || captured3;
|
||||
o.p;
|
||||
}
|
||||
|
||||
let captured4 = { };
|
||||
captured4.p;
|
||||
|
||||
let captured5 = { p: 42 },
|
||||
captured6 = { p: true };
|
||||
(unknown? captured5: captured6).p; // could support this with a bit of extra work
|
||||
|
||||
(function(semiCaptured7){
|
||||
if(unknown)
|
||||
semiCaptured7 = {};
|
||||
semiCaptured7.p = 42;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
(function (){
|
||||
let bound = {};
|
||||
bound::unknown();
|
||||
});
|
||||
|
||||
// semmle-extractor-options: --experimental
|
||||
6
javascript/ql/test/library-tests/CapturedNodes/tst.ts
Normal file
6
javascript/ql/test/library-tests/CapturedNodes/tst.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
class C {
|
||||
constructor(
|
||||
private readonly F: { timeout: number } = { timeout: 1500 }
|
||||
) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user