Skip to content

Commit f921cf7

Browse files
authored
Merge pull request #2512 from erik-krogh/moarExceptions
Approved by esbena, max-schaefer
2 parents 5b5d2f2 + bf56797 commit f921cf7

File tree

6 files changed

+338
-146
lines changed

6 files changed

+338
-146
lines changed

javascript/ql/src/semmle/javascript/StandardLibrary.qll

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,22 @@ private class PromiseFlowStep extends DataFlow::AdditionalFlowStep {
209209
}
210210
}
211211

212+
/**
213+
* A data flow edge from the exceptional return of the promise executor to the promise catch handler.
214+
* This only adds an edge from the exceptional return of the promise executor to a `.catch()` handler.
215+
*/
216+
private class PromiseExceptionalStep extends DataFlow::AdditionalFlowStep {
217+
PromiseDefinition promise;
218+
PromiseExceptionalStep() {
219+
promise = this
220+
}
221+
222+
override predicate step(DataFlow::Node pred, DataFlow::Node succ) {
223+
pred = promise.getExecutor().getExceptionalReturn() and
224+
succ = promise.getACatchHandler().getParameter(0)
225+
}
226+
}
227+
212228
/**
213229
* Holds if taint propagates from `pred` to `succ` through promises.
214230
*/

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

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ module ExceptionXss {
1010
import DomBasedXssCustomizations::DomBasedXss as DomBasedXssCustom
1111
import ReflectedXssCustomizations::ReflectedXss as ReflectedXssCustom
1212
import Xss as Xss
13-
13+
private import semmle.javascript.dataflow.InferredTypes
14+
1415
/**
1516
* Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown.
1617
*/
@@ -24,16 +25,29 @@ module ExceptionXss {
2425
node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument()
2526
}
2627

28+
/**
29+
* Holds if `t` is `null` or `undefined`.
30+
*/
31+
private predicate isNullOrUndefined(InferredType t) {
32+
t = TTNull() or
33+
t = TTUndefined()
34+
}
35+
2736
/**
2837
* Holds if `node` can possibly cause an exception containing sensitive information to be thrown.
2938
*/
3039
predicate canThrowSensitiveInformation(DataFlow::Node node) {
31-
not isUnlikelyToThrowSensitiveInformation(node) and
40+
not isUnlikelyToThrowSensitiveInformation(node) and
3241
(
3342
// in the case of reflective calls the below ensures that both InvokeNodes have no known callee.
34-
forex(DataFlow::InvokeNode call | node = call.getAnArgument() | not exists(call.getACallee()))
43+
forex(DataFlow::InvokeNode call | call.getAnArgument() = node | not exists(call.getACallee()))
3544
or
3645
node.asExpr().getEnclosingStmt() instanceof ThrowStmt
46+
or
47+
exists(DataFlow::PropRef prop |
48+
node = DataFlow::valueNode(prop.getPropertyNameExpr()) and
49+
forex(InferredType t | t = prop.getBase().analyze().getAType() | isNullOrUndefined(t))
50+
)
3751
)
3852
}
3953

@@ -47,6 +61,55 @@ module ExceptionXss {
4761
NotYetThrown() { this = "NotYetThrown" }
4862
}
4963

64+
/**
65+
* A callback that is the last argument to some call, and the callback has the form:
66+
* `function (err, value) {if (err) {...} ... }`
67+
*/
68+
class Callback extends DataFlow::FunctionNode {
69+
DataFlow::ParameterNode errorParameter;
70+
71+
Callback() {
72+
exists(DataFlow::CallNode call | call.getLastArgument().getAFunctionValue() = this) and
73+
this.getNumParameter() = 2 and
74+
errorParameter = this.getParameter(0) and
75+
exists(IfStmt ifStmt |
76+
ifStmt = this.getFunction().getBodyStmt(0) and
77+
errorParameter.flowsToExpr(ifStmt.getCondition())
78+
)
79+
}
80+
81+
/**
82+
* Get the parameter in the callback that contains an error.
83+
* In the current implementation this is always the first parameter.
84+
*/
85+
DataFlow::Node getErrorParam() { result = errorParameter }
86+
}
87+
88+
/**
89+
* Gets the error parameter for a callback that is supplied to the same call as `pred` is an argument to.
90+
* For example: `outerCall(foo, <pred>, bar, (<result>, val) => { ... })`.
91+
*/
92+
DataFlow::Node getCallbackErrorParam(DataFlow::Node pred) {
93+
exists(DataFlow::CallNode call, Callback callback |
94+
pred = call.getAnArgument() and
95+
call.getLastArgument() = callback and
96+
result = callback.getErrorParam() and
97+
not pred = callback
98+
)
99+
}
100+
101+
/**
102+
* Gets the data-flow node to which any exceptions thrown by
103+
* this expression will propagate.
104+
* This predicate adds, on top of `Expr::getExceptionTarget`, exceptions
105+
* propagated by callbacks.
106+
*/
107+
private DataFlow::Node getExceptionTarget(DataFlow::Node pred) {
108+
result = pred.asExpr().getExceptionTarget()
109+
or
110+
result = getCallbackErrorParam(pred)
111+
}
112+
50113
/**
51114
* A taint-tracking configuration for reasoning about XSS with possible exceptional flow.
52115
* Flow labels are used to ensure that we only report taint-flow that has been thrown in
@@ -69,12 +132,15 @@ module ExceptionXss {
69132
DataFlow::Node pred, DataFlow::Node succ, DataFlow::FlowLabel inlbl,
70133
DataFlow::FlowLabel outlbl
71134
) {
72-
inlbl instanceof NotYetThrown and (outlbl.isTaint() or outlbl instanceof NotYetThrown) and
73-
succ = pred.asExpr().getExceptionTarget() and
74-
canThrowSensitiveInformation(pred)
135+
inlbl instanceof NotYetThrown and
136+
(outlbl.isTaint() or outlbl instanceof NotYetThrown) and
137+
canThrowSensitiveInformation(pred) and
138+
succ = getExceptionTarget(pred)
75139
or
76140
// All the usual taint-flow steps apply on data-flow before it has been thrown in an exception.
77-
this.isAdditionalFlowStep(pred, succ) and inlbl instanceof NotYetThrown and outlbl instanceof NotYetThrown
141+
this.isAdditionalFlowStep(pred, succ) and
142+
inlbl instanceof NotYetThrown and
143+
outlbl instanceof NotYetThrown
78144
}
79145
}
80146
}

0 commit comments

Comments
 (0)