mirror of
https://github.com/github/codeql.git
synced 2026-04-28 02:05:14 +02:00
JS: reformulate js/server-crash. Support promises and shorter paths.
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
* @description A server that can be forced to crash may be vulnerable to denial-of-service
|
||||
* attacks.
|
||||
* @kind path-problem
|
||||
* @problem.severity error
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id js/server-crash
|
||||
* @tags security
|
||||
@@ -13,144 +13,114 @@
|
||||
import javascript
|
||||
|
||||
/**
|
||||
* A call that appears to be asynchronous (heuristic).
|
||||
* Gets a function that indirectly invokes an asynchronous callback through `async`, where the callback throws an uncaught exception at `thrower`.
|
||||
*/
|
||||
class AsyncCall extends DataFlow::CallNode {
|
||||
DataFlow::FunctionNode callback;
|
||||
Function invokesCallbackThatThrowsUncaughtException(
|
||||
AsyncSentinelCall async, LikelyExceptionThrower thrower
|
||||
) {
|
||||
async.getAsyncCallee() = throwsUncaughtExceptionInAsyncContext(thrower) and
|
||||
result = async.getEnclosingFunction()
|
||||
or
|
||||
exists(DataFlow::InvokeNode invk, Function fun |
|
||||
fun = invokesCallbackThatThrowsUncaughtException(async, thrower) and
|
||||
// purposely not checking for `getEnclosingTryCatchStmt`. An async callback called from inside a try-catch can still crash the server.
|
||||
result = invk.getEnclosingFunction()
|
||||
|
|
||||
invk.getACallee() = fun
|
||||
or
|
||||
// traverse a slightly extended call graph to get additional TPs
|
||||
invk.(AsyncSentinelCall).getAsyncCallee() = fun
|
||||
)
|
||||
}
|
||||
|
||||
AsyncCall() {
|
||||
callback.flowsTo(getLastArgument()) and
|
||||
callback.getParameter(0).getName() = ["e", "err", "error"] and
|
||||
callback.getNumParameter() = 2 and
|
||||
not exists(callback.getAReturn())
|
||||
/**
|
||||
* Gets a callee of an invocation `invk` that is not guarded by a try statement.
|
||||
*/
|
||||
Function getUncaughtExceptionRethrowerCallee(DataFlow::InvokeNode invk) {
|
||||
not exists(invk.asExpr().getEnclosingStmt().getEnclosingTryCatchStmt()) and
|
||||
result = invk.getACallee()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `thrower` is not guarded by a try statement.
|
||||
*/
|
||||
predicate isUncaughtExceptionThrower(LikelyExceptionThrower thrower) {
|
||||
not exists([thrower.(Expr).getEnclosingStmt(), thrower.(Stmt)].getEnclosingTryCatchStmt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that may throw an uncaught exception originating at `thrower`, which then may escape in an asynchronous calling context.
|
||||
*/
|
||||
Function throwsUncaughtExceptionInAsyncContext(LikelyExceptionThrower thrower) {
|
||||
(
|
||||
isUncaughtExceptionThrower(thrower) and
|
||||
result = thrower.getContainer()
|
||||
or
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
getUncaughtExceptionRethrowerCallee(invk) = throwsUncaughtExceptionInAsyncContext(thrower) and
|
||||
result = invk.getEnclosingFunction()
|
||||
)
|
||||
) and
|
||||
// Anti-case:
|
||||
// An exception from an `async` function results in a rejected promise.
|
||||
// Unhandled promises requires `node --unhandled-rejections=strict ...` to terminate the process
|
||||
// without that flag, the DEP0018 deprecation warning is printed instead (node.js version 14 and below)
|
||||
not result.isAsync() and
|
||||
// pruning optimization since this predicate always is related to `invokesCallbackThatThrowsUncaughtException`
|
||||
result = reachableFromAsyncCallback()
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds if `result` is reachable from a callback that is invoked asynchronously.
|
||||
*/
|
||||
Function reachableFromAsyncCallback() {
|
||||
result instanceof AsyncCallback
|
||||
or
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
invk.getEnclosingFunction() = reachableFromAsyncCallback() and
|
||||
result = invk.getACallee()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* The main predicate of this query: used for both result display and path computation.
|
||||
*/
|
||||
predicate main(
|
||||
HTTP::RouteHandler rh, AsyncSentinelCall async, AsyncCallback cb, LikelyExceptionThrower thrower
|
||||
) {
|
||||
async.getAsyncCallee() = cb and
|
||||
rh.getAstNode() = invokesCallbackThatThrowsUncaughtException(async, thrower)
|
||||
}
|
||||
|
||||
/**
|
||||
* A call that may cause a function to be invoked in an asynchronous context outside of the visible source code.
|
||||
*/
|
||||
class AsyncSentinelCall extends DataFlow::CallNode {
|
||||
Function asyncCallee;
|
||||
|
||||
AsyncSentinelCall() {
|
||||
exists(DataFlow::FunctionNode node | node.getAstNode() = asyncCallee |
|
||||
// manual models
|
||||
exists(string memberName |
|
||||
not "Sync" = memberName.suffix(memberName.length() - 4) and
|
||||
this = NodeJSLib::FS::moduleMember(memberName).getACall() and
|
||||
node = this.getCallback([1 .. 2])
|
||||
)
|
||||
// (add additional cases here to improve the query)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the callback that is invoked asynchronously.
|
||||
* Gets the callee that is invoked in an asynchronous context.
|
||||
*/
|
||||
DataFlow::FunctionNode getCallback() { result = callback }
|
||||
Function getAsyncCallee() { result = asyncCallee }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that is invoked as a consequence of invoking a route handler `rh`.
|
||||
* A callback provided to an asynchronous call (heuristic).
|
||||
*/
|
||||
Function invokedByRouteHandler(HTTP::RouteHandler rh) {
|
||||
rh = result.flow()
|
||||
or
|
||||
// follow the immediate call graph
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
result = invk.getACallee() and
|
||||
// purposely not checking for `getEnclosingTryCatchStmt`. An async callback called from inside a try-catch can still crash the server.
|
||||
invk.getEnclosingFunction() = invokedByRouteHandler(rh)
|
||||
)
|
||||
// if new edges are added here, the `edges` predicate should be updated accordingly
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback provided to an asynchronous call.
|
||||
*/
|
||||
class AsyncCallback extends DataFlow::FunctionNode {
|
||||
AsyncCallback() { this = any(AsyncCall c).getCallback() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that is in a call stack that starts at an asynchronous `callback`, calls in the call stack occur outside of `try` blocks.
|
||||
*/
|
||||
Function inUnguardedAsyncCallStack(AsyncCallback callback) {
|
||||
callback = result.flow()
|
||||
or
|
||||
exists(DataFlow::InvokeNode invk |
|
||||
result = invk.getACallee() and
|
||||
not exists(invk.asExpr().getEnclosingStmt().getEnclosingTryCatchStmt()) and
|
||||
invk.getEnclosingFunction() = inUnguardedAsyncCallStack(callback)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a function that is invoked by `asyncCallback` without any try-block wrapping, `asyncCallback` is in turn is called indirectly by `routeHandler`.
|
||||
*
|
||||
* If the result throws an excection, the server of `routeHandler` will crash.
|
||||
*/
|
||||
Function getAPotentialServerCrasher(
|
||||
HTTP::RouteHandler routeHandler, AsyncCall asyncCall, AsyncCallback asyncCallback
|
||||
) {
|
||||
// the route handler transitively calls an async function
|
||||
asyncCall.getEnclosingFunction() = invokedByRouteHandler(routeHandler) and
|
||||
asyncCallback = asyncCall.getCallback() and
|
||||
// the async function transitively calls a function that may throw an exception out of the the async function
|
||||
result = inUnguardedAsyncCallStack(asyncCallback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a node that is likely to throw an uncaught exception in `fun`.
|
||||
*/
|
||||
LikelyExceptionThrower getALikelyUncaughtExceptionThrower(Function fun) {
|
||||
result.getContainer() = fun and
|
||||
not exists([result.(Expr).getEnclosingStmt(), result.(Stmt)].getEnclosingTryCatchStmt())
|
||||
}
|
||||
|
||||
/**
|
||||
* Edges that builds an explanatory graph that follows the mental model of how the the exception flows.
|
||||
*
|
||||
* - step 1. exception is thrown
|
||||
* - step 2. exception exits the enclosing function
|
||||
* - step 3. exception follows the call graph backwards until an async callee is encountered
|
||||
* - step 4. (at this point, the program crashes)
|
||||
* - step 5. if the program had not crashed, the exception would conceptually follow the call graph backwards to a route handler
|
||||
*/
|
||||
query predicate edges(ASTNode pred, ASTNode succ) {
|
||||
nodes(pred) and
|
||||
nodes(succ) and
|
||||
(
|
||||
// the first step from the alert location to the enclosing function
|
||||
pred = getALikelyUncaughtExceptionThrower(_) and
|
||||
succ = pred.getContainer()
|
||||
or
|
||||
// ordinary flow graph
|
||||
exists(DataFlow::InvokeNode invoke, Function f |
|
||||
invoke.getACallee() = f and
|
||||
succ = invoke.getAstNode() and
|
||||
pred = f
|
||||
or
|
||||
invoke.getContainer() = f and
|
||||
succ = f and
|
||||
pred = invoke.getAstNode()
|
||||
)
|
||||
or
|
||||
// the async step
|
||||
exists(DataFlow::Node predNode, DataFlow::Node succNode |
|
||||
exists(getAPotentialServerCrasher(_, predNode, succNode)) and
|
||||
predNode.getAstNode() = succ and
|
||||
succNode.getAstNode() = pred
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Nodes for building an explanatory graph that follows the mental model of how the the exception flows.
|
||||
*/
|
||||
query predicate nodes(ASTNode node) {
|
||||
exists(HTTP::RouteHandler rh, Function fun |
|
||||
main(rh, _, _) and
|
||||
fun = invokedByRouteHandler(rh)
|
||||
|
|
||||
node = any(DataFlow::InvokeNode invk | invk.getACallee() = fun).getAstNode() or
|
||||
node = fun
|
||||
)
|
||||
or
|
||||
exists(AsyncCallback cb, Function fun |
|
||||
main(_, cb, _) and
|
||||
fun = inUnguardedAsyncCallStack(cb)
|
||||
|
|
||||
node = any(DataFlow::InvokeNode invk | invk.getACallee() = fun).getAstNode() or
|
||||
node = fun
|
||||
)
|
||||
or
|
||||
main(_, _, node)
|
||||
}
|
||||
|
||||
predicate main(HTTP::RouteHandler rh, AsyncCallback asyncCallback, ExprOrStmt crasher) {
|
||||
crasher = getALikelyUncaughtExceptionThrower(getAPotentialServerCrasher(rh, _, asyncCallback))
|
||||
class AsyncCallback extends Function {
|
||||
AsyncCallback() { any(AsyncSentinelCall c).getAsyncCallee() = this }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,8 +142,48 @@ class CompilerConfusingExceptionThrower extends LikelyExceptionThrower {
|
||||
CompilerConfusingExceptionThrower() { none() }
|
||||
}
|
||||
|
||||
from HTTP::RouteHandler rh, AsyncCallback asyncCallback, ExprOrStmt crasher
|
||||
where main(rh, asyncCallback, crasher)
|
||||
select crasher, crasher, rh.getAstNode(),
|
||||
"When an exception is thrown here and later escapes at $@, the server of $@ will crash.",
|
||||
asyncCallback, "this asynchronous callback", rh, "this route handler"
|
||||
/**
|
||||
* Edges that builds an explanatory graph that follows the mental model of how the the exception flows.
|
||||
*
|
||||
* - step 1. exception is thrown
|
||||
* - step 2. exception escapes the enclosing function
|
||||
* - step 3. exception follows the call graph backwards until an async callee is encountered
|
||||
* - step 4. (at this point, the program crashes)
|
||||
*/
|
||||
query predicate edges(ASTNode pred, ASTNode succ) {
|
||||
exists(LikelyExceptionThrower thrower | main(_, _, _, thrower) |
|
||||
pred = thrower and
|
||||
succ = thrower.getContainer()
|
||||
or
|
||||
exists(DataFlow::InvokeNode invk, Function fun |
|
||||
fun = throwsUncaughtExceptionInAsyncContext(thrower)
|
||||
|
|
||||
succ = invk.getAstNode() and
|
||||
pred = invk.getACallee() and
|
||||
pred = fun
|
||||
or
|
||||
succ = fun and
|
||||
succ = invk.getContainer() and
|
||||
pred = invk.getAstNode()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A node in the `edge/2` relation above.
|
||||
*/
|
||||
query predicate nodes(ASTNode node) {
|
||||
edges(node, _) or
|
||||
edges(_, node)
|
||||
}
|
||||
|
||||
from
|
||||
HTTP::RouteHandler rh, AsyncSentinelCall async, DataFlow::Node callbackArg, AsyncCallback cb,
|
||||
ExprOrStmt crasher
|
||||
where
|
||||
main(rh, async, cb, crasher) and
|
||||
callbackArg.getALocalSource().getAstNode() = cb and
|
||||
async.getAnArgument() = callbackArg
|
||||
select crasher, crasher, cb,
|
||||
"The server of $@ will terminate when an uncaught exception from here escapes this $@", rh,
|
||||
"this route handler", callbackArg, "asynchronous callback"
|
||||
|
||||
@@ -1,48 +1,65 @@
|
||||
edges
|
||||
| server-crash.js:5:1:9:1 | functio ... });\\n} | server-crash.js:49:3:49:16 | indirection1() |
|
||||
| server-crash.js:7:5:7:14 | throw err; | server-crash.js:6:28:8:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:10:1:12:1 | functio ... OT OK\\n} | server-crash.js:51:5:51:18 | indirection2() |
|
||||
| server-crash.js:11:3:11:11 | throw 42; | server-crash.js:10:1:12:1 | functio ... OT OK\\n} |
|
||||
| server-crash.js:13:1:19:1 | functio ... e) {}\\n} | server-crash.js:54:3:54:16 | indirection3() |
|
||||
| server-crash.js:16:7:16:16 | throw err; | server-crash.js:15:30:17:5 | (err, x ... K\\n } |
|
||||
| server-crash.js:20:1:22:1 | functio ... aller\\n} | server-crash.js:56:5:56:18 | indirection4() |
|
||||
| server-crash.js:23:1:25:1 | functio ... n6();\\n} | server-crash.js:58:3:58:16 | indirection5() |
|
||||
| server-crash.js:24:3:24:16 | indirection6() | server-crash.js:23:1:25:1 | functio ... n6();\\n} |
|
||||
| server-crash.js:26:1:30:1 | functio ... });\\n} | server-crash.js:24:3:24:16 | indirection6() |
|
||||
| server-crash.js:28:5:28:14 | throw err; | server-crash.js:27:28:29:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:33:5:33:14 | throw err; | server-crash.js:32:28:34:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:49:3:49:16 | indirection1() | server-crash.js:31:25:73:1 | (req, r ... });\\n} |
|
||||
| server-crash.js:51:5:51:18 | indirection2() | server-crash.js:50:28:52:3 | (err, x ... ();\\n } |
|
||||
| server-crash.js:54:3:54:16 | indirection3() | server-crash.js:31:25:73:1 | (req, r ... });\\n} |
|
||||
| server-crash.js:56:5:56:18 | indirection4() | server-crash.js:31:25:73:1 | (req, r ... });\\n} |
|
||||
| server-crash.js:58:3:58:16 | indirection5() | server-crash.js:31:25:73:1 | (req, r ... });\\n} |
|
||||
| server-crash.js:15:5:15:14 | throw err; | server-crash.js:14:23:16:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:18:1:20:1 | functio ... OT OK\\n} | server-crash.js:59:5:59:18 | indirection2() |
|
||||
| server-crash.js:19:3:19:11 | throw 42; | server-crash.js:18:1:20:1 | functio ... OT OK\\n} |
|
||||
| server-crash.js:24:7:24:16 | throw err; | server-crash.js:23:25:25:5 | (err, x ... K\\n } |
|
||||
| server-crash.js:36:5:36:14 | throw err; | server-crash.js:35:23:37:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:41:5:41:14 | throw err; | server-crash.js:40:23:42:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:59:5:59:18 | indirection2() | server-crash.js:58:23:60:3 | (err, x ... ();\\n } |
|
||||
| server-crash.js:88:5:88:14 | throw err; | server-crash.js:87:23:89:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:94:5:94:14 | throw "e"; | server-crash.js:93:22:95:3 | () => { ... OK\\n } |
|
||||
| server-crash.js:102:7:102:16 | throw "e"; | server-crash.js:101:24:103:5 | () => { ... K\\n } |
|
||||
| server-crash.js:109:9:109:18 | throw "e"; | server-crash.js:108:26:110:7 | () => { ... } |
|
||||
| server-crash.js:117:9:117:18 | throw "e"; | server-crash.js:116:26:118:7 | () => { ... } |
|
||||
| server-crash.js:131:7:131:16 | throw err; | server-crash.js:130:25:132:5 | (err, x ... K\\n } |
|
||||
| server-crash.js:152:3:154:3 | functio ... OK\\n } | server-crash.js:157:5:157:16 | throwError() |
|
||||
| server-crash.js:152:3:154:3 | functio ... OK\\n } | server-crash.js:160:5:160:16 | throwError() |
|
||||
| server-crash.js:152:3:154:3 | functio ... OK\\n } | server-crash.js:164:3:164:14 | throwError() |
|
||||
| server-crash.js:153:5:153:22 | throw new Error(); | server-crash.js:152:3:154:3 | functio ... OK\\n } |
|
||||
| server-crash.js:153:11:153:21 | new Error() | server-crash.js:152:3:154:3 | functio ... OK\\n } |
|
||||
| server-crash.js:157:5:157:16 | throwError() | server-crash.js:156:3:158:3 | functio ... ath\\n } |
|
||||
nodes
|
||||
| server-crash.js:5:1:9:1 | functio ... });\\n} |
|
||||
| server-crash.js:6:28:8:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:7:5:7:14 | throw err; |
|
||||
| server-crash.js:10:1:12:1 | functio ... OT OK\\n} |
|
||||
| server-crash.js:11:3:11:11 | throw 42; |
|
||||
| server-crash.js:13:1:19:1 | functio ... e) {}\\n} |
|
||||
| server-crash.js:15:30:17:5 | (err, x ... K\\n } |
|
||||
| server-crash.js:16:7:16:16 | throw err; |
|
||||
| server-crash.js:20:1:22:1 | functio ... aller\\n} |
|
||||
| server-crash.js:23:1:25:1 | functio ... n6();\\n} |
|
||||
| server-crash.js:24:3:24:16 | indirection6() |
|
||||
| server-crash.js:26:1:30:1 | functio ... });\\n} |
|
||||
| server-crash.js:27:28:29:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:28:5:28:14 | throw err; |
|
||||
| server-crash.js:31:25:73:1 | (req, r ... });\\n} |
|
||||
| server-crash.js:32:28:34:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:33:5:33:14 | throw err; |
|
||||
| server-crash.js:49:3:49:16 | indirection1() |
|
||||
| server-crash.js:50:28:52:3 | (err, x ... ();\\n } |
|
||||
| server-crash.js:51:5:51:18 | indirection2() |
|
||||
| server-crash.js:54:3:54:16 | indirection3() |
|
||||
| server-crash.js:56:5:56:18 | indirection4() |
|
||||
| server-crash.js:58:3:58:16 | indirection5() |
|
||||
| server-crash.js:14:23:16:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:15:5:15:14 | throw err; |
|
||||
| server-crash.js:18:1:20:1 | functio ... OT OK\\n} |
|
||||
| server-crash.js:19:3:19:11 | throw 42; |
|
||||
| server-crash.js:23:25:25:5 | (err, x ... K\\n } |
|
||||
| server-crash.js:24:7:24:16 | throw err; |
|
||||
| server-crash.js:35:23:37:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:36:5:36:14 | throw err; |
|
||||
| server-crash.js:40:23:42:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:41:5:41:14 | throw err; |
|
||||
| server-crash.js:58:23:60:3 | (err, x ... ();\\n } |
|
||||
| server-crash.js:59:5:59:18 | indirection2() |
|
||||
| server-crash.js:87:23:89:3 | (err, x ... OK\\n } |
|
||||
| server-crash.js:88:5:88:14 | throw err; |
|
||||
| server-crash.js:93:22:95:3 | () => { ... OK\\n } |
|
||||
| server-crash.js:94:5:94:14 | throw "e"; |
|
||||
| server-crash.js:101:24:103:5 | () => { ... K\\n } |
|
||||
| server-crash.js:102:7:102:16 | throw "e"; |
|
||||
| server-crash.js:108:26:110:7 | () => { ... } |
|
||||
| server-crash.js:109:9:109:18 | throw "e"; |
|
||||
| server-crash.js:116:26:118:7 | () => { ... } |
|
||||
| server-crash.js:117:9:117:18 | throw "e"; |
|
||||
| server-crash.js:130:25:132:5 | (err, x ... K\\n } |
|
||||
| server-crash.js:131:7:131:16 | throw err; |
|
||||
| server-crash.js:152:3:154:3 | functio ... OK\\n } |
|
||||
| server-crash.js:153:5:153:22 | throw new Error(); |
|
||||
| server-crash.js:153:11:153:21 | new Error() |
|
||||
| server-crash.js:156:3:158:3 | functio ... ath\\n } |
|
||||
| server-crash.js:157:5:157:16 | throwError() |
|
||||
| server-crash.js:160:5:160:16 | throwError() |
|
||||
| server-crash.js:164:3:164:14 | throwError() |
|
||||
#select
|
||||
| server-crash.js:7:5:7:14 | throw err; | server-crash.js:7:5:7:14 | throw err; | server-crash.js:31:25:73:1 | (req, r ... });\\n} | When an exception is thrown here and later escapes at $@, the server of $@ will crash. | server-crash.js:6:28:8:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:11:3:11:11 | throw 42; | server-crash.js:11:3:11:11 | throw 42; | server-crash.js:31:25:73:1 | (req, r ... });\\n} | When an exception is thrown here and later escapes at $@, the server of $@ will crash. | server-crash.js:50:28:52:3 | (err, x ... ();\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:16:7:16:16 | throw err; | server-crash.js:16:7:16:16 | throw err; | server-crash.js:31:25:73:1 | (req, r ... });\\n} | When an exception is thrown here and later escapes at $@, the server of $@ will crash. | server-crash.js:15:30:17:5 | (err, x ... K\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:28:5:28:14 | throw err; | server-crash.js:28:5:28:14 | throw err; | server-crash.js:31:25:73:1 | (req, r ... });\\n} | When an exception is thrown here and later escapes at $@, the server of $@ will crash. | server-crash.js:27:28:29:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:33:5:33:14 | throw err; | server-crash.js:33:5:33:14 | throw err; | server-crash.js:31:25:73:1 | (req, r ... });\\n} | When an exception is thrown here and later escapes at $@, the server of $@ will crash. | server-crash.js:32:28:34:3 | (err, x ... OK\\n } | this asynchronous callback | server-crash.js:31:25:73:1 | (req, r ... });\\n} | this route handler |
|
||||
| server-crash.js:15:5:15:14 | throw err; | server-crash.js:15:5:15:14 | throw err; | server-crash.js:14:23:16:3 | (err, x ... OK\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:39:25:85:1 | (req, r ... e) {}\\n} | this route handler | server-crash.js:14:23:16:3 | (err, x ... OK\\n } | asynchronous callback |
|
||||
| server-crash.js:19:3:19:11 | throw 42; | server-crash.js:19:3:19:11 | throw 42; | server-crash.js:58:23:60:3 | (err, x ... ();\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:39:25:85:1 | (req, r ... e) {}\\n} | this route handler | server-crash.js:58:23:60:3 | (err, x ... ();\\n } | asynchronous callback |
|
||||
| server-crash.js:24:7:24:16 | throw err; | server-crash.js:24:7:24:16 | throw err; | server-crash.js:23:25:25:5 | (err, x ... K\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:39:25:85:1 | (req, r ... e) {}\\n} | this route handler | server-crash.js:23:25:25:5 | (err, x ... K\\n } | asynchronous callback |
|
||||
| server-crash.js:36:5:36:14 | throw err; | server-crash.js:36:5:36:14 | throw err; | server-crash.js:35:23:37:3 | (err, x ... OK\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:39:25:85:1 | (req, r ... e) {}\\n} | this route handler | server-crash.js:35:23:37:3 | (err, x ... OK\\n } | asynchronous callback |
|
||||
| server-crash.js:41:5:41:14 | throw err; | server-crash.js:41:5:41:14 | throw err; | server-crash.js:40:23:42:3 | (err, x ... OK\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:39:25:85:1 | (req, r ... e) {}\\n} | this route handler | server-crash.js:40:23:42:3 | (err, x ... OK\\n } | asynchronous callback |
|
||||
| server-crash.js:88:5:88:14 | throw err; | server-crash.js:88:5:88:14 | throw err; | server-crash.js:87:23:89:3 | (err, x ... OK\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:39:25:85:1 | (req, r ... e) {}\\n} | this route handler | server-crash.js:87:23:89:3 | (err, x ... OK\\n } | asynchronous callback |
|
||||
| server-crash.js:94:5:94:14 | throw "e"; | server-crash.js:94:5:94:14 | throw "e"; | server-crash.js:93:22:95:3 | () => { ... OK\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:92:31:120:1 | (req, r ... });\\n} | this route handler | server-crash.js:93:22:95:3 | () => { ... OK\\n } | asynchronous callback |
|
||||
| server-crash.js:102:7:102:16 | throw "e"; | server-crash.js:102:7:102:16 | throw "e"; | server-crash.js:101:24:103:5 | () => { ... K\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:92:31:120:1 | (req, r ... });\\n} | this route handler | server-crash.js:101:24:103:5 | () => { ... K\\n } | asynchronous callback |
|
||||
| server-crash.js:109:9:109:18 | throw "e"; | server-crash.js:109:9:109:18 | throw "e"; | server-crash.js:108:26:110:7 | () => { ... } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:92:31:120:1 | (req, r ... });\\n} | this route handler | server-crash.js:108:26:110:7 | () => { ... } | asynchronous callback |
|
||||
| server-crash.js:117:9:117:18 | throw "e"; | server-crash.js:117:9:117:18 | throw "e"; | server-crash.js:116:26:118:7 | () => { ... } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:92:31:120:1 | (req, r ... });\\n} | this route handler | server-crash.js:116:26:118:7 | () => { ... } | asynchronous callback |
|
||||
| server-crash.js:131:7:131:16 | throw err; | server-crash.js:131:7:131:16 | throw err; | server-crash.js:130:25:132:5 | (err, x ... K\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:128:32:135:1 | async ( ... un();\\n} | this route handler | server-crash.js:130:25:132:5 | (err, x ... K\\n } | asynchronous callback |
|
||||
| server-crash.js:153:5:153:22 | throw new Error(); | server-crash.js:153:5:153:22 | throw new Error(); | server-crash.js:156:3:158:3 | functio ... ath\\n } | The server of $@ will terminate when an uncaught exception from here escapes this $@ | server-crash.js:151:40:166:1 | (req, r ... nc();\\n} | this route handler | server-crash.js:161:16:161:17 | cb | asynchronous callback |
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
const express = require("express");
|
||||
const app = express();
|
||||
const fs = require("fs");
|
||||
const EventEmitter = require("events");
|
||||
const http = require("http");
|
||||
|
||||
const port = 12000;
|
||||
let server = app.listen(port, () =>
|
||||
// for manual testing of the fickle node.js runtime
|
||||
console.log(`Example app listening on port ${port}!`)
|
||||
);
|
||||
|
||||
function indirection1() {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
}
|
||||
@@ -12,7 +20,7 @@ function indirection2() {
|
||||
}
|
||||
function indirection3() {
|
||||
try {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
} catch (e) {}
|
||||
@@ -24,30 +32,30 @@ function indirection5() {
|
||||
indirection6();
|
||||
}
|
||||
function indirection6() {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
}
|
||||
app.get("/async-throw", (req, res) => {
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
try {
|
||||
throw err; // OK: guarded throw
|
||||
} catch (e) {}
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
res.setHeader("reflected", req.query.header); // NOT OK [INCONSISTENCY]
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
try {
|
||||
res.setHeader("reflected", req.query.header); // OK: guarded call
|
||||
} catch (e) {}
|
||||
});
|
||||
|
||||
indirection1();
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
indirection2();
|
||||
});
|
||||
|
||||
@@ -57,17 +65,17 @@ app.get("/async-throw", (req, res) => {
|
||||
} catch (e) {}
|
||||
indirection5();
|
||||
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
req.query.foo; // OK
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
req.query.foo.toString(); // OK
|
||||
});
|
||||
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
req.query.foo.bar; // NOT OK [INCONSISTENCY]: need to add property reads as sinks
|
||||
});
|
||||
fs.readFile("/WHATEVER", (err, x) => {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
res.setHeader("reflected", unknown); // OK
|
||||
});
|
||||
|
||||
@@ -80,3 +88,79 @@ function indirection7() {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
}
|
||||
|
||||
app.get("/async-throw-again", (req, res) => {
|
||||
fs.readFile("foo", () => {
|
||||
throw "e"; // NOT OK
|
||||
});
|
||||
fs.readFileSync("foo", () => {
|
||||
throw "e"; // OK (does not take callbacks at all)
|
||||
});
|
||||
// can nest async calls (and only warns about the inner one)
|
||||
fs.readFile("foo", () => {
|
||||
fs.readFile("bar", () => {
|
||||
throw "e"; // NOT OK
|
||||
});
|
||||
});
|
||||
fs.readFile("foo", () => {
|
||||
// can not catch async exceptions
|
||||
try {
|
||||
fs.readFile("bar", () => {
|
||||
throw "e"; // NOT OK
|
||||
});
|
||||
} catch (e) {}
|
||||
});
|
||||
// can mix sync/async calls
|
||||
fs.readFile("foo", () => {
|
||||
(() =>
|
||||
fs.readFile("bar", () => {
|
||||
throw "e"; // NOT OK
|
||||
}))();
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/throw-in-promise-1", async (req, res) => {
|
||||
async function fun() {
|
||||
throw new Error(); // OK, requires `node --unhandled-rejections=strict ...` to terminate the process
|
||||
}
|
||||
await fun();
|
||||
});
|
||||
app.get("/throw-in-promise-2", async (req, res) => {
|
||||
async function fun() {
|
||||
fs.readFile("/foo", (err, x) => {
|
||||
throw err; // NOT OK
|
||||
});
|
||||
}
|
||||
await fun();
|
||||
});
|
||||
app.get("/throw-in-promise-3", async (req, res) => {
|
||||
fs.readFile("/foo", async (err, x) => {
|
||||
throw err; // OK, requires `node --unhandled-rejections=strict ...` to terminate the process
|
||||
});
|
||||
});
|
||||
|
||||
app.get("/throw-in-event-emitter", async (req, res) => {
|
||||
class MyEmitter extends EventEmitter {}
|
||||
const myEmitter = new MyEmitter();
|
||||
myEmitter.on("event", () => {
|
||||
throw new Error(); // OK, requires `node --unhandled-rejections=strict ...` to terminate the process
|
||||
});
|
||||
myEmitter.emit("event");
|
||||
});
|
||||
|
||||
app.get("/throw-with-ambiguous-paths", (req, res) => {
|
||||
function throwError() {
|
||||
throw new Error(); // NOT OK
|
||||
}
|
||||
|
||||
function cb() {
|
||||
throwError(); // on path
|
||||
}
|
||||
function withAsync() {
|
||||
throwError(); // not on path
|
||||
fs.stat(X, cb);
|
||||
}
|
||||
|
||||
throwError(); // not on path
|
||||
withAsync();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user