diff --git a/cpp/change-notes/2021-05-14-uncontrolled-allocation-size.md b/cpp/change-notes/2021-05-14-uncontrolled-allocation-size.md new file mode 100644 index 00000000000..6f0c9d6fa98 --- /dev/null +++ b/cpp/change-notes/2021-05-14-uncontrolled-allocation-size.md @@ -0,0 +1,2 @@ +lgtm +* The "Tainted allocation size" query (cpp/uncontrolled-allocation-size) has been improved to produce fewer false positives. diff --git a/cpp/change-notes/2021-05-18-static-buffer-overflow.md b/cpp/change-notes/2021-05-18-static-buffer-overflow.md new file mode 100644 index 00000000000..f56040fe8aa --- /dev/null +++ b/cpp/change-notes/2021-05-18-static-buffer-overflow.md @@ -0,0 +1,2 @@ +lgtm +* The "Static buffer overflow" query (cpp/static-buffer-overflow) has been improved to produce fewer false positives. diff --git a/cpp/change-notes/2021-12-05-uncontrolled-arithmetic.md b/cpp/change-notes/2021-12-05-uncontrolled-arithmetic.md new file mode 100644 index 00000000000..56fbc9a44ce --- /dev/null +++ b/cpp/change-notes/2021-12-05-uncontrolled-arithmetic.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* The query "Uncontrolled arithmetic" (`cpp/uncontrolled-arithmetic`) has been improved to produce fewer false positives. diff --git a/cpp/ql/src/Critical/OverflowStatic.ql b/cpp/ql/src/Critical/OverflowStatic.ql index 833ee45499e..011c38ff734 100644 --- a/cpp/ql/src/Critical/OverflowStatic.ql +++ b/cpp/ql/src/Critical/OverflowStatic.ql @@ -14,6 +14,7 @@ import cpp import semmle.code.cpp.commons.Buffer +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis import LoopBounds private predicate staticBufferBase(VariableAccess access, Variable v) { @@ -51,6 +52,8 @@ predicate overflowOffsetInLoop(BufferAccess bufaccess, string msg) { loop.getStmt().getAChild*() = bufaccess.getEnclosingStmt() and loop.limit() >= bufaccess.bufferSize() and loop.counter().getAnAccess() = bufaccess.getArrayOffset() and + // Ensure that we don't have an upper bound on the array index that's less than the buffer size. + not upperBound(bufaccess.getArrayOffset().getFullyConverted()) < bufaccess.bufferSize() and msg = "Potential buffer-overflow: counter '" + loop.counter().toString() + "' <= " + loop.limit().toString() + " but '" + bufaccess.buffer().getName() + "' has " + @@ -94,17 +97,22 @@ class CallWithBufferSize extends FunctionCall { } int statedSizeValue() { - exists(Expr statedSizeSrc | - DataFlow::localExprFlow(statedSizeSrc, statedSizeExpr()) and - result = statedSizeSrc.getValue().toInt() - ) + // `upperBound(e)` defaults to `exprMaxVal(e)` when `e` isn't analyzable. So to get a meaningful + // result in this case we pick the minimum value obtainable from dataflow and range analysis. + result = + upperBound(statedSizeExpr()) + .minimum(min(Expr statedSizeSrc | + DataFlow::localExprFlow(statedSizeSrc, statedSizeExpr()) + | + statedSizeSrc.getValue().toInt() + )) } } predicate wrongBufferSize(Expr error, string msg) { exists(CallWithBufferSize call, int bufsize, Variable buf, int statedSize | staticBuffer(call.buffer(), buf, bufsize) and - statedSize = min(call.statedSizeValue()) and + statedSize = call.statedSizeValue() and statedSize > bufsize and error = call.statedSizeExpr() and msg = diff --git a/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql b/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql index a4b0f131d14..6aad6cca7ce 100644 --- a/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql +++ b/cpp/ql/src/Security/CWE/CWE-190/ArithmeticUncontrolled.ql @@ -15,34 +15,99 @@ import cpp import semmle.code.cpp.security.Overflow import semmle.code.cpp.security.Security import semmle.code.cpp.security.TaintTracking +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis import TaintedWithPath -predicate isRandCall(FunctionCall fc) { fc.getTarget().getName() = "rand" } - -predicate isRandCallOrParent(Expr e) { - isRandCall(e) or - isRandCallOrParent(e.getAChild()) +predicate isUnboundedRandCall(FunctionCall fc) { + fc.getTarget().getName() = "rand" and not bounded(fc) } -predicate isRandValue(Expr e) { - isRandCall(e) +/** + * An operand `e` of a division expression (i.e., `e` is an operand of either a `DivExpr` or + * a `AssignDivExpr`) is bounded when `e` is the left-hand side of the division. + */ +pragma[inline] +predicate boundedDiv(Expr e, Expr left) { e = left } + +/** + * An operand `e` of a remainder expression `rem` (i.e., `rem` is either a `RemExpr` or + * an `AssignRemExpr`) with left-hand side `left` and right-ahnd side `right` is bounded + * when `e` is `left` and `right` is upper bounded by some number that is less than the maximum integer + * allowed by the result type of `rem`. + */ +pragma[inline] +predicate boundedRem(Expr e, Expr rem, Expr left, Expr right) { + e = left and + upperBound(right.getFullyConverted()) < exprMaxVal(rem.getFullyConverted()) +} + +/** + * An operand `e` of a bitwise and expression `andExpr` (i.e., `andExpr` is either an `BitwiseAndExpr` + * or an `AssignAndExpr`) with operands `operand1` and `operand2` is the operand that is not `e` is upper + * bounded by some number that is less than the maximum integer allowed by the result type of `andExpr`. + */ +pragma[inline] +predicate boundedBitwiseAnd(Expr e, Expr andExpr, Expr operand1, Expr operand2) { + operand1 != operand2 and + e = operand1 and + upperBound(operand2.getFullyConverted()) < exprMaxVal(andExpr.getFullyConverted()) +} + +/** + * Holds if `fc` is a part of the left operand of a binary operation that greatly reduces the range + * of possible values. + */ +predicate bounded(Expr e) { + // For `%` and `&` we require that `e` is bounded by a value that is strictly smaller than the + // maximum possible value of the result type of the operation. + // For example, the function call `rand()` is considered bounded in the following program: + // ``` + // int i = rand() % (UINT8_MAX + 1); + // ``` + // but not in: + // ``` + // unsigned char uc = rand() % (UINT8_MAX + 1); + // ``` + exists(RemExpr rem | boundedRem(e, rem, rem.getLeftOperand(), rem.getRightOperand())) + or + exists(AssignRemExpr rem | boundedRem(e, rem, rem.getLValue(), rem.getRValue())) + or + exists(BitwiseAndExpr andExpr | + boundedBitwiseAnd(e, andExpr, andExpr.getAnOperand(), andExpr.getAnOperand()) + ) + or + exists(AssignAndExpr andExpr | + boundedBitwiseAnd(e, andExpr, andExpr.getAnOperand(), andExpr.getAnOperand()) + ) + or + // Optimitically assume that a division always yields a much smaller value. + boundedDiv(e, any(DivExpr div).getLeftOperand()) + or + boundedDiv(e, any(AssignDivExpr div).getLValue()) +} + +predicate isUnboundedRandCallOrParent(Expr e) { + isUnboundedRandCall(e) + or + isUnboundedRandCallOrParent(e.getAChild()) +} + +predicate isUnboundedRandValue(Expr e) { + isUnboundedRandCall(e) or exists(MacroInvocation mi | e = mi.getExpr() and - isRandCallOrParent(e) + isUnboundedRandCallOrParent(e) ) } class SecurityOptionsArith extends SecurityOptions { override predicate isUserInput(Expr expr, string cause) { - isRandValue(expr) and - cause = "rand" and - not expr.getParent*() instanceof DivExpr + isUnboundedRandValue(expr) and + cause = "rand" } } -predicate isDiv(VariableAccess va) { exists(AssignDivExpr div | div.getLValue() = va) } - predicate missingGuard(VariableAccess va, string effect) { exists(Operation op | op.getAnOperand() = va | missingGuardAgainstUnderflow(op, va) and effect = "underflow" @@ -52,29 +117,15 @@ predicate missingGuard(VariableAccess va, string effect) { } class Configuration extends TaintTrackingConfiguration { - override predicate isSink(Element e) { - isDiv(e) - or - missingGuard(e, _) - } -} + override predicate isSink(Element e) { missingGuard(e, _) } -/** - * A value that undergoes division is likely to be bounded within a safe - * range. - */ -predicate guardedByAssignDiv(Expr origin) { - exists(VariableAccess va | - taintedWithPath(origin, va, _, _) and - isDiv(va) - ) + override predicate isBarrier(Expr e) { super.isBarrier(e) or bounded(e) } } from Expr origin, VariableAccess va, string effect, PathNode sourceNode, PathNode sinkNode where taintedWithPath(origin, va, sourceNode, sinkNode) and - missingGuard(va, effect) and - not guardedByAssignDiv(origin) + missingGuard(va, effect) select va, sourceNode, sinkNode, "$@ flows to here and is used in arithmetic, potentially causing an " + effect + ".", origin, "Uncontrolled value" diff --git a/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql b/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql index cc2d52385c7..75cac365a1a 100644 --- a/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql +++ b/cpp/ql/src/Security/CWE/CWE-190/TaintedAllocationSize.ql @@ -12,6 +12,7 @@ */ import cpp +import semmle.code.cpp.rangeanalysis.SimpleRangeAnalysis import semmle.code.cpp.security.TaintTracking import TaintedWithPath @@ -27,6 +28,27 @@ predicate allocSink(Expr alloc, Expr tainted) { class TaintedAllocationSizeConfiguration extends TaintTrackingConfiguration { override predicate isSink(Element tainted) { allocSink(_, tainted) } + + override predicate isBarrier(Expr e) { + super.isBarrier(e) + or + // There can be two separate reasons for `convertedExprMightOverflow` not holding: + // 1. `e` really cannot overflow. + // 2. `e` isn't analyzable. + // If we didn't rule out case 2 we would place barriers on anything that isn't analyzable. + ( + e instanceof UnaryArithmeticOperation or + e instanceof BinaryArithmeticOperation or + e instanceof AssignArithmeticOperation + ) and + not convertedExprMightOverflow(e) + or + // Subtracting two pointers is either well-defined (and the result will likely be small), or + // terribly undefined and dangerous. Here, we assume that the programmer has ensured that the + // result is well-defined (i.e., the two pointers point to the same object), and thus the result + // will likely be small. + e = any(PointerDiffExpr diff).getAnOperand() + } } predicate taintedAllocSize( diff --git a/cpp/ql/src/Summary/LinesOfCode.ql b/cpp/ql/src/Summary/LinesOfCode.ql index 3b2aa2ac4c9..2d816b349e8 100644 --- a/cpp/ql/src/Summary/LinesOfCode.ql +++ b/cpp/ql/src/Summary/LinesOfCode.ql @@ -4,6 +4,7 @@ * @description The total number of lines of C/C++ code across all files, including system headers, libraries, and auto-generated files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code */ import cpp diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll index 8ed61da4c92..6f0d6ff92ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowUtil.qll @@ -748,16 +748,10 @@ private predicate modelFlow(Operand opFrom, Instruction iTo) { ) or exists(int index, ReadSideEffectInstruction read | - modelIn.isParameterDeref(index) and + modelIn.isParameterDerefOrQualifierObject(index) and read = getSideEffectFor(call, index) and opFrom = read.getSideEffectOperand() ) - or - exists(ReadSideEffectInstruction read | - modelIn.isQualifierObject() and - read = getSideEffectFor(call, -1) and - opFrom = read.getSideEffectOperand() - ) ) ) } diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp index c4ccdfad8b3..4f8bcc0fa59 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-119/semmle/tests/tests.cpp @@ -586,6 +586,23 @@ void test21(bool cond) if (ptr[-1] == 0) { return; } // GOOD: accesses buffer[1] } +void test22(bool b, const char* source) { + char buffer[16]; + int k; + for (k = 0; k <= 100; k++) { + if(k < 16) { + buffer[k] = 'x'; // GOOD + } + } + + char dest[128]; + int n = b ? 1024 : 132; + if (n >= 128) { + return; + } + memcpy(dest, source, n); // GOOD +} + int main(int argc, char *argv[]) { long long arr17[19]; @@ -609,6 +626,7 @@ int main(int argc, char *argv[]) test19(argc == 0); test20(); test21(argc == 0); + test22(argc == 0, argv[0]); return 0; } diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected index 25ff3162973..752e9165c07 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/TaintedAllocationSize.expected @@ -1,228 +1,174 @@ edges -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | (size_t)... | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | -| test.cpp:75:25:75:29 | start | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:75:25:75:29 | start | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:75:38:75:40 | end | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:75:38:75:40 | end | test.cpp:79:18:79:28 | ... - ... | -| test.cpp:97:18:97:23 | buffer | test.cpp:100:4:100:15 | buffer | -| test.cpp:97:18:97:23 | buffer | test.cpp:100:17:100:22 | buffer indirection | -| test.cpp:97:18:97:23 | buffer | test.cpp:101:4:101:15 | ... + ... | -| test.cpp:97:18:97:23 | buffer | test.cpp:101:4:101:15 | buffer | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:100:4:100:15 | buffer | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:100:17:100:22 | buffer indirection | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:101:4:101:15 | ... + ... | -| test.cpp:97:18:97:23 | fread output argument | test.cpp:101:4:101:15 | buffer | -| test.cpp:100:4:100:15 | buffer | test.cpp:100:17:100:22 | processData1 output argument | -| test.cpp:100:17:100:22 | buffer indirection | test.cpp:100:17:100:22 | processData1 output argument | -| test.cpp:100:17:100:22 | processData1 output argument | test.cpp:101:4:101:15 | ... + ... | -| test.cpp:100:17:100:22 | processData1 output argument | test.cpp:101:4:101:15 | buffer | -| test.cpp:101:4:101:15 | ... + ... | test.cpp:75:38:75:40 | end | -| test.cpp:101:4:101:15 | buffer | test.cpp:75:25:75:29 | start | -| test.cpp:123:18:123:23 | call to getenv | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:123:18:123:23 | call to getenv | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:123:18:123:31 | (const char *)... | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:123:18:123:31 | (const char *)... | test.cpp:127:24:127:41 | ... * ... | -| test.cpp:132:19:132:24 | call to getenv | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:132:19:132:24 | call to getenv | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:132:19:132:32 | (const char *)... | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:132:19:132:32 | (const char *)... | test.cpp:134:10:134:27 | ... * ... | -| test.cpp:138:19:138:24 | call to getenv | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:138:19:138:24 | call to getenv | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:138:19:138:32 | (const char *)... | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:138:19:138:32 | (const char *)... | test.cpp:142:11:142:28 | ... * ... | -| test.cpp:201:9:201:42 | Store | test.cpp:231:9:231:24 | call to get_tainted_size | -| test.cpp:201:9:201:42 | Store | test.cpp:231:9:231:24 | call to get_tainted_size | -| test.cpp:201:14:201:19 | call to getenv | test.cpp:201:9:201:42 | Store | -| test.cpp:201:14:201:27 | (const char *)... | test.cpp:201:9:201:42 | Store | -| test.cpp:214:23:214:23 | s | test.cpp:215:21:215:21 | s | -| test.cpp:214:23:214:23 | s | test.cpp:215:21:215:21 | s | -| test.cpp:220:21:220:21 | s | test.cpp:221:21:221:21 | s | -| test.cpp:220:21:220:21 | s | test.cpp:221:21:221:21 | s | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | (size_t)... | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:235:2:235:9 | local_size | -| test.cpp:227:24:227:29 | call to getenv | test.cpp:237:2:237:8 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | (size_t)... | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:229:9:229:18 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:235:2:235:9 | local_size | -| test.cpp:227:24:227:37 | (const char *)... | test.cpp:237:2:237:8 | local_size | -| test.cpp:235:2:235:9 | local_size | test.cpp:214:23:214:23 | s | -| test.cpp:237:2:237:8 | local_size | test.cpp:220:21:220:21 | s | -| test.cpp:241:2:241:32 | Chi [array content] | test.cpp:279:17:279:20 | get_size output argument [array content] | -| test.cpp:241:2:241:32 | Chi [array content] | test.cpp:295:18:295:21 | get_size output argument [array content] | -| test.cpp:241:18:241:23 | call to getenv | test.cpp:241:2:241:32 | Chi [array content] | -| test.cpp:241:18:241:31 | (const char *)... | test.cpp:241:2:241:32 | Chi [array content] | -| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:249:20:249:33 | (const char *)... | test.cpp:253:11:253:29 | ... * ... | -| test.cpp:279:17:279:20 | Chi | test.cpp:281:11:281:28 | ... * ... | -| test.cpp:279:17:279:20 | Chi | test.cpp:281:11:281:28 | ... * ... | -| test.cpp:279:17:279:20 | get_size output argument [array content] | test.cpp:279:17:279:20 | Chi | -| test.cpp:295:18:295:21 | Chi | test.cpp:298:10:298:27 | ... * ... | -| test.cpp:295:18:295:21 | Chi | test.cpp:298:10:298:27 | ... * ... | -| test.cpp:295:18:295:21 | get_size output argument [array content] | test.cpp:295:18:295:21 | Chi | -| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:301:19:301:32 | (const char *)... | test.cpp:305:11:305:28 | ... * ... | -| test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | -| test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | -| test.cpp:309:19:309:32 | (const char *)... | test.cpp:314:10:314:27 | ... * ... | -| test.cpp:309:19:309:32 | (const char *)... | test.cpp:314:10:314:27 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | (size_t)... | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | +| test.cpp:124:18:124:23 | call to getenv | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:124:18:124:23 | call to getenv | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:124:18:124:31 | (const char *)... | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:124:18:124:31 | (const char *)... | test.cpp:128:24:128:41 | ... * ... | +| test.cpp:133:19:133:24 | call to getenv | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:133:19:133:24 | call to getenv | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:133:19:133:32 | (const char *)... | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:133:19:133:32 | (const char *)... | test.cpp:135:10:135:27 | ... * ... | +| test.cpp:148:20:148:25 | call to getenv | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:148:20:148:25 | call to getenv | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:148:20:148:33 | (const char *)... | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:148:20:148:33 | (const char *)... | test.cpp:152:11:152:28 | ... * ... | +| test.cpp:211:9:211:42 | Store | test.cpp:241:9:241:24 | call to get_tainted_size | +| test.cpp:211:9:211:42 | Store | test.cpp:241:9:241:24 | call to get_tainted_size | +| test.cpp:211:14:211:19 | call to getenv | test.cpp:211:9:211:42 | Store | +| test.cpp:211:14:211:27 | (const char *)... | test.cpp:211:9:211:42 | Store | +| test.cpp:224:23:224:23 | s | test.cpp:225:21:225:21 | s | +| test.cpp:224:23:224:23 | s | test.cpp:225:21:225:21 | s | +| test.cpp:230:21:230:21 | s | test.cpp:231:21:231:21 | s | +| test.cpp:230:21:230:21 | s | test.cpp:231:21:231:21 | s | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | (size_t)... | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:245:2:245:9 | local_size | +| test.cpp:237:24:237:29 | call to getenv | test.cpp:247:2:247:8 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:239:9:239:18 | (size_t)... | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:239:9:239:18 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:245:2:245:9 | local_size | +| test.cpp:237:24:237:37 | (const char *)... | test.cpp:247:2:247:8 | local_size | +| test.cpp:245:2:245:9 | local_size | test.cpp:224:23:224:23 | s | +| test.cpp:247:2:247:8 | local_size | test.cpp:230:21:230:21 | s | +| test.cpp:251:2:251:32 | Chi [array content] | test.cpp:289:17:289:20 | get_size output argument [array content] | +| test.cpp:251:2:251:32 | Chi [array content] | test.cpp:305:18:305:21 | get_size output argument [array content] | +| test.cpp:251:18:251:23 | call to getenv | test.cpp:251:2:251:32 | Chi [array content] | +| test.cpp:251:18:251:31 | (const char *)... | test.cpp:251:2:251:32 | Chi [array content] | +| test.cpp:259:20:259:25 | call to getenv | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:259:20:259:25 | call to getenv | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:259:20:259:33 | (const char *)... | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:259:20:259:33 | (const char *)... | test.cpp:263:11:263:29 | ... * ... | +| test.cpp:289:17:289:20 | Chi | test.cpp:291:11:291:28 | ... * ... | +| test.cpp:289:17:289:20 | Chi | test.cpp:291:11:291:28 | ... * ... | +| test.cpp:289:17:289:20 | get_size output argument [array content] | test.cpp:289:17:289:20 | Chi | +| test.cpp:305:18:305:21 | Chi | test.cpp:308:10:308:27 | ... * ... | +| test.cpp:305:18:305:21 | Chi | test.cpp:308:10:308:27 | ... * ... | +| test.cpp:305:18:305:21 | get_size output argument [array content] | test.cpp:305:18:305:21 | Chi | nodes -| test.cpp:39:21:39:24 | argv | semmle.label | argv | -| test.cpp:39:21:39:24 | argv | semmle.label | argv | -| test.cpp:42:38:42:44 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:42:38:42:44 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:42:38:42:44 | tainted | semmle.label | tainted | -| test.cpp:42:38:42:44 | tainted | semmle.label | tainted | -| test.cpp:42:38:42:44 | tainted | semmle.label | tainted | -| test.cpp:43:38:43:63 | ... * ... | semmle.label | ... * ... | -| test.cpp:43:38:43:63 | ... * ... | semmle.label | ... * ... | -| test.cpp:43:38:43:63 | ... * ... | semmle.label | ... * ... | -| test.cpp:45:38:45:63 | ... + ... | semmle.label | ... + ... | -| test.cpp:45:38:45:63 | ... + ... | semmle.label | ... + ... | -| test.cpp:45:38:45:63 | ... + ... | semmle.label | ... + ... | -| test.cpp:48:32:48:35 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:48:32:48:35 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:48:32:48:35 | size | semmle.label | size | -| test.cpp:48:32:48:35 | size | semmle.label | size | -| test.cpp:48:32:48:35 | size | semmle.label | size | -| test.cpp:49:26:49:29 | size | semmle.label | size | -| test.cpp:49:26:49:29 | size | semmle.label | size | -| test.cpp:49:26:49:29 | size | semmle.label | size | -| test.cpp:52:35:52:60 | ... * ... | semmle.label | ... * ... | -| test.cpp:52:35:52:60 | ... * ... | semmle.label | ... * ... | -| test.cpp:52:35:52:60 | ... * ... | semmle.label | ... * ... | -| test.cpp:64:25:64:30 | *buffer | semmle.label | *buffer | -| test.cpp:64:25:64:30 | *buffer | semmle.label | *buffer | -| test.cpp:64:25:64:30 | buffer | semmle.label | buffer | -| test.cpp:75:25:75:29 | start | semmle.label | start | -| test.cpp:75:38:75:40 | end | semmle.label | end | -| test.cpp:79:18:79:28 | ... - ... | semmle.label | ... - ... | -| test.cpp:79:18:79:28 | ... - ... | semmle.label | ... - ... | -| test.cpp:79:18:79:28 | ... - ... | semmle.label | ... - ... | -| test.cpp:97:18:97:23 | buffer | semmle.label | buffer | -| test.cpp:97:18:97:23 | fread output argument | semmle.label | fread output argument | -| test.cpp:100:4:100:15 | buffer | semmle.label | buffer | -| test.cpp:100:17:100:22 | buffer indirection | semmle.label | buffer indirection | -| test.cpp:100:17:100:22 | processData1 output argument | semmle.label | processData1 output argument | -| test.cpp:101:4:101:15 | ... + ... | semmle.label | ... + ... | -| test.cpp:101:4:101:15 | buffer | semmle.label | buffer | -| test.cpp:123:18:123:23 | call to getenv | semmle.label | call to getenv | -| test.cpp:123:18:123:31 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:127:24:127:41 | ... * ... | semmle.label | ... * ... | -| test.cpp:127:24:127:41 | ... * ... | semmle.label | ... * ... | -| test.cpp:127:24:127:41 | ... * ... | semmle.label | ... * ... | -| test.cpp:132:19:132:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:132:19:132:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:134:10:134:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:134:10:134:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:134:10:134:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:138:19:138:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:138:19:138:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:142:11:142:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:142:11:142:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:142:11:142:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:201:9:201:42 | Store | semmle.label | Store | -| test.cpp:201:14:201:19 | call to getenv | semmle.label | call to getenv | -| test.cpp:201:14:201:27 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:214:23:214:23 | s | semmle.label | s | -| test.cpp:215:21:215:21 | s | semmle.label | s | -| test.cpp:215:21:215:21 | s | semmle.label | s | -| test.cpp:215:21:215:21 | s | semmle.label | s | -| test.cpp:220:21:220:21 | s | semmle.label | s | -| test.cpp:221:21:221:21 | s | semmle.label | s | -| test.cpp:221:21:221:21 | s | semmle.label | s | -| test.cpp:221:21:221:21 | s | semmle.label | s | -| test.cpp:227:24:227:29 | call to getenv | semmle.label | call to getenv | -| test.cpp:227:24:227:37 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:229:9:229:18 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:229:9:229:18 | (size_t)... | semmle.label | (size_t)... | -| test.cpp:229:9:229:18 | local_size | semmle.label | local_size | -| test.cpp:229:9:229:18 | local_size | semmle.label | local_size | -| test.cpp:229:9:229:18 | local_size | semmle.label | local_size | -| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | -| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | -| test.cpp:231:9:231:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | -| test.cpp:235:2:235:9 | local_size | semmle.label | local_size | -| test.cpp:237:2:237:8 | local_size | semmle.label | local_size | -| test.cpp:241:2:241:32 | Chi [array content] | semmle.label | Chi [array content] | -| test.cpp:241:2:241:32 | ChiPartial | semmle.label | ChiPartial | -| test.cpp:241:18:241:23 | call to getenv | semmle.label | call to getenv | -| test.cpp:241:18:241:31 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:249:20:249:25 | call to getenv | semmle.label | call to getenv | -| test.cpp:249:20:249:33 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... | -| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... | -| test.cpp:253:11:253:29 | ... * ... | semmle.label | ... * ... | -| test.cpp:279:17:279:20 | Chi | semmle.label | Chi | -| test.cpp:279:17:279:20 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | -| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:281:11:281:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:295:18:295:21 | Chi | semmle.label | Chi | -| test.cpp:295:18:295:21 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | -| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:298:10:298:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:301:19:301:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:301:19:301:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:305:11:305:28 | ... * ... | semmle.label | ... * ... | -| test.cpp:309:19:309:24 | call to getenv | semmle.label | call to getenv | -| test.cpp:309:19:309:32 | (const char *)... | semmle.label | (const char *)... | -| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... | -| test.cpp:314:10:314:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:40:21:40:24 | argv | semmle.label | argv | +| test.cpp:40:21:40:24 | argv | semmle.label | argv | +| test.cpp:43:38:43:44 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:43:38:43:44 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:43:38:43:44 | tainted | semmle.label | tainted | +| test.cpp:43:38:43:44 | tainted | semmle.label | tainted | +| test.cpp:43:38:43:44 | tainted | semmle.label | tainted | +| test.cpp:44:38:44:63 | ... * ... | semmle.label | ... * ... | +| test.cpp:44:38:44:63 | ... * ... | semmle.label | ... * ... | +| test.cpp:44:38:44:63 | ... * ... | semmle.label | ... * ... | +| test.cpp:46:38:46:63 | ... + ... | semmle.label | ... + ... | +| test.cpp:46:38:46:63 | ... + ... | semmle.label | ... + ... | +| test.cpp:46:38:46:63 | ... + ... | semmle.label | ... + ... | +| test.cpp:49:32:49:35 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:49:32:49:35 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:49:32:49:35 | size | semmle.label | size | +| test.cpp:49:32:49:35 | size | semmle.label | size | +| test.cpp:49:32:49:35 | size | semmle.label | size | +| test.cpp:50:26:50:29 | size | semmle.label | size | +| test.cpp:50:26:50:29 | size | semmle.label | size | +| test.cpp:50:26:50:29 | size | semmle.label | size | +| test.cpp:53:35:53:60 | ... * ... | semmle.label | ... * ... | +| test.cpp:53:35:53:60 | ... * ... | semmle.label | ... * ... | +| test.cpp:53:35:53:60 | ... * ... | semmle.label | ... * ... | +| test.cpp:124:18:124:23 | call to getenv | semmle.label | call to getenv | +| test.cpp:124:18:124:31 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:128:24:128:41 | ... * ... | semmle.label | ... * ... | +| test.cpp:128:24:128:41 | ... * ... | semmle.label | ... * ... | +| test.cpp:128:24:128:41 | ... * ... | semmle.label | ... * ... | +| test.cpp:133:19:133:24 | call to getenv | semmle.label | call to getenv | +| test.cpp:133:19:133:32 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:135:10:135:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:135:10:135:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:135:10:135:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:148:20:148:25 | call to getenv | semmle.label | call to getenv | +| test.cpp:148:20:148:33 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:152:11:152:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:152:11:152:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:152:11:152:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:211:9:211:42 | Store | semmle.label | Store | +| test.cpp:211:14:211:19 | call to getenv | semmle.label | call to getenv | +| test.cpp:211:14:211:27 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:224:23:224:23 | s | semmle.label | s | +| test.cpp:225:21:225:21 | s | semmle.label | s | +| test.cpp:225:21:225:21 | s | semmle.label | s | +| test.cpp:225:21:225:21 | s | semmle.label | s | +| test.cpp:230:21:230:21 | s | semmle.label | s | +| test.cpp:231:21:231:21 | s | semmle.label | s | +| test.cpp:231:21:231:21 | s | semmle.label | s | +| test.cpp:231:21:231:21 | s | semmle.label | s | +| test.cpp:237:24:237:29 | call to getenv | semmle.label | call to getenv | +| test.cpp:237:24:237:37 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:239:9:239:18 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:239:9:239:18 | (size_t)... | semmle.label | (size_t)... | +| test.cpp:239:9:239:18 | local_size | semmle.label | local_size | +| test.cpp:239:9:239:18 | local_size | semmle.label | local_size | +| test.cpp:239:9:239:18 | local_size | semmle.label | local_size | +| test.cpp:241:9:241:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | +| test.cpp:241:9:241:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | +| test.cpp:241:9:241:24 | call to get_tainted_size | semmle.label | call to get_tainted_size | +| test.cpp:245:2:245:9 | local_size | semmle.label | local_size | +| test.cpp:247:2:247:8 | local_size | semmle.label | local_size | +| test.cpp:251:2:251:32 | Chi [array content] | semmle.label | Chi [array content] | +| test.cpp:251:2:251:32 | ChiPartial | semmle.label | ChiPartial | +| test.cpp:251:18:251:23 | call to getenv | semmle.label | call to getenv | +| test.cpp:251:18:251:31 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:259:20:259:25 | call to getenv | semmle.label | call to getenv | +| test.cpp:259:20:259:33 | (const char *)... | semmle.label | (const char *)... | +| test.cpp:263:11:263:29 | ... * ... | semmle.label | ... * ... | +| test.cpp:263:11:263:29 | ... * ... | semmle.label | ... * ... | +| test.cpp:263:11:263:29 | ... * ... | semmle.label | ... * ... | +| test.cpp:289:17:289:20 | Chi | semmle.label | Chi | +| test.cpp:289:17:289:20 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | +| test.cpp:291:11:291:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:291:11:291:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:291:11:291:28 | ... * ... | semmle.label | ... * ... | +| test.cpp:305:18:305:21 | Chi | semmle.label | Chi | +| test.cpp:305:18:305:21 | get_size output argument [array content] | semmle.label | get_size output argument [array content] | +| test.cpp:308:10:308:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:308:10:308:27 | ... * ... | semmle.label | ... * ... | +| test.cpp:308:10:308:27 | ... * ... | semmle.label | ... * ... | #select -| test.cpp:42:31:42:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:42:38:42:44 | tainted | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:43:31:43:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:43:38:43:63 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:45:31:45:36 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:45:38:45:63 | ... + ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:48:25:48:30 | call to malloc | test.cpp:39:21:39:24 | argv | test.cpp:48:32:48:35 | size | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:49:17:49:30 | new[] | test.cpp:39:21:39:24 | argv | test.cpp:49:26:49:29 | size | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:52:21:52:27 | call to realloc | test.cpp:39:21:39:24 | argv | test.cpp:52:35:52:60 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (argv) | -| test.cpp:79:9:79:29 | new[] | test.cpp:97:18:97:23 | buffer | test.cpp:79:18:79:28 | ... - ... | This allocation size is derived from $@ and might overflow | test.cpp:97:18:97:23 | buffer | user input (fread) | -| test.cpp:127:17:127:22 | call to malloc | test.cpp:123:18:123:23 | call to getenv | test.cpp:127:24:127:41 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:123:18:123:23 | call to getenv | user input (getenv) | -| test.cpp:134:3:134:8 | call to malloc | test.cpp:132:19:132:24 | call to getenv | test.cpp:134:10:134:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:132:19:132:24 | call to getenv | user input (getenv) | -| test.cpp:142:4:142:9 | call to malloc | test.cpp:138:19:138:24 | call to getenv | test.cpp:142:11:142:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:138:19:138:24 | call to getenv | user input (getenv) | -| test.cpp:215:14:215:19 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:215:21:215:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) | -| test.cpp:221:14:221:19 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:221:21:221:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) | -| test.cpp:229:2:229:7 | call to malloc | test.cpp:227:24:227:29 | call to getenv | test.cpp:229:9:229:18 | local_size | This allocation size is derived from $@ and might overflow | test.cpp:227:24:227:29 | call to getenv | user input (getenv) | -| test.cpp:231:2:231:7 | call to malloc | test.cpp:201:14:201:19 | call to getenv | test.cpp:231:9:231:24 | call to get_tainted_size | This allocation size is derived from $@ and might overflow | test.cpp:201:14:201:19 | call to getenv | user input (getenv) | -| test.cpp:253:4:253:9 | call to malloc | test.cpp:249:20:249:25 | call to getenv | test.cpp:253:11:253:29 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:249:20:249:25 | call to getenv | user input (getenv) | -| test.cpp:281:4:281:9 | call to malloc | test.cpp:241:18:241:23 | call to getenv | test.cpp:281:11:281:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:241:18:241:23 | call to getenv | user input (getenv) | -| test.cpp:298:3:298:8 | call to malloc | test.cpp:241:18:241:23 | call to getenv | test.cpp:298:10:298:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:241:18:241:23 | call to getenv | user input (getenv) | -| test.cpp:305:4:305:9 | call to malloc | test.cpp:301:19:301:24 | call to getenv | test.cpp:305:11:305:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:301:19:301:24 | call to getenv | user input (getenv) | -| test.cpp:314:3:314:8 | call to malloc | test.cpp:309:19:309:24 | call to getenv | test.cpp:314:10:314:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:309:19:309:24 | call to getenv | user input (getenv) | +| test.cpp:43:31:43:36 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:43:38:43:44 | tainted | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:44:31:44:36 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:44:38:44:63 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:46:31:46:36 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:46:38:46:63 | ... + ... | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:49:25:49:30 | call to malloc | test.cpp:40:21:40:24 | argv | test.cpp:49:32:49:35 | size | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:50:17:50:30 | new[] | test.cpp:40:21:40:24 | argv | test.cpp:50:26:50:29 | size | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:53:21:53:27 | call to realloc | test.cpp:40:21:40:24 | argv | test.cpp:53:35:53:60 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:40:21:40:24 | argv | user input (argv) | +| test.cpp:128:17:128:22 | call to malloc | test.cpp:124:18:124:23 | call to getenv | test.cpp:128:24:128:41 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:124:18:124:23 | call to getenv | user input (getenv) | +| test.cpp:135:3:135:8 | call to malloc | test.cpp:133:19:133:24 | call to getenv | test.cpp:135:10:135:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:133:19:133:24 | call to getenv | user input (getenv) | +| test.cpp:152:4:152:9 | call to malloc | test.cpp:148:20:148:25 | call to getenv | test.cpp:152:11:152:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:148:20:148:25 | call to getenv | user input (getenv) | +| test.cpp:225:14:225:19 | call to malloc | test.cpp:237:24:237:29 | call to getenv | test.cpp:225:21:225:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:237:24:237:29 | call to getenv | user input (getenv) | +| test.cpp:231:14:231:19 | call to malloc | test.cpp:237:24:237:29 | call to getenv | test.cpp:231:21:231:21 | s | This allocation size is derived from $@ and might overflow | test.cpp:237:24:237:29 | call to getenv | user input (getenv) | +| test.cpp:239:2:239:7 | call to malloc | test.cpp:237:24:237:29 | call to getenv | test.cpp:239:9:239:18 | local_size | This allocation size is derived from $@ and might overflow | test.cpp:237:24:237:29 | call to getenv | user input (getenv) | +| test.cpp:241:2:241:7 | call to malloc | test.cpp:211:14:211:19 | call to getenv | test.cpp:241:9:241:24 | call to get_tainted_size | This allocation size is derived from $@ and might overflow | test.cpp:211:14:211:19 | call to getenv | user input (getenv) | +| test.cpp:263:4:263:9 | call to malloc | test.cpp:259:20:259:25 | call to getenv | test.cpp:263:11:263:29 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:259:20:259:25 | call to getenv | user input (getenv) | +| test.cpp:291:4:291:9 | call to malloc | test.cpp:251:18:251:23 | call to getenv | test.cpp:291:11:291:28 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:251:18:251:23 | call to getenv | user input (getenv) | +| test.cpp:308:3:308:8 | call to malloc | test.cpp:251:18:251:23 | call to getenv | test.cpp:308:10:308:27 | ... * ... | This allocation size is derived from $@ and might overflow | test.cpp:251:18:251:23 | call to getenv | user input (getenv) | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp index 943bc3b1214..b11a136ed24 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/TaintedAllocationSize/test.cpp @@ -7,6 +7,7 @@ void *malloc(size_t size); void *realloc(void *ptr, size_t size); void free(void *ptr); int atoi(const char *nptr); +long atol(const char *nptr); struct MyStruct { char data[256]; @@ -76,7 +77,7 @@ void processData2(char *start, char *end) { char *copy; - copy = new char[end - start]; // GOOD [FALSE POSITIVE] + copy = new char[end - start]; // GOOD // ... @@ -137,6 +138,15 @@ void more_bounded_tests() { { int size = atoi(getenv("USER")); + if (size > 0) + { + malloc(size * sizeof(int)); // GOOD (overflow not possible) + } + } + + { + long size = atol(getenv("USER")); + if (size > 0) { malloc(size * sizeof(int)); // BAD @@ -302,7 +312,7 @@ void equality_cases() { if ((size == 50) || (size == 100)) { - malloc(size * sizeof(int)); // GOOD [FALSE POSITIVE] + malloc(size * sizeof(int)); // GOOD } } { @@ -311,6 +321,15 @@ void equality_cases() { if (size != 50 && size != 100) return; - malloc(size * sizeof(int)); // GOOD [FALSE POSITIVE] + malloc(size * sizeof(int)); // GOOD } } + +char * strstr(char *, const char *); + +void ptr_diff_case() { + char* user = getenv("USER"); + char* admin_begin_pos = strstr(user, "ADMIN"); + int offset = admin_begin_pos ? user - admin_begin_pos : 0; + malloc(offset); // GOOD +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected index ca8dd38fc3b..097efb73b9f 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/ArithmeticUncontrolled.expected @@ -7,30 +7,10 @@ edges | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | -| test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:61:5:61:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:60:13:60:16 | call to rand | test.c:62:5:62:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | -| test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | @@ -67,34 +47,11 @@ nodes | test.c:35:5:35:5 | r | semmle.label | r | | test.c:35:5:35:5 | r | semmle.label | r | | test.c:35:5:35:5 | r | semmle.label | r | -| test.c:39:13:39:21 | ... % ... | semmle.label | ... % ... | -| test.c:39:13:39:21 | ... % ... | semmle.label | ... % ... | -| test.c:40:5:40:5 | r | semmle.label | r | -| test.c:40:5:40:5 | r | semmle.label | r | -| test.c:40:5:40:5 | r | semmle.label | r | | test.c:44:13:44:16 | call to rand | semmle.label | call to rand | | test.c:44:13:44:16 | call to rand | semmle.label | call to rand | | test.c:45:5:45:5 | r | semmle.label | r | | test.c:45:5:45:5 | r | semmle.label | r | | test.c:45:5:45:5 | r | semmle.label | r | -| test.c:54:13:54:16 | call to rand | semmle.label | call to rand | -| test.c:54:13:54:16 | call to rand | semmle.label | call to rand | -| test.c:56:5:56:5 | r | semmle.label | r | -| test.c:56:5:56:5 | r | semmle.label | r | -| test.c:56:5:56:5 | r | semmle.label | r | -| test.c:60:13:60:16 | call to rand | semmle.label | call to rand | -| test.c:60:13:60:16 | call to rand | semmle.label | call to rand | -| test.c:61:5:61:5 | r | semmle.label | r | -| test.c:61:5:61:5 | r | semmle.label | r | -| test.c:61:5:61:5 | r | semmle.label | r | -| test.c:62:5:62:5 | r | semmle.label | r | -| test.c:62:5:62:5 | r | semmle.label | r | -| test.c:62:5:62:5 | r | semmle.label | r | -| test.c:66:13:66:16 | call to rand | semmle.label | call to rand | -| test.c:66:13:66:16 | call to rand | semmle.label | call to rand | -| test.c:67:5:67:5 | r | semmle.label | r | -| test.c:67:5:67:5 | r | semmle.label | r | -| test.c:67:5:67:5 | r | semmle.label | r | | test.c:75:13:75:19 | ... ^ ... | semmle.label | ... ^ ... | | test.c:75:13:75:19 | ... ^ ... | semmle.label | ... ^ ... | | test.c:77:9:77:9 | r | semmle.label | r | @@ -133,10 +90,7 @@ nodes #select | test.c:21:17:21:17 | r | test.c:18:13:18:16 | call to rand | test.c:21:17:21:17 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:18:13:18:16 | call to rand | Uncontrolled value | | test.c:35:5:35:5 | r | test.c:34:13:34:18 | call to rand | test.c:35:5:35:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:34:13:34:18 | call to rand | Uncontrolled value | -| test.c:40:5:40:5 | r | test.c:39:13:39:21 | ... % ... | test.c:40:5:40:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:39:13:39:21 | ... % ... | Uncontrolled value | | test.c:45:5:45:5 | r | test.c:44:13:44:16 | call to rand | test.c:45:5:45:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:44:13:44:16 | call to rand | Uncontrolled value | -| test.c:56:5:56:5 | r | test.c:54:13:54:16 | call to rand | test.c:56:5:56:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:54:13:54:16 | call to rand | Uncontrolled value | -| test.c:67:5:67:5 | r | test.c:66:13:66:16 | call to rand | test.c:67:5:67:5 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.c:66:13:66:16 | call to rand | Uncontrolled value | | test.c:77:9:77:9 | r | test.c:75:13:75:19 | ... ^ ... | test.c:77:9:77:9 | r | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:75:13:75:19 | ... ^ ... | Uncontrolled value | | test.c:100:5:100:5 | r | test.c:99:14:99:19 | call to rand | test.c:100:5:100:5 | r | $@ flows to here and is used in arithmetic, potentially causing an underflow. | test.c:99:14:99:19 | call to rand | Uncontrolled value | | test.cpp:25:7:25:7 | r | test.cpp:8:9:8:12 | call to rand | test.cpp:25:7:25:7 | r | $@ flows to here and is used in arithmetic, potentially causing an overflow. | test.cpp:8:9:8:12 | call to rand | Uncontrolled value | diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c index 2b67b499a3c..61f39a8e851 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/uncontrolled/test.c @@ -37,7 +37,7 @@ void randomTester() { { int r = RANDN(100); - r += 100; // GOOD: The return from RANDN is bounded [FALSE POSITIVE] + r += 100; // GOOD: The return from RANDN is bounded } { @@ -53,7 +53,7 @@ void randomTester() { { int r = rand(); r = r / 10; - r += 100; // GOOD [FALSE POSITIVE] + r += 100; // GOOD } { @@ -64,7 +64,7 @@ void randomTester() { { int r = rand() & 0xFF; - r += 100; // GOOD [FALSE POSITIVE] + r += 100; // GOOD } { diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 99ad4c8f963..197edc2c162 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -415,7 +415,7 @@ namespace Semmle.Autobuild.CSharp.Tests actions.RunProcess["cmd.exe /C dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C dotnet restore C:\Project\test.csproj"] = 0; - actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental C:\Project\test.csproj"] = 0; + actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto dotnet build --no-incremental /p:UseSharedCompilation=false C:\Project\test.csproj"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Project\test.csproj"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; @@ -439,9 +439,6 @@ namespace Semmle.Autobuild.CSharp.Tests [Fact] public void TestLinuxCSharpAutoBuilder() { - actions.RunProcess["dotnet --list-runtimes"] = 0; - actions.RunProcessOut["dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess["dotnet --info"] = 0; actions.RunProcess[@"dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"dotnet restore C:\Project/test.csproj"] = 0; @@ -463,7 +460,7 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.LoadXml[@"C:\Project/test.csproj"] = xml; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 0, 5); + TestAutobuilderScript(autobuilder, 0, 4); } [Fact] @@ -603,8 +600,6 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap [Fact] public void TestLinuxBuildCommand() { - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto ""./build.sh --skip-tests"""] = 0; actions.FileExists["csharp.log"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; @@ -615,7 +610,7 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap SkipVsWhere(); var autobuilder = CreateAutoBuilder(false, buildCommand: "./build.sh --skip-tests"); - TestAutobuilderScript(autobuilder, 0, 2); + TestAutobuilderScript(autobuilder, 0, 1); } [Fact] @@ -626,14 +621,12 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; actions.RunProcess[@"/bin/chmod u+x C:\Project/build/build.sh"] = 0; - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/build/build.sh"] = 0; actions.RunProcessWorkingDirectory[@"C:\odasa/tools/odasa index --auto C:\Project/build/build.sh"] = @"C:\Project/build"; actions.FileExists["csharp.log"] = true; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 0, 3); + TestAutobuilderScript(autobuilder, 0, 2); } [Fact] @@ -645,14 +638,12 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; actions.RunProcess[@"/bin/chmod u+x C:\Project/build.sh"] = 0; - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = 0; actions.RunProcessWorkingDirectory[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = @"C:\Project"; actions.FileExists["csharp.log"] = false; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 1, 3); + TestAutobuilderScript(autobuilder, 1, 2); } [Fact] @@ -664,14 +655,12 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = ""; actions.RunProcess[@"/bin/chmod u+x C:\Project/build.sh"] = 0; - actions.RunProcess["dotnet --list-runtimes"] = 1; - actions.RunProcessOut["dotnet --list-runtimes"] = ""; actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = 5; actions.RunProcessWorkingDirectory[@"C:\odasa/tools/odasa index --auto C:\Project/build.sh"] = @"C:\Project"; actions.FileExists["csharp.log"] = true; var autobuilder = CreateAutoBuilder(false); - TestAutobuilderScript(autobuilder, 1, 3); + TestAutobuilderScript(autobuilder, 1, 2); } [Fact] @@ -872,9 +861,6 @@ Microsoft.NETCore.App 2.2.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap [Fact] public void TestSkipNugetDotnet() { - actions.RunProcess["dotnet --list-runtimes"] = 0; - actions.RunProcessOut["dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess["dotnet --info"] = 0; actions.RunProcess[@"dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"dotnet restore C:\Project/test.csproj"] = 0; @@ -896,7 +882,7 @@ Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.LoadXml[@"C:\Project/test.csproj"] = xml; var autobuilder = CreateAutoBuilder(false, dotnetArguments: "--no-restore"); // nugetRestore=false does not work for now. - TestAutobuilderScript(autobuilder, 0, 5); + TestAutobuilderScript(autobuilder, 0, 4); } [Fact] @@ -907,13 +893,10 @@ Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0; actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project/.dotnet"] = 0; actions.RunProcess[@"rm dotnet-install.sh"] = 0; - actions.RunProcess[@"C:\Project/.dotnet/dotnet --list-runtimes"] = 0; - actions.RunProcessOut[@"C:\Project/.dotnet/dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess[@"C:\Project/.dotnet/dotnet --info"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet restore C:\Project/test.csproj"] = 0; - actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/.dotnet/dotnet build --no-incremental C:\Project/test.csproj"] = 0; + actions.RunProcess[@"C:\odasa/tools/odasa index --auto C:\Project/.dotnet/dotnet build --no-incremental /p:UseSharedCompilation=false C:\Project/test.csproj"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists["test.csproj"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; @@ -933,7 +916,7 @@ Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.DownloadFiles.Add(("https://dot.net/v1/dotnet-install.sh", "dotnet-install.sh")); var autobuilder = CreateAutoBuilder(false, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 9); + TestAutobuilderScript(autobuilder, 0, 8); } [Fact] @@ -945,11 +928,6 @@ Microsoft.NETCore.App 3.0.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0; actions.RunProcess[@"./dotnet-install.sh --channel release --version 2.1.3 --install-dir C:\Project/.dotnet"] = 0; actions.RunProcess[@"rm dotnet-install.sh"] = 0; - actions.RunProcess[@"C:\Project/.dotnet/dotnet --list-runtimes"] = 0; - actions.RunProcessOut[@"C:\Project/.dotnet/dotnet --list-runtimes"] = @"Microsoft.AspNetCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.AspNetCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App] -Microsoft.NETCore.App 2.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App] -Microsoft.NETCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]"; actions.RunProcess[@"C:\Project/.dotnet/dotnet --info"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet clean C:\Project/test.csproj"] = 0; actions.RunProcess[@"C:\Project/.dotnet/dotnet restore C:\Project/test.csproj"] = 0; @@ -973,7 +951,7 @@ Microsoft.NETCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.DownloadFiles.Add(("https://dot.net/v1/dotnet-install.sh", "dotnet-install.sh")); var autobuilder = CreateAutoBuilder(false, dotnetVersion: "2.1.3"); - TestAutobuilderScript(autobuilder, 0, 9); + TestAutobuilderScript(autobuilder, 0, 8); } private void TestDotnetVersionWindows(Action action, int commandsRun) @@ -984,7 +962,7 @@ Microsoft.NETCore.App 2.1.4 [/usr/local/share/dotnet/shared/Microsoft.NETCore.Ap actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet --info"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet clean C:\Project\test.csproj"] = 0; actions.RunProcess[@"cmd.exe /C C:\Project\.dotnet\dotnet restore C:\Project\test.csproj"] = 0; - actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental C:\Project\test.csproj"] = 0; + actions.RunProcess[@"cmd.exe /C C:\odasa\tools\odasa index --auto C:\Project\.dotnet\dotnet build --no-incremental /p:UseSharedCompilation=false C:\Project\test.csproj"] = 0; actions.FileExists["csharp.log"] = true; actions.FileExists[@"C:\Project\test.csproj"] = true; actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = ""; diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs index a456c9407db..3d7a1168e30 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp/DotNetRule.cs @@ -36,7 +36,7 @@ namespace Semmle.Autobuild.CSharp builder.Log(Severity.Info, "Attempting to build using .NET Core"); } - return WithDotNet(builder, (dotNetPath, environment, compatibleClr) => + return WithDotNet(builder, (dotNetPath, environment) => { var ret = GetInfoCommand(builder.Actions, dotNetPath, environment); foreach (var projectOrSolution in builder.ProjectsOrSolutionsToBuild) @@ -49,7 +49,7 @@ namespace Semmle.Autobuild.CSharp restoreCommand.QuoteArgument(projectOrSolution.FullPath); var restore = restoreCommand.Script; - var build = GetBuildScript(builder, dotNetPath, environment, compatibleClr, projectOrSolution.FullPath); + var build = GetBuildScript(builder, dotNetPath, environment, projectOrSolution.FullPath); ret &= BuildScript.Try(clean) & BuildScript.Try(restore) & build; } @@ -57,7 +57,7 @@ namespace Semmle.Autobuild.CSharp }); } - private static BuildScript WithDotNet(Autobuilder builder, Func?, bool, BuildScript> f) + private static BuildScript WithDotNet(Autobuilder builder, Func?, BuildScript> f) { var installDir = builder.Actions.PathCombine(builder.Options.RootDirectory, ".dotnet"); var installScript = DownloadDotNet(builder, installDir); @@ -81,35 +81,10 @@ namespace Semmle.Autobuild.CSharp env = null; } - // The CLR tracer is always compatible on Windows - if (builder.Actions.IsWindows()) - return f(installDir, env, true); - - // The CLR tracer is only compatible on .NET Core >= 3 on Linux and macOS (see - // https://github.com/dotnet/coreclr/issues/19622) - return BuildScript.Bind(GetInstalledRuntimesScript(builder.Actions, installDir, env), (runtimes, runtimesRet) => - { - var compatibleClr = false; - if (runtimesRet == 0) - { - var minimumVersion = new Version(3, 0); - var regex = new Regex(@"Microsoft\.NETCore\.App (\d\.\d\.\d)"); - compatibleClr = runtimes - .Select(runtime => regex.Match(runtime)) - .Where(m => m.Success) - .Select(m => m.Groups[1].Value) - .Any(m => Version.TryParse(m, out var v) && v >= minimumVersion); - } - - if (!compatibleClr) - { - if (env is null) - env = new Dictionary(); - env.Add("UseSharedCompilation", "false"); - } - - return f(installDir, env, compatibleClr); - }); + if (env is null) + env = new Dictionary(); + env.Add("UseSharedCompilation", "false"); + return f(installDir, env); }); } @@ -122,7 +97,7 @@ namespace Semmle.Autobuild.CSharp /// are needed). /// public static BuildScript WithDotNet(Autobuilder builder, Func?, BuildScript> f) - => WithDotNet(builder, (_1, env, _2) => f(env)); + => WithDotNet(builder, (_1, env) => f(env)); /// /// Returns a script for downloading relevant versions of the @@ -259,34 +234,17 @@ namespace Semmle.Autobuild.CSharp return restore; } - private static BuildScript GetInstalledRuntimesScript(IBuildActions actions, string? dotNetPath, IDictionary? environment) - { - var listSdks = new CommandBuilder(actions, environment: environment, silent: true). - RunCommand(DotNetCommand(actions, dotNetPath)). - Argument("--list-runtimes"); - return listSdks.Script; - } - /// /// Gets the `dotnet build` script. - /// - /// The CLR tracer only works on .NET Core >= 3 on Linux and macOS (see - /// https://github.com/dotnet/coreclr/issues/19622), so in case we are - /// running on an older .NET Core, we disable shared compilation (and - /// hence the need for CLR tracing), by adding a - /// `/p:UseSharedCompilation=false` argument. /// - private static BuildScript GetBuildScript(Autobuilder builder, string? dotNetPath, IDictionary? environment, bool compatibleClr, string projOrSln) + private static BuildScript GetBuildScript(Autobuilder builder, string? dotNetPath, IDictionary? environment, string projOrSln) { var build = new CommandBuilder(builder.Actions, null, environment); var script = builder.MaybeIndex(build, DotNetCommand(builder.Actions, dotNetPath)). Argument("build"). Argument("--no-incremental"); - return compatibleClr ? - script.Argument(builder.Options.DotNetArguments). - QuoteArgument(projOrSln). - Script : + return script.Argument("/p:UseSharedCompilation=false"). Argument(builder.Options.DotNetArguments). QuoteArgument(projOrSln). diff --git a/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql b/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql index 9156a5e4a7f..e93e3c7416f 100644 --- a/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql +++ b/csharp/ql/src/Metrics/Summaries/LinesOfCode.ql @@ -4,6 +4,7 @@ * @description The total number of lines of code across all files. This is a useful metric of the size of a database. For all files that were seen during the build, this query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code */ import csharp diff --git a/docs/codeql/codeql-cli/creating-codeql-databases.rst b/docs/codeql/codeql-cli/creating-codeql-databases.rst index 4f7212050df..637df58555b 100644 --- a/docs/codeql/codeql-cli/creating-codeql-databases.rst +++ b/docs/codeql/codeql-cli/creating-codeql-databases.rst @@ -165,13 +165,15 @@ build steps, you may need to explicitly define each step in the command line. .. pull-quote:: Creating databases for Go - For Go, you should always use the CodeQL autobuilder. Install the Go - toolchain (version 1.11 or later) and, if there are dependencies, the - appropriate dependency manager (such as `dep + For Go, install the Go toolchain (version 1.11 or later) and, if there + are dependencies, the appropriate dependency manager (such as `dep `__). - Do not specify any build commands, as you will override the autobuilder - invocation, which will create an empty database. + The Go autobuilder attempts to automatically detect code written in Go in a repository, + and only runs build scripts in an attempt to fetch dependencies. To force + CodeQL to limit extraction to the files compiled by your build script, set the environment variable + `CODEQL_EXTRACTOR_GO_BUILD_TRACING=on` or use the ``--command`` option to specify a + build command. Specifying build commands ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -200,6 +202,14 @@ commands that you can specify for compiled languages. codeql database create csharp-database --language=csharp --command='dotnet build /p:UseSharedCompilation=false /t:rebuild' +- Go project built using the ``COEQL_EXTRACTOR_GO_BUILD_TRACING=on`` environment variable:: + + CODEQL_EXTRACTOR_GO_BUILD_TRACING=on codeql database create go-database --language=go + +- Go project built using a custom build script:: + + codeql database create go-database --language=go --command='./scripts/build.sh' + - Java project built using Gradle:: codeql database create java-database --language=java --command='gradle clean test' diff --git a/docs/codeql/support/reusables/frameworks.rst b/docs/codeql/support/reusables/frameworks.rst index b20bead83e2..be9949aad77 100644 --- a/docs/codeql/support/reusables/frameworks.rst +++ b/docs/codeql/support/reusables/frameworks.rst @@ -157,8 +157,11 @@ Python built-in support Tornado, Web framework PyYAML, Serialization dill, Serialization + simplejson, Serialization + ujson, Serialization fabric, Utility library invoke, Utility library + idna, Utility library mysql-connector-python, Database MySQLdb, Database psycopg2, Database diff --git a/java/change-notes/2021-05-03-jackson-dataflow-deserialization.md b/java/change-notes/2021-05-03-jackson-dataflow-deserialization.md new file mode 100644 index 00000000000..475f8dbee4f --- /dev/null +++ b/java/change-notes/2021-05-03-jackson-dataflow-deserialization.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Increase coverage of dataflow through Jackson JSON deserialized objects. diff --git a/java/ql/src/Metrics/Summaries/LinesOfCode.ql b/java/ql/src/Metrics/Summaries/LinesOfCode.ql index d6db7c6ee6b..c622f8b08ba 100644 --- a/java/ql/src/Metrics/Summaries/LinesOfCode.ql +++ b/java/ql/src/Metrics/Summaries/LinesOfCode.ql @@ -6,6 +6,7 @@ * or comments. * @kind metric * @tags summary + * lines-of-code */ import java diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.java b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.java new file mode 100644 index 00000000000..5c1796e1f60 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.java @@ -0,0 +1,49 @@ +import org.python.util.PythonInterpreter; + +public class JythonInjection extends HttpServlet { + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + + // BAD: allow execution of arbitrary Python code + interpreter.exec(code); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + out.close(); + } + } + + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + + try { + interpreter = new PythonInterpreter(); + // BAD: allow execution of arbitrary Python code + PyObject py = interpreter.eval(code); + + response.getWriter().print(py.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.qhelp new file mode 100644 index 00000000000..8916296f93b --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.qhelp @@ -0,0 +1,34 @@ + + + + +

Python has been the most widely used programming language in recent years, and Jython + (formerly known as JPython) is a popular Java implementation of Python. It allows + embedded Python scripting inside Java applications and provides an interactive interpreter + that can be used to interact with Java packages or with running Java applications. If an + expression is built using attacker-controlled data and then evaluated, it may allow the + attacker to run arbitrary code.

+
+ + +

In general, including user input in Jython expression should be avoided. If user input + must be included in an expression, it should be then evaluated in a safe context that + doesn't allow arbitrary code invocation.

+
+ + +

The following code could execute arbitrary code in Jython Interpreter

+ +
+ + +
  • + Jython Organization: Jython and Java Integration +
  • +
  • + PortSwigger: Python code injection +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.ql new file mode 100644 index 00000000000..c44eee23602 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/JythonInjection.ql @@ -0,0 +1,112 @@ +/** + * @name Injection in Jython + * @description Evaluation of a user-controlled malicious expression in Java Python + * interpreter may lead to remote code execution. + * @kind path-problem + * @id java/jython-injection + * @tags security + * external/cwe/cwe-094 + * external/cwe/cwe-095 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.frameworks.spring.SpringController +import DataFlow::PathGraph + +/** The class `org.python.util.PythonInterpreter`. */ +class PythonInterpreter extends RefType { + PythonInterpreter() { this.hasQualifiedName("org.python.util", "PythonInterpreter") } +} + +/** A method that evaluates, compiles or executes a Jython expression. */ +class InterpretExprMethod extends Method { + InterpretExprMethod() { + this.getDeclaringType().getAnAncestor*() instanceof PythonInterpreter and + getName().matches(["exec%", "run%", "eval", "compile"]) + } +} + +/** The class `org.python.core.BytecodeLoader`. */ +class BytecodeLoader extends RefType { + BytecodeLoader() { this.hasQualifiedName("org.python.core", "BytecodeLoader") } +} + +/** Holds if a Jython expression if evaluated, compiled or executed. */ +predicate runsCode(MethodAccess ma, Expr sink) { + exists(Method m | m = ma.getMethod() | + m instanceof InterpretExprMethod and + sink = ma.getArgument(0) + ) +} + +/** A method that loads Java class data. */ +class LoadClassMethod extends Method { + LoadClassMethod() { + this.getDeclaringType().getAnAncestor*() instanceof BytecodeLoader and + hasName(["makeClass", "makeCode"]) + } +} + +/** + * Holds if `ma` is a call to a class-loading method, and `sink` is the byte array + * representing the class to be loaded. + */ +predicate loadsClass(MethodAccess ma, Expr sink) { + exists(Method m, int i | m = ma.getMethod() | + m instanceof LoadClassMethod and + m.getParameter(i).getType() instanceof Array and // makeClass(java.lang.String name, byte[] data, ...) + sink = ma.getArgument(i) + ) +} + +/** The class `org.python.core.Py`. */ +class Py extends RefType { + Py() { this.hasQualifiedName("org.python.core", "Py") } +} + +/** A method declared on class `Py` or one of its descendants that compiles Python code. */ +class PyCompileMethod extends Method { + PyCompileMethod() { + this.getDeclaringType().getAnAncestor*() instanceof Py and + getName().matches("compile%") + } +} + +/** Holds if source code is compiled with `PyCompileMethod`. */ +predicate compile(MethodAccess ma, Expr sink) { + exists(Method m | m = ma.getMethod() | + m instanceof PyCompileMethod and + sink = ma.getArgument(0) + ) +} + +/** An expression loaded by Jython. */ +class CodeInjectionSink extends DataFlow::ExprNode { + MethodAccess methodAccess; + + CodeInjectionSink() { + runsCode(methodAccess, this.getExpr()) or + loadsClass(methodAccess, this.getExpr()) or + compile(methodAccess, this.getExpr()) + } + + MethodAccess getMethodAccess() { result = methodAccess } +} + +/** + * A taint configuration for tracking flow from `RemoteFlowSource` to a Jython method call + * `CodeInjectionSink` that executes injected code. + */ +class CodeInjectionConfiguration extends TaintTracking::Configuration { + CodeInjectionConfiguration() { this = "CodeInjectionConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof CodeInjectionSink } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, CodeInjectionConfiguration conf +where conf.hasFlowPath(source, sink) +select sink.getNode().(CodeInjectionSink).getMethodAccess(), source, sink, "Jython evaluate $@.", + source.getNode(), "user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.java b/java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.java new file mode 100644 index 00000000000..15adfbe4524 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/RhinoInjection.java @@ -0,0 +1,40 @@ +import org.mozilla.javascript.ClassShutter; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Scriptable; + +public class RhinoInjection extends HttpServlet { + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + { + // BAD: allow arbitrary Java and JavaScript code to be executed + Scriptable scope = ctx.initStandardObjects(); + } + + { + // GOOD: enable the safe mode + Scriptable scope = ctx.initSafeStandardObjects(); + } + + { + // GOOD: enforce a constraint on allowed classes + Scriptable scope = ctx.initStandardObjects(); + ctx.setClassShutter(new ClassShutter() { + public boolean visibleToScripts(String className) { + return className.startsWith("com.example."); + } + }); + } + + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp deleted file mode 100644 index 74159c562c5..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.qhelp +++ /dev/null @@ -1,26 +0,0 @@ - - - - -

    The ScriptEngine API has been available since the release of Java 6. -It allows applications to interact with scripts written in languages such as JavaScript.

    -
    - - -

    Use "Cloudbees Rhino Sandbox" or sandboxing with SecurityManager or use graalvm instead.

    -
    - - -

    The following code could execute random JavaScript code

    - - -
    - - -
  • -CERT coding standard: ScriptEngine code injection -
  • -
    -
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql deleted file mode 100644 index 5e52a61b5c3..00000000000 --- a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptEngine.ql +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @name ScriptEngine evaluation - * @description Malicious Javascript code could cause arbitrary command execution at the OS level - * @kind path-problem - * @problem.severity error - * @precision high - * @id java/unsafe-eval - * @tags security - * external/cwe/cwe-094 - */ - -import java -import semmle.code.java.dataflow.FlowSources -import DataFlow::PathGraph - -class ScriptEngineMethod extends Method { - ScriptEngineMethod() { - this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and - this.hasName("eval") - } -} - -predicate scriptEngine(MethodAccess ma, Expr sink) { - exists(Method m | m = ma.getMethod() | - m instanceof ScriptEngineMethod and - sink = ma.getArgument(0) - ) -} - -class ScriptEngineSink extends DataFlow::ExprNode { - ScriptEngineSink() { scriptEngine(_, this.getExpr()) } - - MethodAccess getMethodAccess() { scriptEngine(result, this.getExpr()) } -} - -class ScriptEngineConfiguration extends TaintTracking::Configuration { - ScriptEngineConfiguration() { this = "ScriptEngineConfiguration" } - - override predicate isSource(DataFlow::Node source) { - source instanceof RemoteFlowSource - or - source instanceof LocalUserInput - } - - override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptEngineSink } -} - -from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptEngineConfiguration conf -where conf.hasFlowPath(source, sink) -select sink.getNode().(ScriptEngineSink).getMethodAccess(), source, sink, "ScriptEngine eval $@.", - source.getNode(), "user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.qhelp b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.qhelp new file mode 100644 index 00000000000..2683cf9ad29 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.qhelp @@ -0,0 +1,52 @@ + + + + +

    The Java Scripting API has been available since the release of Java 6. It allows + applications to interact with scripts written in languages such as JavaScript. It serves + as an embedded scripting engine inside Java applications which allows Java-to-JavaScript + interoperability and provides a seamless integration between the two languages. If an + expression is built using attacker-controlled data, and then evaluated in a powerful + context, it may allow the attacker to run arbitrary code.

    +
    + + +

    In general, including user input in a Java Script Engine expression should be avoided. + If user input must be included in the expression, it should be then evaluated in a safe + context that doesn't allow arbitrary code invocation. Use "Cloudbees Rhino Sandbox" or + sandboxing with SecurityManager, which will be deprecated in a future release, or use + GraalVM instead.

    +
    + + +

    The following code could execute user-supplied JavaScript code in ScriptEngine

    + + + +

    The following example shows two ways of using Rhino expression. In the 'BAD' case, + an unsafe context is initialized with initStandardObjects that allows arbitrary + Java code to be executed. In the 'GOOD' case, a safe context is initialized with + initSafeStandardObjects or setClassShutter.

    + +
    + + +
  • +CERT coding standard: ScriptEngine code injection +
  • +
  • +GraalVM: Secure by Default +
  • +
  • + Mozilla Rhino: Rhino: JavaScript in Java +
  • +
  • + Rhino Sandbox: A sandbox to execute JavaScript code with Rhino in Java +
  • +
  • + GuardRails: Code Injection +
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql new file mode 100644 index 00000000000..fb8bf867501 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-094/ScriptInjection.ql @@ -0,0 +1,146 @@ +/** + * @name Injection in Java Script Engine + * @description Evaluation of user-controlled data using the Java Script Engine may + * lead to remote code execution. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/unsafe-eval + * @tags security + * external/cwe/cwe-094 + */ + +import java +import semmle.code.java.dataflow.FlowSources +import DataFlow::PathGraph + +/** A method of ScriptEngine that allows code injection. */ +class ScriptEngineMethod extends Method { + ScriptEngineMethod() { + this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngine") and + this.hasName("eval") + or + this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "Compilable") and + this.hasName("compile") + or + this.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") and + this.hasName(["getProgram", "getMethodCallSyntax"]) + } +} + +/** The context class `org.mozilla.javascript.Context` of Rhino Java Script Engine. */ +class RhinoContext extends RefType { + RhinoContext() { this.hasQualifiedName("org.mozilla.javascript", "Context") } +} + +/** A method that evaluates a Rhino expression with `org.mozilla.javascript.Context`. */ +class RhinoEvaluateExpressionMethod extends Method { + RhinoEvaluateExpressionMethod() { + this.getDeclaringType().getAnAncestor*() instanceof RhinoContext and + this.hasName([ + "evaluateString", "evaluateReader", "compileFunction", "compileReader", "compileString" + ]) + } +} + +/** + * A method that compiles a Rhino expression with + * `org.mozilla.javascript.optimizer.ClassCompiler`. + */ +class RhinoCompileClassMethod extends Method { + RhinoCompileClassMethod() { + this.getDeclaringType() + .getASupertype*() + .hasQualifiedName("org.mozilla.javascript.optimizer", "ClassCompiler") and + this.hasName("compileToClassFiles") + } +} + +/** + * A method that defines a Java class from a Rhino expression with + * `org.mozilla.javascript.GeneratedClassLoader`. + */ +class RhinoDefineClassMethod extends Method { + RhinoDefineClassMethod() { + this.getDeclaringType() + .getASupertype*() + .hasQualifiedName("org.mozilla.javascript", "GeneratedClassLoader") and + this.hasName("defineClass") + } +} + +/** + * Holds if `ma` is a call to a `ScriptEngineMethod` and `sink` is an argument that + * will be executed. + */ +predicate isScriptArgument(MethodAccess ma, Expr sink) { + exists(ScriptEngineMethod m | + m = ma.getMethod() and + if m.getDeclaringType().getASupertype*().hasQualifiedName("javax.script", "ScriptEngineFactory") + then sink = ma.getArgument(_) // all arguments allow script injection + else sink = ma.getArgument(0) + ) +} + +/** + * Holds if a Rhino expression evaluation method is vulnerable to code injection. + */ +predicate evaluatesRhinoExpression(MethodAccess ma, Expr sink) { + exists(RhinoEvaluateExpressionMethod m | m = ma.getMethod() | + ( + if ma.getMethod().getName() = "compileReader" + then sink = ma.getArgument(0) // The first argument is the input reader + else sink = ma.getArgument(1) // The second argument is the JavaScript or Java input + ) and + not exists(MethodAccess ca | + ca.getMethod().hasName(["initSafeStandardObjects", "setClassShutter"]) and // safe mode or `ClassShutter` constraint is enforced + ma.getQualifier() = ca.getQualifier().(VarAccess).getVariable().getAnAccess() + ) + ) +} + +/** + * Holds if a Rhino expression compilation method is vulnerable to code injection. + */ +predicate compilesScript(MethodAccess ma, Expr sink) { + exists(RhinoCompileClassMethod m | m = ma.getMethod() | sink = ma.getArgument(0)) +} + +/** + * Holds if a Rhino class loading method is vulnerable to code injection. + */ +predicate definesRhinoClass(MethodAccess ma, Expr sink) { + exists(RhinoDefineClassMethod m | m = ma.getMethod() | sink = ma.getArgument(1)) +} + +/** A script injection sink. */ +class ScriptInjectionSink extends DataFlow::ExprNode { + MethodAccess methodAccess; + + ScriptInjectionSink() { + isScriptArgument(methodAccess, this.getExpr()) or + evaluatesRhinoExpression(methodAccess, this.getExpr()) or + compilesScript(methodAccess, this.getExpr()) or + definesRhinoClass(methodAccess, this.getExpr()) + } + + /** An access to the method associated with this sink. */ + MethodAccess getMethodAccess() { result = methodAccess } +} + +/** + * A taint tracking configuration that tracks flow from `RemoteFlowSource` to an argument + * of a method call that executes injected script. + */ +class ScriptInjectionConfiguration extends TaintTracking::Configuration { + ScriptInjectionConfiguration() { this = "ScriptInjectionConfiguration" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof ScriptInjectionSink } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, ScriptInjectionConfiguration conf +where conf.hasFlowPath(source, sink) +select sink.getNode().(ScriptInjectionSink).getMethodAccess(), source, sink, + "Java Script Engine evaluate $@.", source.getNode(), "user input" diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.java b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.java new file mode 100644 index 00000000000..eba64aab6a8 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.java @@ -0,0 +1,46 @@ +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +@Controller +public class SpringUrlRedirect { + + private final static String VALID_REDIRECT = "http://127.0.0.1"; + + @GetMapping("url1") + public RedirectView bad1(String redirectUrl, HttpServletResponse response) throws Exception { + RedirectView rv = new RedirectView(); + rv.setUrl(redirectUrl); + return rv; + } + + @GetMapping("url2") + public String bad2(String redirectUrl) { + String url = "redirect:" + redirectUrl; + return url; + } + + @GetMapping("url3") + public RedirectView bad3(String redirectUrl) { + RedirectView rv = new RedirectView(redirectUrl); + return rv; + } + + @GetMapping("url4") + public ModelAndView bad4(String redirectUrl) { + return new ModelAndView("redirect:" + redirectUrl); + } + + @GetMapping("url5") + public RedirectView good1(String redirectUrl) { + RedirectView rv = new RedirectView(); + if (redirectUrl.startsWith(VALID_REDIRECT)){ + rv.setUrl(redirectUrl); + }else { + rv.setUrl(VALID_REDIRECT); + } + return rv; + } +} diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qhelp b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qhelp new file mode 100644 index 00000000000..6fe70dfb113 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qhelp @@ -0,0 +1,37 @@ + + + + + +

    Directly incorporating user input into a URL redirect request without validating the input +can facilitate phishing attacks. In these attacks, unsuspecting users can be redirected to a +malicious site that looks very similar to the real site they intend to visit, but which is +controlled by the attacker.

    + +
    + + +

    To guard against untrusted URL redirection, it is advisable to avoid putting user input +directly into a redirect URL. Instead, maintain a list of authorized +redirects on the server; then choose from that list based on the user input provided.

    + +
    + + +

    The following examples show the bad case and the good case respectively. +In bad1 method and bad2 method and bad3 method and +bad4 method, shows an HTTP request parameter being used directly in a URL redirect +without validating the input, which facilitates phishing attacks. In good1 method, +shows how to solve this problem by verifying whether the user input is a known fixed string beginning. +

    + + + +
    + +
  • A Guide To Spring Redirects: Spring Redirects.
  • +
  • Url redirection - attack and defense: Url Redirection.
  • +
    +
    diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql new file mode 100644 index 00000000000..b02bd3e4c30 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql @@ -0,0 +1,66 @@ +/** + * @name Spring url redirection from remote source + * @description Spring url redirection based on unvalidated user-input + * may cause redirection to malicious web sites. + * @kind path-problem + * @problem.severity error + * @precision high + * @id java/spring-unvalidated-url-redirection + * @tags security + * external/cwe-601 + */ + +import java +import SpringUrlRedirect +import semmle.code.java.dataflow.FlowSources +import DataFlow::PathGraph + +private class StartsWithSanitizer extends DataFlow::BarrierGuard { + StartsWithSanitizer() { + this.(MethodAccess).getMethod().hasName("startsWith") and + this.(MethodAccess).getMethod().getDeclaringType() instanceof TypeString and + this.(MethodAccess).getMethod().getNumberOfParameters() = 1 + } + + override predicate checks(Expr e, boolean branch) { + e = this.(MethodAccess).getQualifier() and branch = true + } +} + +class SpringUrlRedirectFlowConfig extends TaintTracking::Configuration { + SpringUrlRedirectFlowConfig() { this = "SpringUrlRedirectFlowConfig" } + + override predicate isSource(DataFlow::Node source) { source instanceof RemoteFlowSource } + + override predicate isSink(DataFlow::Node sink) { sink instanceof SpringUrlRedirectSink } + + override predicate isSanitizerGuard(DataFlow::BarrierGuard guard) { + guard instanceof StartsWithSanitizer + } + + override predicate isSanitizer(DataFlow::Node node) { + // Exclude the case where the left side of the concatenated string is not `redirect:`. + // E.g: `String url = "/path?token=" + request.getParameter("token");` + // Note this is quite a broad sanitizer (it will also sanitize the right-hand side of `url = "http://" + request.getParameter("token")`); + // Consider making this stricter in future. + exists(AddExpr ae | + ae.getRightOperand() = node.asExpr() and + not ae instanceof RedirectBuilderExpr + ) + or + exists(MethodAccess ma, int index | + ma.getMethod().hasName("format") and + ma.getMethod().getDeclaringType() instanceof TypeString and + ma.getArgument(index) = node.asExpr() and + ( + index != 0 and + not ma.getArgument(0).(CompileTimeConstantExpr).getStringValue().regexpMatch("^%s.*") + ) + ) + } +} + +from DataFlow::PathNode source, DataFlow::PathNode sink, SpringUrlRedirectFlowConfig conf +where conf.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "Potentially untrusted URL redirection due to $@.", + source.getNode(), "user-provided value" diff --git a/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll new file mode 100644 index 00000000000..4a86640d4d4 --- /dev/null +++ b/java/ql/src/experimental/Security/CWE/CWE-601/SpringUrlRedirect.qll @@ -0,0 +1,73 @@ +import java +import DataFlow +import semmle.code.java.dataflow.FlowSources +import semmle.code.java.dataflow.DataFlow2 +import semmle.code.java.dataflow.TaintTracking +import semmle.code.java.frameworks.spring.SpringController + +/** + * A concatenate expression using the string `redirect:` or `ajaxredirect:` or `forward:` on the left. + * + * E.g: `"redirect:" + redirectUrl` + */ +class RedirectBuilderExpr extends AddExpr { + RedirectBuilderExpr() { + this.getLeftOperand().(CompileTimeConstantExpr).getStringValue() in [ + "redirect:", "ajaxredirect:", "forward:" + ] + } +} + +/** + * A call to `StringBuilder.append` or `StringBuffer.append` method, and the parameter value is + * `"redirect:"` or `"ajaxredirect:"` or `"forward:"`. + * + * E.g: `StringBuilder.append("redirect:")` + */ +class RedirectAppendCall extends MethodAccess { + RedirectAppendCall() { + this.getMethod().hasName("append") and + this.getMethod().getDeclaringType() instanceof StringBuildingType and + this.getArgument(0).(CompileTimeConstantExpr).getStringValue() in [ + "redirect:", "ajaxredirect:", "forward:" + ] + } +} + +/** A URL redirection sink from spring controller method. */ +class SpringUrlRedirectSink extends DataFlow::Node { + SpringUrlRedirectSink() { + exists(RedirectBuilderExpr rbe | + rbe.getRightOperand() = this.asExpr() and + any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable()) + ) + or + exists(MethodAccess ma, RedirectAppendCall rac | + DataFlow2::localExprFlow(rac.getQualifier(), ma.getQualifier()) and + ma.getMethod().hasName("append") and + ma.getArgument(0) = this.asExpr() and + any(SpringRequestMappingMethod sqmm).polyCalls*(this.getEnclosingCallable()) + ) + or + exists(MethodAccess ma | + ma.getMethod().hasName("setUrl") and + ma.getMethod() + .getDeclaringType() + .hasQualifiedName("org.springframework.web.servlet.view", "AbstractUrlBasedView") and + ma.getArgument(0) = this.asExpr() + ) + or + exists(ClassInstanceExpr cie | + cie.getConstructedType() + .hasQualifiedName("org.springframework.web.servlet.view", "RedirectView") and + cie.getArgument(0) = this.asExpr() + ) + or + exists(ClassInstanceExpr cie | + cie.getConstructedType().hasQualifiedName("org.springframework.web.servlet", "ModelAndView") and + exists(RedirectBuilderExpr rbe | + rbe = cie.getArgument(0) and rbe.getRightOperand() = this.asExpr() + ) + ) + } +} diff --git a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll index 5d159840611..76f685e7d20 100644 --- a/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll @@ -77,6 +77,7 @@ private module Frameworks { private import semmle.code.java.frameworks.ApacheHttp private import semmle.code.java.frameworks.apache.Lang private import semmle.code.java.frameworks.guava.Guava + private import semmle.code.java.frameworks.jackson.JacksonSerializability private import semmle.code.java.security.ResponseSplitting private import semmle.code.java.security.XSS private import semmle.code.java.security.LdapInjection diff --git a/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll b/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll index 3356e31d965..6cda84512a0 100644 --- a/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll +++ b/java/ql/src/semmle/code/java/frameworks/jackson/JacksonSerializability.qll @@ -9,6 +9,7 @@ import semmle.code.java.Reflection import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.DataFlow5 import semmle.code.java.dataflow.FlowSteps +private import semmle.code.java.dataflow.ExternalFlow /** * A `@com.fasterxml.jackson.annotation.JsonIgnore` annoation. @@ -28,7 +29,7 @@ abstract class JacksonSerializableType extends Type { } * A method used for serializing objects using Jackson. The final parameter is the object to be * serialized. */ -library class JacksonWriteValueMethod extends Method, TaintPreservingCallable { +private class JacksonWriteValueMethod extends Method, TaintPreservingCallable { JacksonWriteValueMethod() { ( getDeclaringType().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectWriter") or @@ -50,8 +51,20 @@ library class JacksonWriteValueMethod extends Method, TaintPreservingCallable { } } +private class JacksonReadValueMethod extends Method, TaintPreservingCallable { + JacksonReadValueMethod() { + ( + getDeclaringType().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectReader") or + getDeclaringType().hasQualifiedName("com.fasterxml.jackson.databind", "ObjectMapper") + ) and + hasName(["readValue", "readValues"]) + } + + override predicate returnsTaintFrom(int arg) { arg = 0 } +} + /** A type whose values are explicitly serialized in a call to a Jackson method. */ -library class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializableType { +private class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializableType { ExplicitlyWrittenJacksonSerializableType() { exists(MethodAccess ma | // A call to a Jackson write method... @@ -63,7 +76,7 @@ library class ExplicitlyWrittenJacksonSerializableType extends JacksonSerializab } /** A type used in a `JacksonSerializableField` declaration. */ -library class FieldReferencedJacksonSerializableType extends JacksonSerializableType { +private class FieldReferencedJacksonSerializableType extends JacksonSerializableType { FieldReferencedJacksonSerializableType() { exists(JacksonSerializableField f | usesType(f.getType(), this)) } @@ -96,17 +109,24 @@ private class TypeLiteralToJacksonDatabindFlowConfiguration extends DataFlow5::C } /** A type whose values are explicitly deserialized in a call to a Jackson method. */ -library class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType { +private class ExplicitlyReadJacksonDeserializableType extends JacksonDeserializableType { ExplicitlyReadJacksonDeserializableType() { exists(TypeLiteralToJacksonDatabindFlowConfiguration conf | usesType(conf.getSourceWithFlowToJacksonDatabind().getTypeName().getType(), this) ) + or + exists(MethodAccess ma | + // A call to a Jackson read method... + ma.getMethod() instanceof JacksonReadValueMethod and + // ...where `this` is used in the final argument, indicating that this type will be deserialized. + usesType(ma.getArgument(ma.getNumArgument() - 1).getType(), this) + ) } } /** A type used in a `JacksonDeserializableField` declaration. */ -library class FieldReferencedJacksonDeSerializableType extends JacksonDeserializableType { - FieldReferencedJacksonDeSerializableType() { +private class FieldReferencedJacksonDeserializableType extends JacksonDeserializableType { + FieldReferencedJacksonDeserializableType() { exists(JacksonDeserializableField f | usesType(f.getType(), this)) } } @@ -135,6 +155,21 @@ class JacksonDeserializableField extends DeserializableField { } } +/** A call to a field that may be deserialized using the Jackson JSON framework. */ +private class JacksonDeserializableFieldAccess extends FieldAccess { + JacksonDeserializableFieldAccess() { getField() instanceof JacksonDeserializableField } +} + +/** + * When an object is deserialized by the Jackson JSON framework using a tainted input source, + * the fields that the framework deserialized are themselves tainted input data. + */ +private class JacksonDeserializedTaintStep extends AdditionalTaintStep { + override predicate step(DataFlow::Node node1, DataFlow::Node node2) { + DataFlow::getFieldQualifier(node2.asExpr().(JacksonDeserializableFieldAccess)) = node1 + } +} + /** * A call to the `addMixInAnnotations` or `addMixIn` Jackson method. * @@ -239,3 +274,13 @@ class JacksonMixedInCallable extends Callable { ) } } + +private class JacksonModel extends SummaryModelCsv { + override predicate row(string row) { + row = + [ + "com.fasterxml.jackson.databind;ObjectMapper;true;valueToTree;;;Argument[0];ReturnValue;taint", + "com.fasterxml.jackson.databind;ObjectMapper;true;convertValue;;;Argument[0];ReturnValue;taint" + ] + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected new file mode 100644 index 00000000000..4f66cc83fbd --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.expected @@ -0,0 +1,21 @@ +edges +| JythonInjection.java:28:23:28:50 | getParameter(...) : String | JythonInjection.java:36:30:36:33 | code | +| JythonInjection.java:53:23:53:50 | getParameter(...) : String | JythonInjection.java:58:44:58:47 | code | +| JythonInjection.java:73:23:73:50 | getParameter(...) : String | JythonInjection.java:81:35:81:38 | code | +| JythonInjection.java:97:23:97:50 | getParameter(...) : String | JythonInjection.java:106:61:106:75 | getBytes(...) | +nodes +| JythonInjection.java:28:23:28:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:36:30:36:33 | code | semmle.label | code | +| JythonInjection.java:53:23:53:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:58:44:58:47 | code | semmle.label | code | +| JythonInjection.java:73:23:73:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:81:35:81:38 | code | semmle.label | code | +| JythonInjection.java:97:23:97:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| JythonInjection.java:106:61:106:75 | getBytes(...) | semmle.label | getBytes(...) | +| JythonInjection.java:131:40:131:63 | getInputStream(...) | semmle.label | getInputStream(...) | +#select +| JythonInjection.java:36:13:36:34 | exec(...) | JythonInjection.java:28:23:28:50 | getParameter(...) : String | JythonInjection.java:36:30:36:33 | code | Jython evaluate $@. | JythonInjection.java:28:23:28:50 | getParameter(...) | user input | +| JythonInjection.java:58:27:58:48 | eval(...) | JythonInjection.java:53:23:53:50 | getParameter(...) : String | JythonInjection.java:58:44:58:47 | code | Jython evaluate $@. | JythonInjection.java:53:23:53:50 | getParameter(...) | user input | +| JythonInjection.java:81:13:81:39 | runsource(...) | JythonInjection.java:73:23:73:50 | getParameter(...) : String | JythonInjection.java:81:35:81:38 | code | Jython evaluate $@. | JythonInjection.java:73:23:73:50 | getParameter(...) | user input | +| JythonInjection.java:106:29:106:134 | makeCode(...) | JythonInjection.java:97:23:97:50 | getParameter(...) : String | JythonInjection.java:106:61:106:75 | getBytes(...) | Jython evaluate $@. | JythonInjection.java:97:23:97:50 | getParameter(...) | user input | +| JythonInjection.java:131:29:131:109 | compile(...) | JythonInjection.java:131:40:131:63 | getInputStream(...) | JythonInjection.java:131:40:131:63 | getInputStream(...) | Jython evaluate $@. | JythonInjection.java:131:40:131:63 | getInputStream(...) | user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.java b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.java new file mode 100644 index 00000000000..f9b29fec6cc --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.java @@ -0,0 +1,144 @@ +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.python.core.BytecodeLoader; +import org.python.core.Py; +import org.python.core.PyCode; +import org.python.core.PyException; +import org.python.core.PyObject; +import org.python.util.InteractiveInterpreter; +import org.python.util.PythonInterpreter; + +public class JythonInjection extends HttpServlet { + private static final long serialVersionUID = 1L; + + public JythonInjection() { + super(); + } + + // BAD: allow execution of arbitrary Python code + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + interpreter.exec(code); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + out.close(); + } + } + + // BAD: allow execution of arbitrary Python code + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + + try { + interpreter = new PythonInterpreter(); + PyObject py = interpreter.eval(code); + + response.getWriter().print(py.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } + + // BAD: allow arbitrary Jython expression to run + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + InteractiveInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new InteractiveInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + interpreter.runsource(code); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } + + // BAD: load arbitrary class file to execute + protected void doTrace(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + + PyCode pyCode = BytecodeLoader.makeCode("test", code.getBytes(), getServletContext().getRealPath("/com/example/test.pyc")); + interpreter.exec(pyCode); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } + + // BAD: Compile Python code to execute + protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + PythonInterpreter interpreter = null; + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + try { + interpreter = new PythonInterpreter(); + interpreter.setOut(out); + interpreter.setErr(out); + + PyCode pyCode = Py.compile(request.getInputStream(), "Test.py", org.python.core.CompileMode.eval); + interpreter.exec(pyCode); + out.flush(); + + response.getWriter().print(out.toString()); + } catch(PyException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + if (interpreter != null) { + interpreter.close(); + } + } + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.qlref new file mode 100644 index 00000000000..0ba9fd60621 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/JythonInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-094/JythonInjection.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/RhinoServlet.java b/java/ql/test/experimental/query-tests/security/CWE-094/RhinoServlet.java new file mode 100644 index 00000000000..e76a9543f87 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/RhinoServlet.java @@ -0,0 +1,91 @@ +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.mozilla.javascript.ClassShutter; +import org.mozilla.javascript.CompilerEnvirons; +import org.mozilla.javascript.Context; +import org.mozilla.javascript.DefiningClassLoader; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.optimizer.ClassCompiler; + +/** + * Servlet implementation class RhinoServlet + */ +public class RhinoServlet extends HttpServlet { + private static final long serialVersionUID = 1L; + + public RhinoServlet() { + super(); + } + + // BAD: allow arbitrary Java and JavaScript code to be executed + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + Scriptable scope = ctx.initStandardObjects(); + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } + + // GOOD: enable the safe mode + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + Scriptable scope = ctx.initSafeStandardObjects(); + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } + + // GOOD: enforce a constraint on allowed classes + protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + response.setContentType("text/plain"); + String code = request.getParameter("code"); + Context ctx = Context.enter(); + try { + Scriptable scope = ctx.initStandardObjects(); + ctx.setClassShutter(new ClassShutter() { + public boolean visibleToScripts(String className) { + return className.startsWith("com.example."); + } + }); + + Object result = ctx.evaluateString(scope, code, "", 1, null); + response.getWriter().print(Context.toString(result)); + } catch(RhinoException ex) { + response.getWriter().println(ex.getMessage()); + } finally { + Context.exit(); + } + } + + // BAD: allow arbitrary code to be compiled for subsequent execution + protected void doGet2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String code = request.getParameter("code"); + ClassCompiler compiler = new ClassCompiler(new CompilerEnvirons()); + Object[] objs = compiler.compileToClassFiles(code, "/sourceLocation", 1, "mainClassName"); + } + + // BAD: allow arbitrary code to be loaded for subsequent execution + protected void doPost2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String code = request.getParameter("code"); + Class clazz = new DefiningClassLoader().defineClass("Powerfunc", code.getBytes()); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.expected b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.expected deleted file mode 100644 index a65faafc6bd..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.expected +++ /dev/null @@ -1,32 +0,0 @@ -edges -| ScriptEngineTest.java:8:44:8:55 | input : String | ScriptEngineTest.java:12:37:12:41 | input | -| ScriptEngineTest.java:15:51:15:62 | input : String | ScriptEngineTest.java:19:31:19:35 | input | -| ScriptEngineTest.java:23:58:23:69 | input : String | ScriptEngineTest.java:27:31:27:35 | input | -| ScriptEngineTest.java:30:46:30:57 | input : String | ScriptEngineTest.java:34:31:34:35 | input | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:38:56:38:62 | ...[...] : String | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:39:63:39:69 | ...[...] : String | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:40:70:40:76 | ...[...] : String | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:41:58:41:64 | ...[...] : String | -| ScriptEngineTest.java:38:56:38:62 | ...[...] : String | ScriptEngineTest.java:8:44:8:55 | input : String | -| ScriptEngineTest.java:39:63:39:69 | ...[...] : String | ScriptEngineTest.java:15:51:15:62 | input : String | -| ScriptEngineTest.java:40:70:40:76 | ...[...] : String | ScriptEngineTest.java:23:58:23:69 | input : String | -| ScriptEngineTest.java:41:58:41:64 | ...[...] : String | ScriptEngineTest.java:30:46:30:57 | input : String | -nodes -| ScriptEngineTest.java:8:44:8:55 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:12:37:12:41 | input | semmle.label | input | -| ScriptEngineTest.java:15:51:15:62 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:19:31:19:35 | input | semmle.label | input | -| ScriptEngineTest.java:23:58:23:69 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:27:31:27:35 | input | semmle.label | input | -| ScriptEngineTest.java:30:46:30:57 | input : String | semmle.label | input : String | -| ScriptEngineTest.java:34:31:34:35 | input | semmle.label | input | -| ScriptEngineTest.java:37:26:37:38 | args : String[] | semmle.label | args : String[] | -| ScriptEngineTest.java:38:56:38:62 | ...[...] : String | semmle.label | ...[...] : String | -| ScriptEngineTest.java:39:63:39:69 | ...[...] : String | semmle.label | ...[...] : String | -| ScriptEngineTest.java:40:70:40:76 | ...[...] : String | semmle.label | ...[...] : String | -| ScriptEngineTest.java:41:58:41:64 | ...[...] : String | semmle.label | ...[...] : String | -#select -| ScriptEngineTest.java:12:19:12:42 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:12:37:12:41 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | -| ScriptEngineTest.java:19:19:19:36 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:19:31:19:35 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | -| ScriptEngineTest.java:27:19:27:36 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:27:31:27:35 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | -| ScriptEngineTest.java:34:19:34:36 | eval(...) | ScriptEngineTest.java:37:26:37:38 | args : String[] | ScriptEngineTest.java:34:31:34:35 | input | ScriptEngine eval $@. | ScriptEngineTest.java:37:26:37:38 | args | user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref deleted file mode 100644 index 9566c986778..00000000000 --- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngine.qlref +++ /dev/null @@ -1 +0,0 @@ -experimental/Security/CWE/CWE-094/ScriptEngine.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java index e04ec615f30..ed7099d7598 100755 --- a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java +++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptEngineTest.java @@ -1,9 +1,21 @@ +import javax.script.AbstractScriptEngine; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; + import jdk.nashorn.api.scripting.NashornScriptEngine; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; -import javax.script.*; +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; -public class ScriptEngineTest { +public class ScriptEngineTest extends HttpServlet { public void testWithScriptEngineReference(String input) throws ScriptException { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); @@ -33,26 +45,59 @@ public class ScriptEngineTest { MyCustomScriptEngine engine = (MyCustomScriptEngine) factory.getScriptEngine(new String[] { "-scripting" }); Object result = engine.eval(input); } - - public static void main(String[] args) throws ScriptException { - new ScriptEngineTest().testWithScriptEngineReference(args[0]); - new ScriptEngineTest().testNashornWithScriptEngineReference(args[0]); - new ScriptEngineTest().testNashornWithNashornScriptEngineReference(args[0]); - new ScriptEngineTest().testCustomScriptEngineReference(args[0]); + + public void testScriptEngineCompilable(String input) throws ScriptException { + NashornScriptEngineFactory factory = new NashornScriptEngineFactory(); + Compilable engine = (Compilable) factory.getScriptEngine(new String[] { "-scripting" }); + CompiledScript script = engine.compile(input); + Object result = script.eval(); } - + + public void testScriptEngineGetProgram(String input) throws ScriptException { + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); + ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn"); + String program = engine.getFactory().getProgram(input); + Object result = engine.eval(program); + } + private static class MyCustomScriptEngine extends AbstractScriptEngine { - public Object eval(String var1) throws ScriptException { - return null; - } + public Object eval(String var1) throws ScriptException { return null; } + + @Override + public ScriptEngineFactory getFactory() { return null; } } private static class MyCustomFactory implements ScriptEngineFactory { public MyCustomFactory() { - } - - public ScriptEngine getScriptEngine() { return null; } + } + + @Override + public ScriptEngine getScriptEngine() { return null; } public ScriptEngine getScriptEngine(String... args) { return null; } + + @Override + public String getEngineName() { return null; } + + @Override + public String getMethodCallSyntax(final String obj, final String method, final String... args) { return null; } + + @Override + public String getProgram(final String... statements) { return null; } + } + + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + try { + String code = request.getParameter("code"); + + new ScriptEngineTest().testWithScriptEngineReference(code); + new ScriptEngineTest().testNashornWithScriptEngineReference(code); + new ScriptEngineTest().testNashornWithNashornScriptEngineReference(code); + new ScriptEngineTest().testCustomScriptEngineReference(code); + new ScriptEngineTest().testScriptEngineCompilable(code); + new ScriptEngineTest().testScriptEngineGetProgram(code); + } catch (ScriptException se) { + throw new IOException(se.getMessage()); + } } } diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected new file mode 100644 index 00000000000..5f1d250e9a2 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.expected @@ -0,0 +1,58 @@ +edges +| RhinoServlet.java:28:23:28:50 | getParameter(...) : String | RhinoServlet.java:32:55:32:58 | code | +| RhinoServlet.java:81:23:81:50 | getParameter(...) : String | RhinoServlet.java:83:54:83:57 | code | +| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | RhinoServlet.java:89:74:89:88 | getBytes(...) | +| ScriptEngineTest.java:20:44:20:55 | input : String | ScriptEngineTest.java:24:37:24:41 | input | +| ScriptEngineTest.java:27:51:27:62 | input : String | ScriptEngineTest.java:31:31:31:35 | input | +| ScriptEngineTest.java:35:58:35:69 | input : String | ScriptEngineTest.java:39:31:39:35 | input | +| ScriptEngineTest.java:42:46:42:57 | input : String | ScriptEngineTest.java:46:31:46:35 | input | +| ScriptEngineTest.java:49:41:49:52 | input : String | ScriptEngineTest.java:52:42:52:46 | input | +| ScriptEngineTest.java:56:41:56:52 | input : String | ScriptEngineTest.java:59:51:59:55 | input | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:93:57:93:60 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:94:64:94:67 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:95:71:95:74 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:96:59:96:62 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:97:54:97:57 | code : String | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:98:54:98:57 | code : String | +| ScriptEngineTest.java:93:57:93:60 | code : String | ScriptEngineTest.java:20:44:20:55 | input : String | +| ScriptEngineTest.java:94:64:94:67 | code : String | ScriptEngineTest.java:27:51:27:62 | input : String | +| ScriptEngineTest.java:95:71:95:74 | code : String | ScriptEngineTest.java:35:58:35:69 | input : String | +| ScriptEngineTest.java:96:59:96:62 | code : String | ScriptEngineTest.java:42:46:42:57 | input : String | +| ScriptEngineTest.java:97:54:97:57 | code : String | ScriptEngineTest.java:49:41:49:52 | input : String | +| ScriptEngineTest.java:98:54:98:57 | code : String | ScriptEngineTest.java:56:41:56:52 | input : String | +nodes +| RhinoServlet.java:28:23:28:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RhinoServlet.java:32:55:32:58 | code | semmle.label | code | +| RhinoServlet.java:81:23:81:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RhinoServlet.java:83:54:83:57 | code | semmle.label | code | +| RhinoServlet.java:88:23:88:50 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| RhinoServlet.java:89:74:89:88 | getBytes(...) | semmle.label | getBytes(...) | +| ScriptEngineTest.java:20:44:20:55 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:24:37:24:41 | input | semmle.label | input | +| ScriptEngineTest.java:27:51:27:62 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:31:31:31:35 | input | semmle.label | input | +| ScriptEngineTest.java:35:58:35:69 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:39:31:39:35 | input | semmle.label | input | +| ScriptEngineTest.java:42:46:42:57 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:46:31:46:35 | input | semmle.label | input | +| ScriptEngineTest.java:49:41:49:52 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:52:42:52:46 | input | semmle.label | input | +| ScriptEngineTest.java:56:41:56:52 | input : String | semmle.label | input : String | +| ScriptEngineTest.java:59:51:59:55 | input | semmle.label | input | +| ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | +| ScriptEngineTest.java:93:57:93:60 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:94:64:94:67 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:95:71:95:74 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:96:59:96:62 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:97:54:97:57 | code : String | semmle.label | code : String | +| ScriptEngineTest.java:98:54:98:57 | code : String | semmle.label | code : String | +#select +| RhinoServlet.java:32:29:32:78 | evaluateString(...) | RhinoServlet.java:28:23:28:50 | getParameter(...) : String | RhinoServlet.java:32:55:32:58 | code | Java Script Engine evaluate $@. | RhinoServlet.java:28:23:28:50 | getParameter(...) | user input | +| RhinoServlet.java:83:25:83:97 | compileToClassFiles(...) | RhinoServlet.java:81:23:81:50 | getParameter(...) : String | RhinoServlet.java:83:54:83:57 | code | Java Script Engine evaluate $@. | RhinoServlet.java:81:23:81:50 | getParameter(...) | user input | +| RhinoServlet.java:89:23:89:89 | defineClass(...) | RhinoServlet.java:88:23:88:50 | getParameter(...) : String | RhinoServlet.java:89:74:89:88 | getBytes(...) | Java Script Engine evaluate $@. | RhinoServlet.java:88:23:88:50 | getParameter(...) | user input | +| ScriptEngineTest.java:24:19:24:42 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:24:37:24:41 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:31:19:31:36 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:31:31:31:35 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:39:19:39:36 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:39:31:39:35 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:46:19:46:36 | eval(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:46:31:46:35 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:52:27:52:47 | compile(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:52:42:52:46 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | +| ScriptEngineTest.java:59:20:59:56 | getProgram(...) | ScriptEngineTest.java:91:18:91:45 | getParameter(...) : String | ScriptEngineTest.java:59:51:59:55 | input | Java Script Engine evaluate $@. | ScriptEngineTest.java:91:18:91:45 | getParameter(...) | user input | diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.qlref b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.qlref new file mode 100644 index 00000000000..da2b4287d0c --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-094/ScriptInjection.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-094/ScriptInjection.ql \ No newline at end of file diff --git a/java/ql/test/experimental/query-tests/security/CWE-094/options b/java/ql/test/experimental/query-tests/security/CWE-094/options index 18e3518fc97..48cc00e0a17 100644 --- a/java/ql/test/experimental/query-tests/security/CWE-094/options +++ b/java/ql/test/experimental/query-tests/security/CWE-094/options @@ -1,2 +1,2 @@ -//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4 +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/springframework-5.2.3:${testdir}/../../../../stubs/mvel2-2.4.7:${testdir}/../../../../stubs/jsr223-api:${testdir}/../../../../stubs/apache-commons-jexl-2.1.1:${testdir}/../../../../stubs/apache-commons-jexl-3.1:${testdir}/../../../../stubs/scriptengine:${testdir}/../../../../stubs/java-ee-el:${testdir}/../../../../stubs/juel-2.2:${testdir}/../../../stubs/groovy-all-3.0.7:${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/jython-2.7.2:${testdir}/../../../../experimental/stubs/rhino-1.7.13 diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected new file mode 100644 index 00000000000..bcf5e892e1b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.expected @@ -0,0 +1,35 @@ +edges +| SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | SpringUrlRedirect.java:15:19:15:29 | redirectUrl | +| SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | SpringUrlRedirect.java:21:36:21:46 | redirectUrl | +| SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | SpringUrlRedirect.java:27:44:27:54 | redirectUrl | +| SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | SpringUrlRedirect.java:33:47:33:57 | redirectUrl | +| SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | SpringUrlRedirect.java:40:29:40:39 | redirectUrl | +| SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | SpringUrlRedirect.java:48:30:48:40 | redirectUrl | +| SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | SpringUrlRedirect.java:54:30:54:66 | format(...) | +| SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | SpringUrlRedirect.java:59:30:59:76 | format(...) | +nodes +| SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:15:19:15:29 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:21:36:21:46 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:27:44:27:54 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:33:47:33:57 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:40:29:40:39 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:48:30:48:40 | redirectUrl | semmle.label | redirectUrl | +| SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:54:30:54:66 | format(...) | semmle.label | format(...) | +| SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | semmle.label | redirectUrl : String | +| SpringUrlRedirect.java:59:30:59:76 | format(...) | semmle.label | format(...) | +#select +| SpringUrlRedirect.java:15:19:15:29 | redirectUrl | SpringUrlRedirect.java:13:30:13:47 | redirectUrl : String | SpringUrlRedirect.java:15:19:15:29 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:13:30:13:47 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:21:36:21:46 | redirectUrl | SpringUrlRedirect.java:20:24:20:41 | redirectUrl : String | SpringUrlRedirect.java:21:36:21:46 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:20:24:20:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:27:44:27:54 | redirectUrl | SpringUrlRedirect.java:26:30:26:47 | redirectUrl : String | SpringUrlRedirect.java:27:44:27:54 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:26:30:26:47 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:33:47:33:57 | redirectUrl | SpringUrlRedirect.java:32:30:32:47 | redirectUrl : String | SpringUrlRedirect.java:33:47:33:57 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:32:30:32:47 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:40:29:40:39 | redirectUrl | SpringUrlRedirect.java:37:24:37:41 | redirectUrl : String | SpringUrlRedirect.java:40:29:40:39 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:37:24:37:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:48:30:48:40 | redirectUrl | SpringUrlRedirect.java:45:24:45:41 | redirectUrl : String | SpringUrlRedirect.java:48:30:48:40 | redirectUrl | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:45:24:45:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:54:30:54:66 | format(...) | SpringUrlRedirect.java:53:24:53:41 | redirectUrl : String | SpringUrlRedirect.java:54:30:54:66 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:53:24:53:41 | redirectUrl | user-provided value | +| SpringUrlRedirect.java:59:30:59:76 | format(...) | SpringUrlRedirect.java:58:24:58:41 | redirectUrl : String | SpringUrlRedirect.java:59:30:59:76 | format(...) | Potentially untrusted URL redirection due to $@. | SpringUrlRedirect.java:58:24:58:41 | redirectUrl | user-provided value | diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.java b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.java new file mode 100644 index 00000000000..f3958cba102 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.java @@ -0,0 +1,83 @@ +import javax.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.view.RedirectView; + +@Controller +public class SpringUrlRedirect { + + private final static String VALID_REDIRECT = "http://127.0.0.1"; + + @GetMapping("url1") + public RedirectView bad1(String redirectUrl, HttpServletResponse response) throws Exception { + RedirectView rv = new RedirectView(); + rv.setUrl(redirectUrl); + return rv; + } + + @GetMapping("url2") + public String bad2(String redirectUrl) { + String url = "redirect:" + redirectUrl; + return url; + } + + @GetMapping("url3") + public RedirectView bad3(String redirectUrl) { + RedirectView rv = new RedirectView(redirectUrl); + return rv; + } + + @GetMapping("url4") + public ModelAndView bad4(String redirectUrl) { + return new ModelAndView("redirect:" + redirectUrl); + } + + @GetMapping("url5") + public String bad5(String redirectUrl) { + StringBuffer stringBuffer = new StringBuffer(); + stringBuffer.append("redirect:"); + stringBuffer.append(redirectUrl); + return stringBuffer.toString(); + } + + @GetMapping("url6") + public String bad6(String redirectUrl) { + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.append("redirect:"); + stringBuilder.append(redirectUrl); + return stringBuilder.toString(); + } + + @GetMapping("url7") + public String bad7(String redirectUrl) { + return "redirect:" + String.format("%s/?aaa", redirectUrl); + } + + @GetMapping("url8") + public String bad8(String redirectUrl, String token) { + return "redirect:" + String.format(redirectUrl + "?token=%s", token); + } + + @GetMapping("url9") + public RedirectView good1(String redirectUrl) { + RedirectView rv = new RedirectView(); + if (redirectUrl.startsWith(VALID_REDIRECT)){ + rv.setUrl(redirectUrl); + }else { + rv.setUrl(VALID_REDIRECT); + } + return rv; + } + + @GetMapping("url10") + public ModelAndView good2(String token) { + String url = "/edit?token=" + token; + return new ModelAndView("redirect:" + url); + } + + @GetMapping("url11") + public String good3(String status) { + return "redirect:" + String.format("/stories/search/criteria?status=%s", status); + } +} diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.qlref b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.qlref new file mode 100644 index 00000000000..418be1d307b --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/SpringUrlRedirect.qlref @@ -0,0 +1 @@ +experimental/Security/CWE/CWE-601/SpringUrlRedirect.ql diff --git a/java/ql/test/experimental/query-tests/security/CWE-601/options b/java/ql/test/experimental/query-tests/security/CWE-601/options new file mode 100644 index 00000000000..a9289108747 --- /dev/null +++ b/java/ql/test/experimental/query-tests/security/CWE-601/options @@ -0,0 +1 @@ +//semmle-extractor-options: --javac-args -cp ${testdir}/../../../../stubs/servlet-api-2.4:${testdir}/../../../../stubs/springframework-5.2.3/ \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ClassShutter.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ClassShutter.java new file mode 100644 index 00000000000..f425e08e966 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ClassShutter.java @@ -0,0 +1,56 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** +Embeddings that wish to filter Java classes that are visible to scripts +through the LiveConnect, should implement this interface. + +@see Context#setClassShutter(ClassShutter) +@since 1.5 Release 4 +@author Norris Boyd +*/ + + public interface ClassShutter { + + /** + * Return true iff the Java class with the given name should be exposed + * to scripts. + *

    + * An embedding may filter which Java classes are exposed through + * LiveConnect to JavaScript scripts. + *

    + * Due to the fact that there is no package reflection in Java, + * this method will also be called with package names. There + * is no way for Rhino to tell if "Packages.a.b" is a package name + * or a class that doesn't exist. What Rhino does is attempt + * to load each segment of "Packages.a.b.c": It first attempts to + * load class "a", then attempts to load class "a.b", then + * finally attempts to load class "a.b.c". On a Rhino installation + * without any ClassShutter set, and without any of the + * above classes, the expression "Packages.a.b.c" will result in + * a [JavaPackage a.b.c] and not an error. + *

    + * With ClassShutter supplied, Rhino will first call + * visibleToScripts before attempting to look up the class name. If + * visibleToScripts returns false, the class name lookup is not + * performed and subsequent Rhino execution assumes the class is + * not present. So for "java.lang.System.out.println" the lookup + * of "java.lang.System" is skipped and thus Rhino assumes that + * "java.lang.System" doesn't exist. So then for "java.lang.System.out", + * Rhino attempts to load the class "java.lang.System.out" because + * it assumes that "java.lang.System" is a package name. + *

    + * @param fullClassName the full name of the class (including the package + * name, with '.' as a delimiter). For example the + * standard string class is "java.lang.String" + * @return whether or not to reveal this class to scripts + */ + public boolean visibleToScripts(String fullClassName); +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/CompilerEnvirons.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/CompilerEnvirons.java new file mode 100644 index 00000000000..3cb0619499e --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/CompilerEnvirons.java @@ -0,0 +1,12 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + package org.mozilla.javascript; + + public class CompilerEnvirons { + public CompilerEnvirons() { + } + } \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Context.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Context.java new file mode 100644 index 00000000000..1bda212cfa4 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Context.java @@ -0,0 +1,695 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +import java.io.Closeable; +import java.io.IOException; +import java.io.Reader; +import java.util.Locale; + +/** + * This class represents the runtime context of an executing script. + * + * Before executing a script, an instance of Context must be created + * and associated with the thread that will be executing the script. + * The Context will be used to store information about the executing + * of the script such as the call stack. Contexts are associated with + * the current thread using the {@link #call(ContextAction)} + * or {@link #enter()} methods.

    + * + * Different forms of script execution are supported. Scripts may be + * evaluated from the source directly, or first compiled and then later + * executed. Interactive execution is also supported.

    + * + * Some aspects of script execution, such as type conversions and + * object creation, may be accessed directly through methods of + * Context. + * + * @see Scriptable + * @author Norris Boyd + * @author Brendan Eich + */ + +public class Context + implements Closeable +{ + /** + * Creates a new Context. The context will be associated with the {@link + * ContextFactory#getGlobal() global context factory}. + * + * Note that the Context must be associated with a thread before + * it can be used to execute a script. + * @deprecated this constructor is deprecated because it creates a + * dependency on a static singleton context factory. Use + * {@link ContextFactory#enter()} or + * {@link ContextFactory#call(ContextAction)} instead. If you subclass + * this class, consider using {@link #Context(ContextFactory)} constructor + * instead in the subclasses' constructors. + */ + @Deprecated + public Context() + { + } + + /** + * Creates a new context. Provided as a preferred super constructor for + * subclasses in place of the deprecated default public constructor. + * @param factory the context factory associated with this context (most + * likely, the one that created the context). Can not be null. The context + * features are inherited from the factory, and the context will also + * otherwise use its factory's services. + * @throws IllegalArgumentException if factory parameter is null. + */ + protected Context(ContextFactory factory) + { + } + + /** + * Get the current Context. + * + * The current Context is per-thread; this method looks up + * the Context associated with the current thread.

    + * + * @return the Context associated with the current thread, or + * null if no context is associated with the current + * thread. + * @see ContextFactory#enterContext() + * @see ContextFactory#call(ContextAction) + */ + public static Context getCurrentContext() + { + return null; + } + + /** + * Same as calling {@link ContextFactory#enterContext()} on the global + * ContextFactory instance. + * @return a Context associated with the current thread + * @see #getCurrentContext() + * @see #exit() + * @see #call(ContextAction) + */ + public static Context enter() + { + return null; + } + + /** + * Get a Context associated with the current thread, using + * the given Context if need be. + *

    + * The same as enter() except that cx + * is associated with the current thread and returned if + * the current thread has no associated context and cx + * is not associated with any other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + * @deprecated use {@link ContextFactory#enterContext(Context)} instead as + * this method relies on usage of a static singleton "global" ContextFactory. + * @see ContextFactory#enterContext(Context) + * @see ContextFactory#call(ContextAction) + */ + @Deprecated + public static Context enter(Context cx) + { + return null; + } + + static final Context enter(Context cx, ContextFactory factory) + { + return null; + } + + /** + * Exit a block of code requiring a Context. + * + * Calling exit() will remove the association between + * the current thread and a Context if the prior call to + * {@link ContextFactory#enterContext()} on this thread newly associated a + * Context with this thread. Once the current thread no longer has an + * associated Context, it cannot be used to execute JavaScript until it is + * again associated with a Context. + * @see ContextFactory#enterContext() + */ + public static void exit() + { + } + + @Override + public void close() { + } + + /** + * Return {@link ContextFactory} instance used to create this Context. + */ + public final ContextFactory getFactory() + { + return null; + } + + /** + * Checks if this is a sealed Context. A sealed Context instance does not + * allow to modify any of its properties and will throw an exception + * on any such attempt. + * @see #seal(Object sealKey) + */ + public final boolean isSealed() + { + return false; + } + + /** + * Seal this Context object so any attempt to modify any of its properties + * including calling {@link #enter()} and {@link #exit()} methods will + * throw an exception. + *

    + * If sealKey is not null, calling + * {@link #unseal(Object sealKey)} with the same key unseals + * the object. If sealKey is null, unsealing is no longer possible. + * + * @see #isSealed() + * @see #unseal(Object) + */ + public final void seal(Object sealKey) + { + } + + /** + * Unseal previously sealed Context object. + * The sealKey argument should not be null and should match + * sealKey suplied with the last call to + * {@link #seal(Object)} or an exception will be thrown. + * + * @see #isSealed() + * @see #seal(Object sealKey) + */ + public final void unseal(Object sealKey) + { + } + + /** + * Get the current language version. + *

    + * The language version number affects JavaScript semantics as detailed + * in the overview documentation. + * + * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc. + */ + public final int getLanguageVersion() + { + return -1; + } + + /** + * Set the language version. + * + *

    + * Setting the language version will affect functions and scripts compiled + * subsequently. See the overview documentation for version-specific + * behavior. + * + * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc. + */ + public void setLanguageVersion(int version) + { + } + + public static boolean isValidLanguageVersion(int version) + { + return false; + } + + public static void checkLanguageVersion(int version) + { + } + + /** + * Get the implementation version. + * + *

    + * The implementation version is of the form + *

    +     *    "name langVer release relNum date"
    +     * 
    + * where name is the name of the product, langVer is + * the language version, relNum is the release number, and + * date is the release date for that specific + * release in the form "yyyy mm dd". + * + * @return a string that encodes the product, language version, release + * number, and date. + */ + public final String getImplementationVersion() { + return null; + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @return the initialized scope + */ + public final ScriptableObject initStandardObjects() + { + return null; + } + + /** + * Initialize the standard objects, leaving out those that offer access directly + * to Java classes. This sets up "scope" to have access to all the standard + * JavaScript classes, but does not create global objects for any top-level + * Java packages. In addition, the "Packages," "JavaAdapter," and + * "JavaImporter" classes, and the "getClass" function, are not + * initialized. + * + * The result of this function is a scope that may be safely used in a "sandbox" + * environment where it is not desirable to give access to Java code from JavaScript. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @return the initialized scope + */ + public final ScriptableObject initSafeStandardObjects() + { + return null; + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object which + * is an instance {@link ScriptableObject}. + */ + public final Scriptable initStandardObjects(ScriptableObject scope) + { + return null; + } + + /** + * Initialize the standard objects, leaving out those that offer access directly + * to Java classes. This sets up "scope" to have access to all the standard + * JavaScript classes, but does not create global objects for any top-level + * Java packages. In addition, the "Packages," "JavaAdapter," and + * "JavaImporter" classes, and the "getClass" function, are not + * initialized. + * + * The result of this function is a scope that may be safely used in a "sandbox" + * environment where it is not desirable to give access to Java code from JavaScript. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object which + * is an instance {@link ScriptableObject}. + */ + public final Scriptable initSafeStandardObjects(ScriptableObject scope) + { + return null; + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon.

    + * + * This form of the method also allows for creating "sealed" standard + * objects. An object that is sealed cannot have properties added, changed, + * or removed. This is useful to create a "superglobal" that can be shared + * among several top-level objects. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @param sealed whether or not to create sealed standard objects that + * cannot be modified. + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object. + * @since 1.4R3 + */ + public ScriptableObject initStandardObjects(ScriptableObject scope, + boolean sealed) + { + return null; + } + + /** + * Initialize the standard objects, leaving out those that offer access directly + * to Java classes. This sets up "scope" to have access to all the standard + * JavaScript classes, but does not create global objects for any top-level + * Java packages. In addition, the "Packages," "JavaAdapter," and + * "JavaImporter" classes, and the "getClass" function, are not + * initialized. + * + * The result of this function is a scope that may be safely used in a "sandbox" + * environment where it is not desirable to give access to Java code from JavaScript. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

    + * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

    + * + * This method does not affect the Context it is called upon.

    + * + * This form of the method also allows for creating "sealed" standard + * objects. An object that is sealed cannot have properties added, changed, + * or removed. This is useful to create a "superglobal" that can be shared + * among several top-level objects. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @param sealed whether or not to create sealed standard objects that + * cannot be modified. + * @return the initialized scope. The method returns the value of the scope + * argument if it is not null or newly allocated scope object. + * @since 1.7.6 + */ + public ScriptableObject initSafeStandardObjects(ScriptableObject scope, + boolean sealed) + { + return null; + } + + /** + * Get the singleton object that represents the JavaScript Undefined value. + */ + public static Object getUndefinedValue() + { + return null; + } + + /** + * Evaluate a JavaScript source string. + * + * The provided source name and line number are used for error messages + * and for producing debug information. + * + * @param scope the scope to execute in + * @param source the JavaScript source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the string + * @see org.mozilla.javascript.SecurityController + */ + public final Object evaluateString(Scriptable scope, String source, + String sourceName, int lineno, + Object securityDomain) + { + return null; + } + + /** + * Evaluate a reader as JavaScript source. + * + * All characters of the reader are consumed. + * + * @param scope the scope to execute in + * @param in the Reader to get JavaScript source from + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the source + * + * @exception IOException if an IOException was generated by the Reader + */ + public final Object evaluateReader(Scriptable scope, Reader in, + String sourceName, int lineno, + Object securityDomain) + throws IOException + { + return null; + } + + /** + * @deprecated + * @see #compileReader(Reader in, String sourceName, int lineno, Object securityDomain) + */ + @Deprecated + public final Script compileReader( + Scriptable scope, Reader in, String sourceName, int lineno, Object securityDomain) + throws IOException { + return null; + } + + /** + * Compiles the source in the given reader. + * + *

    Returns a script that may later be executed. Will consume all the source in the reader. + * + * @param in the input reader + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors + * @param securityDomain an arbitrary object that specifies security information about the + * origin or owner of the script. For implementations that don't care about security, this + * value may be null. + * @return a script that may later be executed + * @exception IOException if an IOException was generated by the Reader + * @see org.mozilla.javascript.Script + */ + public final Script compileReader( + Reader in, String sourceName, int lineno, Object securityDomain) throws IOException { + return null; + } + + /** + * Compiles the source in the given string. + * + *

    Returns a script that may later be executed. + * + * @param source the source string + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors. Use 0 if the line number is + * unknown. + * @param securityDomain an arbitrary object that specifies security information about the + * origin or owner of the script. For implementations that don't care about security, this + * value may be null. + * @return a script that may later be executed + * @see org.mozilla.javascript.Script + */ + public final Script compileString( + String source, String sourceName, int lineno, Object securityDomain) { + return null; + } + + /** + * Compile a JavaScript function. + * + *

    The function source must be a function definition as defined by ECMA (e.g., "function f(a) + * { return a; }"). + * + * @param scope the scope to compile relative to + * @param source the function definition source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security information about the + * origin or owner of the script. For implementations that don't care about security, this + * value may be null. + * @return a Function that may later be called + * @see org.mozilla.javascript.Function + */ + public final Function compileFunction( + Scriptable scope, String source, String sourceName, int lineno, Object securityDomain) { + return null; + } + + /** + * Convert the value to a JavaScript boolean value. + *

    + * See ECMA 9.2. + * + * @param value a JavaScript value + * @return the corresponding boolean value converted using + * the ECMA rules + */ + public static boolean toBoolean(Object value) + { + return false; + } + + /** + * Convert the value to a JavaScript Number value. + *

    + * Returns a Java double for the JavaScript Number. + *

    + * See ECMA 9.3. + * + * @param value a JavaScript value + * @return the corresponding double value converted using + * the ECMA rules + */ + public static double toNumber(Object value) + { + return -1; + } + + /** + * Convert the value to a JavaScript String value. + *

    + * See ECMA 9.8. + *

    + * @param value a JavaScript value + * @return the corresponding String value converted using + * the ECMA rules + */ + public static String toString(Object value) + { + return null; + } + + /** + * Convert the value to an JavaScript object value. + *

    + * Note that a scope must be provided to look up the constructors + * for Number, Boolean, and String. + *

    + * See ECMA 9.9. + *

    + * Additionally, arbitrary Java objects and classes will be + * wrapped in a Scriptable object with its Java fields and methods + * reflected as JavaScript properties of the object. + * + * @param value any Java object + * @param scope global scope containing constructors for Number, + * Boolean, and String + * @return new JavaScript object + */ + public static Scriptable toObject(Object value, Scriptable scope) + { + return null; + } + + /** + * Convenient method to convert java value to its closest representation + * in JavaScript. + *

    + * If value is an instance of String, Number, Boolean, Function or + * Scriptable, it is returned as it and will be treated as the corresponding + * JavaScript type of string, number, boolean, function and object. + *

    + * Note that for Number instances during any arithmetic operation in + * JavaScript the engine will always use the result of + * Number.doubleValue() resulting in a precision loss if + * the number can not fit into double. + *

    + * If value is an instance of Character, it will be converted to string of + * length 1 and its JavaScript type will be string. + *

    + * The rest of values will be wrapped as LiveConnect objects + * by calling {@link WrapFactory#wrap(Context cx, Scriptable scope, + * Object obj, Class staticType)} as in: + *

    +     *    Context cx = Context.getCurrentContext();
    +     *    return cx.getWrapFactory().wrap(cx, scope, value, null);
    +     * 
    + * + * @param value any Java object + * @param scope top scope object + * @return value suitable to pass to any API that takes JavaScript values. + */ + public static Object javaToJS(Object value, Scriptable scope) + { + return null; + } + + /** + * Convert a JavaScript value into the desired type. + * Uses the semantics defined with LiveConnect3 and throws an + * Illegal argument exception if the conversion cannot be performed. + * @param value the JavaScript value to convert + * @param desiredType the Java type to convert to. Primitive Java + * types are represented using the TYPE fields in the corresponding + * wrapper class in java.lang. + * @return the converted value + * @throws EvaluatorException if the conversion cannot be performed + */ + public static Object jsToJava(Object value, Class desiredType) + { + return null; + } + + /** + * Set the LiveConnect access filter for this context. + *

    {@link ClassShutter} may only be set if it is currently null. + * Otherwise a SecurityException is thrown. + * @param shutter a ClassShutter object + * @throws SecurityException if there is already a ClassShutter + * object for this Context + */ + public synchronized final void setClassShutter(ClassShutter shutter) + { + } + + final synchronized ClassShutter getClassShutter() + { + return null; + } + + public interface ClassShutterSetter { + public void setClassShutter(ClassShutter shutter); + public ClassShutter getClassShutter(); + } + + public final synchronized ClassShutterSetter getClassShutterSetter() { + return null; + } +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ContextFactory.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ContextFactory.java new file mode 100644 index 00000000000..a7f83f2095d --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ContextFactory.java @@ -0,0 +1,314 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * Factory class that Rhino runtime uses to create new {@link Context} + * instances. A ContextFactory can also notify listeners + * about context creation and release. + *

    + * When the Rhino runtime needs to create new {@link Context} instance during + * execution of {@link Context#enter()} or {@link Context}, it will call + * {@link #makeContext()} of the current global ContextFactory. + * See {@link #getGlobal()} and {@link #initGlobal(ContextFactory)}. + *

    + * It is also possible to use explicit ContextFactory instances for Context + * creation. This is useful to have a set of independent Rhino runtime + * instances under single JVM. See {@link #call(ContextAction)}. + *

    + * The following example demonstrates Context customization to terminate + * scripts running more then 10 seconds and to provide better compatibility + * with JavaScript code using MSIE-specific features. + *

    + * import org.mozilla.javascript.*;
    + *
    + * class MyFactory extends ContextFactory
    + * {
    + *
    + *     // Custom {@link Context} to store execution time.
    + *     private static class MyContext extends Context
    + *     {
    + *         long startTime;
    + *     }
    + *
    + *     static {
    + *         // Initialize GlobalFactory with custom factory
    + *         ContextFactory.initGlobal(new MyFactory());
    + *     }
    + *
    + *     // Override {@link #makeContext()}
    + *     protected Context makeContext()
    + *     {
    + *         MyContext cx = new MyContext();
    + *         // Make Rhino runtime to call observeInstructionCount
    + *         // each 10000 bytecode instructions
    + *         cx.setInstructionObserverThreshold(10000);
    + *         return cx;
    + *     }
    + *
    + *     // Override {@link #hasFeature(Context, int)}
    + *     public boolean hasFeature(Context cx, int featureIndex)
    + *     {
    + *         // Turn on maximum compatibility with MSIE scripts
    + *         switch (featureIndex) {
    + *             case {@link Context#FEATURE_NON_ECMA_GET_YEAR}:
    + *                 return true;
    + *
    + *             case {@link Context#FEATURE_MEMBER_EXPR_AS_FUNCTION_NAME}:
    + *                 return true;
    + *
    + *             case {@link Context#FEATURE_RESERVED_KEYWORD_AS_IDENTIFIER}:
    + *                 return true;
    + *
    + *             case {@link Context#FEATURE_PARENT_PROTO_PROPERTIES}:
    + *                 return false;
    + *         }
    + *         return super.hasFeature(cx, featureIndex);
    + *     }
    + *
    + *     // Override {@link #observeInstructionCount(Context, int)}
    + *     protected void observeInstructionCount(Context cx, int instructionCount)
    + *     {
    + *         MyContext mcx = (MyContext)cx;
    + *         long currentTime = System.currentTimeMillis();
    + *         if (currentTime - mcx.startTime > 10*1000) {
    + *             // More then 10 seconds from Context creation time:
    + *             // it is time to stop the script.
    + *             // Throw Error instance to ensure that script will never
    + *             // get control back through catch or finally.
    + *             throw new Error();
    + *         }
    + *     }
    + *
    + *     // Override {@link #doTopCall(Callable,
    +                               Context, Scriptable,
    +                               Scriptable, Object[])}
    + *     protected Object doTopCall(Callable callable,
    + *                                Context cx, Scriptable scope,
    + *                                Scriptable thisObj, Object[] args)
    + *     {
    + *         MyContext mcx = (MyContext)cx;
    + *         mcx.startTime = System.currentTimeMillis();
    + *
    + *         return super.doTopCall(callable, cx, scope, thisObj, args);
    + *     }
    + *
    + * }
    + * 
    + */ + +public class ContextFactory +{ + + /** + * Listener of {@link Context} creation and release events. + */ + public interface Listener + { + /** + * Notify about newly created {@link Context} object. + */ + public void contextCreated(Context cx); + + /** + * Notify that the specified {@link Context} instance is no longer + * associated with the current thread. + */ + public void contextReleased(Context cx); + } + + /** + * Get global ContextFactory. + * + * @see #hasExplicitGlobal() + * @see #initGlobal(ContextFactory) + */ + public static ContextFactory getGlobal() + { + return null; + } + + /** + * Check if global factory was set. + * Return true to indicate that {@link #initGlobal(ContextFactory)} was + * already called and false to indicate that the global factory was not + * explicitly set. + * + * @see #getGlobal() + * @see #initGlobal(ContextFactory) + */ + public static boolean hasExplicitGlobal() + { + return false; + } + + /** + * Set global ContextFactory. + * The method can only be called once. + * + * @see #getGlobal() + * @see #hasExplicitGlobal() + */ + public synchronized static void initGlobal(ContextFactory factory) + { + } + + public interface GlobalSetter { + public void setContextFactoryGlobal(ContextFactory factory); + public ContextFactory getContextFactoryGlobal(); + } + + public synchronized static GlobalSetter getGlobalSetter() { + return null; + } + + /** + * Create new {@link Context} instance to be associated with the current + * thread. + * This is a callback method used by Rhino to create {@link Context} + * instance when it is necessary to associate one with the current + * execution thread. makeContext() is allowed to call + * {@link Context#seal(Object)} on the result to prevent + * {@link Context} changes by hostile scripts or applets. + */ + protected Context makeContext() + { + return null; + } + + /** + * Implementation of {@link Context#hasFeature(int featureIndex)}. + * This can be used to customize {@link Context} without introducing + * additional subclasses. + */ + protected boolean hasFeature(Context cx, int featureIndex) + { + return false; + } + + /** + * Get ClassLoader to use when searching for Java classes. + * Unless it was explicitly initialized with + * {@link #initApplicationClassLoader(ClassLoader)} the method returns + * null to indicate that Thread.getContextClassLoader() should be used. + */ + public final ClassLoader getApplicationClassLoader() + { + return null; + } + + /** + * Set explicit class loader to use when searching for Java classes. + * + * @see #getApplicationClassLoader() + */ + public final void initApplicationClassLoader(ClassLoader loader) + { + } + + /** + * Checks if this is a sealed ContextFactory. + * @see #seal() + */ + public final boolean isSealed() + { + return false; + } + + /** + * Seal this ContextFactory so any attempt to modify it like to add or + * remove its listeners will throw an exception. + * @see #isSealed() + */ + public final void seal() + { + } + + /** + * Get a context associated with the current thread, creating one if need + * be. The Context stores the execution state of the JavaScript engine, so + * it is required that the context be entered before execution may begin. + * Once a thread has entered a Context, then getCurrentContext() may be + * called to find the context that is associated with the current thread. + *

    + * Calling enterContext() will return either the Context + * currently associated with the thread, or will create a new context and + * associate it with the current thread. Each call to + * enterContext() must have a matching call to + * {@link Context#exit()}. + *

    +     *      Context cx = contextFactory.enterContext();
    +     *      try {
    +     *          ...
    +     *          cx.evaluateString(...);
    +     *      } finally {
    +     *          Context.exit();
    +     *      }
    +     * 
    + * Instead of using enterContext(), exit() pair consider + * using {@link #call(ContextAction)} which guarantees proper association + * of Context instances with the current thread. + * With this method the above example becomes: + *
    +     *      ContextFactory.call(new ContextAction() {
    +     *          public Object run(Context cx) {
    +     *              ...
    +     *              cx.evaluateString(...);
    +     *              return null;
    +     *          }
    +     *      });
    +     * 
    + * @return a Context associated with the current thread + * @see Context#getCurrentContext() + * @see Context#exit() + * @see #call(ContextAction) + */ + public Context enterContext() + { + return null; + } + + /** + * @deprecated use {@link #enterContext()} instead + * @return a Context associated with the current thread + */ + @Deprecated + public final Context enter() + { + return null; + } + + /** + * @deprecated Use {@link Context#exit()} instead. + */ + @Deprecated + public final void exit() + { + } + + /** + * Get a Context associated with the current thread, using the given + * Context if need be. + *

    + * The same as enterContext() except that cx + * is associated with the current thread and returned if the current thread + * has no associated context and cx is not associated with any + * other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + * @see #enterContext() + * @see #call(ContextAction) + * @throws IllegalStateException if cx is already associated + * with a different thread + */ + public final Context enterContext(Context cx) + { + return null; + } +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/DefiningClassLoader.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/DefiningClassLoader.java new file mode 100644 index 00000000000..3819798c351 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/DefiningClassLoader.java @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + package org.mozilla.javascript; + + /** + * Load generated classes. + * + * @author Norris Boyd + */ + public class DefiningClassLoader extends ClassLoader + implements GeneratedClassLoader + { + public DefiningClassLoader() { + } + + public DefiningClassLoader(ClassLoader parentLoader) { + } + + @Override + public Class defineClass(String name, byte[] data) { + return null; + } + + @Override + public void linkClass(Class cl) { + } + + @Override + public Class loadClass(String name, boolean resolve) + throws ClassNotFoundException + { + return null; + } + } \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Function.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Function.java new file mode 100644 index 00000000000..a35a7c2dfba --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Function.java @@ -0,0 +1,46 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all functions in JavaScript must implement. The interface provides for + * calling functions and constructors. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ +public interface Function extends Scriptable { + /** + * Call the function. + * + *

    Note that the array of arguments is not guaranteed to have length greater than 0. + * + * @param cx the current Context for this thread + * @param scope the scope to execute the function relative to. This is set to the value returned + * by getParentScope() except when the function is called from a closure. + * @param thisObj the JavaScript this object + * @param args the array of arguments + * @return the result of the call + */ + Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args); + + /** + * Call the function as a constructor. + * + *

    This method is invoked by the runtime in order to satisfy a use of the JavaScript + * new operator. This method is expected to create a new object and return it. + * + * @param cx the current Context for this thread + * @param scope an enclosing scope of the caller except when the function is called from a + * closure. + * @param args the array of arguments + * @return the allocated object + */ + Scriptable construct(Context cx, Scriptable scope, Object[] args); +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/GeneratedClassLoader.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/GeneratedClassLoader.java new file mode 100644 index 00000000000..c7450862917 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/GeneratedClassLoader.java @@ -0,0 +1,34 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * Interface to define classes from generated byte code. + */ +public interface GeneratedClassLoader { + + /** + * Define a new Java class. + * Classes created via this method should have the same class loader. + * + * @param name fully qualified class name + * @param data class byte code + * @return new class object + */ + public Class defineClass(String name, byte[] data); + + /** + * Link the given class. + * + * @param cl Class instance returned from the previous call to + * {@link #defineClass(String, byte[])} + * @see java.lang.ClassLoader + */ + public void linkClass(Class cl); +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/RhinoException.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/RhinoException.java new file mode 100644 index 00000000000..b11befb4a63 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/RhinoException.java @@ -0,0 +1,15 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +package org.mozilla.javascript; + +/** + * The class of exceptions thrown by the JavaScript engine. + */ +public abstract class RhinoException extends RuntimeException +{ +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Script.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Script.java new file mode 100644 index 00000000000..824dc0241c1 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Script.java @@ -0,0 +1,41 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * All compiled scripts implement this interface. + *

    + * This class encapsulates script execution relative to an + * object scope. + * @since 1.3 + * @author Norris Boyd + */ + +public interface Script { + + /** + * Execute the script. + *

    + * The script is executed in a particular runtime Context, which + * must be associated with the current thread. + * The script is executed relative to a scope--definitions and + * uses of global top-level variables and functions will access + * properties of the scope object. For compliant ECMA + * programs, the scope must be an object that has been initialized + * as a global object using Context.initStandardObjects. + *

    + * + * @param cx the Context associated with the current thread + * @param scope the scope to execute relative to + * @return the result of executing the script + * @see org.mozilla.javascript.Context#initStandardObjects() + */ + public Object exec(Context cx, Scriptable scope); + +} \ No newline at end of file diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Scriptable.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Scriptable.java new file mode 100644 index 00000000000..34616f7ad74 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/Scriptable.java @@ -0,0 +1,304 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * This is interface that all objects in JavaScript must implement. + * The interface provides for the management of properties and for + * performing conversions. + *

    + * Host system implementors may find it easier to extend the ScriptableObject + * class rather than implementing Scriptable when writing host objects. + *

    + * There are many static methods defined in ScriptableObject that perform + * the multiple calls to the Scriptable interface needed in order to + * manipulate properties in prototype chains. + *

    + * + * @see org.mozilla.javascript.ScriptableObject + * @author Norris Boyd + * @author Nick Thompson + * @author Brendan Eich + */ + +public interface Scriptable { + + /** + * Get the name of the set of objects implemented by this Java class. + * This corresponds to the [[Class]] operation in ECMA and is used + * by Object.prototype.toString() in ECMA.

    + * See ECMA 8.6.2 and 15.2.4.2. + */ + public String getClassName(); + + /** + * Get a named property from the object. + * + * Looks property up in this object and returns the associated value + * if found. Returns NOT_FOUND if not found. + * Note that this method is not expected to traverse the prototype + * chain. This is different from the ECMA [[Get]] operation. + * + * Depending on the property selector, the runtime will call + * this method or the form of get that takes an + * integer: + * + * + * + * + * + * + * + * + * + * + *
    JavaScript codeJava code
    a.b a.get("b", a)
    a["foo"] a.get("foo", a)
    a[3] a.get(3, a)
    a["3"] a.get(3, a)
    a[3.0] a.get(3, a)
    a["3.0"] a.get("3.0", a)
    a[1.1] a.get("1.1", a)
    a[-4] a.get(-4, a)
    + *

    + * The values that may be returned are limited to the following: + *

      + *
    • java.lang.Boolean objects
    • + *
    • java.lang.String objects
    • + *
    • java.lang.Number objects
    • + *
    • org.mozilla.javascript.Scriptable objects
    • + *
    • null
    • + *
    • The value returned by Context.getUndefinedValue()
    • + *
    • NOT_FOUND
    • + *
    + * @param name the name of the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + * @see org.mozilla.javascript.Context#getUndefinedValue + */ + public Object get(String name, Scriptable start); + + /** + * Get a property from the object selected by an integral index. + * + * Identical to get(String, Scriptable) except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return the value of the property (may be null), or NOT_FOUND + * @see org.mozilla.javascript.Scriptable#get(String,Scriptable) + */ + public Object get(int index, Scriptable start); + + /** + * Indicates whether or not a named property is defined in an object. + * + * Does not traverse the prototype chain.

    + * + * The property is specified by a String name + * as defined for the get method.

    + * + * @param name the name of the property + * @param start the object in which the lookup began + * @return true if and only if the named property is found in the object + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, String) + */ + public boolean has(String name, Scriptable start); + + /** + * Indicates whether or not an indexed property is defined in an object. + * + * Does not traverse the prototype chain.

    + * + * The property is specified by an integral index + * as defined for the get method.

    + * + * @param index the numeric index for the property + * @param start the object in which the lookup began + * @return true if and only if the indexed property is found in the object + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#getProperty(Scriptable, int) + */ + public boolean has(int index, Scriptable start); + + /** + * Sets a named property in this object. + *

    + * The property is specified by a string name + * as defined for get. + *

    + * The possible values that may be passed in are as defined for + * get. A class that implements this method may choose + * to ignore calls to set certain properties, in which case those + * properties are effectively read-only.

    + * For properties defined in a prototype chain, + * use putProperty in ScriptableObject.

    + * Note that if a property a is defined in the prototype p + * of an object o, then evaluating o.a = 23 will cause + * set to be called on the prototype p with + * o as the start parameter. + * To preserve JavaScript semantics, it is the Scriptable + * object's responsibility to modify o.

    + * This design allows properties to be defined in prototypes and implemented + * in terms of getters and setters of Java values without consuming slots + * in each instance. + *

    + * The values that may be set are limited to the following: + *

      + *
    • java.lang.Boolean objects
    • + *
    • java.lang.String objects
    • + *
    • java.lang.Number objects
    • + *
    • org.mozilla.javascript.Scriptable objects
    • + *
    • null
    • + *
    • The value returned by Context.getUndefinedValue()
    • + *

    + * Arbitrary Java objects may be wrapped in a Scriptable by first calling + * Context.toObject. This allows the property of a JavaScript + * object to contain an arbitrary Java object as a value.

    + * Note that has will be called by the runtime first before + * set is called to determine in which object the + * property is defined. + * Note that this method is not expected to traverse the prototype chain, + * which is different from the ECMA [[Put]] operation. + * @param name the name of the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(String, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, String, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void put(String name, Scriptable start, Object value); + + /** + * Sets an indexed property in this object. + *

    + * The property is specified by an integral index + * as defined for get.

    + * + * Identical to put(String, Scriptable, Object) except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @param start the object whose property is being set + * @param value value to set the property to + * @see org.mozilla.javascript.Scriptable#has(int, Scriptable) + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#putProperty(Scriptable, int, Object) + * @see org.mozilla.javascript.Context#toObject(Object, Scriptable) + */ + public void put(int index, Scriptable start, Object value); + + /** + * Removes a property from this object. + * This operation corresponds to the ECMA [[Delete]] except that + * the no result is returned. The runtime will guarantee that this + * method is called only if the property exists. After this method + * is called, the runtime will call Scriptable.has to see if the + * property has been removed in order to determine the boolean + * result of the delete operator as defined by ECMA 11.4.1. + *

    + * A property can be made permanent by ignoring calls to remove + * it.

    + * The property is specified by a String name + * as defined for get. + *

    + * To delete properties defined in a prototype chain, + * see deleteProperty in ScriptableObject. + * @param name the identifier for the property + * @see org.mozilla.javascript.Scriptable#get(String, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, String) + */ + public void delete(String name); + + /** + * Removes a property from this object. + * + * The property is specified by an integral index + * as defined for get. + *

    + * To delete properties defined in a prototype chain, + * see deleteProperty in ScriptableObject. + * + * Identical to delete(String) except that + * an integral index is used to select the property. + * + * @param index the numeric index for the property + * @see org.mozilla.javascript.Scriptable#get(int, Scriptable) + * @see org.mozilla.javascript.ScriptableObject#deleteProperty(Scriptable, int) + */ + public void delete(int index); + + /** + * Get the prototype of the object. + * @return the prototype + */ + public Scriptable getPrototype(); + + /** + * Set the prototype of the object. + * @param prototype the prototype to set + */ + public void setPrototype(Scriptable prototype); + + /** + * Get the parent scope of the object. + * @return the parent scope + */ + public Scriptable getParentScope(); + + /** + * Set the parent scope of the object. + * @param parent the parent scope to set + */ + public void setParentScope(Scriptable parent); + + /** + * Get an array of property ids. + * + * Not all property ids need be returned. Those properties + * whose ids are not returned are considered non-enumerable. + * + * @return an array of Objects. Each entry in the array is either + * a java.lang.String or a java.lang.Number + */ + public Object[] getIds(); + + /** + * Get the default value of the object with a given hint. + * The hints are String.class for type String, Number.class for type + * Number, Scriptable.class for type Object, and Boolean.class for + * type Boolean.

    + * + * A hint of null means "no hint". + * + * See ECMA 8.6.2.6. + * + * @param hint the type hint + * @return the default value + */ + public Object getDefaultValue(Class hint); + + /** + * The instanceof operator. + * + *

    + * The JavaScript code "lhs instanceof rhs" causes rhs.hasInstance(lhs) to + * be called. + * + *

    + * The return value is implementation dependent so that embedded host objects can + * return an appropriate value. See the JS 1.3 language documentation for more + * detail. + * + *

    This operator corresponds to the proposed EMCA [[HasInstance]] operator. + * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * + * @return an implementation dependent value + */ + public boolean hasInstance(Scriptable instance); +} + diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ScriptableObject.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ScriptableObject.java new file mode 100644 index 00000000000..298c4fc7fb0 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/ScriptableObject.java @@ -0,0 +1,27 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// API class + +package org.mozilla.javascript; + +/** + * This is the default implementation of the Scriptable interface. This + * class provides convenient default behavior that makes it easier to + * define host objects. + *

    + * Various properties and methods of JavaScript objects can be conveniently + * defined using methods of ScriptableObject. + *

    + * Classes extending ScriptableObject must define the getClassName method. + * + * @see org.mozilla.javascript.Scriptable + * @author Norris Boyd + */ + +public abstract class ScriptableObject implements Scriptable +{ +} diff --git a/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/optimizer/ClassCompiler.java b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/optimizer/ClassCompiler.java new file mode 100644 index 00000000000..cb2332d3f61 --- /dev/null +++ b/java/ql/test/experimental/stubs/rhino-1.7.13/org/mozilla/javascript/optimizer/ClassCompiler.java @@ -0,0 +1,112 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + package org.mozilla.javascript.optimizer; + + import org.mozilla.javascript.CompilerEnvirons; + + /** + * Generates class files from script sources. + * + * since 1.5 Release 5 + * @author Igor Bukanov + */ + + public class ClassCompiler + { + /** + * Construct ClassCompiler that uses the specified compiler environment + * when generating classes. + */ + public ClassCompiler(CompilerEnvirons compilerEnv) + { + } + + /** + * Set the class name to use for main method implementation. + * The class must have a method matching + * public static void main(Script sc, String[] args), it will be + * called when main(String[] args) is called in the generated + * class. The class name should be fully qulified name and include the + * package name like in org.foo.Bar. + */ + public void setMainMethodClass(String className) + { + } + + /** + * Get the name of the class for main method implementation. + * @see #setMainMethodClass(String) + */ + public String getMainMethodClass() + { + return null; + } + + /** + * Get the compiler environment the compiler uses. + */ + public CompilerEnvirons getCompilerEnv() + { + return null; + } + + /** + * Get the class that the generated target will extend. + */ + public Class getTargetExtends() + { + return null; + } + + /** + * Set the class that the generated target will extend. + * + * @param extendsClass the class it extends + */ + public void setTargetExtends(Class extendsClass) + { + } + + /** + * Get the interfaces that the generated target will implement. + */ + public Class[] getTargetImplements() + { + return null; + } + + /** + * Set the interfaces that the generated target will implement. + * + * @param implementsClasses an array of Class objects, one for each + * interface the target will extend + */ + public void setTargetImplements(Class[] implementsClasses) + { + } + + /** + * Compile JavaScript source into one or more Java class files. + * The first compiled class will have name mainClassName. + * If the results of {@link #getTargetExtends()} or + * {@link #getTargetImplements()} are not null, then the first compiled + * class will extend the specified super class and implement + * specified interfaces. + * + * @return array where elements with even indexes specifies class name + * and the following odd index gives class file body as byte[] + * array. The initial element of the array always holds + * mainClassName and array[1] holds its byte code. + */ + public Object[] compileToClassFiles(String source, + String sourceLocation, + int lineno, + String mainClassName) + { + return null; + } + } \ No newline at end of file diff --git a/java/ql/test/library-tests/dataflow/taint-jackson/Test.java b/java/ql/test/library-tests/dataflow/taint-jackson/Test.java index 2caf9e4ee80..3be85336e26 100644 --- a/java/ql/test/library-tests/dataflow/taint-jackson/Test.java +++ b/java/ql/test/library-tests/dataflow/taint-jackson/Test.java @@ -3,33 +3,53 @@ import java.io.FileOutputStream; import java.io.OutputStream; import java.io.StringWriter; import java.io.Writer; +import java.util.Iterator; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.ObjectReader; class Test { + public static class Potato { + private String name; + + private String getName() { + return name; + } + } + public static String taint() { return "tainted"; } + public static void sink(Object any) {} + public static void jacksonObjectMapper() throws java.io.FileNotFoundException, java.io.UnsupportedEncodingException { String s = taint(); ObjectMapper om = new ObjectMapper(); File file = new File("testFile"); om.writeValue(file, s); + sink(file); //$hasTaintFlow OutputStream out = new FileOutputStream(file); om.writeValue(out, s); + sink(file); //$hasTaintFlow Writer writer = new StringWriter(); om.writeValue(writer, s); + sink(writer); //$hasTaintFlow JsonGenerator generator = new JsonFactory().createGenerator(new StringWriter()); om.writeValue(generator, s); + sink(generator); //$hasTaintFlow String t = om.writeValueAsString(s); - System.out.println(t); + sink(t); //$hasTaintFlow byte[] bs = om.writeValueAsBytes(s); String reconstructed = new String(bs, "utf-8"); - System.out.println(reconstructed); + sink(bs); //$hasTaintFlow + sink(reconstructed); //$hasTaintFlow } public static void jacksonObjectWriter() throws java.io.FileNotFoundException, java.io.UnsupportedEncodingException { @@ -37,16 +57,56 @@ class Test { ObjectWriter ow = new ObjectWriter(); File file = new File("testFile"); ow.writeValue(file, s); + sink(file); //$hasTaintFlow OutputStream out = new FileOutputStream(file); ow.writeValue(out, s); + sink(out); //$hasTaintFlow Writer writer = new StringWriter(); ow.writeValue(writer, s); + sink(writer); //$hasTaintFlow JsonGenerator generator = new JsonFactory().createGenerator(new StringWriter()); ow.writeValue(generator, s); + sink(generator); //$hasTaintFlow String t = ow.writeValueAsString(s); - System.out.println(t); + sink(t); //$hasTaintFlow byte[] bs = ow.writeValueAsBytes(s); String reconstructed = new String(bs, "utf-8"); - System.out.println(reconstructed); + sink(bs); //$hasTaintFlow + sink(reconstructed); //$hasTaintFlow + } + + public static void jacksonObjectReader() throws java.io.IOException { + String s = taint(); + ObjectMapper om = new ObjectMapper(); + ObjectReader reader = om.readerFor(Potato.class); + sink(reader.readValue(s)); //$hasTaintFlow + sink(reader.readValue(s, Potato.class).name); //$hasTaintFlow + sink(reader.readValue(s, Potato.class).getName()); //$hasTaintFlow + } + + public static void jacksonObjectReaderIterable() throws java.io.IOException { + String s = taint(); + ObjectMapper om = new ObjectMapper(); + ObjectReader reader = om.readerFor(Potato.class); + sink(reader.readValues(s)); //$hasTaintFlow + Iterator pIterator = reader.readValues(s, Potato.class); + while(pIterator.hasNext()) { + Potato p = pIterator.next(); + sink(p); //$hasTaintFlow + sink(p.name); //$hasTaintFlow + sink(p.getName()); //$hasTaintFlow + } + } + + public static void jacksonTwoStepDeserialization() throws java.io.IOException { + String s = taint(); + Map taintedParams = new HashMap<>(); + taintedParams.put("name", s); + ObjectMapper om = new ObjectMapper(); + JsonNode jn = om.valueToTree(taintedParams); + sink(jn); //$hasTaintFlow + Potato p = om.convertValue(jn, Potato.class); + sink(p); //$hasTaintFlow + sink(p.getName()); //$hasTaintFlow } } diff --git a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected index 122b21d50fe..e69de29bb2d 100644 --- a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected +++ b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.expected @@ -1,48 +0,0 @@ -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:10:43:10:54 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:13:73:13:84 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:16:44:16:55 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:19:36:19:47 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:22:35:22:46 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java:26:36:26:47 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:10:43:10:54 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:13:73:13:84 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:16:44:16:55 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:19:36:19:47 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:22:35:22:46 | value | -| ../../../stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectWriter.java:26:36:26:47 | value | -| Test.java:18:14:18:20 | taint(...) | -| Test.java:21:17:21:20 | file [post update] | -| Test.java:21:23:21:23 | s | -| Test.java:22:43:22:46 | file | -| Test.java:23:17:23:19 | out [post update] | -| Test.java:23:22:23:22 | s | -| Test.java:25:17:25:22 | writer [post update] | -| Test.java:25:25:25:25 | s | -| Test.java:27:17:27:25 | generator [post update] | -| Test.java:27:28:27:28 | s | -| Test.java:28:14:28:37 | writeValueAsString(...) | -| Test.java:28:36:28:36 | s | -| Test.java:29:22:29:22 | t | -| Test.java:30:15:30:37 | writeValueAsBytes(...) | -| Test.java:30:36:30:36 | s | -| Test.java:31:26:31:48 | new String(...) | -| Test.java:31:37:31:38 | bs | -| Test.java:32:22:32:34 | reconstructed | -| Test.java:36:14:36:20 | taint(...) | -| Test.java:39:17:39:20 | file [post update] | -| Test.java:39:23:39:23 | s | -| Test.java:40:43:40:46 | file | -| Test.java:41:17:41:19 | out [post update] | -| Test.java:41:22:41:22 | s | -| Test.java:43:17:43:22 | writer [post update] | -| Test.java:43:25:43:25 | s | -| Test.java:45:17:45:25 | generator [post update] | -| Test.java:45:28:45:28 | s | -| Test.java:46:14:46:37 | writeValueAsString(...) | -| Test.java:46:36:46:36 | s | -| Test.java:47:22:47:22 | t | -| Test.java:48:15:48:37 | writeValueAsBytes(...) | -| Test.java:48:36:48:36 | s | -| Test.java:49:26:49:48 | new String(...) | -| Test.java:49:37:49:38 | bs | -| Test.java:50:22:50:34 | reconstructed | diff --git a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql index 333cf485f07..0836906530b 100644 --- a/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql +++ b/java/ql/test/library-tests/dataflow/taint-jackson/dataFlow.ql @@ -1,17 +1,34 @@ +import java import semmle.code.java.dataflow.DataFlow import semmle.code.java.dataflow.TaintTracking import semmle.code.java.dataflow.FlowSources +import TestUtilities.InlineExpectationsTest class Conf extends TaintTracking::Configuration { Conf() { this = "qltest:dataflow:jackson" } - override predicate isSource(DataFlow::Node source) { - source.asExpr().(MethodAccess).getMethod().hasName("taint") + override predicate isSource(DataFlow::Node n) { + n.asExpr().(MethodAccess).getMethod().hasName("taint") + or + n instanceof RemoteFlowSource } - override predicate isSink(DataFlow::Node sink) { any() } + override predicate isSink(DataFlow::Node n) { + exists(MethodAccess ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument()) + } } -from DataFlow::Node source, DataFlow::Node sink, Conf config -where config.hasFlow(source, sink) -select sink +class HasFlowTest extends InlineExpectationsTest { + HasFlowTest() { this = "HasFlowTest" } + + override string getARelevantTag() { result = "hasTaintFlow" } + + override predicate hasActualResult(Location location, string element, string tag, string value) { + tag = "hasTaintFlow" and + exists(DataFlow::Node src, DataFlow::Node sink, Conf conf | conf.hasFlow(src, sink) | + sink.getLocation() = location and + element = sink.toString() and + value = "" + ) + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java index a26eb2592c6..b04572cd4da 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/JsonNode.java @@ -1,6 +1,8 @@ package com.fasterxml.jackson.databind; -public class JsonNode { +import java.util.*; + +public abstract class JsonNode implements Iterable { public JsonNode() { } -} \ No newline at end of file +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java new file mode 100644 index 00000000000..ac427ef01c9 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/MappingIterator.java @@ -0,0 +1,28 @@ +package com.fasterxml.jackson.databind; + +import java.io.Closeable; +import java.io.IOException; +import java.util.*; + +public class MappingIterator implements Iterator, Closeable { + + @Override + public boolean hasNext() { + return false; + } + + @Override + public T next() { + return null; + } + + @Override + public void remove() { + + } + + @Override + public void close() throws IOException { + + } +} diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java index 455e0c0d309..71dc99a351d 100644 --- a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectMapper.java @@ -26,4 +26,16 @@ public class ObjectMapper { public String writeValueAsString(Object value) { return null; } + + public ObjectReader readerFor(Class type) { + return null; + } + + public T valueToTree(Object fromValue) throws IllegalArgumentException { + return null; + } + + public T convertValue(Object fromValue, Class toValueType) throws IllegalArgumentException { + return null; + } } diff --git a/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectReader.java b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectReader.java new file mode 100644 index 00000000000..f067a3e95a4 --- /dev/null +++ b/java/ql/test/stubs/jackson-databind-2.10/com/fasterxml/jackson/databind/ObjectReader.java @@ -0,0 +1,82 @@ +package com.fasterxml.jackson.databind; + +import java.io.*; + +public class ObjectReader { + public ObjectReader forType(Class valueType) { + return null; + } + + public T readValue(String src) { + return null; + } + + public T readValue(String src, Class valueType) throws IOException { + return null; + } + + public T readValue(byte[] content) throws IOException { + return null; + } + + public T readValue(byte[] content, Class valueType) throws IOException { + return null; + } + + public T readValue(File src) throws IOException { + return null; + } + + public T readValue(InputStream src) throws IOException { + return null; + } + + public T readValue(InputStream src, Class valueType) throws IOException { + return null; + } + + public T readValue(Reader src) throws IOException { + return null; + } + + public T readValue(Reader src, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(String src) { + return null; + } + + public MappingIterator readValues(String src, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(byte[] content) throws IOException { + return null; + } + + public MappingIterator readValues(byte[] content, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(File src) throws IOException { + return null; + } + + public MappingIterator readValues(InputStream src) throws IOException { + return null; + } + + public MappingIterator readValues(InputStream src, Class valueType) throws IOException { + return null; + } + + public MappingIterator readValues(Reader src) throws IOException { + return null; + } + + public MappingIterator readValues(Reader src, Class valueType) throws IOException { + return null; + } + +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/antlr/base/mod.java b/java/ql/test/stubs/jython-2.7.2/org/python/antlr/base/mod.java new file mode 100644 index 00000000000..785212f62fa --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/antlr/base/mod.java @@ -0,0 +1,5 @@ +// Autogenerated AST node +package org.python.antlr.base; + +public abstract class mod { +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/BytecodeLoader.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/BytecodeLoader.java new file mode 100644 index 00000000000..e414216ed03 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/BytecodeLoader.java @@ -0,0 +1,47 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.util.List; + +/** + * Utility class for loading compiled Python modules and Java classes defined in Python modules. + */ +public class BytecodeLoader { + + /** + * Turn the Java class file data into a Java class. + * + * @param name fully-qualified binary name of the class + * @param data a class file as a byte array + * @param referents super-classes and interfaces that the new class will reference. + */ + @SuppressWarnings("unchecked") + public static Class makeClass(String name, byte[] data, Class... referents) { + return null; + } + + /** + * Turn the Java class file data into a Java class. + * + * @param name the name of the class + * @param referents super-classes and interfaces that the new class will reference. + * @param data a class file as a byte array + */ + public static Class makeClass(String name, List> referents, byte[] data) { + return null; + } + + /** + * Turn the Java class file data for a compiled Python module into a {@code PyCode} object, by + * constructing an instance of the named class and calling the instance's + * {@link PyRunnable#getMain()}. + * + * @param name fully-qualified binary name of the class + * @param data a class file as a byte array + * @param filename to provide to the constructor of the named class + * @return the {@code PyCode} object produced by the named class' {@code getMain} + */ + public static PyCode makeCode(String name, byte[] data, String filename) { + return null; + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/CompileMode.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompileMode.java new file mode 100644 index 00000000000..cf7ad1e7201 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompileMode.java @@ -0,0 +1,11 @@ +package org.python.core; + +public enum CompileMode { + eval, + single, + exec; + + public static CompileMode getMode(String mode) { + return null; + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/CompilerFlags.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompilerFlags.java new file mode 100644 index 00000000000..916b93c84ea --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/CompilerFlags.java @@ -0,0 +1,17 @@ +// At some future point this will also be extended - in conjunction with +// Py#compileFlags - to add +// support for a compiler factory that user code can choose in place of the +// normal compiler. +// (Perhaps a better name might have been "CompilerOptions".) + +package org.python.core; + +import java.io.Serializable; + +public class CompilerFlags implements Serializable { + public CompilerFlags() { + } + + public CompilerFlags(int co_flags) { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/Py.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/Py.java new file mode 100644 index 00000000000..cc0c9f1e4bd --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/Py.java @@ -0,0 +1,134 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.io.InputStream; +import java.io.Serializable; + +import org.python.antlr.base.mod; + +public final class Py { + /** + Convert a given PyObject to an instance of a Java class. + Identical to o.__tojava__(c) except that it will + raise a TypeError if the conversion fails. + @param o the PyObject to convert. + @param c the class to convert it to. + **/ + @SuppressWarnings("unchecked") + public static T tojava(PyObject o, Class c) { + return null; + } + + // ??pending: was @deprecated but is actually used by proxie code. + // Can get rid of it? + public static Object tojava(PyObject o, String s) { + return null; + } + + /** + * Uses the PyObjectAdapter passed to {@link PySystemState#initialize} to turn o into a PyObject. + * + * @see ClassicPyObjectAdapter - default PyObjectAdapter type + */ + public static PyObject java2py(Object o) { + return null; + } + + /** + * Uses the PyObjectAdapter passed to {@link PySystemState#initialize} to turn + * objects into an array of PyObjects. + * + * @see ClassicPyObjectAdapter - default PyObjectAdapter type + */ + public static PyObject[] javas2pys(Object... objects) { + return null; + } + + public static PyObject makeClass(String name, PyObject[] bases, PyCode code, + PyObject[] closure_cells) { + return null; + } + + public static PyObject makeClass(String name, PyObject base, PyObject dict) { + return null; + } + + /** + * Create a new Python class. + * + * @param name the String name of the class + * @param bases an array of PyObject base classes + * @param dict the class's namespace, containing the class body + * definition + * @return a new Python Class PyObject + */ + public static PyObject makeClass(String name, PyObject[] bases, PyObject dict) { + return null; + } + + public static CompilerFlags getCompilerFlags() { + return null; + } + + public static CompilerFlags getCompilerFlags(int flags, boolean dont_inherit) { + return null; + } + + public static CompilerFlags getCompilerFlags(CompilerFlags flags, boolean dont_inherit) { + return null; + } + + // w/o compiler-flags + public static PyCode compile(InputStream istream, String filename, CompileMode kind) { + return null; + } + + /** + * Entry point for compiling modules. + * + * @param node Module node, coming from the parsing process + * @param name Internal name for the compiled code. Typically generated by + * calling {@link #getName()}. + * @param filename Source file name + * @param linenumbers True to track source line numbers on the generated + * code + * @param printResults True to call the sys.displayhook on the result of + * the code + * @param cflags Compiler flags + * @return Code object for the compiled module + */ + public static PyCode compile_flags(mod node, String name, String filename, + boolean linenumbers, boolean printResults, + CompilerFlags cflags) { + return null; + } + + public static PyCode compile_flags(mod node, String filename, + CompileMode kind, CompilerFlags cflags) { + return null; + } + + /** + * Compiles python source code coming from a file or another external stream + */ + public static PyCode compile_flags(InputStream istream, String filename, + CompileMode kind, CompilerFlags cflags) { + return null; + } + + /** + * Compiles python source code coming from String (raw bytes) data. + * + * If the String is properly decoded (from PyUnicode) the PyCF_SOURCE_IS_UTF8 flag + * should be specified. + */ + public static PyCode compile_flags(String data, String filename, + CompileMode kind, CompilerFlags cflags) { + return null; + } + + public static PyObject compile_command_flags(String string, String filename, + CompileMode kind, CompilerFlags cflags, boolean stdprompt) { + return null; + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PyCode.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyCode.java new file mode 100644 index 00000000000..9b7c99f94fa --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyCode.java @@ -0,0 +1,43 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +/** + * A super class for all python code implementations. + */ +public abstract class PyCode extends PyObject +{ + abstract public PyObject call(ThreadState state, + PyObject args[], String keywords[], + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject self, PyObject args[], + String keywords[], + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject globals, + PyObject[] defaults, PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject arg2, + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject arg2, PyObject arg3, + PyObject globals, PyObject[] defaults, + PyObject closure); + + abstract public PyObject call(ThreadState state, + PyObject arg1, PyObject arg2, PyObject arg3, PyObject arg4, + PyObject globals, PyObject[] defaults, + PyObject closure); + +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PyException.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyException.java new file mode 100644 index 00000000000..3a0a6c52c69 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyException.java @@ -0,0 +1,12 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; +import java.io.*; + +/** + * A wrapper for all python exception. Note that the well-known python exceptions are not + * subclasses of PyException. Instead the python exception class is stored in the type + * field and value or class instance is stored in the value field. + */ +public class PyException extends RuntimeException +{ +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PyObject.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyObject.java new file mode 100644 index 00000000000..00993123461 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PyObject.java @@ -0,0 +1,11 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.io.Serializable; + +/** + * All objects known to the Jython runtime system are represented by an instance of the class + * {@code PyObject} or one of its subclasses. + */ +public class PyObject implements Serializable { +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/PySystemState.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/PySystemState.java new file mode 100644 index 00000000000..8444bbba70e --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/PySystemState.java @@ -0,0 +1,177 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +import java.io.File; +import java.io.IOException; +import java.util.Properties; + +/** + * The "sys" module. + */ +// xxx Many have lamented, this should really be a module! +// but it will require some refactoring to see this wish come true. +public class PySystemState extends PyObject { + public PySystemState() { + } + + public static void classDictInit(PyObject dict) { + } + + public ClassLoader getSyspathJavaLoader() { + return null; + } + + // xxx fix this accessors + public PyObject __findattr_ex__(String name) { + return null; + } + + public void __setattr__(String name, PyObject value) { + } + + public void __delattr__(String name) { + } + + public PyObject gettrace() { + return null; + } + + public void settrace(PyObject tracefunc) { + } + + /** + * Change the current working directory to the specified path. + * + * path is assumed to be absolute and canonical (via os.path.realpath). + * + * @param path a path String + */ + public void setCurrentWorkingDir(String path) { + } + + /** + * Return a string representing the current working directory. + * + * @return a path String + */ + public String getCurrentWorkingDir() { + return null; + } + + /** + * Resolve a path. Returns the full path taking the current working directory into account. + * + * @param path a path String + * @return a resolved path String + */ + public String getPath(String path) { + return null; + } + + /** + * Resolve a path, returning a {@link File}, taking the current working directory into account. + * + * @param path a path String + * @return a resolved File + */ + public File getFile(String path) { + return null; + } + + public ClassLoader getClassLoader() { + return null; + } + + public void setClassLoader(ClassLoader classLoader) { + } + + public static Properties getBaseProperties() { + return null; + } + + public static synchronized void initialize() { + } + + public static synchronized void initialize(Properties preProperties, + Properties postProperties) { + } + + public static synchronized void initialize(Properties preProperties, Properties postProperties, + String[] argv) { + } + + public static synchronized void initialize(Properties preProperties, Properties postProperties, + String[] argv, ClassLoader classLoader) { + } + + /** + * Add a classpath directory to the list of places that are searched for java packages. + *

    + * Note. Classes found in directory and sub-directory are not made available to jython by + * this call. It only makes the java package found in the directory available. This call is + * mostly useful if jython is embedded in an application that deals with its own class loaders. + * A servlet container is a very good example. Calling + * {@code add_classdir("/WEB-INF/classes")} makes the java packages in WEB-INF classes + * available to jython import. However the actual class loading is completely handled by the + * servlet container's context classloader. + */ + public static void add_classdir(String directoryPath) { + } + + /** + * Add a .jar and .zip directory to the list of places that are searched for java .jar and .zip + * files. The .jar and .zip files found will not be cached. + *

    + * Note. Classes in .jar and .zip files found in the directory are not made available to + * jython by this call. See the note for add_classdir(dir) for more details. + * + * @param directoryPath The name of a directory. + * + * @see #add_classdir + */ + public static void add_extdir(String directoryPath) { + } + + /** + * Add a .jar and .zip directory to the list of places that are searched for java .jar and .zip + * files. + *

    + * Note. Classes in .jar and .zip files found in the directory are not made available to + * jython by this call. See the note for add_classdir(dir) for more details. + * + * @param directoryPath The name of a directory. + * @param cache Controls if the packages in the zip and jar file should be cached. + * + * @see #add_classdir + */ + public static void add_extdir(String directoryPath, boolean cache) { + } + + // Not public by design. We can't rebind the displayhook if + // a reflected function is inserted in the class dict. + + /** + * Exit a Python program with the given status. + * + * @param status the value to exit with + * @throws PyException {@code SystemExit} always throws this exception. When caught at top level + * the program will exit. + */ + public static void exit(PyObject status) { + } + + /** + * Exit a Python program with the status 0. + */ + public static void exit() { + } + + public static void exc_clear() { + } + + public void cleanup() { + } + + public void close() { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/core/ThreadState.java b/java/ql/test/stubs/jython-2.7.2/org/python/core/ThreadState.java new file mode 100644 index 00000000000..920270fe053 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/core/ThreadState.java @@ -0,0 +1,28 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.core; + +// a ThreadState refers to one PySystemState; this weak ref allows for tracking all ThreadState objects +// that refer to a given PySystemState + +public class ThreadState { + + public PyException exception; + + public ThreadState(PySystemState systemState) { + setSystemState(systemState); + } + + public void setSystemState(PySystemState systemState) { + } + + public PySystemState getSystemState() { + return null; + } + + public boolean enterRepr(PyObject obj) { + return false; + } + + public void exitRepr(PyObject obj) { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/util/InteractiveInterpreter.java b/java/ql/test/stubs/jython-2.7.2/org/python/util/InteractiveInterpreter.java new file mode 100644 index 00000000000..b12fa617227 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/util/InteractiveInterpreter.java @@ -0,0 +1,114 @@ +// Copyright (c) Corporation for National Research Initiatives +package org.python.util; + +import org.python.core.*; + +/** + * This class provides the interface for compiling and running code that supports an interactive + * interpreter. + */ +// Based on CPython-1.5.2's code module +public class InteractiveInterpreter extends PythonInterpreter { + + /** + * Construct an InteractiveInterpreter with all default characteristics: default state (from + * {@link Py#getSystemState()}), and a new empty dictionary of local variables. + * */ + public InteractiveInterpreter() { + } + + /** + * Construct an InteractiveInterpreter with state (from {@link Py#getSystemState()}), and the + * specified dictionary of local variables. + * + * @param locals dictionary to use, or if null, a new empty one will be created + */ + public InteractiveInterpreter(PyObject locals) { + } + + /** + * Construct an InteractiveInterpreter with, and system state the specified dictionary of local + * variables. + * + * @param locals dictionary to use, or if null, a new empty one will be created + * @param systemState interpreter state, or if null use {@link Py#getSystemState()} + */ + public InteractiveInterpreter(PyObject locals, PySystemState systemState) { + } + + /** + * Compile and run some source in the interpreter, in the mode {@link CompileMode#single} which + * is used for incremental compilation at the interactive console, known as {@code }. + * + * @param source Python code + * @return true to indicate a partial statement was entered + */ + public boolean runsource(String source) { + return false; + } + + /** + * Compile and run some source in the interpreter, in the mode {@link CompileMode#single} which + * is used for incremental compilation at the interactive console. + * + * @param source Python code + * @param filename name with which to label this console input (e.g. in error messages). + * @return true to indicate a partial statement was entered + */ + public boolean runsource(String source, String filename) { + return false; + } + + /** + * Compile and run some source in the interpreter, according to the {@link CompileMode} given. + * This method supports incremental compilation and interpretation through the return value, + * where {@code true} signifies that more input is expected in order to complete the Python + * statement. An interpreter can use this to decide whether to use {@code sys.ps1} + * ("{@code >>> }") or {@code sys.ps2} ("{@code ... }") to prompt the next line. The arguments + * are the same as the mandatory ones in the Python {@code compile()} command. + *

    + * One the following can happen: + *

      + *
    1. The input is incorrect; compilation raised an exception (SyntaxError or OverflowError). A + * syntax traceback will be printed by calling {@link #showexception(PyException)}. Return is + * {@code false}.
    2. + * + *
    3. The input is incomplete, and more input is required; compilation returned no code. + * Nothing happens. Return is {@code true}.
    4. + * + *
    5. The input is complete; compilation returned a code object. The code is executed by + * calling {@link #runcode(PyObject)} (which also handles run-time exceptions, except for + * SystemExit). Return is {@code false}.
    6. + *
    + * + * @param source Python code + * @param filename name with which to label this console input (e.g. in error messages). + * @param kind of compilation required: {@link CompileMode#eval}, {@link CompileMode#exec} or + * {@link CompileMode#single} + * @return {@code true} to indicate a partial statement was provided + */ + public boolean runsource(String source, String filename, CompileMode kind) { + return false; + } + + /** + * Execute a code object. When an exception occurs, {@link #showexception(PyException)} is + * called to display a stack trace, except in the case of SystemExit, which is re-raised. + *

    + * A note about KeyboardInterrupt: this exception may occur elsewhere in this code, and may not + * always be caught. The caller should be prepared to deal with it. + **/ + + // Make this run in another thread somehow???? + public void runcode(PyObject code) { + } + + public void showexception(PyException exc) { + } + + public void write(String data) { + } + + public void resetbuffer() { + } +} diff --git a/java/ql/test/stubs/jython-2.7.2/org/python/util/PythonInterpreter.java b/java/ql/test/stubs/jython-2.7.2/org/python/util/PythonInterpreter.java new file mode 100644 index 00000000000..92c50917b59 --- /dev/null +++ b/java/ql/test/stubs/jython-2.7.2/org/python/util/PythonInterpreter.java @@ -0,0 +1,252 @@ +package org.python.util; + +import java.io.Closeable; +import java.io.Reader; +import java.io.StringReader; +import java.util.Properties; + +import org.python.core.PyCode; +import org.python.core.PyObject; + +/** + * The PythonInterpreter class is a standard wrapper for a Jython interpreter for embedding in a + * Java application. + */ +public class PythonInterpreter implements Closeable { + + /** + * Initializes the Jython runtime. This should only be called once, before any other Python + * objects (including PythonInterpreter) are created. + * + * @param preProperties A set of properties. Typically System.getProperties() is used. + * preProperties override properties from the registry file. + * @param postProperties Another set of properties. Values like python.home, python.path and all + * other values from the registry files can be added to this property set. + * postProperties override system properties and registry properties. + * @param argv Command line arguments, assigned to sys.argv. + */ + public static void + initialize(Properties preProperties, Properties postProperties, String[] argv) { + } + + /** + * Creates a new interpreter with an empty local namespace. + */ + public PythonInterpreter() { + } + + /** + * Creates a new interpreter with the ability to maintain a separate local namespace for each + * thread (set by invoking setLocals()). + * + * @param dict a Python mapping object (e.g., a dictionary) for use as the default namespace + */ + public static PythonInterpreter threadLocalStateInterpreter(PyObject dict) { + return null; + } + + /** + * Creates a new interpreter with a specified local namespace. + * + * @param dict a Python mapping object (e.g., a dictionary) for use as the namespace + */ + public PythonInterpreter(PyObject dict) { + } + + /** + * Sets a Python object to use for the standard output stream, sys.stdout. This + * stream is used in a byte-oriented way (mostly) that depends on the type of file-like object. + * The behaviour as implemented is: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Stream behaviour for various object types
    Python type of object o written
    str/bytesunicodeAny other type
    {@link PyFile}as bytes directlyrespect {@link PyFile#encoding}call str(o) first
    {@link PyFileWriter}each byte value as a charwrite as Java charscall o.toString() first
    Other {@link PyObject} finvoke f.write(str(o))invoke f.write(o)invoke f.write(str(o))
    + * + * @param outStream Python file-like object to use as the output stream + */ + public void setOut(PyObject outStream) { + } + + /** + * Sets a {@link java.io.Writer} to use for the standard output stream, sys.stdout. + * The behaviour as implemented is to output each object o by calling + * o.toString() and writing this as UTF-16. + * + * @param outStream to use as the output stream + */ + public void setOut(java.io.Writer outStream) { + } + + /** + * Sets a {@link java.io.OutputStream} to use for the standard output stream. + * + * @param outStream OutputStream to use as output stream + */ + public void setOut(java.io.OutputStream outStream) { + } + + /** + * Sets a Python object to use for the standard output stream, sys.stderr. This + * stream is used in a byte-oriented way (mostly) that depends on the type of file-like object, + * in the same way as {@link #setOut(PyObject)}. + * + * @param outStream Python file-like object to use as the error output stream + */ + public void setErr(PyObject outStream) { + } + + /** + * Sets a {@link java.io.Writer} to use for the standard output stream, sys.stdout. + * The behaviour as implemented is to output each object o by calling + * o.toString() and writing this as UTF-16. + * + * @param outStream to use as the error output stream + */ + public void setErr(java.io.Writer outStream) { + } + + public void setErr(java.io.OutputStream outStream) { + } + + /** + * Evaluates a string as a Python expression and returns the result. + */ + public PyObject eval(String s) { + return null; + } + + /** + * Evaluates a Python code object and returns the result. + */ + public PyObject eval(PyObject code) { + return null; + } + + /** + * Executes a string of Python source in the local namespace. + * + * In some environments, such as Windows, Unicode characters in the script will be converted + * into ascii question marks (?). This can be avoided by first compiling the fragment using + * PythonInterpreter.compile(), and using the overridden form of this method which takes a + * PyCode object. Code page declarations are not supported. + */ + public void exec(String s) { + } + + /** + * Executes a Python code object in the local namespace. + */ + public void exec(PyObject code) { + } + + /** + * Executes a file of Python source in the local namespace. + */ + public void execfile(String filename) { + } + + public void execfile(java.io.InputStream s) { + } + + public void execfile(java.io.InputStream s, String name) { + } + + /** + * Compiles a string of Python source as either an expression (if possible) or a module. + * + * Designed for use by a JSR 223 implementation: "the Scripting API does not distinguish between + * scripts which return values and those which do not, nor do they make the corresponding + * distinction between evaluating or executing objects." (SCR.4.2.1) + */ + public PyCode compile(String script) { + return null; + } + + public PyCode compile(Reader reader) { + return null; + } + + public PyCode compile(String script, String filename) { + return null; + } + + public PyCode compile(Reader reader, String filename) { + return null; + } + + /** + * Sets a variable in the local namespace. + * + * @param name the name of the variable + * @param value the object to set the variable to (as converted to an appropriate Python object) + */ + public void set(String name, Object value) { + } + + /** + * Sets a variable in the local namespace. + * + * @param name the name of the variable + * @param value the Python object to set the variable to + */ + public void set(String name, PyObject value) { + } + + /** + * Returns the value of a variable in the local namespace. + * + * @param name the name of the variable + * @return the value of the variable, or null if that name isn't assigned + */ + public PyObject get(String name) { + return null; + } + + /** + * Returns the value of a variable in the local namespace. + * + * The value will be returned as an instance of the given Java class. + * interp.get("foo", Object.class) will return the most appropriate generic Java + * object. + * + * @param name the name of the variable + * @param javaclass the class of object to return + * @return the value of the variable as the given class, or null if that name isn't assigned + */ + public T get(String name, Class javaclass) { + return null; + } + + public void cleanup() { + } + + public void close() { + } +} diff --git a/java/ql/test/stubs/scriptengine/javax/script/Bindings.java b/java/ql/test/stubs/scriptengine/javax/script/Bindings.java new file mode 100644 index 00000000000..a8eeeb6fe5e --- /dev/null +++ b/java/ql/test/stubs/scriptengine/javax/script/Bindings.java @@ -0,0 +1,14 @@ +package javax.script; +import java.util.Map; + +public interface Bindings extends Map { + public Object put(String name, Object value); + + public void putAll(Map toMerge); + + public boolean containsKey(Object key); + + public Object get(Object key); + + public Object remove(Object key); +} diff --git a/java/ql/test/stubs/scriptengine/javax/script/Compilable.java b/java/ql/test/stubs/scriptengine/javax/script/Compilable.java new file mode 100644 index 00000000000..ce6700c5a66 --- /dev/null +++ b/java/ql/test/stubs/scriptengine/javax/script/Compilable.java @@ -0,0 +1,9 @@ +package javax.script; + +import java.io.Reader; + +public interface Compilable { + public CompiledScript compile(String script) throws ScriptException; + + public CompiledScript compile(Reader script) throws ScriptException; +} diff --git a/java/ql/test/stubs/scriptengine/javax/script/CompiledScript.java b/java/ql/test/stubs/scriptengine/javax/script/CompiledScript.java new file mode 100644 index 00000000000..2f03d58c9a7 --- /dev/null +++ b/java/ql/test/stubs/scriptengine/javax/script/CompiledScript.java @@ -0,0 +1,17 @@ +package javax.script; + +public abstract class CompiledScript { + + public abstract Object eval(ScriptContext context) throws ScriptException; + + public Object eval(Bindings bindings) throws ScriptException { + return null; + } + + public Object eval() throws ScriptException { + return null; + } + + public abstract ScriptEngine getEngine(); + +} \ No newline at end of file diff --git a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java index 4dc4f8c3186..35b91119e4f 100644 --- a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java +++ b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngine.java @@ -2,5 +2,7 @@ package javax.script; public interface ScriptEngine { Object eval(String var1) throws ScriptException; + + public ScriptEngineFactory getFactory(); } diff --git a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java index 7441c8a4ade..f802d86f80b 100644 --- a/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java +++ b/java/ql/test/stubs/scriptengine/javax/script/ScriptEngineFactory.java @@ -1,6 +1,11 @@ package javax.script; public interface ScriptEngineFactory { + public String getEngineName(); + + public String getMethodCallSyntax(String obj, String m, String... args); + + public String getProgram(String... statements); + ScriptEngine getScriptEngine(); } - diff --git a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/ClassFilter.java b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/ClassFilter.java new file mode 100644 index 00000000000..fcc624fc106 --- /dev/null +++ b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/ClassFilter.java @@ -0,0 +1,5 @@ +package jdk.nashorn.api.scripting; + +public interface ClassFilter { + public boolean exposeToScripts(String className); +} diff --git a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java index 8dc3c1afa10..e89282dfa27 100644 --- a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java +++ b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngine.java @@ -1,10 +1,31 @@ package jdk.nashorn.api.scripting; -import javax.script.*; +import java.io.Reader; -public final class NashornScriptEngine extends AbstractScriptEngine { +import javax.script.AbstractScriptEngine; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; + +public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable { public Object eval(String var1) throws ScriptException { return null; } -} + @Override + public ScriptEngineFactory getFactory() { + return null; + } + + @Override + public CompiledScript compile(final Reader reader) throws ScriptException { + return null; + } + + @Override + public CompiledScript compile(final String str) throws ScriptException { + return null; + } +} diff --git a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java index 763e098ddbe..177bf463eb3 100644 --- a/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java +++ b/java/ql/test/stubs/scriptengine/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java @@ -3,20 +3,48 @@ package jdk.nashorn.api.scripting; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; - public final class NashornScriptEngineFactory implements ScriptEngineFactory { public NashornScriptEngineFactory() { } + @Override + public String getEngineName() { + return null; + } + @Override + public String getMethodCallSyntax(final String obj, final String method, final String... args) { + return null; + } + + @Override + public String getProgram(final String... statements) { + return null; + } + + @Override public ScriptEngine getScriptEngine() { return null; } + public ScriptEngine getScriptEngine(final ClassLoader appLoader) { + return null; + } - public ScriptEngine getScriptEngine(String... args) { + public ScriptEngine getScriptEngine(final ClassFilter classFilter) { + return null; + } + + public ScriptEngine getScriptEngine(final String... args) { + return null; + } + + public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader) { + return null; + } + + public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) { return null; } } - diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/ModelAndView.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/ModelAndView.java new file mode 100644 index 00000000000..53e337d5053 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/ModelAndView.java @@ -0,0 +1,107 @@ +package org.springframework.web.servlet; + +import java.util.Map; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; + +public class ModelAndView { + @Nullable + private Object view; + @Nullable + private HttpStatus status; + private boolean cleared = false; + + public ModelAndView() { + } + + public ModelAndView(String viewName) { + this.view = viewName; + } + + public ModelAndView(View view) { + this.view = view; + } + + public ModelAndView(String viewName, @Nullable Map model) { } + + public ModelAndView(View view, @Nullable Map model) { } + + public ModelAndView(String viewName, HttpStatus status) { } + + public ModelAndView(@Nullable String viewName, @Nullable Map model, @Nullable HttpStatus status) { } + + public ModelAndView(String viewName, String modelName, Object modelObject) { } + + public ModelAndView(View view, String modelName, Object modelObject) { } + + public void setViewName(@Nullable String viewName) { + this.view = viewName; + } + + @Nullable + public String getViewName() { + return ""; + } + + public void setView(@Nullable View view) { } + + @Nullable + public View getView() { + return null; + } + + public boolean hasView() { + return true; + } + + public boolean isReference() { + return true; + } + + @Nullable + protected Map getModelInternal() { + return null; + } + + public Map getModel() { + return null; + } + + public void setStatus(@Nullable HttpStatus status) { } + + @Nullable + public HttpStatus getStatus() { + return this.status; + } + + public ModelAndView addObject(String attributeName, @Nullable Object attributeValue) { + return null; + } + + public ModelAndView addObject(Object attributeValue) { + return null; + } + + public ModelAndView addAllObjects(@Nullable Map modelMap) { + return null; + } + + public void clear() { } + + public boolean isEmpty() { + return true; + } + + public boolean wasCleared() { + return true; + } + + public String toString() { + return ""; + } + + private String formatView() { + return ""; + } +} + diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/View.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/View.java new file mode 100644 index 00000000000..b2281b8c250 --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/View.java @@ -0,0 +1,20 @@ +package org.springframework.web.servlet; + +import java.util.Map; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.lang.Nullable; + +public interface View { + String RESPONSE_STATUS_ATTRIBUTE = View.class.getName() + ".responseStatus"; + String PATH_VARIABLES = View.class.getName() + ".pathVariables"; + String SELECTED_CONTENT_TYPE = View.class.getName() + ".selectedContentType"; + + @Nullable + default String getContentType() { + return null; + } + + void render(@Nullable Map var1, HttpServletRequest var2, HttpServletResponse var3) throws Exception; +} + diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/AbstractUrlBasedView.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/AbstractUrlBasedView.java new file mode 100644 index 00000000000..9efd87af12f --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/AbstractUrlBasedView.java @@ -0,0 +1,39 @@ +package org.springframework.web.servlet.view; + +import java.util.Locale; +import org.springframework.lang.Nullable; + +public abstract class AbstractUrlBasedView { + @Nullable + private String url; + + protected AbstractUrlBasedView() { } + + protected AbstractUrlBasedView(String url) { + this.url = url; + } + + public void setUrl(@Nullable String url) { + this.url = url; + } + + @Nullable + public String getUrl() { + return ""; + } + + public void afterPropertiesSet() throws Exception { } + + protected boolean isUrlRequired() { + return true; + } + + public boolean checkResource(Locale locale) throws Exception { + return true; + } + + public String toString() { + return ""; + } +} + diff --git a/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/RedirectView.java b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/RedirectView.java new file mode 100644 index 00000000000..ee18868231a --- /dev/null +++ b/java/ql/test/stubs/springframework-5.2.3/org/springframework/web/servlet/view/RedirectView.java @@ -0,0 +1,129 @@ +package org.springframework.web.servlet.view; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.lang.reflect.Array; +import java.net.URLEncoder; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.lang.Nullable; + +public class RedirectView extends AbstractUrlBasedView { + private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); + private boolean contextRelative = false; + private boolean http10Compatible = true; + private boolean exposeModelAttributes = true; + @Nullable + private String encodingScheme; + @Nullable + private HttpStatus statusCode; + private boolean expandUriTemplateVariables = true; + private boolean propagateQueryParams = false; + @Nullable + private String[] hosts; + + public RedirectView() { } + + public RedirectView(String url) { } + + public RedirectView(String url, boolean contextRelative) { } + + public RedirectView(String url, boolean contextRelative, boolean http10Compatible) { } + + public RedirectView(String url, boolean contextRelative, boolean http10Compatible, boolean exposeModelAttributes) { } + + public void setContextRelative(boolean contextRelative) { } + + public void setHttp10Compatible(boolean http10Compatible) { } + + public void setExposeModelAttributes(boolean exposeModelAttributes) { } + + public void setEncodingScheme(String encodingScheme) { } + + public void setStatusCode(HttpStatus statusCode) { } + + public void setExpandUriTemplateVariables(boolean expandUriTemplateVariables) { } + + public void setPropagateQueryParams(boolean propagateQueryParams) { } + + public boolean isPropagateQueryProperties() { + return true; + } + + public void setHosts(@Nullable String... hosts) { } + + @Nullable + public String[] getHosts() { + return this.hosts; + } + + public boolean isRedirectView() { + return true; + } + + protected boolean isContextRequired() { + return false; + } + + protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws IOException { } + + protected final String createTargetUrl(Map model, HttpServletRequest request) throws UnsupportedEncodingException { + return ""; + } + + private String getContextPath(HttpServletRequest request) { + return ""; + } + + protected StringBuilder replaceUriTemplateVariables(String targetUrl, Map model, Map currentUriVariables, String encodingScheme) throws UnsupportedEncodingException { + return null; + } + + private Map getCurrentRequestUriVariables(HttpServletRequest request) { + return null; + } + + protected void appendCurrentQueryParams(StringBuilder targetUrl, HttpServletRequest request) { } + + protected void appendQueryProperties(StringBuilder targetUrl, Map model, String encodingScheme) throws UnsupportedEncodingException { } + + protected Map queryProperties(Map model) { + return null; + } + + protected boolean isEligibleProperty(String key, @Nullable Object value) { + return true; + } + + protected boolean isEligibleValue(@Nullable Object value) { + return true; + } + + protected String urlEncode(String input, String encodingScheme) throws UnsupportedEncodingException { + return ""; + } + + protected String updateTargetUrl(String targetUrl, Map model, HttpServletRequest request, HttpServletResponse response) { + return ""; + } + + protected void sendRedirect(HttpServletRequest request, HttpServletResponse response, String targetUrl, boolean http10Compatible) throws IOException { } + + protected boolean isRemoteHost(String targetUrl) { + return true; + } + + protected HttpStatus getHttp11StatusCode(HttpServletRequest request, HttpServletResponse response, String targetUrl) { + return this.statusCode; + } +} + diff --git a/javascript/change-notes/2021-05-18-clone.md b/javascript/change-notes/2021-05-18-clone.md new file mode 100644 index 00000000000..42d52d72a3d --- /dev/null +++ b/javascript/change-notes/2021-05-18-clone.md @@ -0,0 +1,4 @@ +lgtm,codescanning +* The dataflow libraries now model dataflow in the `clone` library. + Affected packages are + [clone](https://npmjs.com/package/clone) diff --git a/javascript/ql/src/Summary/LinesOfCode.ql b/javascript/ql/src/Summary/LinesOfCode.ql index 9f89e0e2163..cadf0a9cf8f 100644 --- a/javascript/ql/src/Summary/LinesOfCode.ql +++ b/javascript/ql/src/Summary/LinesOfCode.ql @@ -4,6 +4,7 @@ * @description The total number of lines of JavaScript or TypeScript code across all files checked into the repository, except in `node_modules`. This is a useful metric of the size of a database. For all files that were seen during extraction, this query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code */ import javascript diff --git a/javascript/ql/src/semmle/javascript/ApiGraphs.qll b/javascript/ql/src/semmle/javascript/ApiGraphs.qll index 385dc1e76d3..590142590a4 100644 --- a/javascript/ql/src/semmle/javascript/ApiGraphs.qll +++ b/javascript/ql/src/semmle/javascript/ApiGraphs.qll @@ -183,6 +183,11 @@ module API { */ Node getPromised() { result = getASuccessor(Label::promised()) } + /** + * Gets a node representing the error wrapped in the `Promise` object represented by this node. + */ + Node getPromisedError() { result = getASuccessor(Label::promisedError()) } + /** * Gets a string representation of the lexicographically least among all shortest access paths * from the root to this node. @@ -468,6 +473,9 @@ module API { or lbl = Label::promised() and PromiseFlow::storeStep(rhs, pred, Promises::valueProp()) + or + lbl = Label::promisedError() and + PromiseFlow::storeStep(rhs, pred, Promises::errorProp()) ) or exists(DataFlow::ClassNode cls, string name | @@ -482,6 +490,12 @@ module API { rhs = f.getAReturn() ) or + exists(DataFlow::FunctionNode f | + base = MkAsyncFuncResult(f) and + lbl = Label::promisedError() and + rhs = f.getExceptionalReturn() + ) + or exists(int i | lbl = Label::parameter(i) and argumentPassing(base, i, rhs) @@ -559,6 +573,9 @@ module API { or lbl = Label::promised() and PromiseFlow::loadStep(pred, ref, Promises::valueProp()) + or + lbl = Label::promisedError() and + PromiseFlow::loadStep(pred, ref, Promises::errorProp()) ) or exists(DataFlow::Node def, DataFlow::FunctionNode fn | @@ -962,6 +979,9 @@ private module Label { /** Gets the `promised` edge label connecting a promise to its contained value. */ string promised() { result = "promised" } + + /** Gets the `promisedError` edge label connecting a promise to its rejected value. */ + string promisedError() { result = "promisedError" } } private class NodeModuleSourcesNodes extends DataFlow::SourceNode::Range { diff --git a/javascript/ql/src/semmle/javascript/Extend.qll b/javascript/ql/src/semmle/javascript/Extend.qll index 9b7255983b9..d50054fc4ee 100644 --- a/javascript/ql/src/semmle/javascript/Extend.qll +++ b/javascript/ql/src/semmle/javascript/Extend.qll @@ -174,3 +174,17 @@ private class ExtendCallTaintStep extends TaintTracking::SharedTaintStep { ) } } + +private import semmle.javascript.dataflow.internal.PreCallGraphStep + +/** + * A step for the `clone` package. + */ +private class CloneStep extends PreCallGraphStep { + override predicate step(DataFlow::Node pred, DataFlow::Node succ) { + exists(DataFlow::CallNode call | call = DataFlow::moduleImport("clone").getACall() | + pred = call.getArgument(0) and + succ = call + ) + } +} diff --git a/javascript/ql/src/semmle/javascript/TypeScript.qll b/javascript/ql/src/semmle/javascript/TypeScript.qll index 4fe263c8389..0b611140449 100644 --- a/javascript/ql/src/semmle/javascript/TypeScript.qll +++ b/javascript/ql/src/semmle/javascript/TypeScript.qll @@ -725,7 +725,7 @@ class TypeAccess extends @typeaccess, TypeExpr, TypeRef { spec.getImportedName() = exportedName and this = spec.getLocal().(TypeDecl).getLocalTypeName().getAnAccess() or - spec instanceof ImportNamespaceSpecifier and + (spec instanceof ImportNamespaceSpecifier or spec instanceof ImportDefaultSpecifier) and this = spec.getLocal().(LocalNamespaceDecl).getLocalNamespaceName().getAMemberAccess(exportedName) ) diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index c73c894ca4b..f02e9b0f287 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -239,7 +239,6 @@ module DataFlow { private TypeAnnotation getFallbackTypeAnnotation() { exists(BindingPattern pattern | this = valueNode(pattern) and - not ast_node_type(pattern, _) and result = pattern.getTypeAnnotation() ) or diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index 451ad3f24d2..18204c5b59b 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -206,19 +206,14 @@ module ClientRequest { /** * A model of a URL request made using the `axios` library. */ - class AxiosUrlRequest extends ClientRequest::Range { + class AxiosUrlRequest extends ClientRequest::Range, API::CallNode { string method; AxiosUrlRequest() { - exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | - moduleName = "axios" and - ( - callee = DataFlow::moduleImport(moduleName) and method = "request" - or - callee = DataFlow::moduleMember(moduleName, method) and - (method = httpMethodName() or method = "request") - ) - ) + this = API::moduleImport("axios").getACall() and method = "request" + or + this = API::moduleImport("axios").getMember(method).getACall() and + method = [httpMethodName(), "request"] } private int getOptionsArgIndex() { @@ -247,12 +242,10 @@ module ClientRequest { method = "request" and result = getOptionArgument(0, "data") or - (method = "post" or method = "put" or method = "put") and - (result = getArgument(1) or result = getOptionArgument(2, "data")) + method = ["post", "put"] and + result = [getArgument(1), getOptionArgument(2, "data")] or - exists(string name | name = "headers" or name = "params" | - result = getOptionArgument([0 .. 2], name) - ) + result = getOptionArgument([0 .. 2], ["headers", "params"]) } /** Gets the response type from the options passed in. */ @@ -275,6 +268,10 @@ module ClientRequest { responseType = getResponseType() and promise = true and result = this + or + responseType = getResponseType() and + promise = false and + result = getReturn().getPromisedError().getMember("response").getAnImmediateUse() } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll index 53266c10ef8..7ef4bf7525a 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll @@ -19,6 +19,9 @@ module Express { or // `app = express.createServer()` result = DataFlow::moduleMember("express", "createServer").getAnInvocation() + or + // `app = express().disable(x)`, and other chaining methods + result = appCreation().getAMemberCall(["engine", "set", "param", "enable", "disable", "on"]) } /** diff --git a/javascript/ql/src/semmle/javascript/security/internal/SensitiveDataHeuristics.qll b/javascript/ql/src/semmle/javascript/security/internal/SensitiveDataHeuristics.qll index ddf95b1b534..589c37120b9 100644 --- a/javascript/ql/src/semmle/javascript/security/internal/SensitiveDataHeuristics.qll +++ b/javascript/ql/src/semmle/javascript/security/internal/SensitiveDataHeuristics.qll @@ -58,7 +58,8 @@ module HeuristicNames { */ string maybeAccountInfo() { result = "(?is).*acc(ou)?nt.*" or - result = "(?is).*(puid|username|userid).*" + result = "(?is).*(puid|username|userid).*" or + result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*" } /** diff --git a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected index 95c6d7e03d9..47e57e0a242 100644 --- a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected +++ b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.expected @@ -1,2 +1,6 @@ +underlyingTypeNode +| foo | Bar | foo.ts:3:1:5:1 | use (instance (member Bar (member exports (module foo)))) | +| foo | Bar | foo.ts:3:12:3:12 | use (instance (member Bar (member exports (module foo)))) | +#select | tst.ts:8:14:8:16 | arg | Base in global scope | | tst.ts:8:14:8:16 | arg | Sub in global scope | diff --git a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.ql b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.ql index 77e311b3b76..72d4e6d0f3d 100644 --- a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.ql +++ b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/HasUnderlyingType.ql @@ -3,3 +3,7 @@ import javascript from Expr e, TypeName typeName where e.getType().hasUnderlyingTypeName(typeName) select e, typeName + +query API::Node underlyingTypeNode(string mod, string name) { + result = API::Node::ofType(mod, name) +} diff --git a/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/foo.ts b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/foo.ts new file mode 100644 index 00000000000..1b5be79068a --- /dev/null +++ b/javascript/ql/test/library-tests/TypeScript/HasUnderlyingType/foo.ts @@ -0,0 +1,5 @@ +import foo from "foo"; + +function f(x: foo.Bar) { + return x; +} diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected index e448b888e12..27a9fa10f72 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected @@ -87,6 +87,7 @@ test_ClientRequest | tst.js:271:3:271:61 | proxy.w ... 080' }) | | tst.js:274:1:283:2 | httpPro ... true\\n}) | | tst.js:286:20:286:55 | new Web ... :8080') | +| tst.js:296:5:299:6 | axios({ ... \\n }) | test_getADataNode | tst.js:53:5:53:23 | axios({data: data}) | tst.js:53:18:53:21 | data | | tst.js:57:5:57:39 | axios.p ... data2}) | tst.js:57:19:57:23 | data1 | @@ -227,6 +228,8 @@ test_getUrl | tst.js:271:3:271:61 | proxy.w ... 080' }) | tst.js:271:33:271:58 | 'http:/ ... m:8080' | | tst.js:274:1:283:2 | httpPro ... true\\n}) | tst.js:275:13:281:5 | {\\n ... ,\\n } | | tst.js:286:20:286:55 | new Web ... :8080') | tst.js:286:34:286:54 | 'ws://l ... t:8080' | +| tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:296:11:299:5 | {\\n ... ,\\n } | +| tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:298:14:298:44 | "http:/ ... -axios" | test_getAResponseDataNode | tst.js:19:5:19:23 | requestPromise(url) | tst.js:19:5:19:23 | requestPromise(url) | text | true | | tst.js:21:5:21:23 | superagent.get(url) | tst.js:21:5:21:23 | superagent.get(url) | stream | true | @@ -294,3 +297,7 @@ test_getAResponseDataNode | tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:67:235:70 | resp | fetch.response | false | | tst.js:235:5:237:6 | needle. ... \\n }) | tst.js:235:73:235:76 | body | json | false | | tst.js:286:20:286:55 | new Web ... :8080') | tst.js:291:44:291:53 | event.data | json | false | +| tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:296:5:299:6 | axios({ ... \\n }) | json | true | +| tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:302:28:302:39 | err.response | json | false | +| tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:303:26:303:37 | err.response | json | false | +| tst.js:296:5:299:6 | axios({ ... \\n }) | tst.js:304:27:304:38 | err.response | json | false | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index bc15565c072..40dcfc481f4 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -290,4 +290,18 @@ function webSocket() { socket.addEventListener('message', function (event) { console.log("Data from server: " + event.data); }); +} + +function moreAxios() { + axios({ + method: 'GET', + url: "http://example.org/more-axios", + }).then( + x => res.send(x.data), + (err) => { + const status = err.response.status; + const data = err.response.data; + const agent = err.response.headers.useragent; + } + ); } \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/Express/tests.expected b/javascript/ql/test/library-tests/frameworks/Express/tests.expected index c6b9ec97fc0..586a404b48a 100644 --- a/javascript/ql/test/library-tests/frameworks/Express/tests.expected +++ b/javascript/ql/test/library-tests/frameworks/Express/tests.expected @@ -1422,6 +1422,7 @@ test_appCreation | src/express.js:2:11:2:19 | express() | | src/inheritedFromNode.js:2:11:2:19 | express() | | src/params.js:2:11:2:19 | express() | +| src/params.js:4:1:12:2 | app.par ... }\\n}) | | src/responseExprs.js:2:11:2:19 | express() | | src/routesetups.js:7:11:7:32 | express ... erver() | | src/subrouter.js:2:11:2:19 | express() | @@ -1519,6 +1520,7 @@ test_RouteExpr | src/express.js:46:1:51:2 | app.pos ... me];\\n}) | src/express.js:2:11:2:19 | express() | | src/inheritedFromNode.js:4:1:8:2 | app.pos ... url;\\n}) | src/inheritedFromNode.js:2:11:2:19 | express() | | src/params.js:4:1:12:2 | app.par ... }\\n}) | src/params.js:2:11:2:19 | express() | +| src/params.js:4:1:12:2 | app.par ... }\\n}) | src/params.js:4:1:12:2 | app.par ... }\\n}) | | src/params.js:14:1:16:2 | app.get ... o");\\n}) | src/params.js:2:11:2:19 | express() | | src/responseExprs.js:4:1:6:2 | app.get ... res1\\n}) | src/responseExprs.js:2:11:2:19 | express() | | src/responseExprs.js:7:1:9:2 | app.get ... es2;\\n}) | src/responseExprs.js:2:11:2:19 | express() | @@ -2174,6 +2176,7 @@ test_isRouterCreation | src/express.js:2:11:2:19 | express() | | src/inheritedFromNode.js:2:11:2:19 | express() | | src/params.js:2:11:2:19 | express() | +| src/params.js:4:1:12:2 | app.par ... }\\n}) | | src/responseExprs.js:2:11:2:19 | express() | | src/route.js:2:14:2:29 | express.Router() | | src/routesetups.js:3:1:3:16 | express.Router() | @@ -2264,6 +2267,7 @@ test_RouterDefinition_RouterDefinition | src/express.js:2:11:2:19 | express() | | src/inheritedFromNode.js:2:11:2:19 | express() | | src/params.js:2:11:2:19 | express() | +| src/params.js:4:1:12:2 | app.par ... }\\n}) | | src/responseExprs.js:2:11:2:19 | express() | | src/route.js:2:14:2:29 | express.Router() | | src/routesetups.js:3:1:3:16 | express.Router() | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected index f8ceeb45b62..b1d940b437c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXss.expected @@ -174,6 +174,19 @@ nodes | tst2.js:18:12:18:12 | p | | tst2.js:21:14:21:14 | p | | tst2.js:21:14:21:14 | p | +| tst2.js:30:7:30:24 | p | +| tst2.js:30:9:30:9 | p | +| tst2.js:30:9:30:9 | p | +| tst2.js:33:11:33:11 | p | +| tst2.js:36:12:36:12 | p | +| tst2.js:36:12:36:12 | p | +| tst2.js:37:12:37:18 | other.p | +| tst2.js:37:12:37:18 | other.p | +| tst3.js:5:7:5:24 | p | +| tst3.js:5:9:5:9 | p | +| tst3.js:5:9:5:9 | p | +| tst3.js:6:12:6:12 | p | +| tst3.js:6:12:6:12 | p | edges | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | @@ -318,6 +331,17 @@ edges | tst2.js:14:7:14:24 | p | tst2.js:21:14:21:14 | p | | tst2.js:14:9:14:9 | p | tst2.js:14:7:14:24 | p | | tst2.js:14:9:14:9 | p | tst2.js:14:7:14:24 | p | +| tst2.js:30:7:30:24 | p | tst2.js:33:11:33:11 | p | +| tst2.js:30:7:30:24 | p | tst2.js:36:12:36:12 | p | +| tst2.js:30:7:30:24 | p | tst2.js:36:12:36:12 | p | +| tst2.js:30:9:30:9 | p | tst2.js:30:7:30:24 | p | +| tst2.js:30:9:30:9 | p | tst2.js:30:7:30:24 | p | +| tst2.js:33:11:33:11 | p | tst2.js:37:12:37:18 | other.p | +| tst2.js:33:11:33:11 | p | tst2.js:37:12:37:18 | other.p | +| tst3.js:5:7:5:24 | p | tst3.js:6:12:6:12 | p | +| tst3.js:5:7:5:24 | p | tst3.js:6:12:6:12 | p | +| tst3.js:5:9:5:9 | p | tst3.js:5:7:5:24 | p | +| tst3.js:5:9:5:9 | p | tst3.js:5:7:5:24 | p | #select | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:8:33:8:45 | req.params.id | user-provided value | | ReflectedXss.js:17:12:17:39 | "Unknow ... rams.id | ReflectedXss.js:17:31:17:39 | params.id | ReflectedXss.js:17:12:17:39 | "Unknow ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXss.js:17:31:17:39 | params.id | user-provided value | @@ -359,3 +383,6 @@ edges | tst2.js:8:12:8:12 | r | tst2.js:6:12:6:15 | q: r | tst2.js:8:12:8:12 | r | Cross-site scripting vulnerability due to $@. | tst2.js:6:12:6:15 | q: r | user-provided value | | tst2.js:18:12:18:12 | p | tst2.js:14:9:14:9 | p | tst2.js:18:12:18:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:14:9:14:9 | p | user-provided value | | tst2.js:21:14:21:14 | p | tst2.js:14:9:14:9 | p | tst2.js:21:14:21:14 | p | Cross-site scripting vulnerability due to $@. | tst2.js:14:9:14:9 | p | user-provided value | +| tst2.js:36:12:36:12 | p | tst2.js:30:9:30:9 | p | tst2.js:36:12:36:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:30:9:30:9 | p | user-provided value | +| tst2.js:37:12:37:18 | other.p | tst2.js:30:9:30:9 | p | tst2.js:37:12:37:18 | other.p | Cross-site scripting vulnerability due to $@. | tst2.js:30:9:30:9 | p | user-provided value | +| tst3.js:6:12:6:12 | p | tst3.js:5:9:5:9 | p | tst3.js:6:12:6:12 | p | Cross-site scripting vulnerability due to $@. | tst3.js:5:9:5:9 | p | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected index 719a82171a8..6a919f7b43a 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/ReflectedXssWithCustomSanitizer.expected @@ -37,3 +37,6 @@ | tst2.js:8:12:8:12 | r | Cross-site scripting vulnerability due to $@. | tst2.js:6:12:6:15 | q: r | user-provided value | | tst2.js:18:12:18:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:14:9:14:9 | p | user-provided value | | tst2.js:21:14:21:14 | p | Cross-site scripting vulnerability due to $@. | tst2.js:14:9:14:9 | p | user-provided value | +| tst2.js:36:12:36:12 | p | Cross-site scripting vulnerability due to $@. | tst2.js:30:9:30:9 | p | user-provided value | +| tst2.js:37:12:37:18 | other.p | Cross-site scripting vulnerability due to $@. | tst2.js:30:9:30:9 | p | user-provided value | +| tst3.js:6:12:6:12 | p | Cross-site scripting vulnerability due to $@. | tst3.js:5:9:5:9 | p | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst2.js b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst2.js index 521b6b20a7c..034b0791217 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst2.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst2.js @@ -22,3 +22,17 @@ app.get('/bar', function(req, res) { else res.send(p); // OK }); + + +const clone = require('clone'); + +app.get('/baz', function(req, res) { + let { p } = req.params; + + var obj = {}; + obj.p = p; + var other = clone(obj); + + res.send(p); // NOT OK + res.send(other.p); // NOT OK +}); \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst3.js b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst3.js new file mode 100644 index 00000000000..1fa1758c41c --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss/tst3.js @@ -0,0 +1,7 @@ +var express = require('express'); + +var app = express(); +app.enable('x-powered-by').disable('x-powered-by').get('/', function (req, res) { + let { p } = req.params; + res.send(p); // NOT OK +}); diff --git a/javascript/ql/test/query-tests/Security/CWE-338/InsecureRandomness.expected b/javascript/ql/test/query-tests/Security/CWE-338/InsecureRandomness.expected index e4ab385cc07..42da210c266 100644 --- a/javascript/ql/test/query-tests/Security/CWE-338/InsecureRandomness.expected +++ b/javascript/ql/test/query-tests/Security/CWE-338/InsecureRandomness.expected @@ -66,6 +66,32 @@ nodes | tst.js:95:33:95:45 | Math.random() | | tst.js:95:33:95:45 | Math.random() | | tst.js:95:33:95:45 | Math.random() | +| tst.js:115:16:115:56 | Math.fl ... 00_000) | +| tst.js:115:16:115:56 | Math.fl ... 00_000) | +| tst.js:115:27:115:39 | Math.random() | +| tst.js:115:27:115:39 | Math.random() | +| tst.js:115:27:115:55 | Math.ra ... 000_000 | +| tst.js:116:22:116:62 | Math.fl ... 00_000) | +| tst.js:116:22:116:62 | Math.fl ... 00_000) | +| tst.js:116:33:116:45 | Math.random() | +| tst.js:116:33:116:45 | Math.random() | +| tst.js:116:33:116:61 | Math.ra ... 000_000 | +| tst.js:117:15:117:55 | Math.fl ... 00_000) | +| tst.js:117:15:117:55 | Math.fl ... 00_000) | +| tst.js:117:26:117:38 | Math.random() | +| tst.js:117:26:117:38 | Math.random() | +| tst.js:117:26:117:54 | Math.ra ... 000_000 | +| tst.js:118:23:118:63 | Math.fl ... 00_000) | +| tst.js:118:23:118:63 | Math.fl ... 00_000) | +| tst.js:118:34:118:46 | Math.random() | +| tst.js:118:34:118:46 | Math.random() | +| tst.js:118:34:118:62 | Math.ra ... 000_000 | +| tst.js:120:16:120:28 | Math.random() | +| tst.js:120:16:120:28 | Math.random() | +| tst.js:120:16:120:28 | Math.random() | +| tst.js:121:18:121:30 | Math.random() | +| tst.js:121:18:121:30 | Math.random() | +| tst.js:121:18:121:30 | Math.random() | edges | tst.js:2:20:2:32 | Math.random() | tst.js:2:20:2:32 | Math.random() | | tst.js:6:31:6:43 | Math.random() | tst.js:6:20:6:43 | "prefix ... andom() | @@ -114,6 +140,24 @@ edges | tst.js:84:19:84:31 | Math.random() | tst.js:84:19:84:31 | Math.random() | | tst.js:90:32:90:44 | Math.random() | tst.js:90:32:90:44 | Math.random() | | tst.js:95:33:95:45 | Math.random() | tst.js:95:33:95:45 | Math.random() | +| tst.js:115:27:115:39 | Math.random() | tst.js:115:27:115:55 | Math.ra ... 000_000 | +| tst.js:115:27:115:39 | Math.random() | tst.js:115:27:115:55 | Math.ra ... 000_000 | +| tst.js:115:27:115:55 | Math.ra ... 000_000 | tst.js:115:16:115:56 | Math.fl ... 00_000) | +| tst.js:115:27:115:55 | Math.ra ... 000_000 | tst.js:115:16:115:56 | Math.fl ... 00_000) | +| tst.js:116:33:116:45 | Math.random() | tst.js:116:33:116:61 | Math.ra ... 000_000 | +| tst.js:116:33:116:45 | Math.random() | tst.js:116:33:116:61 | Math.ra ... 000_000 | +| tst.js:116:33:116:61 | Math.ra ... 000_000 | tst.js:116:22:116:62 | Math.fl ... 00_000) | +| tst.js:116:33:116:61 | Math.ra ... 000_000 | tst.js:116:22:116:62 | Math.fl ... 00_000) | +| tst.js:117:26:117:38 | Math.random() | tst.js:117:26:117:54 | Math.ra ... 000_000 | +| tst.js:117:26:117:38 | Math.random() | tst.js:117:26:117:54 | Math.ra ... 000_000 | +| tst.js:117:26:117:54 | Math.ra ... 000_000 | tst.js:117:15:117:55 | Math.fl ... 00_000) | +| tst.js:117:26:117:54 | Math.ra ... 000_000 | tst.js:117:15:117:55 | Math.fl ... 00_000) | +| tst.js:118:34:118:46 | Math.random() | tst.js:118:34:118:62 | Math.ra ... 000_000 | +| tst.js:118:34:118:46 | Math.random() | tst.js:118:34:118:62 | Math.ra ... 000_000 | +| tst.js:118:34:118:62 | Math.ra ... 000_000 | tst.js:118:23:118:63 | Math.fl ... 00_000) | +| tst.js:118:34:118:62 | Math.ra ... 000_000 | tst.js:118:23:118:63 | Math.fl ... 00_000) | +| tst.js:120:16:120:28 | Math.random() | tst.js:120:16:120:28 | Math.random() | +| tst.js:121:18:121:30 | Math.random() | tst.js:121:18:121:30 | Math.random() | #select | tst.js:2:20:2:32 | Math.random() | tst.js:2:20:2:32 | Math.random() | tst.js:2:20:2:32 | Math.random() | Cryptographically insecure $@ in a security context. | tst.js:2:20:2:32 | Math.random() | random value | | tst.js:6:20:6:43 | "prefix ... andom() | tst.js:6:31:6:43 | Math.random() | tst.js:6:20:6:43 | "prefix ... andom() | Cryptographically insecure $@ in a security context. | tst.js:6:31:6:43 | Math.random() | random value | @@ -131,3 +175,9 @@ edges | tst.js:84:19:84:31 | Math.random() | tst.js:84:19:84:31 | Math.random() | tst.js:84:19:84:31 | Math.random() | Cryptographically insecure $@ in a security context. | tst.js:84:19:84:31 | Math.random() | random value | | tst.js:90:32:90:44 | Math.random() | tst.js:90:32:90:44 | Math.random() | tst.js:90:32:90:44 | Math.random() | Cryptographically insecure $@ in a security context. | tst.js:90:32:90:44 | Math.random() | random value | | tst.js:95:33:95:45 | Math.random() | tst.js:95:33:95:45 | Math.random() | tst.js:95:33:95:45 | Math.random() | Cryptographically insecure $@ in a security context. | tst.js:95:33:95:45 | Math.random() | random value | +| tst.js:115:16:115:56 | Math.fl ... 00_000) | tst.js:115:27:115:39 | Math.random() | tst.js:115:16:115:56 | Math.fl ... 00_000) | Cryptographically insecure $@ in a security context. | tst.js:115:27:115:39 | Math.random() | random value | +| tst.js:116:22:116:62 | Math.fl ... 00_000) | tst.js:116:33:116:45 | Math.random() | tst.js:116:22:116:62 | Math.fl ... 00_000) | Cryptographically insecure $@ in a security context. | tst.js:116:33:116:45 | Math.random() | random value | +| tst.js:117:15:117:55 | Math.fl ... 00_000) | tst.js:117:26:117:38 | Math.random() | tst.js:117:15:117:55 | Math.fl ... 00_000) | Cryptographically insecure $@ in a security context. | tst.js:117:26:117:38 | Math.random() | random value | +| tst.js:118:23:118:63 | Math.fl ... 00_000) | tst.js:118:34:118:46 | Math.random() | tst.js:118:23:118:63 | Math.fl ... 00_000) | Cryptographically insecure $@ in a security context. | tst.js:118:34:118:46 | Math.random() | random value | +| tst.js:120:16:120:28 | Math.random() | tst.js:120:16:120:28 | Math.random() | tst.js:120:16:120:28 | Math.random() | Cryptographically insecure $@ in a security context. | tst.js:120:16:120:28 | Math.random() | random value | +| tst.js:121:18:121:30 | Math.random() | tst.js:121:18:121:30 | Math.random() | tst.js:121:18:121:30 | Math.random() | Cryptographically insecure $@ in a security context. | tst.js:121:18:121:30 | Math.random() | random value | diff --git a/javascript/ql/test/query-tests/Security/CWE-338/tst.js b/javascript/ql/test/query-tests/Security/CWE-338/tst.js index 123799426b5..77393b8983c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-338/tst.js +++ b/javascript/ql/test/query-tests/Security/CWE-338/tst.js @@ -109,4 +109,14 @@ function f18() { } }; var secret = genRandom(); // OK - Math.random() is only a fallback. -})(); \ No newline at end of file +})(); + +function uid() { + var uuid = Math.floor(Math.random() * 4_000_000_000); // NOT OK + var sessionUid = Math.floor(Math.random() * 4_000_000_000); // NOT OK + var uid = Math.floor(Math.random() * 4_000_000_000); // NOT OK + var my_nice_uid = Math.floor(Math.random() * 4_000_000_000); // NOT OK + var liquid = Math.random(); // OK + var UUID = Math.random(); // NOT OK + var MY_UID = Math.random(); // NOK OK +} \ No newline at end of file diff --git a/python/change-notes/2021-05-10-idna-add-modeling.md b/python/change-notes/2021-05-10-idna-add-modeling.md new file mode 100644 index 00000000000..95856ffe5a8 --- /dev/null +++ b/python/change-notes/2021-05-10-idna-add-modeling.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added modeling of the PyPI package `idna`, for encoding/decoding Internationalised Domain Names in Applications. diff --git a/python/change-notes/2021-05-10-simplejson-add-modeling.md b/python/change-notes/2021-05-10-simplejson-add-modeling.md new file mode 100644 index 00000000000..910441bdeac --- /dev/null +++ b/python/change-notes/2021-05-10-simplejson-add-modeling.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added modeling of the PyPI package `simplejson`. diff --git a/python/change-notes/2021-05-10-ujson-add-modeling.md b/python/change-notes/2021-05-10-ujson-add-modeling.md new file mode 100644 index 00000000000..2d7cdc4b68a --- /dev/null +++ b/python/change-notes/2021-05-10-ujson-add-modeling.md @@ -0,0 +1,2 @@ +lgtm,codescanning +* Added modeling of the PyPI package `ujson`. diff --git a/python/ql/src/Summary/LinesOfCode.ql b/python/ql/src/Summary/LinesOfCode.ql index d9bfc4f872c..ad0b77730de 100644 --- a/python/ql/src/Summary/LinesOfCode.ql +++ b/python/ql/src/Summary/LinesOfCode.ql @@ -5,6 +5,7 @@ * database. This query counts the lines of code, excluding whitespace or comments. * @kind metric * @tags summary + * lines-of-code * @id py/summary/lines-of-code */ diff --git a/python/ql/src/semmle/python/Frameworks.qll b/python/ql/src/semmle/python/Frameworks.qll index a00511ca545..3115c3ffac6 100644 --- a/python/ql/src/semmle/python/Frameworks.qll +++ b/python/ql/src/semmle/python/Frameworks.qll @@ -2,17 +2,22 @@ * Helper file that imports all framework modeling. */ +// If you add modeling of a new framework/library, remember to add it it to the docs in +// `docs/codeql/support/reusables/frameworks.rst` private import semmle.python.frameworks.Cryptodome private import semmle.python.frameworks.Cryptography private import semmle.python.frameworks.Dill private import semmle.python.frameworks.Django private import semmle.python.frameworks.Fabric private import semmle.python.frameworks.Flask +private import semmle.python.frameworks.Idna private import semmle.python.frameworks.Invoke private import semmle.python.frameworks.MysqlConnectorPython private import semmle.python.frameworks.MySQLdb private import semmle.python.frameworks.Psycopg2 private import semmle.python.frameworks.PyMySQL +private import semmle.python.frameworks.Simplejson private import semmle.python.frameworks.Stdlib private import semmle.python.frameworks.Tornado +private import semmle.python.frameworks.Ujson private import semmle.python.frameworks.Yaml diff --git a/python/ql/src/semmle/python/frameworks/Idna.qll b/python/ql/src/semmle/python/frameworks/Idna.qll new file mode 100644 index 00000000000..331cafbd084 --- /dev/null +++ b/python/ql/src/semmle/python/frameworks/Idna.qll @@ -0,0 +1,40 @@ +/** + * Provides classes modeling security-relevant aspects of the `idna` PyPI package. + * See https://pypi.org/project/idna/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.TaintTracking +private import semmle.python.Concepts +private import semmle.python.ApiGraphs + +/** + * Provides models for the `idna` PyPI package. + * See https://pypi.org/project/idna/. + */ +private module IdnaModel { + /** A call to `idna.encode`. */ + private class IdnaEncodeCall extends Encoding::Range, DataFlow::CallCfgNode { + IdnaEncodeCall() { this = API::moduleImport("idna").getMember("encode").getACall() } + + override DataFlow::Node getAnInput() { result = [this.getArg(0), this.getArgByName("s")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "IDNA" } + } + + /** A call to `idna.decode`. */ + private class IdnaDecodeCall extends Decoding::Range, DataFlow::CallCfgNode { + IdnaDecodeCall() { this = API::moduleImport("idna").getMember("decode").getACall() } + + override DataFlow::Node getAnInput() { result = [this.getArg(0), this.getArgByName("s")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "IDNA" } + + override predicate mayExecuteInput() { none() } + } +} diff --git a/python/ql/src/semmle/python/frameworks/Simplejson.qll b/python/ql/src/semmle/python/frameworks/Simplejson.qll new file mode 100644 index 00000000000..41676705a40 --- /dev/null +++ b/python/ql/src/semmle/python/frameworks/Simplejson.qll @@ -0,0 +1,84 @@ +/** + * Provides classes modeling security-relevant aspects of the `simplejson` PyPI package. + * See https://simplejson.readthedocs.io/en/latest/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.TaintTracking +private import semmle.python.Concepts +private import semmle.python.ApiGraphs + +/** + * Provides models for the `simplejson` PyPI package. + * See https://simplejson.readthedocs.io/en/latest/. + */ +private module SimplejsonModel { + /** + * A call to `simplejson.dumps`. + * + * See https://simplejson.readthedocs.io/en/latest/#simplejson.dumps + */ + private class SimplejsonDumpsCall extends Encoding::Range, DataFlow::CallCfgNode { + SimplejsonDumpsCall() { this = API::moduleImport("simplejson").getMember("dumps").getACall() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("obj")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + } + + /** + * A call to `simplejson.dump`. + * + * See https://simplejson.readthedocs.io/en/latest/#simplejson.dump + */ + private class SimplejsonDumpCall extends Encoding::Range, DataFlow::CallCfgNode { + SimplejsonDumpCall() { this = API::moduleImport("simplejson").getMember("dump").getACall() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("obj")] } + + override DataFlow::Node getOutput() { + result.(DataFlow::PostUpdateNode).getPreUpdateNode() in [ + this.getArg(1), this.getArgByName("fp") + ] + } + + override string getFormat() { result = "JSON" } + } + + /** + * A call to `simplejson.loads`. + * + * See https://simplejson.readthedocs.io/en/latest/#simplejson.loads + */ + private class SimplejsonLoadsCall extends Decoding::Range, DataFlow::CallCfgNode { + SimplejsonLoadsCall() { this = API::moduleImport("simplejson").getMember("loads").getACall() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("s")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + + override predicate mayExecuteInput() { none() } + } + + /** + * A call to `simplejson.load`. + * + * See https://simplejson.readthedocs.io/en/latest/#simplejson.load + */ + private class SimplejsonLoadCall extends Decoding::Range, DataFlow::CallCfgNode { + SimplejsonLoadCall() { this = API::moduleImport("simplejson").getMember("load").getACall() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("fp")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + + override predicate mayExecuteInput() { none() } + } +} diff --git a/python/ql/src/semmle/python/frameworks/Stdlib.qll b/python/ql/src/semmle/python/frameworks/Stdlib.qll index 07753e9fde5..69023b8940a 100644 --- a/python/ql/src/semmle/python/frameworks/Stdlib.qll +++ b/python/ql/src/semmle/python/frameworks/Stdlib.qll @@ -511,7 +511,23 @@ private module Stdlib { override predicate mayExecuteInput() { none() } - override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("s")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + } + + /** + * A call to `json.load` + * See https://docs.python.org/3/library/json.html#json.load + */ + private class JsonLoadCall extends Decoding::Range, DataFlow::CallCfgNode { + JsonLoadCall() { this = json().getMember("load").getACall() } + + override predicate mayExecuteInput() { none() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("fp")] } override DataFlow::Node getOutput() { result = this } @@ -525,13 +541,31 @@ private module Stdlib { private class JsonDumpsCall extends Encoding::Range, DataFlow::CallCfgNode { JsonDumpsCall() { this = json().getMember("dumps").getACall() } - override DataFlow::Node getAnInput() { result.asCfgNode() = node.getArg(0) } + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("obj")] } override DataFlow::Node getOutput() { result = this } override string getFormat() { result = "JSON" } } + /** + * A call to `json.dump` + * See https://docs.python.org/3/library/json.html#json.dump + */ + private class JsonDumpCall extends Encoding::Range, DataFlow::CallCfgNode { + JsonDumpCall() { this = json().getMember("dump").getACall() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("obj")] } + + override DataFlow::Node getOutput() { + result.(DataFlow::PostUpdateNode).getPreUpdateNode() in [ + this.getArg(1), this.getArgByName("fp") + ] + } + + override string getFormat() { result = "JSON" } + } + // --------------------------------------------------------------------------- // cgi // --------------------------------------------------------------------------- diff --git a/python/ql/src/semmle/python/frameworks/Ujson.qll b/python/ql/src/semmle/python/frameworks/Ujson.qll new file mode 100644 index 00000000000..145f7f883a9 --- /dev/null +++ b/python/ql/src/semmle/python/frameworks/Ujson.qll @@ -0,0 +1,76 @@ +/** + * Provides classes modeling security-relevant aspects of the `ujson` PyPI package. + * See https://pypi.org/project/ujson/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import semmle.python.dataflow.new.TaintTracking +private import semmle.python.Concepts +private import semmle.python.ApiGraphs + +/** + * Provides models for the `ujson` PyPI package. + * See https://pypi.org/project/ujson/. + */ +private module UjsonModel { + /** + * A call to `usjon.dumps` or `ujson.encode`. + */ + private class UjsonDumpsCall extends Encoding::Range, DataFlow::CallCfgNode { + UjsonDumpsCall() { this = API::moduleImport("ujson").getMember(["dumps", "encode"]).getACall() } + + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("obj")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + } + + /** + * A call to `ujson.dump`. + */ + private class UjsonDumpCall extends Encoding::Range, DataFlow::CallCfgNode { + UjsonDumpCall() { this = API::moduleImport("ujson").getMember("dump").getACall() } + + override DataFlow::Node getAnInput() { result = this.getArg(0) } + + override DataFlow::Node getOutput() { + result.(DataFlow::PostUpdateNode).getPreUpdateNode() = this.getArg(1) + } + + override string getFormat() { result = "JSON" } + } + + /** + * A call to `ujson.loads` or `ujson.decode`. + */ + private class UjsonLoadsCall extends Decoding::Range, DataFlow::CallCfgNode { + UjsonLoadsCall() { this = API::moduleImport("ujson").getMember(["loads", "decode"]).getACall() } + + // Note: Most other JSON libraries allow the keyword argument `s`, but as of version + // 4.0.2 `ujson` uses `obj` instead. + override DataFlow::Node getAnInput() { result in [this.getArg(0), this.getArgByName("obj")] } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + + override predicate mayExecuteInput() { none() } + } + + /** + * A call to `ujson.load`. + */ + private class UjsonLoadCall extends Decoding::Range, DataFlow::CallCfgNode { + UjsonLoadCall() { this = API::moduleImport("ujson").getMember("load").getACall() } + + override DataFlow::Node getAnInput() { result = this.getArg(0) } + + override DataFlow::Node getOutput() { result = this } + + override string getFormat() { result = "JSON" } + + override predicate mayExecuteInput() { none() } + } +} diff --git a/python/ql/src/semmle/python/security/internal/SensitiveDataHeuristics.qll b/python/ql/src/semmle/python/security/internal/SensitiveDataHeuristics.qll index ddf95b1b534..589c37120b9 100644 --- a/python/ql/src/semmle/python/security/internal/SensitiveDataHeuristics.qll +++ b/python/ql/src/semmle/python/security/internal/SensitiveDataHeuristics.qll @@ -58,7 +58,8 @@ module HeuristicNames { */ string maybeAccountInfo() { result = "(?is).*acc(ou)?nt.*" or - result = "(?is).*(puid|username|userid).*" + result = "(?is).*(puid|username|userid).*" or + result = "(?s).*([uU]|^|_|[a-z](?=U))([uU][iI][dD]).*" } /** diff --git a/python/ql/test/experimental/dataflow/tainttracking/defaultAdditionalTaintStep/test_json.py b/python/ql/test/experimental/dataflow/tainttracking/defaultAdditionalTaintStep/test_json.py index a7398b4d001..b1b0536b03b 100644 --- a/python/ql/test/experimental/dataflow/tainttracking/defaultAdditionalTaintStep/test_json.py +++ b/python/ql/test/experimental/dataflow/tainttracking/defaultAdditionalTaintStep/test_json.py @@ -11,54 +11,43 @@ if TYPE_CHECKING: # Actual tests from io import StringIO - -# Workaround for Python3 not having unicode -import sys -if sys.version_info[0] == 3: - unicode = str +import json def test(): print("\n# test") ts = TAINTED_STRING - import json + + encoded = json.dumps(ts) ensure_tainted( + encoded, # $ tainted json.dumps(ts), # $ tainted - json.loads(json.dumps(ts)), # $ tainted + json.dumps(obj=ts), # $ tainted + json.loads(encoded), # $ tainted + json.loads(s=encoded), # $ tainted ) - # For Python2, need to convert to unicode for StringIO to work - tainted_filelike = StringIO(unicode(json.dumps(ts))) + # load/dump with file-like + tainted_filelike = StringIO() + json.dump(ts, tainted_filelike) + tainted_filelike.seek(0) ensure_tainted( - tainted_filelike, # $ MISSING: tainted - json.load(tainted_filelike), # $ MISSING: tainted + tainted_filelike, # $ tainted + json.load(tainted_filelike), # $ tainted ) -def non_syntacical(): - print("\n# non_syntacical") - ts = TAINTED_STRING - - # a less syntactical approach - from json import load, loads, dumps - - dumps_alias = dumps + # load/dump with file-like using keyword-args + tainted_filelike = StringIO() + json.dump(obj=ts, fp=tainted_filelike) + tainted_filelike.seek(0) ensure_tainted( - dumps(ts), # $ tainted - dumps_alias(ts), # $ tainted - loads(dumps(ts)), # $ tainted + tainted_filelike, # $ tainted + json.load(fp=tainted_filelike), # $ tainted ) - # For Python2, need to convert to unicode for StringIO to work - tainted_filelike = StringIO(unicode(dumps(ts))) - - ensure_tainted( - tainted_filelike, # $ MISSING: tainted - load(tainted_filelike), # $ MISSING: tainted - ) # Make tests runable test() -non_syntacical() diff --git a/python/ql/test/library-tests/frameworks/idna/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/idna/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/frameworks/idna/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/idna/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/idna/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/idna/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/idna/InlineTaintTest.expected new file mode 100644 index 00000000000..79d760d87f4 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/idna/InlineTaintTest.expected @@ -0,0 +1,3 @@ +argumentToEnsureNotTaintedNotMarkedAsSpurious +untaintedArgumentToEnsureTaintedNotMarkedAsMissing +failures diff --git a/python/ql/test/library-tests/frameworks/idna/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/idna/InlineTaintTest.ql new file mode 100644 index 00000000000..027ad8667be --- /dev/null +++ b/python/ql/test/library-tests/frameworks/idna/InlineTaintTest.ql @@ -0,0 +1 @@ +import experimental.meta.InlineTaintTest diff --git a/python/ql/test/library-tests/frameworks/idna/taint_test.py b/python/ql/test/library-tests/frameworks/idna/taint_test.py new file mode 100644 index 00000000000..61b9dad166c --- /dev/null +++ b/python/ql/test/library-tests/frameworks/idna/taint_test.py @@ -0,0 +1,13 @@ +import idna + +def test_idna(): + ts = TAINTED_STRING + tb = TAINTED_BYTES + + ensure_tainted( + idna.encode(ts), # $ tainted encodeInput=ts encodeOutput=Attribute() encodeFormat=IDNA + idna.encode(s=ts), # $ tainted encodeInput=ts encodeOutput=Attribute() encodeFormat=IDNA + + idna.decode(tb), # $ tainted decodeInput=tb decodeOutput=Attribute() decodeFormat=IDNA + idna.decode(s=tb), # $ tainted decodeInput=tb decodeOutput=Attribute() decodeFormat=IDNA + ) diff --git a/python/ql/test/library-tests/frameworks/simplejson/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/simplejson/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/frameworks/simplejson/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/simplejson/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/simplejson/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/simplejson/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/simplejson/InlineTaintTest.expected new file mode 100644 index 00000000000..79d760d87f4 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/simplejson/InlineTaintTest.expected @@ -0,0 +1,3 @@ +argumentToEnsureNotTaintedNotMarkedAsSpurious +untaintedArgumentToEnsureTaintedNotMarkedAsMissing +failures diff --git a/python/ql/test/library-tests/frameworks/simplejson/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/simplejson/InlineTaintTest.ql new file mode 100644 index 00000000000..027ad8667be --- /dev/null +++ b/python/ql/test/library-tests/frameworks/simplejson/InlineTaintTest.ql @@ -0,0 +1 @@ +import experimental.meta.InlineTaintTest diff --git a/python/ql/test/library-tests/frameworks/simplejson/taint_test.py b/python/ql/test/library-tests/frameworks/simplejson/taint_test.py new file mode 100644 index 00000000000..6f5d45b0b33 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/simplejson/taint_test.py @@ -0,0 +1,46 @@ +import simplejson +from io import StringIO + +def test(): + ts = TAINTED_STRING + tainted_obj = {"foo": ts} + + encoded = simplejson.dumps(tainted_obj) # $ encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + + ensure_tainted( + encoded, # $ tainted + simplejson.dumps(tainted_obj), # $ tainted encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + simplejson.dumps(obj=tainted_obj), # $ tainted encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + simplejson.loads(encoded), # $ tainted decodeOutput=Attribute() decodeFormat=JSON decodeInput=encoded + simplejson.loads(s=encoded), # $ tainted decodeOutput=Attribute() decodeFormat=JSON decodeInput=encoded + ) + + # load/dump with file-like + tainted_filelike = StringIO() + simplejson.dump(tainted_obj, tainted_filelike) # $ encodeFormat=JSON encodeInput=tainted_obj + + tainted_filelike.seek(0) + ensure_tainted( + tainted_filelike, # $ MISSING: tainted + simplejson.load(tainted_filelike), # $ decodeOutput=Attribute() decodeFormat=JSON decodeInput=tainted_filelike MISSING: tainted + ) + + # load/dump with file-like using keyword-args + tainted_filelike = StringIO() + simplejson.dump(obj=tainted_obj, fp=tainted_filelike) # $ encodeFormat=JSON encodeInput=tainted_obj + + tainted_filelike.seek(0) + ensure_tainted( + tainted_filelike, # $ MISSING: tainted + simplejson.load(fp=tainted_filelike), # $ decodeOutput=Attribute() decodeFormat=JSON decodeInput=tainted_filelike MISSING: tainted + ) + +# To make things runable + +TAINTED_STRING = "TAINTED_STRING" +def ensure_tainted(*args): + print("- ensure_tainted") + for i, arg in enumerate(args): + print("arg {}: {!r}".format(i, arg)) + +test() diff --git a/python/ql/test/library-tests/frameworks/ujson/ConceptsTest.expected b/python/ql/test/library-tests/frameworks/ujson/ConceptsTest.expected new file mode 100644 index 00000000000..e69de29bb2d diff --git a/python/ql/test/library-tests/frameworks/ujson/ConceptsTest.ql b/python/ql/test/library-tests/frameworks/ujson/ConceptsTest.ql new file mode 100644 index 00000000000..b557a0bccb6 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/ujson/ConceptsTest.ql @@ -0,0 +1,2 @@ +import python +import experimental.meta.ConceptsTest diff --git a/python/ql/test/library-tests/frameworks/ujson/InlineTaintTest.expected b/python/ql/test/library-tests/frameworks/ujson/InlineTaintTest.expected new file mode 100644 index 00000000000..79d760d87f4 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/ujson/InlineTaintTest.expected @@ -0,0 +1,3 @@ +argumentToEnsureNotTaintedNotMarkedAsSpurious +untaintedArgumentToEnsureTaintedNotMarkedAsMissing +failures diff --git a/python/ql/test/library-tests/frameworks/ujson/InlineTaintTest.ql b/python/ql/test/library-tests/frameworks/ujson/InlineTaintTest.ql new file mode 100644 index 00000000000..027ad8667be --- /dev/null +++ b/python/ql/test/library-tests/frameworks/ujson/InlineTaintTest.ql @@ -0,0 +1 @@ +import experimental.meta.InlineTaintTest diff --git a/python/ql/test/library-tests/frameworks/ujson/taint_test.py b/python/ql/test/library-tests/frameworks/ujson/taint_test.py new file mode 100644 index 00000000000..2c965518393 --- /dev/null +++ b/python/ql/test/library-tests/frameworks/ujson/taint_test.py @@ -0,0 +1,44 @@ +import ujson +from io import StringIO + +def test(): + ts = TAINTED_STRING + tainted_obj = {"foo": ts} + + encoded = ujson.dumps(tainted_obj) # $ encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + + ensure_tainted( + encoded, # $ tainted + ujson.dumps(tainted_obj), # $ tainted encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + ujson.dumps(obj=tainted_obj), # $ tainted encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + ujson.loads(encoded), # $ tainted decodeOutput=Attribute() decodeFormat=JSON decodeInput=encoded + ujson.loads(obj=encoded), # $ tainted decodeOutput=Attribute() decodeFormat=JSON decodeInput=encoded + + ujson.encode(tainted_obj), # $ tainted encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + ujson.encode(obj=tainted_obj), # $ tainted encodeOutput=Attribute() encodeFormat=JSON encodeInput=tainted_obj + ujson.decode(encoded), # $ tainted decodeOutput=Attribute() decodeFormat=JSON decodeInput=encoded + ujson.decode(obj=encoded), # $ tainted decodeOutput=Attribute() decodeFormat=JSON decodeInput=encoded + ) + + # load/dump with file-like + tainted_filelike = StringIO() + ujson.dump(tainted_obj, tainted_filelike) # $ encodeFormat=JSON encodeInput=tainted_obj + + tainted_filelike.seek(0) + ensure_tainted( + tainted_filelike, # $ MISSING: tainted + ujson.load(tainted_filelike), # $ decodeOutput=Attribute() decodeFormat=JSON decodeInput=tainted_filelike MISSING: tainted + ) + + # load/dump with file-like using keyword-args does not work in `ujson` + + +# To make things runable + +TAINTED_STRING = "TAINTED_STRING" +def ensure_tainted(*args): + print("- ensure_tainted") + for i, arg in enumerate(args): + print("arg {}: {!r}".format(i, arg)) + +test()