mirror of
https://github.com/github/codeql.git
synced 2026-05-02 12:15:17 +02:00
Merge pull request #2855 from asger-semmle/js/returned-partial-call
Approved by esbena
This commit is contained in:
@@ -71,6 +71,7 @@
|
||||
private import javascript
|
||||
private import internal.FlowSteps
|
||||
private import internal.AccessPaths
|
||||
private import internal.CallGraphs
|
||||
|
||||
/**
|
||||
* A data flow tracking configuration for finding inter-procedural paths from
|
||||
@@ -620,10 +621,11 @@ private predicate exploratoryFlowStep(
|
||||
isAdditionalStoreStep(pred, succ, _, cfg) or
|
||||
isAdditionalLoadStep(pred, succ, _, cfg) or
|
||||
isAdditionalLoadStoreStep(pred, succ, _, cfg) or
|
||||
// the following two disjuncts taken together over-approximate flow through
|
||||
// the following three disjuncts taken together over-approximate flow through
|
||||
// higher-order calls
|
||||
callback(pred, succ) or
|
||||
succ = pred.(DataFlow::FunctionNode).getAParameter()
|
||||
succ = pred.(DataFlow::FunctionNode).getAParameter() or
|
||||
exploratoryBoundInvokeStep(pred, succ)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,7 +753,7 @@ private predicate flowThroughCall(
|
||||
) {
|
||||
exists(Function f, DataFlow::ValueNode ret |
|
||||
ret.asExpr() = f.getAReturnedExpr() and
|
||||
calls(output, f) and // Do not consider partial calls
|
||||
(calls(output, f) or callsBound(output, f, _)) and // Do not consider partial calls
|
||||
reachableFromInput(f, output, input, ret, cfg, summary) and
|
||||
not isBarrierEdge(cfg, ret, output) and
|
||||
not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and
|
||||
@@ -761,7 +763,7 @@ private predicate flowThroughCall(
|
||||
exists(Function f, DataFlow::Node invk, DataFlow::Node ret |
|
||||
DataFlow::exceptionalFunctionReturnNode(ret, f) and
|
||||
DataFlow::exceptionalInvocationReturnNode(output, invk.asExpr()) and
|
||||
calls(invk, f) and
|
||||
(calls(invk, f) or callsBound(invk, f, _)) and
|
||||
reachableFromInput(f, invk, input, ret, cfg, summary) and
|
||||
not isBarrierEdge(cfg, ret, output) and
|
||||
not isLabeledBarrierEdge(cfg, ret, output, summary.getEndLabel()) and
|
||||
@@ -1032,6 +1034,13 @@ private predicate flowIntoHigherOrderCall(
|
||||
succ = cb.getParameter(i) and
|
||||
summary = oldSummary.append(PathSummary::call())
|
||||
)
|
||||
or
|
||||
exists(DataFlow::SourceNode cb, DataFlow::FunctionNode f, int i, int boundArgs, PathSummary oldSummary |
|
||||
higherOrderCall(pred, cb, i, cfg, oldSummary) and
|
||||
cb = CallGraph::getABoundFunctionReference(f, boundArgs, false) and
|
||||
succ = f.getParameter(boundArgs + i) and
|
||||
summary = oldSummary.append(PathSummary::call())
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -147,7 +147,7 @@ module DataFlow {
|
||||
final FunctionNode getABoundFunctionValue(int boundArgs) {
|
||||
result = getAFunctionValue() and boundArgs = 0
|
||||
or
|
||||
CallGraph::getABoundFunctionReference(result, boundArgs).flowsTo(this)
|
||||
CallGraph::getABoundFunctionReference(result, boundArgs, _).flowsTo(this)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -74,7 +74,7 @@ module CallGraph {
|
||||
*/
|
||||
pragma[nomagic]
|
||||
private
|
||||
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
|
||||
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) {
|
||||
exists(DataFlow::PartialInvokeNode partial, DataFlow::Node callback |
|
||||
result = partial.getBoundFunction(callback, boundArgs) and
|
||||
getAFunctionReference(function, 0, t.continue()).flowsTo(callback)
|
||||
@@ -90,7 +90,7 @@ module CallGraph {
|
||||
private
|
||||
DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) {
|
||||
exists(DataFlow::SourceNode prev |
|
||||
prev = getABoundFunctionReference(function, boundArgs, t) and
|
||||
prev = getABoundFunctionReferenceAux(function, boundArgs, t) and
|
||||
DataFlow::StepSummary::step(prev, result, summary)
|
||||
)
|
||||
}
|
||||
@@ -100,8 +100,12 @@ module CallGraph {
|
||||
* with `function` as the underlying function.
|
||||
*/
|
||||
cached
|
||||
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs) {
|
||||
result = getABoundFunctionReference(function, boundArgs, DataFlow::TypeTracker::end())
|
||||
DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, boolean contextDependent) {
|
||||
exists(DataFlow::TypeTracker t |
|
||||
result = getABoundFunctionReferenceAux(function, boundArgs, t) and
|
||||
t.end() and
|
||||
contextDependent = t.hasCall()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import javascript
|
||||
import semmle.javascript.dataflow.Configuration
|
||||
import semmle.javascript.dataflow.internal.CallGraphs
|
||||
|
||||
/**
|
||||
* Holds if flow should be tracked through properties of `obj`.
|
||||
@@ -91,6 +92,32 @@ private module CachedSteps {
|
||||
cached
|
||||
predicate calls(DataFlow::InvokeNode invk, Function f) { f = invk.getACallee(0) }
|
||||
|
||||
/**
|
||||
* Holds if `invk` may invoke a bound version of `f` with `boundArgs` already bound.
|
||||
*
|
||||
* The receiver is assumed to be bound as well, and should not propagate into `f`.
|
||||
*
|
||||
* Does not hold for context-dependent call sites, such as callback invocations.
|
||||
*/
|
||||
cached
|
||||
predicate callsBound(DataFlow::InvokeNode invk, Function f, int boundArgs) {
|
||||
CallGraph::getABoundFunctionReference(f.flow(), boundArgs, false).flowsTo(invk.getCalleeNode())
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `pred` may flow to `succ` through an invocation of a bound function.
|
||||
*
|
||||
* Should only be used for graph pruning, as the edge may lead to spurious flow.
|
||||
*/
|
||||
cached
|
||||
predicate exploratoryBoundInvokeStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(DataFlow::InvokeNode invk, DataFlow::FunctionNode f, int i, int boundArgs |
|
||||
CallGraph::getABoundFunctionReference(f, boundArgs, _).flowsTo(invk.getCalleeNode()) and
|
||||
pred = invk.getArgument(i) and
|
||||
succ = f.getParameter(i + boundArgs)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `invk` may invoke `f` indirectly through the given `callback` argument.
|
||||
*
|
||||
@@ -141,6 +168,14 @@ private module CachedSteps {
|
||||
partiallyCalls(invk, callback, f) and
|
||||
parm = DataFlow::thisNode(f)
|
||||
)
|
||||
or
|
||||
exists(int boundArgs, int i, Parameter p |
|
||||
callsBound(invk, f, boundArgs) and
|
||||
f.getParameter(boundArgs + i) = p and
|
||||
not p.isRestParameter() and
|
||||
arg = invk.getArgument(i) and
|
||||
parm = DataFlow::parameterNode(p)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +193,7 @@ private module CachedSteps {
|
||||
*/
|
||||
cached
|
||||
predicate returnStep(DataFlow::Node pred, DataFlow::Node succ) {
|
||||
exists(Function f | calls(succ, f) |
|
||||
exists(Function f | calls(succ, f) or callsBound(succ, f, _) |
|
||||
returnExpr(f, pred, _)
|
||||
or
|
||||
succ instanceof DataFlow::NewNode and
|
||||
@@ -167,8 +202,11 @@ private module CachedSteps {
|
||||
or
|
||||
exists(InvokeExpr invoke, Function fun |
|
||||
DataFlow::exceptionalFunctionReturnNode(pred, fun) and
|
||||
DataFlow::exceptionalInvocationReturnNode(succ, invoke) and
|
||||
DataFlow::exceptionalInvocationReturnNode(succ, invoke)
|
||||
|
|
||||
calls(invoke.flow(), fun)
|
||||
or
|
||||
callsBound(invoke.flow(), fun, _)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -464,4 +502,3 @@ module PathSummary {
|
||||
*/
|
||||
PathSummary return() { exists(FlowLabel lbl | result = MkPathSummary(true, false, lbl, lbl)) }
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
spuriousCallee
|
||||
missingCallee
|
||||
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} |
|
||||
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} |
|
||||
| constructor-field.ts:40:5:40:14 | f3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 |
|
||||
| constructor-field.ts:71:1:71:11 | bf3.build() | constructor-field.ts:13:3:13:12 | build() {} | -1 |
|
||||
badAnnotation
|
||||
|
||||
@@ -33,16 +33,37 @@ class AnnotatedCall extends InvokeExpr {
|
||||
string getCallTargetName() { result = calls }
|
||||
|
||||
AnnotatedFunction getAnExpectedCallee() { result.getCalleeName() = getCallTargetName() }
|
||||
|
||||
int getBoundArgs() {
|
||||
result = getAnnotation(this, "boundArgs").toInt()
|
||||
}
|
||||
|
||||
int getBoundArgsOrMinusOne() {
|
||||
result = getBoundArgs()
|
||||
or
|
||||
not exists(getBoundArgs()) and
|
||||
result = -1
|
||||
}
|
||||
}
|
||||
|
||||
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target) {
|
||||
FlowSteps::calls(call.flow(), target) and
|
||||
not target = call.getAnExpectedCallee()
|
||||
predicate callEdge(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
|
||||
FlowSteps::calls(call.flow(), target) and boundArgs = -1
|
||||
or
|
||||
FlowSteps::callsBound(call.flow(), target, boundArgs)
|
||||
}
|
||||
|
||||
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target) {
|
||||
not FlowSteps::calls(call.flow(), target) and
|
||||
target = call.getAnExpectedCallee()
|
||||
query predicate spuriousCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
|
||||
callEdge(call, target, boundArgs) and
|
||||
not (
|
||||
target = call.getAnExpectedCallee() and
|
||||
boundArgs = call.getBoundArgsOrMinusOne()
|
||||
)
|
||||
}
|
||||
|
||||
query predicate missingCallee(AnnotatedCall call, AnnotatedFunction target, int boundArgs) {
|
||||
not callEdge(call, target, boundArgs) and
|
||||
target = call.getAnExpectedCallee() and
|
||||
boundArgs = call.getBoundArgsOrMinusOne()
|
||||
}
|
||||
|
||||
query predicate badAnnotation(string name) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
var url = require('url')
|
||||
var http = require('http')
|
||||
|
||||
function Mount () {}
|
||||
|
||||
/** name:mount.serve */
|
||||
Mount.prototype.serve = function (x) {
|
||||
}
|
||||
|
||||
function makeMount() {
|
||||
var m = new Mount()
|
||||
return m.serve.bind(m);
|
||||
}
|
||||
|
||||
function makeMount2(x) {
|
||||
var m = new Mount()
|
||||
return m.serve.bind(m, x);
|
||||
}
|
||||
|
||||
var mount = makeMount()
|
||||
var mount2 = makeMount2(null);
|
||||
|
||||
http.createServer(function (req, res) {
|
||||
/** calls:mount.serve */
|
||||
/** boundArgs:0 */
|
||||
mount(req, res)
|
||||
|
||||
/** calls:mount.serve */
|
||||
/** boundArgs:1 */
|
||||
mount2(res)
|
||||
});
|
||||
@@ -22,6 +22,10 @@
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x |
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x |
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y |
|
||||
| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val |
|
||||
| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v |
|
||||
| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v |
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x |
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x |
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y |
|
||||
| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val |
|
||||
| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v |
|
||||
| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v |
|
||||
|
||||
@@ -27,6 +27,10 @@
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:15:15:15:15 | x |
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:21:15:21:15 | x |
|
||||
| partial.js:5:15:5:24 | "tainted1" | partial.js:27:15:27:15 | x |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:10:15:10:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:16:15:16:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:22:15:22:15 | y |
|
||||
| partial.js:6:15:6:24 | "tainted2" | partial.js:28:15:28:15 | y |
|
||||
| promises.js:2:16:2:24 | "tainted" | promises.js:7:16:7:18 | val |
|
||||
| promises.js:2:16:2:24 | "tainted" | promises.js:38:32:38:32 | v |
|
||||
| promises.js:11:22:11:31 | "resolved" | promises.js:19:20:19:20 | v |
|
||||
|
||||
@@ -15,6 +15,12 @@ typeInferenceMismatch
|
||||
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:13:10:13:10 | x |
|
||||
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:19:10:19:10 | x |
|
||||
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:22:10:22:10 | x |
|
||||
| bound-function.js:12:12:12:19 | source() | bound-function.js:4:10:4:10 | y |
|
||||
| bound-function.js:14:6:14:13 | source() | bound-function.js:4:10:4:10 | y |
|
||||
| bound-function.js:22:8:22:15 | source() | bound-function.js:25:10:25:10 | y |
|
||||
| bound-function.js:45:10:45:17 | source() | bound-function.js:45:6:45:18 | id3(source()) |
|
||||
| bound-function.js:49:12:49:19 | source() | bound-function.js:54:6:54:14 | source0() |
|
||||
| bound-function.js:49:12:49:19 | source() | bound-function.js:55:6:55:14 | source1() |
|
||||
| callbacks.js:4:6:4:13 | source() | callbacks.js:34:27:34:27 | x |
|
||||
| callbacks.js:4:6:4:13 | source() | callbacks.js:35:27:35:27 | x |
|
||||
| callbacks.js:5:6:5:13 | source() | callbacks.js:34:27:34:27 | x |
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:13:10:13:10 | x |
|
||||
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:19:10:19:10 | x |
|
||||
| booleanOps.js:2:11:2:18 | source() | booleanOps.js:22:10:22:10 | x |
|
||||
| bound-function.js:12:12:12:19 | source() | bound-function.js:4:10:4:10 | y |
|
||||
| bound-function.js:14:6:14:13 | source() | bound-function.js:4:10:4:10 | y |
|
||||
| bound-function.js:22:8:22:15 | source() | bound-function.js:25:10:25:10 | y |
|
||||
| bound-function.js:45:10:45:17 | source() | bound-function.js:45:6:45:18 | id3(source()) |
|
||||
| bound-function.js:49:12:49:19 | source() | bound-function.js:54:6:54:14 | source0() |
|
||||
| bound-function.js:49:12:49:19 | source() | bound-function.js:55:6:55:14 | source1() |
|
||||
| callbacks.js:4:6:4:13 | source() | callbacks.js:34:27:34:27 | x |
|
||||
| callbacks.js:4:6:4:13 | source() | callbacks.js:35:27:35:27 | x |
|
||||
| callbacks.js:5:6:5:13 | source() | callbacks.js:34:27:34:27 | x |
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import * as dummy from 'dummy';
|
||||
|
||||
function foo(x, y) {
|
||||
sink(y);
|
||||
}
|
||||
|
||||
let foo0 = foo.bind(null);
|
||||
let foo1 = foo.bind(null, null);
|
||||
let foo2 = foo.bind(null, null, null);
|
||||
|
||||
foo0(source(), null); // OK
|
||||
foo0(null, source()); // NOT OK
|
||||
|
||||
foo1(source()); // NOT OK
|
||||
foo1(null, source()); // OK
|
||||
|
||||
foo2(source()); // OK
|
||||
foo2(null, source()); // OK
|
||||
|
||||
|
||||
function takesCallback(cb) {
|
||||
cb(source()); // NOT OK
|
||||
}
|
||||
function callback(x, y) {
|
||||
sink(y);
|
||||
}
|
||||
takesCallback(callback.bind(null, null));
|
||||
|
||||
function id(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
let sourceGetter = id.bind(null, source());
|
||||
let constGetter = id.bind(null, 'safe');
|
||||
|
||||
sink(sourceGetter()); // NOT OK - but not flagged
|
||||
sink(constGetter()); // OK
|
||||
|
||||
function id2(x, y) {
|
||||
return y;
|
||||
}
|
||||
|
||||
let id3 = id2.bind(null, null);
|
||||
|
||||
sink(id3(source())); // NOT OK
|
||||
sink(id3('safe')); // OK
|
||||
|
||||
function getSource() {
|
||||
return source();
|
||||
}
|
||||
let source0 = getSource.bind(null);
|
||||
let source1 = getSource.bind(null, null);
|
||||
|
||||
sink(source0()); // NOT OK
|
||||
sink(source1()); // NOT OK
|
||||
Reference in New Issue
Block a user