From 05c8610bbc641f780fcd6a8e758634b91c48fb93 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 7 Feb 2020 16:22:09 -0800 Subject: [PATCH 001/236] C++: tests for alias analysis of malloc --- .../dataflow/security-taint/tainted.expected | 15 +++++++ .../security-taint/tainted_diff.expected | 9 ++++ .../security-taint/tainted_ir.expected | 6 +++ .../dataflow/security-taint/test.cpp | 13 ++++++ .../ir/ssa/aliased_ssa_ir.expected | 43 +++++++++++++++++++ .../ir/ssa/aliased_ssa_ir_unsound.expected | 43 +++++++++++++++++++ cpp/ql/test/library-tests/ir/ssa/ssa.cpp | 8 ++++ .../ir/ssa/unaliased_ssa_ir.expected | 40 +++++++++++++++++ .../ir/ssa/unaliased_ssa_ir_unsound.expected | 40 +++++++++++++++++ 9 files changed, 217 insertions(+) diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected index 7359b068d8e..a62b9b3e204 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted.expected @@ -52,3 +52,18 @@ | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected index ed954b5444a..c33772a39cc 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected @@ -8,3 +8,12 @@ | test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | AST only | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected index 114c213ff54..1b828c3cf1b 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected @@ -40,3 +40,9 @@ | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp b/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp index 5eb7ee79b46..9d28f9680ca 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp +++ b/cpp/ql/test/library-tests/dataflow/security-taint/test.cpp @@ -76,3 +76,16 @@ void guard() { if (len > 1000) return; char **node = (char **) malloc(len * sizeof(char *)); } + +const char *alias_global; + +void mallocBuffer() { + const char *userName = getenv("USER_NAME"); + char *alias = (char*)malloc(4096); + char *copy = (char*)malloc(4096); + strcpy(copy, userName); + alias_global = alias; // to force a Chi node on all aliased memory + if (!strcmp(copy, "admin")) { // copy should be tainted + isAdmin = true; + } +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index 5c12a3d2173..a1a2e4a0452 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -1240,3 +1240,46 @@ ssa.cpp: # 254| v254_10(void) = UnmodeledUse : mu* # 254| v254_11(void) = AliasedUse : ~m262_1 # 254| v254_12(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| m268_2(unknown) = AliasedDefinition : +# 268| m268_3(unknown) = InitializeNonLocal : +# 268| m268_4(unknown) = Chi : total:m268_2, partial:m268_3 +# 268| mu268_5(unknown) = UnmodeledDefinition : +# 268| r268_6(glval) = VariableAddress[s] : +# 268| m268_7(void *) = InitializeParameter[s] : &:r268_6 +# 268| r268_8(void *) = Load : &:r268_6, m268_7 +# 268| m268_9(unknown) = InitializeIndirection[s] : &:r268_8 +# 268| r268_10(glval) = VariableAddress[size] : +# 268| m268_11(int) = InitializeParameter[size] : &:r268_10 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_11 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| m269_6(unknown) = ^CallSideEffect : ~m268_9 +# 269| m269_7(unknown) = Chi : total:m268_9, partial:m269_6 +# 269| m269_8(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_8 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_7 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_11 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m269_7 +# 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 270| m270_11(unknown) = Chi : total:m269_7, partial:m270_10 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_8 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_12(void) = ReturnIndirection : &:r268_8, ~m270_11 +# 268| r268_13(glval) = VariableAddress[#return] : +# 268| v268_14(void) = ReturnValue : &:r268_13, m271_4 +# 268| v268_15(void) = UnmodeledUse : mu* +# 268| v268_16(void) = AliasedUse : ~m270_11 +# 268| v268_17(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index 2a012447a68..2bfb941ab3b 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -1235,3 +1235,46 @@ ssa.cpp: # 254| v254_10(void) = UnmodeledUse : mu* # 254| v254_11(void) = AliasedUse : ~m262_1 # 254| v254_12(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| m268_2(unknown) = AliasedDefinition : +# 268| m268_3(unknown) = InitializeNonLocal : +# 268| m268_4(unknown) = Chi : total:m268_2, partial:m268_3 +# 268| mu268_5(unknown) = UnmodeledDefinition : +# 268| r268_6(glval) = VariableAddress[s] : +# 268| m268_7(void *) = InitializeParameter[s] : &:r268_6 +# 268| r268_8(void *) = Load : &:r268_6, m268_7 +# 268| m268_9(unknown) = InitializeIndirection[s] : &:r268_8 +# 268| r268_10(glval) = VariableAddress[size] : +# 268| m268_11(int) = InitializeParameter[size] : &:r268_10 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_11 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| m269_6(unknown) = ^CallSideEffect : ~m268_4 +# 269| m269_7(unknown) = Chi : total:m268_4, partial:m269_6 +# 269| m269_8(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_8 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_7 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_11 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m268_9 +# 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 270| m270_11(unknown) = Chi : total:m269_7, partial:m270_10 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_8 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_12(void) = ReturnIndirection : &:r268_8, m268_9 +# 268| r268_13(glval) = VariableAddress[#return] : +# 268| v268_14(void) = ReturnValue : &:r268_13, m271_4 +# 268| v268_15(void) = UnmodeledUse : mu* +# 268| v268_16(void) = AliasedUse : ~m270_11 +# 268| v268_17(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp index 5bdfd254e8f..ee75a2f28e4 100644 --- a/cpp/ql/test/library-tests/ir/ssa/ssa.cpp +++ b/cpp/ql/test/library-tests/ir/ssa/ssa.cpp @@ -262,3 +262,11 @@ char StringLiteralAliasing2(bool b) { const char* s = "Literal"; return s[2]; } + +void *malloc(int size); + +void *MallocAliasing(void *s, int size) { + void *buf = malloc(size); + memcpy(buf, s, size); + return buf; +} \ No newline at end of file diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index 4319cb86587..545dd6f611c 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected @@ -1149,3 +1149,43 @@ ssa.cpp: # 254| v254_9(void) = UnmodeledUse : mu* # 254| v254_10(void) = AliasedUse : ~mu254_4 # 254| v254_11(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| mu268_2(unknown) = AliasedDefinition : +# 268| mu268_3(unknown) = InitializeNonLocal : +# 268| mu268_4(unknown) = UnmodeledDefinition : +# 268| r268_5(glval) = VariableAddress[s] : +# 268| m268_6(void *) = InitializeParameter[s] : &:r268_5 +# 268| r268_7(void *) = Load : &:r268_5, m268_6 +# 268| mu268_8(unknown) = InitializeIndirection[s] : &:r268_7 +# 268| r268_9(glval) = VariableAddress[size] : +# 268| m268_10(int) = InitializeParameter[size] : &:r268_9 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_10 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| mu269_6(unknown) = ^CallSideEffect : ~mu268_4 +# 269| m269_7(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_7 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_6 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_10 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~mu268_4 +# 270| mu270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_7 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_11(void) = ReturnIndirection : &:r268_7, ~mu268_4 +# 268| r268_12(glval) = VariableAddress[#return] : +# 268| v268_13(void) = ReturnValue : &:r268_12, m271_4 +# 268| v268_14(void) = UnmodeledUse : mu* +# 268| v268_15(void) = AliasedUse : ~mu268_4 +# 268| v268_16(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index 4319cb86587..545dd6f611c 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected @@ -1149,3 +1149,43 @@ ssa.cpp: # 254| v254_9(void) = UnmodeledUse : mu* # 254| v254_10(void) = AliasedUse : ~mu254_4 # 254| v254_11(void) = ExitFunction : + +# 268| void* MallocAliasing(void*, int) +# 268| Block 0 +# 268| v268_1(void) = EnterFunction : +# 268| mu268_2(unknown) = AliasedDefinition : +# 268| mu268_3(unknown) = InitializeNonLocal : +# 268| mu268_4(unknown) = UnmodeledDefinition : +# 268| r268_5(glval) = VariableAddress[s] : +# 268| m268_6(void *) = InitializeParameter[s] : &:r268_5 +# 268| r268_7(void *) = Load : &:r268_5, m268_6 +# 268| mu268_8(unknown) = InitializeIndirection[s] : &:r268_7 +# 268| r268_9(glval) = VariableAddress[size] : +# 268| m268_10(int) = InitializeParameter[size] : &:r268_9 +# 269| r269_1(glval) = VariableAddress[buf] : +# 269| r269_2(glval) = FunctionAddress[malloc] : +# 269| r269_3(glval) = VariableAddress[size] : +# 269| r269_4(int) = Load : &:r269_3, m268_10 +# 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 +# 269| mu269_6(unknown) = ^CallSideEffect : ~mu268_4 +# 269| m269_7(void *) = Store : &:r269_1, r269_5 +# 270| r270_1(glval) = FunctionAddress[memcpy] : +# 270| r270_2(glval) = VariableAddress[buf] : +# 270| r270_3(void *) = Load : &:r270_2, m269_7 +# 270| r270_4(glval) = VariableAddress[s] : +# 270| r270_5(void *) = Load : &:r270_4, m268_6 +# 270| r270_6(glval) = VariableAddress[size] : +# 270| r270_7(int) = Load : &:r270_6, m268_10 +# 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~mu268_4 +# 270| mu270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 +# 271| r271_1(glval) = VariableAddress[#return] : +# 271| r271_2(glval) = VariableAddress[buf] : +# 271| r271_3(void *) = Load : &:r271_2, m269_7 +# 271| m271_4(void *) = Store : &:r271_1, r271_3 +# 268| v268_11(void) = ReturnIndirection : &:r268_7, ~mu268_4 +# 268| r268_12(glval) = VariableAddress[#return] : +# 268| v268_13(void) = ReturnValue : &:r268_12, m271_4 +# 268| v268_14(void) = UnmodeledUse : mu* +# 268| v268_15(void) = AliasedUse : ~mu268_4 +# 268| v268_16(void) = ExitFunction : From 7f69cdfb5672d976bd10975311e5c3da8c263932 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 7 Feb 2020 15:02:12 -0800 Subject: [PATCH 002/236] C++: Dynamic allocations in IR alias analysis --- .../code/cpp/ir/implementation/Opcode.qll | 5 +++ .../aliased_ssa/Instruction.qll | 14 +++++++++ .../internal/AliasConfiguration.qll | 31 +++++++++++++++++++ .../aliased_ssa/internal/AliasedSSA.qll | 8 +++-- .../cpp/ir/implementation/raw/Instruction.qll | 14 +++++++++ .../raw/internal/TranslatedCall.qll | 29 ++++++++++++----- .../raw/internal/TranslatedElement.qll | 4 ++- .../unaliased_ssa/Instruction.qll | 14 +++++++++ .../security-taint/tainted_diff.expected | 6 ---- .../security-taint/tainted_ir.expected | 6 ++++ .../ir/ssa/aliased_ssa_ir.expected | 11 ++++--- .../ir/ssa/aliased_ssa_ir_unsound.expected | 11 ++++--- .../ir/ssa/unaliased_ssa_ir.expected | 7 +++-- .../ir/ssa/unaliased_ssa_ir_unsound.expected | 7 +++-- 14 files changed, 135 insertions(+), 32 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index 886183c32ba..987f6ae3c36 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -82,6 +82,7 @@ private newtype TOpcode = TSizedBufferReadSideEffect() or TSizedBufferMustWriteSideEffect() or TSizedBufferMayWriteSideEffect() or + TInitializeDynamicAllocation() or TChi() or TInlineAsm() or TUnreached() or @@ -695,6 +696,10 @@ module Opcode { final override string toString() { result = "SizedBufferMayWriteSideEffect" } } + class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, TInitializeDynamicAllocation { + final override string toString() { result = "InitializeDynamicAllocation" } + } + class Chi extends Opcode, TChi { final override string toString() { result = "Chi" } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 4b993849046..432608372cf 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1340,6 +1340,20 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } + +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll index d9937294d70..82aec427a80 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -7,6 +7,9 @@ private newtype TAllocation = TVariableAllocation(IRVariable var) or TIndirectParameterAllocation(IRAutomaticUserVariable var) { exists(InitializeIndirectionInstruction instr | instr.getIRVariable() = var) + } or + TDynamicAllocation(CallInstruction call) { + exists(InitializeDynamicAllocationInstruction instr | instr.getPrimaryInstruction() = call) } /** @@ -95,3 +98,31 @@ class IndirectParameterAllocation extends Allocation, TIndirectParameterAllocati final override predicate alwaysEscapes() { none() } } + +class DynamicAllocation extends Allocation, TDynamicAllocation { + CallInstruction call; + + DynamicAllocation() { this = TDynamicAllocation(call) } + + final override string toString() { + result = call.toString() + " at " + call.getLocation() // TODO: make this both short and unique + } + + final override CallInstruction getABaseInstruction() { result = call } + + final override IRFunction getEnclosingIRFunction() { result = call.getEnclosingIRFunction() } + + final override Language::Location getLocation() { result = call.getLocation() } + + final override string getUniqueId() { result = call.getUniqueId() } + + final override IRType getIRType() { + result instanceof IRUnknownType // TODO: look at casts and sizes? + } + + final override predicate isReadOnly() { none() } + + final override predicate isAlwaysAllocatedOnStack() { none() } + + final override predicate alwaysEscapes() { none() } +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index 7f7a62acda3..078ff65b76a 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -68,8 +68,12 @@ private newtype TMemoryLocation = ) and languageType = type.getCanonicalLanguageType() } or - TEntireAllocationMemoryLocation(IndirectParameterAllocation var, boolean isMayAccess) { - isMayAccess = false or isMayAccess = true + TEntireAllocationMemoryLocation(Allocation var, boolean isMayAccess) { + ( + var instanceof IndirectParameterAllocation or + var instanceof DynamicAllocation + ) and + (isMayAccess = false or isMayAccess = true) } or TUnknownMemoryLocation(IRFunction irFunc, boolean isMayAccess) { isMayAccess = false or isMayAccess = true diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 4b993849046..432608372cf 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1340,6 +1340,20 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } + +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll index 93e5c43bf3b..ca2353c0a80 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll @@ -341,16 +341,31 @@ class TranslatedSideEffects extends TranslatedElement, TTranslatedSideEffects { ) } - override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) { none() } + override predicate hasInstruction(Opcode opcode, InstructionTag tag, CppType type) { + expr.getTarget() instanceof AllocationFunction and + opcode instanceof Opcode::InitializeDynamicAllocation and + tag = OnlyInstructionTag() and + type = getUnknownType() // TODO: precise type + } - override Instruction getFirstInstruction() { result = getChild(0).getFirstInstruction() } + override Instruction getFirstInstruction() { + if expr.getTarget() instanceof AllocationFunction + then result = getInstruction(OnlyInstructionTag()) + else result = getChild(0).getFirstInstruction() + } - override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { none() } + override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { + tag = OnlyInstructionTag() and + kind = gotoEdge() and + if exists(getChild(0)) + then result = getChild(0).getFirstInstruction() + else result = getParent().getChildSuccessor(this) + } - override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) { none() } - - override CppType getInstructionOperandType(InstructionTag tag, TypedOperandTag operandTag) { - none() + override Instruction getInstructionOperand(InstructionTag tag, OperandTag operandTag) { + tag = OnlyInstructionTag() and + operandTag = addressOperand() and + result = getPrimaryInstructionForSideEffect(OnlyInstructionTag()) } override Instruction getPrimaryInstructionForSideEffect(InstructionTag tag) { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll index 64dc7e869c2..81abc58495d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedElement.qll @@ -411,7 +411,9 @@ newtype TTranslatedElement = TTranslatedConditionDecl(ConditionDeclExpr expr) { not ignoreExpr(expr) } or // The side effects of a `Call` TTranslatedSideEffects(Call expr) { - exists(TTranslatedArgumentSideEffect(expr, _, _, _)) or expr instanceof ConstructorCall + exists(TTranslatedArgumentSideEffect(expr, _, _, _)) or + expr instanceof ConstructorCall or + expr.getTarget() instanceof AllocationFunction } or // A precise side effect of an argument to a `Call` TTranslatedArgumentSideEffect(Call call, Expr expr, int n, boolean isWrite) { ( diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 4b993849046..432608372cf 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1340,6 +1340,20 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } + +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected index c33772a39cc..ca905d71823 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_diff.expected @@ -8,12 +8,6 @@ | test.cpp:68:28:68:33 | call to getenv | test.cpp:69:10:69:13 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:70:12:70:15 | copy | AST only | | test.cpp:68:28:68:33 | call to getenv | test.cpp:71:12:71:15 | copy | AST only | -| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | AST only | | test.cpp:83:28:83:33 | call to getenv | test.cpp:11:20:11:21 | s1 | AST only | | test.cpp:83:28:83:33 | call to getenv | test.cpp:85:8:85:11 | copy | AST only | | test.cpp:83:28:83:33 | call to getenv | test.cpp:86:9:86:12 | copy | AST only | -| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | AST only | -| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | AST only | -| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | AST only | -| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | AST only | -| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | AST only | diff --git a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected index 1b828c3cf1b..1a7027d399c 100644 --- a/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected +++ b/cpp/ql/test/library-tests/dataflow/security-taint/tainted_ir.expected @@ -40,9 +40,15 @@ | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:15:75:18 | call to atoi | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:25 | call to getenv | | | test.cpp:75:20:75:25 | call to getenv | test.cpp:75:20:75:45 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:8:24:8:25 | s1 | | | test.cpp:83:28:83:33 | call to getenv | test.cpp:11:36:11:37 | s2 | | | test.cpp:83:28:83:33 | call to getenv | test.cpp:83:17:83:24 | userName | | | test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:33 | call to getenv | | | test.cpp:83:28:83:33 | call to getenv | test.cpp:83:28:83:46 | (const char *)... | | | test.cpp:83:28:83:33 | call to getenv | test.cpp:86:2:86:7 | call to strcpy | | | test.cpp:83:28:83:33 | call to getenv | test.cpp:86:15:86:22 | userName | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:6:88:27 | ! ... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:12 | call to strcmp | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:7:88:27 | (bool)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | (const char *)... | | +| test.cpp:83:28:83:33 | call to getenv | test.cpp:88:14:88:17 | copy | | diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected index a1a2e4a0452..26321220936 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir.expected @@ -1261,21 +1261,22 @@ ssa.cpp: # 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 # 269| m269_6(unknown) = ^CallSideEffect : ~m268_9 # 269| m269_7(unknown) = Chi : total:m268_9, partial:m269_6 -# 269| m269_8(void *) = Store : &:r269_1, r269_5 +# 269| m269_8(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_9(void *) = Store : &:r269_1, r269_5 # 270| r270_1(glval) = FunctionAddress[memcpy] : # 270| r270_2(glval) = VariableAddress[buf] : -# 270| r270_3(void *) = Load : &:r270_2, m269_8 +# 270| r270_3(void *) = Load : &:r270_2, m269_9 # 270| r270_4(glval) = VariableAddress[s] : # 270| r270_5(void *) = Load : &:r270_4, m268_7 # 270| r270_6(glval) = VariableAddress[size] : # 270| r270_7(int) = Load : &:r270_6, m268_11 # 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 -# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m269_7 +# 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m269_8 # 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 -# 270| m270_11(unknown) = Chi : total:m269_7, partial:m270_10 +# 270| m270_11(unknown) = Chi : total:m269_8, partial:m270_10 # 271| r271_1(glval) = VariableAddress[#return] : # 271| r271_2(glval) = VariableAddress[buf] : -# 271| r271_3(void *) = Load : &:r271_2, m269_8 +# 271| r271_3(void *) = Load : &:r271_2, m269_9 # 271| m271_4(void *) = Store : &:r271_1, r271_3 # 268| v268_12(void) = ReturnIndirection : &:r268_8, ~m270_11 # 268| r268_13(glval) = VariableAddress[#return] : diff --git a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected index 2bfb941ab3b..e886e907382 100644 --- a/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/aliased_ssa_ir_unsound.expected @@ -1256,10 +1256,11 @@ ssa.cpp: # 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 # 269| m269_6(unknown) = ^CallSideEffect : ~m268_4 # 269| m269_7(unknown) = Chi : total:m268_4, partial:m269_6 -# 269| m269_8(void *) = Store : &:r269_1, r269_5 +# 269| m269_8(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_9(void *) = Store : &:r269_1, r269_5 # 270| r270_1(glval) = FunctionAddress[memcpy] : # 270| r270_2(glval) = VariableAddress[buf] : -# 270| r270_3(void *) = Load : &:r270_2, m269_8 +# 270| r270_3(void *) = Load : &:r270_2, m269_9 # 270| r270_4(glval) = VariableAddress[s] : # 270| r270_5(void *) = Load : &:r270_4, m268_7 # 270| r270_6(glval) = VariableAddress[size] : @@ -1267,14 +1268,14 @@ ssa.cpp: # 270| r270_8(void *) = Call : func:r270_1, 0:r270_3, 1:r270_5, 2:r270_7 # 270| v270_9(void) = ^SizedBufferReadSideEffect[1] : &:r270_5, r270_7, ~m268_9 # 270| m270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 -# 270| m270_11(unknown) = Chi : total:m269_7, partial:m270_10 +# 270| m270_11(unknown) = Chi : total:m269_8, partial:m270_10 # 271| r271_1(glval) = VariableAddress[#return] : # 271| r271_2(glval) = VariableAddress[buf] : -# 271| r271_3(void *) = Load : &:r271_2, m269_8 +# 271| r271_3(void *) = Load : &:r271_2, m269_9 # 271| m271_4(void *) = Store : &:r271_1, r271_3 # 268| v268_12(void) = ReturnIndirection : &:r268_8, m268_9 # 268| r268_13(glval) = VariableAddress[#return] : # 268| v268_14(void) = ReturnValue : &:r268_13, m271_4 # 268| v268_15(void) = UnmodeledUse : mu* -# 268| v268_16(void) = AliasedUse : ~m270_11 +# 268| v268_16(void) = AliasedUse : ~m269_7 # 268| v268_17(void) = ExitFunction : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected index 545dd6f611c..8f5f1db2aa7 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir.expected @@ -1168,10 +1168,11 @@ ssa.cpp: # 269| r269_4(int) = Load : &:r269_3, m268_10 # 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 # 269| mu269_6(unknown) = ^CallSideEffect : ~mu268_4 -# 269| m269_7(void *) = Store : &:r269_1, r269_5 +# 269| mu269_7(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_8(void *) = Store : &:r269_1, r269_5 # 270| r270_1(glval) = FunctionAddress[memcpy] : # 270| r270_2(glval) = VariableAddress[buf] : -# 270| r270_3(void *) = Load : &:r270_2, m269_7 +# 270| r270_3(void *) = Load : &:r270_2, m269_8 # 270| r270_4(glval) = VariableAddress[s] : # 270| r270_5(void *) = Load : &:r270_4, m268_6 # 270| r270_6(glval) = VariableAddress[size] : @@ -1181,7 +1182,7 @@ ssa.cpp: # 270| mu270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 # 271| r271_1(glval) = VariableAddress[#return] : # 271| r271_2(glval) = VariableAddress[buf] : -# 271| r271_3(void *) = Load : &:r271_2, m269_7 +# 271| r271_3(void *) = Load : &:r271_2, m269_8 # 271| m271_4(void *) = Store : &:r271_1, r271_3 # 268| v268_11(void) = ReturnIndirection : &:r268_7, ~mu268_4 # 268| r268_12(glval) = VariableAddress[#return] : diff --git a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected index 545dd6f611c..8f5f1db2aa7 100644 --- a/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected +++ b/cpp/ql/test/library-tests/ir/ssa/unaliased_ssa_ir_unsound.expected @@ -1168,10 +1168,11 @@ ssa.cpp: # 269| r269_4(int) = Load : &:r269_3, m268_10 # 269| r269_5(void *) = Call : func:r269_2, 0:r269_4 # 269| mu269_6(unknown) = ^CallSideEffect : ~mu268_4 -# 269| m269_7(void *) = Store : &:r269_1, r269_5 +# 269| mu269_7(unknown) = ^InitializeDynamicAllocation : &:r269_5 +# 269| m269_8(void *) = Store : &:r269_1, r269_5 # 270| r270_1(glval) = FunctionAddress[memcpy] : # 270| r270_2(glval) = VariableAddress[buf] : -# 270| r270_3(void *) = Load : &:r270_2, m269_7 +# 270| r270_3(void *) = Load : &:r270_2, m269_8 # 270| r270_4(glval) = VariableAddress[s] : # 270| r270_5(void *) = Load : &:r270_4, m268_6 # 270| r270_6(glval) = VariableAddress[size] : @@ -1181,7 +1182,7 @@ ssa.cpp: # 270| mu270_10(unknown) = ^SizedBufferMustWriteSideEffect[0] : &:r270_3, r270_7 # 271| r271_1(glval) = VariableAddress[#return] : # 271| r271_2(glval) = VariableAddress[buf] : -# 271| r271_3(void *) = Load : &:r271_2, m269_7 +# 271| r271_3(void *) = Load : &:r271_2, m269_8 # 271| m271_4(void *) = Store : &:r271_1, r271_3 # 268| v268_11(void) = ReturnIndirection : &:r268_7, ~mu268_4 # 268| r268_12(glval) = VariableAddress[#return] : From 3ea351967468ec66ab1c91a488c33dac8525ac4e Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 7 Feb 2020 15:52:58 -0800 Subject: [PATCH 003/236] C++/C#: sync changes --- .../code/csharp/ir/implementation/Opcode.qll | 5 +++++ .../csharp/ir/implementation/raw/Instruction.qll | 14 ++++++++++++++ .../implementation/unaliased_ssa/Instruction.qll | 14 ++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index 886183c32ba..987f6ae3c36 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -82,6 +82,7 @@ private newtype TOpcode = TSizedBufferReadSideEffect() or TSizedBufferMustWriteSideEffect() or TSizedBufferMayWriteSideEffect() or + TInitializeDynamicAllocation() or TChi() or TInlineAsm() or TUnreached() or @@ -695,6 +696,10 @@ module Opcode { final override string toString() { result = "SizedBufferMayWriteSideEffect" } } + class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, TInitializeDynamicAllocation { + final override string toString() { result = "InitializeDynamicAllocation" } + } + class Chi extends Opcode, TChi { final override string toString() { result = "Chi" } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll index 4b993849046..432608372cf 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll @@ -1340,6 +1340,20 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } + +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll index 4b993849046..432608372cf 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1340,6 +1340,20 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio Instruction getSizeDef() { result = getAnOperand().(BufferSizeOperand).getDef() } } +/** + * + */ +class InitializeDynamicAllocationInstruction extends SideEffectInstruction { + InitializeDynamicAllocationInstruction() { + getOpcode() instanceof Opcode::InitializeDynamicAllocation + } + + final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + + final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } + +} + /** * An instruction representing a GNU or MSVC inline assembly statement. */ From 3266a8902376203ca95d44ba18affceaa1910dea Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Fri, 7 Feb 2020 16:56:57 -0800 Subject: [PATCH 004/236] C++: document DynamicAllocationInstruction --- .../ir/implementation/aliased_ssa/Instruction.qll | 12 +++++++++--- .../code/cpp/ir/implementation/raw/Instruction.qll | 12 +++++++++--- .../ir/implementation/unaliased_ssa/Instruction.qll | 12 +++++++++--- .../csharp/ir/implementation/raw/Instruction.qll | 12 +++++++++--- .../ir/implementation/unaliased_ssa/Instruction.qll | 12 +++++++++--- 5 files changed, 45 insertions(+), 15 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 432608372cf..f63f349e415 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1341,17 +1341,23 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio } /** - * + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc` */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { getOpcode() instanceof Opcode::InitializeDynamicAllocation } - + + /** + * Gets the address of the allocation this instruction is initializing. + */ final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + /** + * Gets the operand for the allocation this instruction is initializing. + */ final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } - } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 432608372cf..f63f349e415 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1341,17 +1341,23 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio } /** - * + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc` */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { getOpcode() instanceof Opcode::InitializeDynamicAllocation } - + + /** + * Gets the address of the allocation this instruction is initializing. + */ final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + /** + * Gets the operand for the allocation this instruction is initializing. + */ final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } - } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 432608372cf..f63f349e415 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1341,17 +1341,23 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio } /** - * + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc` */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { getOpcode() instanceof Opcode::InitializeDynamicAllocation } - + + /** + * Gets the address of the allocation this instruction is initializing. + */ final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + /** + * Gets the operand for the allocation this instruction is initializing. + */ final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } - } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll index 432608372cf..f63f349e415 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll @@ -1341,17 +1341,23 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio } /** - * + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc` */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { getOpcode() instanceof Opcode::InitializeDynamicAllocation } - + + /** + * Gets the address of the allocation this instruction is initializing. + */ final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + /** + * Gets the operand for the allocation this instruction is initializing. + */ final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } - } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll index 432608372cf..f63f349e415 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1341,17 +1341,23 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio } /** - * + * An instruction representing the initial value of newly allocated memory, e.g. the result of a + * call to `malloc` */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { getOpcode() instanceof Opcode::InitializeDynamicAllocation } - + + /** + * Gets the address of the allocation this instruction is initializing. + */ final AddressOperand getAllocationAddressOperand() { result = getAnOperand() } + /** + * Gets the operand for the allocation this instruction is initializing. + */ final Instruction getAllocationAddress() { result = getAllocationAddressOperand().getDef() } - } /** From f467260815313a2f7c35a4a37347aa959e97c73a Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 11 Feb 2020 12:17:46 -0800 Subject: [PATCH 005/236] C++: respond to PR comments. --- .../code/cpp/ir/implementation/aliased_ssa/Instruction.qll | 2 +- .../aliased_ssa/internal/AliasConfiguration.qll | 6 ++---- .../semmle/code/cpp/ir/implementation/raw/Instruction.qll | 2 +- .../cpp/ir/implementation/raw/internal/TranslatedCall.qll | 3 ++- .../cpp/ir/implementation/unaliased_ssa/Instruction.qll | 2 +- cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll | 2 +- .../code/csharp/ir/implementation/raw/Instruction.qll | 2 +- .../csharp/ir/implementation/unaliased_ssa/Instruction.qll | 2 +- 8 files changed, 10 insertions(+), 11 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index f63f349e415..981aa52bdab 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1342,7 +1342,7 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio /** * An instruction representing the initial value of newly allocated memory, e.g. the result of a - * call to `malloc` + * call to `malloc`. */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll index 82aec427a80..fa0c625be9e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -105,7 +105,7 @@ class DynamicAllocation extends Allocation, TDynamicAllocation { DynamicAllocation() { this = TDynamicAllocation(call) } final override string toString() { - result = call.toString() + " at " + call.getLocation() // TODO: make this both short and unique + result = call.toString() + " at " + call.getLocation() // This isn't performant, but it's only used in test/dump code right not } final override CallInstruction getABaseInstruction() { result = call } @@ -116,9 +116,7 @@ class DynamicAllocation extends Allocation, TDynamicAllocation { final override string getUniqueId() { result = call.getUniqueId() } - final override IRType getIRType() { - result instanceof IRUnknownType // TODO: look at casts and sizes? - } + final override IRType getIRType() { result instanceof IRUnknownType } final override predicate isReadOnly() { none() } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index f63f349e415..981aa52bdab 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1342,7 +1342,7 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio /** * An instruction representing the initial value of newly allocated memory, e.g. the result of a - * call to `malloc` + * call to `malloc`. */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll index ca2353c0a80..ba763a4c03c 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll @@ -345,7 +345,7 @@ class TranslatedSideEffects extends TranslatedElement, TTranslatedSideEffects { expr.getTarget() instanceof AllocationFunction and opcode instanceof Opcode::InitializeDynamicAllocation and tag = OnlyInstructionTag() and - type = getUnknownType() // TODO: precise type + type = getUnknownType() } override Instruction getFirstInstruction() { @@ -357,6 +357,7 @@ class TranslatedSideEffects extends TranslatedElement, TTranslatedSideEffects { override Instruction getInstructionSuccessor(InstructionTag tag, EdgeKind kind) { tag = OnlyInstructionTag() and kind = gotoEdge() and + expr.getTarget() instanceof AllocationFunction and if exists(getChild(0)) then result = getChild(0).getFirstInstruction() else result = getParent().getChildSuccessor(this) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index f63f349e415..981aa52bdab 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1342,7 +1342,7 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio /** * An instruction representing the initial value of newly allocated memory, e.g. the result of a - * call to `malloc` + * call to `malloc`. */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { diff --git a/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll b/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll index 27870fb3671..e20dfd83efd 100644 --- a/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll @@ -2,4 +2,4 @@ * Support for tracking tainted data through the program. */ -import TaintTrackingImpl +import semmle.code.cpp.ir.dataflow.DefaultTaintTracking diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll index f63f349e415..981aa52bdab 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll @@ -1342,7 +1342,7 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio /** * An instruction representing the initial value of newly allocated memory, e.g. the result of a - * call to `malloc` + * call to `malloc`. */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll index f63f349e415..981aa52bdab 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1342,7 +1342,7 @@ class SizedBufferMayWriteSideEffectInstruction extends WriteSideEffectInstructio /** * An instruction representing the initial value of newly allocated memory, e.g. the result of a - * call to `malloc` + * call to `malloc`. */ class InitializeDynamicAllocationInstruction extends SideEffectInstruction { InitializeDynamicAllocationInstruction() { From 837fe84cec576c80da94649889e0e140eefd4d80 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Tue, 11 Feb 2020 12:18:45 -0800 Subject: [PATCH 006/236] C++/C#: autoformat Opcode.qll --- cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll | 3 ++- csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index 987f6ae3c36..33bb0895d8c 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -696,7 +696,8 @@ module Opcode { final override string toString() { result = "SizedBufferMayWriteSideEffect" } } - class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, TInitializeDynamicAllocation { + class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, + TInitializeDynamicAllocation { final override string toString() { result = "InitializeDynamicAllocation" } } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index 987f6ae3c36..33bb0895d8c 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -696,7 +696,8 @@ module Opcode { final override string toString() { result = "SizedBufferMayWriteSideEffect" } } - class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, TInitializeDynamicAllocation { + class InitializeDynamicAllocation extends SideEffectOpcode, EntireAllocationWriteOpcode, + TInitializeDynamicAllocation { final override string toString() { result = "InitializeDynamicAllocation" } } From 52b164434d488ffc440dee7bf4fbf89bf299cdf5 Mon Sep 17 00:00:00 2001 From: Robert Marsh Date: Wed, 12 Feb 2020 15:23:30 -0800 Subject: [PATCH 007/236] C++: remove accidental commit --- cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll b/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll index e20dfd83efd..27870fb3671 100644 --- a/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/security/TaintTracking.qll @@ -2,4 +2,4 @@ * Support for tracking tainted data through the program. */ -import semmle.code.cpp.ir.dataflow.DefaultTaintTracking +import TaintTrackingImpl From 8748a1218deb86e4553c51ae910145c9a9e2a93a Mon Sep 17 00:00:00 2001 From: Tom Hvitved Date: Thu, 13 Feb 2020 13:02:46 +0100 Subject: [PATCH 008/236] C#: Generate fresh TRAP ID for local functions --- .../Entities/LocalFunction.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs index f58c027eca5..e570802ab3d 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp/Entities/LocalFunction.cs @@ -1,5 +1,6 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; using System.IO; using System.Linq; @@ -13,16 +14,12 @@ namespace Semmle.Extraction.CSharp.Entities public override void WriteId(TextWriter trapFile) { - trapFile.WriteSubId(ContainingType); - trapFile.Write("."); - trapFile.WriteSubId(Location); - if (symbol.IsGenericMethod && !IsSourceDeclaration) - { - trapFile.Write('<'); - trapFile.BuildList(",", symbol.TypeArguments, (ta, tb0) => AddSignatureTypeToId(Context, tb0, symbol, ta)); - trapFile.Write('>'); - } - trapFile.Write(";localfunction"); + throw new InvalidOperationException(); + } + + public override void WriteQuotedId(TextWriter trapFile) + { + trapFile.Write('*'); } public static new LocalFunction Create(Context cx, IMethodSymbol field) => LocalFunctionFactory.Instance.CreateEntity(cx, field); From 9e1ea01be82d29ed32a33ff53c8a9673ff21abc9 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 13 Feb 2020 13:01:09 -0700 Subject: [PATCH 009/236] Fix typo --- .../implementation/aliased_ssa/internal/AliasConfiguration.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll index fa0c625be9e..e95086c89fc 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasConfiguration.qll @@ -105,7 +105,7 @@ class DynamicAllocation extends Allocation, TDynamicAllocation { DynamicAllocation() { this = TDynamicAllocation(call) } final override string toString() { - result = call.toString() + " at " + call.getLocation() // This isn't performant, but it's only used in test/dump code right not + result = call.toString() + " at " + call.getLocation() // This isn't performant, but it's only used in test/dump code right now. } final override CallInstruction getABaseInstruction() { result = call } From 3b45fbc87c923859137ff2c946a119550b6caadb Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 13 Feb 2020 14:22:00 -0800 Subject: [PATCH 010/236] Adds rough modernization. --- python/ql/src/Expressions/UseofApply.ql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql index 8f913e6980d..999d96b7198 100644 --- a/python/ql/src/Expressions/UseofApply.ql +++ b/python/ql/src/Expressions/UseofApply.ql @@ -10,8 +10,10 @@ */ import python +private import semmle.python.objects.TObject +private import semmle.python.types.Builtins from CallNode call, ControlFlowNode func where -major_version() = 2 and call.getFunction() = func and func.refersTo(Object::builtin("apply")) +major_version() = 2 and call.getFunction() = func and func.pointsTo(TBuiltinFunctionObject(Builtin::special("apply"))) select call, "Call to the obsolete builtin function 'apply'." From b665f54a31e725defabef26feebb89c131ffa936 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 13 Feb 2020 14:48:46 -0800 Subject: [PATCH 011/236] Corrects query to use `builtin` instead of `special` --- python/ql/src/Expressions/UseofApply.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql index 999d96b7198..dc1ef849854 100644 --- a/python/ql/src/Expressions/UseofApply.ql +++ b/python/ql/src/Expressions/UseofApply.ql @@ -15,5 +15,5 @@ private import semmle.python.types.Builtins from CallNode call, ControlFlowNode func where -major_version() = 2 and call.getFunction() = func and func.pointsTo(TBuiltinFunctionObject(Builtin::special("apply"))) -select call, "Call to the obsolete builtin function 'apply'." +major_version() = 2 and call.getFunction() = func and func.pointsTo(TBuiltinFunctionObject(Builtin::builtin("apply"))) +select call, "Call to the obsolete builtin function 'apply'." \ No newline at end of file From 6a04004d94425bbd5eac8c25e2f2f98a4b10b298 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 13 Feb 2020 14:49:01 -0800 Subject: [PATCH 012/236] Adds test cases and qlref. --- .../Expressions/general/UseofApply.expected | 1 + .../Expressions/general/UseofApply.qlref | 1 + .../Expressions/general/expressions_test.py | 37 +++++++++++++++++++ 3 files changed, 39 insertions(+) create mode 100644 python/ql/test/query-tests/Expressions/general/UseofApply.expected create mode 100644 python/ql/test/query-tests/Expressions/general/UseofApply.qlref diff --git a/python/ql/test/query-tests/Expressions/general/UseofApply.expected b/python/ql/test/query-tests/Expressions/general/UseofApply.expected new file mode 100644 index 00000000000..99a64a8e895 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/UseofApply.expected @@ -0,0 +1 @@ +| expressions_test.py:265:3:265:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | diff --git a/python/ql/test/query-tests/Expressions/general/UseofApply.qlref b/python/ql/test/query-tests/Expressions/general/UseofApply.qlref new file mode 100644 index 00000000000..abf684e3918 --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/UseofApply.qlref @@ -0,0 +1 @@ +Expressions/UseofApply.ql diff --git a/python/ql/test/query-tests/Expressions/general/expressions_test.py b/python/ql/test/query-tests/Expressions/general/expressions_test.py index 1d802f1bfd6..5e07b58e204 100644 --- a/python/ql/test/query-tests/Expressions/general/expressions_test.py +++ b/python/ql/test/query-tests/Expressions/general/expressions_test.py @@ -242,3 +242,40 @@ from types import MappingProxyType def mpt_arg(d=MappingProxyType({})): return 1 in d + + + + + + + +#### UseofApply.ql + +# Use of the builtin function `apply` is generally considered bad now that the +# ability to destructure lists of arguments is possible, but we should not flag +# cases where the function is merely named `apply` rather than being the actual +# builtin `apply` function. + +def useofapply(): + + def foo(): + pass + + + + # Positive Cases + + # This use of `apply` is a reference to the builtin function and so SHOULD be + # caught by the query. + apply(foo, [1]) + + + + # Negative Cases + + # This use of `apply` is a reference to the locally defined function inside of + # `local`, and so SHOULD NOT be caught by the query. + def local(): + def apply(f): + pass + apply(foo)([1]) From a2c1d5ff452c5be0052e07056c746c20e50e83ff Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 17 Feb 2020 11:46:53 -0800 Subject: [PATCH 013/236] Moves to higher level API --- python/ql/src/Expressions/UseofApply.ql | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql index dc1ef849854..1ba7876eea7 100644 --- a/python/ql/src/Expressions/UseofApply.ql +++ b/python/ql/src/Expressions/UseofApply.ql @@ -10,10 +10,9 @@ */ import python -private import semmle.python.objects.TObject private import semmle.python.types.Builtins from CallNode call, ControlFlowNode func where -major_version() = 2 and call.getFunction() = func and func.pointsTo(TBuiltinFunctionObject(Builtin::builtin("apply"))) +major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply")) select call, "Call to the obsolete builtin function 'apply'." \ No newline at end of file From 13cd8d2435d8807491986d3403f889ef37a95e82 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 17 Feb 2020 11:47:03 -0800 Subject: [PATCH 014/236] Fixes expected results bug --- .../ql/test/query-tests/Expressions/general/UseofApply.expected | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/test/query-tests/Expressions/general/UseofApply.expected b/python/ql/test/query-tests/Expressions/general/UseofApply.expected index 99a64a8e895..1f21475f56a 100644 --- a/python/ql/test/query-tests/Expressions/general/UseofApply.expected +++ b/python/ql/test/query-tests/Expressions/general/UseofApply.expected @@ -1 +1 @@ -| expressions_test.py:265:3:265:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | +| expressions_test.py:270:3:270:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | From d0617ef7bcd38bf17172c638d93235d0deff212a Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 18 Feb 2020 09:00:31 -0800 Subject: [PATCH 015/236] Autoformat --- python/ql/src/Expressions/UseofApply.ql | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Expressions/UseofApply.ql b/python/ql/src/Expressions/UseofApply.ql index 1ba7876eea7..7a0d72b43cf 100644 --- a/python/ql/src/Expressions/UseofApply.ql +++ b/python/ql/src/Expressions/UseofApply.ql @@ -13,6 +13,5 @@ import python private import semmle.python.types.Builtins from CallNode call, ControlFlowNode func -where -major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply")) -select call, "Call to the obsolete builtin function 'apply'." \ No newline at end of file +where major_version() = 2 and call.getFunction() = func and func.pointsTo(Value::named("apply")) +select call, "Call to the obsolete builtin function 'apply'." From 73a7d406a543cfaa63b21d3b80d1cafba133f80a Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 18 Feb 2020 18:54:27 +0100 Subject: [PATCH 016/236] add query for useless use of cat --- .../ql/src/Security/CWE-078/UselessCat.ql | 20 +++++ .../javascript/security/UselessUseOfCat.qll | 59 +++++++++++++++ .../CWE-078/CommandInjection.expected | 9 +++ .../Security/CWE-078/UselessCat.expected | 2 + .../Security/CWE-078/UselessCat.ql | 18 +++++ .../Security/CWE-078/uselesscat.js | 73 +++++++++++++++++++ 6 files changed, 181 insertions(+) create mode 100644 javascript/ql/src/Security/CWE-078/UselessCat.ql create mode 100644 javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll create mode 100644 javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected create mode 100644 javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql create mode 100644 javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js diff --git a/javascript/ql/src/Security/CWE-078/UselessCat.ql b/javascript/ql/src/Security/CWE-078/UselessCat.ql new file mode 100644 index 00000000000..d2f0e13fe2e --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/UselessCat.ql @@ -0,0 +1,20 @@ +/** + * @name Useless use of cat + * @description Using cat to simply read a file can lead to unintended bugs, and at worst security issues. + * @kind problem + * @problem.severity error + * @precision high + * @id js/useless-use-of-cat + * @tags correctness + * security + * external/cwe/cwe-078 + * external/cwe/cwe-088 + */ + +import javascript +import semmle.javascript.security.UselessUseOfCat + +from UselessCat cat +select cat.getCommand(), "Useless use of `cat` in $@.", cat, "command execution" + + diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll new file mode 100644 index 00000000000..caf6c2a666a --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -0,0 +1,59 @@ +/** + * Provides predicates and classes for working with useless uses of `cat`. + */ + +import javascript +import semmle.javascript.security.dataflow.IndirectCommandArgument + +/** + * Gets the first string from a string/string-concatenation. + */ +private string getStartingString(DataFlow::Node node) { + node.mayHaveStringValue(result) or + node.(StringOps::ConcatenationRoot).getFirstLeaf().mayHaveStringValue(result) +} + +/** + * Gets a string from a string/string-concatenation. + */ +private string getAString(DataFlow::Node node) { + node.mayHaveStringValue(result) or + node.(StringOps::ConcatenationRoot).getALeaf().mayHaveStringValue(result) +} + +/** + * An command-line execution of `cat` that only reads a file. + */ +class UselessCat extends DataFlow::Node { + DataFlow::Node command; + + UselessCat() { + command = this.(SystemCommandExecution).getACommandArgument() and + exists(string cat | + cat = "cat" or cat = "/bin/cat" or cat = "sudo cat" or cat = "sudo /bin/cat" + | + exists(string commandString | + commandString = getStartingString(command).trim() and + (commandString = cat or commandString.regexpMatch(cat + " .*")) + ) and + // `cat` is OK in combination with pipes and wildcards. + not getAString(command).regexpMatch(".*(\\*|\\|).*") and + // It is OK just to spawn "cat" without any arguments. + not ( + command.mayHaveStringValue(cat) and + not exists( + this + .(SystemCommandExecution) + .getArgumentList() + .(DataFlow::ArrayCreationNode) + .getAnElement() + ) + ) + ) + } + + /** + * Gets the dataflow node determining the command executed. + */ + DataFlow::Node getCommand() { result = command } +} diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected index 589deda96c9..3211c119645 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected @@ -89,6 +89,10 @@ nodes | third-party-command-injection.js:5:20:5:26 | command | | third-party-command-injection.js:6:21:6:27 | command | | third-party-command-injection.js:6:21:6:27 | command | +| uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | +| uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | +| uselesscat.js:18:40:18:54 | req.params.data | +| uselesscat.js:18:40:18:54 | req.params.data | edges | child_process-test.js:6:9:6:49 | cmd | child_process-test.js:17:13:17:15 | cmd | | child_process-test.js:6:9:6:49 | cmd | child_process-test.js:17:13:17:15 | cmd | @@ -178,6 +182,10 @@ edges | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | +| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | +| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | +| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | +| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | #select | child_process-test.js:17:13:17:15 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:17:13:17:15 | cmd | This command depends on $@. | child_process-test.js:6:25:6:31 | req.url | a user-provided value | | child_process-test.js:18:17:18:19 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:18:17:18:19 | cmd | This command depends on $@. | child_process-test.js:6:25:6:31 | req.url | a user-provided value | @@ -211,3 +219,4 @@ edges | other.js:18:22:18:24 | cmd | other.js:5:25:5:31 | req.url | other.js:18:22:18:24 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value | | other.js:19:36:19:38 | cmd | other.js:5:25:5:31 | req.url | other.js:19:36:19:38 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value | | third-party-command-injection.js:6:21:6:27 | command | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | This command depends on $@. | third-party-command-injection.js:5:20:5:26 | command | a server-provided value | +| uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | This command depends on $@. | uselesscat.js:18:40:18:54 | req.params.data | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected new file mode 100644 index 00000000000..b19c4833670 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected @@ -0,0 +1,2 @@ +| False negative | uselesscat.js:69:42:69:69 | // NOT ... lagged] | +| False positive | uselesscat.js:18:70:18:118 | // OK [ ... jection | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql new file mode 100644 index 00000000000..bfb342f7ad1 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql @@ -0,0 +1,18 @@ +import javascript +import semmle.javascript.security.UselessUseOfCat + +from LineComment comment, string msg +where + comment.getFile().getAbsolutePath().regexpMatch(".*/uselesscat.js") and + ( + comment.getText().regexpMatch(".*NOT OK.*") and + not any(UselessCat cat).asExpr().getLocation().getStartLine() = + comment.getLocation().getStartLine() and + msg = "False negative" + or + comment.getText().regexpMatch(".* OK.*") and + not comment.getText().regexpMatch(".*NOT OK.*") and + any(UselessCat cat).asExpr().getLocation().getStartLine() = comment.getLocation().getStartLine() and + msg = "False positive" + ) +select msg, comment diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js new file mode 100644 index 00000000000..d613908e8d3 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -0,0 +1,73 @@ +var express = require('express'); +var child_process = require('child_process'); +var execSync = child_process.execSync; +var exec = child_process.exec; +var spawn = child_process.spawn; +var spawnSync = child_process.spawnSync; +var fs = require('fs'); +var app = express(); + +function readStatus(id) { + exec("cat /proc/" + id + "/status", function (err, out) { // NOT OK + console.log(out); + }); +}; + +var basePath = '/foo/bar'; +app.get('/:data', function (req, res) { + res.send(execSync('cat ' + basePath + req.params.data).toString()); // OK [but flagged] - caught by command-injection +}); + +spawn('cat', ['package.json']); // NOT OK + +spawn('/bin/cat', ['package.json']); // NOT + +spawn('/bin/cat', [someValue]); // NOT OK +spawn('sudo /bin/cat', [someValue]); // NOT OK +spawn('sudo cat', [someValue]); // NOT OK + +child_process.spawnSync('cat', [someValue]); // NOT OK + +child_process.execSync('cat package.json | wc -l'); // OK - pipes! + +const cat = child_process.spawn('cat') // OK - benign use (usually pipe to and from). + +spawn('cat', ['package.json']); // NOT OK. + +exec('cat *.js') // OK - wildcard use. +exec('cat *.js | wc -l') // OK - wildcard use and pipes! +exec('cat *.js | wc -l', { cwd: './' }, function () { }); // OK - wildcard and pipes + +spawnSync('cat', ['/proc/cpuinfo']) // NOT OK. + +exec(`cat ${path.join(__dirname, 'package.json')} | sort | uniq`, () => {}); // OK: pipes + +execSync('cat /proc/cpuinfo').toString(); // NOT OK. + +var cmd = "cat /proc/cpuinfo" +execSync(cmd); // NOT OK + +execSync("cat /proc/cpuinfo | grep -c '" + someValue + "'"); // OK - pipes + +function cat(file) { + return execSync('cat ' + file).toString(); // NOT OK [flagged] +} + +execSync(`cat ${files.join(' ')} > ${outFile}`); // NOT OK [flagged] + +var cmd = 'cat package.json | grep' +exec(cmd); // OK - pipes! + +execSync("sudo cat " + newpath + "*.js | grep foo").toString(); // OK - wildcard and pipes + +execSync(`cat ${newpath}`); // NOT OK + +exec("cat /proc/cpuinfo | grep name"); // OK - pipes + +execSync(`cat ${newpath} | ${othertool}`); // OK - pipes + +execSync("sh -c 'cat " + newpath + "'"); // NOT OK. [but not flagged] + +exec(` cat ${newpath}`) // NOT OK + +exec(` cat ${newpath} | grep foo`) // OK - pipes \ No newline at end of file From e55f01d90502f7ae7c857be282d33753675864a6 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 18 Feb 2020 12:12:25 -0800 Subject: [PATCH 017/236] Adds new UseofApply test case and results to the Python2 tests dir --- .../Expressions/UseofApply.expected | 2 +- .../2/query-tests/Expressions/UseofApply.py | 30 +++++++++++++++++++ .../Expressions/expressions_test.py | 4 --- 3 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 python/ql/test/2/query-tests/Expressions/UseofApply.py diff --git a/python/ql/test/2/query-tests/Expressions/UseofApply.expected b/python/ql/test/2/query-tests/Expressions/UseofApply.expected index 082296ef25c..4c4d84b6ce5 100644 --- a/python/ql/test/2/query-tests/Expressions/UseofApply.expected +++ b/python/ql/test/2/query-tests/Expressions/UseofApply.expected @@ -1 +1 @@ -| expressions_test.py:3:5:3:21 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | +| UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | diff --git a/python/ql/test/2/query-tests/Expressions/UseofApply.py b/python/ql/test/2/query-tests/Expressions/UseofApply.py new file mode 100644 index 00000000000..9109636f99e --- /dev/null +++ b/python/ql/test/2/query-tests/Expressions/UseofApply.py @@ -0,0 +1,30 @@ +#### UseofApply.ql + +# Use of the builtin function `apply` is generally considered bad now that the +# ability to destructure lists of arguments is possible, but we should not flag +# cases where the function is merely named `apply` rather than being the actual +# builtin `apply` function. + +def useofapply(): + + def foo(): + pass + + + + # Positive Cases + + # This use of `apply` is a reference to the builtin function and so SHOULD be + # caught by the query. + apply(foo, [1]) + + + + # Negative Cases + + # This use of `apply` is a reference to the locally defined function inside of + # `local`, and so SHOULD NOT be caught by the query. + def local(): + def apply(f): + pass + apply(foo)([1]) diff --git a/python/ql/test/2/query-tests/Expressions/expressions_test.py b/python/ql/test/2/query-tests/Expressions/expressions_test.py index 943bcea98a8..61a0d702041 100644 --- a/python/ql/test/2/query-tests/Expressions/expressions_test.py +++ b/python/ql/test/2/query-tests/Expressions/expressions_test.py @@ -1,7 +1,3 @@ - -def use_of_apply(func, args): - apply(func, args) - def use_of_input(): return input() From 810efef9de231ba57b9f79dd3e056d132825dd6a Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 18 Feb 2020 15:02:47 -0800 Subject: [PATCH 018/236] Adds python3 test --- .../UseofApply/UseofApply.expected | 1 + .../Expressions/UseofApply/UseofApply.py | 30 +++++++++++++++++++ .../Expressions/UseofApply/UseofApply.qlref | 1 + 3 files changed, 32 insertions(+) create mode 100644 python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected create mode 100644 python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py create mode 100644 python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected new file mode 100644 index 00000000000..4c4d84b6ce5 --- /dev/null +++ b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected @@ -0,0 +1 @@ +| UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py new file mode 100644 index 00000000000..9109636f99e --- /dev/null +++ b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.py @@ -0,0 +1,30 @@ +#### UseofApply.ql + +# Use of the builtin function `apply` is generally considered bad now that the +# ability to destructure lists of arguments is possible, but we should not flag +# cases where the function is merely named `apply` rather than being the actual +# builtin `apply` function. + +def useofapply(): + + def foo(): + pass + + + + # Positive Cases + + # This use of `apply` is a reference to the builtin function and so SHOULD be + # caught by the query. + apply(foo, [1]) + + + + # Negative Cases + + # This use of `apply` is a reference to the locally defined function inside of + # `local`, and so SHOULD NOT be caught by the query. + def local(): + def apply(f): + pass + apply(foo)([1]) diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref new file mode 100644 index 00000000000..abf684e3918 --- /dev/null +++ b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.qlref @@ -0,0 +1 @@ +Expressions/UseofApply.ql From 344060e1395d56b6d33a13bd68c91aa6f82063c8 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 19 Feb 2020 10:12:24 +0100 Subject: [PATCH 019/236] accept IO redirections as OK --- .../javascript/security/UselessUseOfCat.qll | 4 ++-- .../Security/CWE-078/UselessCat.expected | 1 + .../query-tests/Security/CWE-078/uselesscat.js | 15 ++++++++++++--- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index caf6c2a666a..e5c5e8a0a1a 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -36,8 +36,8 @@ class UselessCat extends DataFlow::Node { commandString = getStartingString(command).trim() and (commandString = cat or commandString.regexpMatch(cat + " .*")) ) and - // `cat` is OK in combination with pipes and wildcards. - not getAString(command).regexpMatch(".*(\\*|\\|).*") and + // `cat` is OK in combination with pipes, wildcards, and redirections. + not getAString(command).regexpMatch(".*(\\*|\\||>|<).*") and // It is OK just to spawn "cat" without any arguments. not ( command.mayHaveStringValue(cat) and diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected index b19c4833670..67b59aa9d6e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected @@ -1,2 +1,3 @@ | False negative | uselesscat.js:69:42:69:69 | // NOT ... lagged] | | False positive | uselesscat.js:18:70:18:118 | // OK [ ... jection | +| False positive | uselesscat.js:82:80:82:128 | // OK ( ... / gid)) | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index d613908e8d3..4fe7ccd75d2 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -50,10 +50,10 @@ execSync(cmd); // NOT OK execSync("cat /proc/cpuinfo | grep -c '" + someValue + "'"); // OK - pipes function cat(file) { - return execSync('cat ' + file).toString(); // NOT OK [flagged] + return execSync('cat ' + file).toString(); // NOT OK } -execSync(`cat ${files.join(' ')} > ${outFile}`); // NOT OK [flagged] +execSync(`cat ${files.join(' ')} > ${outFile}`); // OK var cmd = 'cat package.json | grep' exec(cmd); // OK - pipes! @@ -70,4 +70,13 @@ execSync("sh -c 'cat " + newpath + "'"); // NOT OK. [but not flagged] exec(` cat ${newpath}`) // NOT OK -exec(` cat ${newpath} | grep foo`) // OK - pipes \ No newline at end of file +exec(` cat ${newpath} | grep foo`) // OK - pipes + +execSync('cat /proc/cpuinfo > foo/bar/baz').toString(); // OK. + +execSync(`cat ${newpath} > ${destpath}`).toString(); // OK. + +const Opts = {encoding: 'utf8'} +execSync(`cat foo/bar/${newpath}`, Opts).slice(0, 7); // NOT OK ("encoding" is used EXACTLY the same way in fs.readFileSync) + +execSync("/bin/cat /proc/cpuinfo", { uid: 1000, gid: 1000, encoding: 'utf8'}); // OK (fs.readFileSync cannot emulate uid / gid)) \ No newline at end of file From 381668871d5e5cbe0d59c64b63f9ad4568a38c37 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 6 Feb 2020 14:48:31 +0100 Subject: [PATCH 020/236] Python: Autoformat statements --- .../src/Statements/AssertLiteralConstant.ql | 4 +- python/ql/src/Statements/AssertOnTuple.ql | 17 +++--- .../src/Statements/BreakOrReturnInFinally.ql | 18 +++--- .../ql/src/Statements/C_StyleParentheses.ql | 33 ++++++----- .../src/Statements/ConstantInConditional.ql | 16 +++--- python/ql/src/Statements/DocStrings.ql | 21 +++---- python/ql/src/Statements/ExecUsed.ql | 4 +- .../Statements/IterableStringOrSequence.ql | 25 ++++---- .../MismatchInMultipleAssignment.ql | 31 +++++----- .../ql/src/Statements/ModificationOfLocals.ql | 9 ++- .../src/Statements/NestedLoopsSameVariable.ql | 15 ++--- .../NestedLoopsSameVariableWithReuse.ql | 18 +++--- .../ql/src/Statements/NonIteratorInForLoop.ql | 14 +++-- .../ql/src/Statements/RedundantAssignment.ql | 37 ++++++------ .../ReturnOrYieldOutsideFunction.ql | 16 +++--- .../src/Statements/ShouldUseWithStatement.ql | 26 +++++---- .../ql/src/Statements/SideEffectInAssert.ql | 17 ++++-- python/ql/src/Statements/StatementNoEffect.ql | 57 +++++++++---------- .../Statements/StringConcatenationInLoop.ql | 14 ++--- python/ql/src/Statements/TopLevelPrint.ql | 18 +++--- python/ql/src/Statements/UnnecessaryDelete.ql | 12 ++-- .../src/Statements/UnnecessaryElseClause.ql | 14 +++-- python/ql/src/Statements/UnnecessaryPass.ql | 19 +++---- .../src/Statements/UnusedExceptionObject.ql | 7 ++- python/ql/src/Statements/UseOfExit.ql | 4 +- 25 files changed, 249 insertions(+), 217 deletions(-) diff --git a/python/ql/src/Statements/AssertLiteralConstant.ql b/python/ql/src/Statements/AssertLiteralConstant.ql index bf575dd0e25..cdb39ff0955 100644 --- a/python/ql/src/Statements/AssertLiteralConstant.ql +++ b/python/ql/src/Statements/AssertLiteralConstant.ql @@ -26,7 +26,5 @@ where value = test.(NameConstant).toString() ) and /* Exclude asserts appearing at the end of a chain of `elif`s */ - not exists(If i | - i.getElif().getAnOrelse() = a - ) + not exists(If i | i.getElif().getAnOrelse() = a) select a, "Assert of literal constant " + value + "." diff --git a/python/ql/src/Statements/AssertOnTuple.ql b/python/ql/src/Statements/AssertOnTuple.ql index 8ca00f2391e..0a2c83f986c 100644 --- a/python/ql/src/Statements/AssertOnTuple.ql +++ b/python/ql/src/Statements/AssertOnTuple.ql @@ -14,11 +14,14 @@ import python from Assert a, string b, string non -where a.getTest() instanceof Tuple and - (if exists(((Tuple)a.getTest()).getAnElt()) then - (b = "True" and non = "non-") - else - (b = "False" and non = "") - ) +where + a.getTest() instanceof Tuple and + ( + if exists(a.getTest().(Tuple).getAnElt()) + then ( + b = "True" and non = "non-" + ) else ( + b = "False" and non = "" + ) + ) select a, "Assertion of " + non + "empty tuple is always " + b + "." - diff --git a/python/ql/src/Statements/BreakOrReturnInFinally.ql b/python/ql/src/Statements/BreakOrReturnInFinally.ql index 1d9bc7296c9..7dbc2fa8edd 100644 --- a/python/ql/src/Statements/BreakOrReturnInFinally.ql +++ b/python/ql/src/Statements/BreakOrReturnInFinally.ql @@ -15,13 +15,13 @@ import python from Stmt s, string kind -where -s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s)) -or -s instanceof Break and kind = "break" and -exists(Try t | t.getFinalbody().contains(s) | - not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop)) - and - not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop)) -) +where + s instanceof Return and kind = "return" and exists(Try t | t.getFinalbody().contains(s)) + or + s instanceof Break and + kind = "break" and + exists(Try t | t.getFinalbody().contains(s) | + not exists(For loop | loop.contains(s) and t.getFinalbody().contains(loop)) and + not exists(While loop | loop.contains(s) and t.getFinalbody().contains(loop)) + ) select s, "'" + kind + "' in a finally block will swallow any exceptions raised." diff --git a/python/ql/src/Statements/C_StyleParentheses.ql b/python/ql/src/Statements/C_StyleParentheses.ql index c670876e15a..1c0f27bf8b6 100644 --- a/python/ql/src/Statements/C_StyleParentheses.ql +++ b/python/ql/src/Statements/C_StyleParentheses.ql @@ -14,19 +14,22 @@ import python from Expr e, Location l, string kind, string what -where e.isParenthesized() and -not e instanceof Tuple and -( - exists(If i | i.getTest() = e) and kind = "if" and what = "condition" - or - exists(While w | w.getTest() = e) and kind = "while" and what = "condition" - or - exists(Return r | r.getValue() = e) and kind = "return" and what = "value" - or - exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and kind = "assert" and what = "test" -) -and -// These require parentheses -(not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and -l = e.getLocation() and l.getStartLine() = l.getEndLine() +where + e.isParenthesized() and + not e instanceof Tuple and + ( + exists(If i | i.getTest() = e) and kind = "if" and what = "condition" + or + exists(While w | w.getTest() = e) and kind = "while" and what = "condition" + or + exists(Return r | r.getValue() = e) and kind = "return" and what = "value" + or + exists(Assert a | a.getTest() = e and not exists(a.getMsg())) and + kind = "assert" and + what = "test" + ) and + // These require parentheses + (not e instanceof Yield and not e instanceof YieldFrom and not e instanceof GeneratorExp) and + l = e.getLocation() and + l.getStartLine() = l.getEndLine() select e, "Parenthesized " + what + " in '" + kind + "' statement." diff --git a/python/ql/src/Statements/ConstantInConditional.ql b/python/ql/src/Statements/ConstantInConditional.ql index 06a63cf037a..e01e693467a 100644 --- a/python/ql/src/Statements/ConstantInConditional.ql +++ b/python/ql/src/Statements/ConstantInConditional.ql @@ -15,16 +15,15 @@ import python - predicate is_condition(Expr cond) { - exists(If i | i.getTest() = cond) or - exists(IfExp ie | ie.getTest() = cond) + exists(If i | i.getTest() = cond) or + exists(IfExp ie | ie.getTest() = cond) } /* Treat certain unmodified builtins as constants as well. */ predicate effective_constant(Name cond) { exists(GlobalVariable var | var = cond.getVariable() and not exists(NameNode f | f.defines(var)) | - var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented" + var.getId() = "True" or var.getId() = "False" or var.getId() = "NotImplemented" ) } @@ -34,9 +33,10 @@ predicate test_makes_code_unreachable(Expr cond) { exists(While w | w.getTest() = cond and w.getStmt(0).isUnreachable()) } - from Expr cond -where is_condition(cond) and (cond.isConstant() or effective_constant(cond)) and -/* Ignore cases where test makes code unreachable, as that is handled in different query */ -not test_makes_code_unreachable(cond) +where + is_condition(cond) and + (cond.isConstant() or effective_constant(cond)) and + /* Ignore cases where test makes code unreachable, as that is handled in different query */ + not test_makes_code_unreachable(cond) select cond, "Testing a constant will always give the same result." diff --git a/python/ql/src/Statements/DocStrings.ql b/python/ql/src/Statements/DocStrings.ql index 4bf458bd22b..7c55c65e204 100644 --- a/python/ql/src/Statements/DocStrings.ql +++ b/python/ql/src/Statements/DocStrings.ql @@ -9,7 +9,9 @@ * @precision medium * @id py/missing-docstring */ -/* NOTE: precision of 'medium' reflects the lack of precision in the underlying rule. + +/* + * NOTE: precision of 'medium' reflects the lack of precision in the underlying rule. * Do we care whether a function has a docstring? That often depends on the reader of that docstring. */ @@ -18,25 +20,26 @@ import python predicate needs_docstring(Scope s) { s.isPublic() and ( - not s instanceof Function - or - function_needs_docstring(s) + not s instanceof Function + or + function_needs_docstring(s) ) } predicate function_needs_docstring(Function f) { not exists(FunctionObject fo, FunctionObject base | fo.overrides(base) and fo.getFunction() = f | - not function_needs_docstring(base.getFunction())) and + not function_needs_docstring(base.getFunction()) + ) and f.getName() != "lambda" and - (f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 - and not exists(PythonPropertyObject p | + (f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and + not exists(PythonPropertyObject p | p.getGetter().getFunction() = f or p.getSetter().getFunction() = f ) } string scope_type(Scope s) { - result = "Module" and s instanceof Module and not ((Module)s).isPackage() + result = "Module" and s instanceof Module and not s.(Module).isPackage() or result = "Class" and s instanceof Class or @@ -46,5 +49,3 @@ string scope_type(Scope s) { from Scope s where needs_docstring(s) and not exists(s.getDocString()) select s, scope_type(s) + " " + s.getName() + " does not have a docstring" - - diff --git a/python/ql/src/Statements/ExecUsed.ql b/python/ql/src/Statements/ExecUsed.ql index 37d92b77c54..44d4da02e32 100644 --- a/python/ql/src/Statements/ExecUsed.ql +++ b/python/ql/src/Statements/ExecUsed.ql @@ -19,9 +19,9 @@ string message() { } predicate exec_function_call(Call c) { - exists(GlobalVariable exec | exec = ((Name)c.getFunc()).getVariable() and exec.getId() = "exec") + exists(GlobalVariable exec | exec = c.getFunc().(Name).getVariable() and exec.getId() = "exec") } from AstNode exec where exec_function_call(exec) or exec instanceof Exec -select exec, message() \ No newline at end of file +select exec, message() diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index a44c3ae7286..96872a9a556 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -15,16 +15,19 @@ import python predicate is_a_string_type(ClassObject seqtype) { seqtype = theBytesType() and major_version() = 2 - or - seqtype = theUnicodeType() + or + seqtype = theUnicodeType() } -from For loop, ControlFlowNode iter, Object str, Object seq, ControlFlowNode seq_origin, ClassObject strtype, ClassObject seqtype, ControlFlowNode str_origin -where loop.getIter().getAFlowNode() = iter and -iter.refersTo(str, strtype, str_origin) and -iter.refersTo(seq, seqtype, seq_origin) and -is_a_string_type(strtype) and -seqtype.isIterable() and -not is_a_string_type(seqtype) - -select loop, "Iteration over $@, of class " + seqtype.getName() + ", may also iterate over $@.", seq_origin, "sequence", str_origin, "string" \ No newline at end of file +from + For loop, ControlFlowNode iter, Object str, Object seq, ControlFlowNode seq_origin, + ClassObject strtype, ClassObject seqtype, ControlFlowNode str_origin +where + loop.getIter().getAFlowNode() = iter and + iter.refersTo(str, strtype, str_origin) and + iter.refersTo(seq, seqtype, seq_origin) and + is_a_string_type(strtype) and + seqtype.isIterable() and + not is_a_string_type(seqtype) +select loop, "Iteration over $@, of class " + seqtype.getName() + ", may also iterate over $@.", + seq_origin, "sequence", str_origin, "string" diff --git a/python/ql/src/Statements/MismatchInMultipleAssignment.ql b/python/ql/src/Statements/MismatchInMultipleAssignment.ql index 8dee6d5eb5f..2c619c78857 100644 --- a/python/ql/src/Statements/MismatchInMultipleAssignment.ql +++ b/python/ql/src/Statements/MismatchInMultipleAssignment.ql @@ -14,18 +14,19 @@ import python -private int len(ExprList el) { - result = count(el.getAnItem()) -} +private int len(ExprList el) { result = count(el.getAnItem()) } predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequenceType) { exists(ExprList l, ExprList r | - (a.getATarget().(Tuple).getElts() = l or - a.getATarget().(List).getElts() = l) - and - ((a.getValue().(Tuple).getElts() = r and sequenceType = "tuple") or - (a.getValue().(List).getElts() = r and sequenceType = "list")) - and + ( + a.getATarget().(Tuple).getElts() = l or + a.getATarget().(List).getElts() = l + ) and + ( + a.getValue().(Tuple).getElts() = r and sequenceType = "tuple" + or + a.getValue().(List).getElts() = r and sequenceType = "list" + ) and loc = a.getValue().getLocation() and lcount = len(l) and rcount = len(r) and @@ -36,9 +37,10 @@ predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequ predicate mismatched_tuple_rhs(Assign a, int lcount, int rcount, Location loc) { exists(ExprList l, TupleObject r, AstNode origin | - (a.getATarget().(Tuple).getElts() = l or - a.getATarget().(List).getElts() = l) - and + ( + a.getATarget().(Tuple).getElts() = l or + a.getATarget().(List).getElts() = l + ) and a.getValue().refersTo(r, origin) and loc = origin.getLocation() and lcount = len(l) and @@ -48,11 +50,12 @@ predicate mismatched_tuple_rhs(Assign a, int lcount, int rcount, Location loc) { ) } - from Assign a, int lcount, int rcount, Location loc, string sequenceType where mismatched(a, lcount, rcount, loc, sequenceType) or mismatched_tuple_rhs(a, lcount, rcount, loc) and sequenceType = "tuple" -select a, "Left hand side of assignment contains " + lcount + " variables, but right hand side is a $@ of length " + rcount + "." , loc, sequenceType +select a, + "Left hand side of assignment contains " + lcount + + " variables, but right hand side is a $@ of length " + rcount + ".", loc, sequenceType diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql index a20843410ef..38e5ccabcf5 100644 --- a/python/ql/src/Statements/ModificationOfLocals.ql +++ b/python/ql/src/Statements/ModificationOfLocals.ql @@ -22,14 +22,14 @@ Object aFunctionLocalsObject() { ) } - - predicate modification_of_locals(ControlFlowNode f) { - f.(SubscriptNode).getObject().refersTo(aFunctionLocalsObject()) and (f.isStore() or f.isDelete()) + f.(SubscriptNode).getObject().refersTo(aFunctionLocalsObject()) and + (f.isStore() or f.isDelete()) or exists(string mname, AttrNode attr | attr = f.(CallNode).getFunction() and - attr.getObject(mname).refersTo(aFunctionLocalsObject(), _) | + attr.getObject(mname).refersTo(aFunctionLocalsObject(), _) + | mname = "pop" or mname = "popitem" or mname = "update" or @@ -39,5 +39,4 @@ predicate modification_of_locals(ControlFlowNode f) { from AstNode a, ControlFlowNode f where modification_of_locals(f) and a = f.getNode() - select a, "Modification of the locals() dictionary will have no effect on the local variables." diff --git a/python/ql/src/Statements/NestedLoopsSameVariable.ql b/python/ql/src/Statements/NestedLoopsSameVariable.ql index 6c1ed0f68ff..8e966077c16 100644 --- a/python/ql/src/Statements/NestedLoopsSameVariable.ql +++ b/python/ql/src/Statements/NestedLoopsSameVariable.ql @@ -10,20 +10,21 @@ * @precision very-high * @id py/nested-loops-with-same-variable */ + import python -predicate loop_variable(For f, Variable v) { - f.getTarget().defines(v) -} +predicate loop_variable(For f, Variable v) { f.getTarget().defines(v) } predicate variableUsedInNestedLoops(For inner, For outer, Variable v) { /* Only treat loops in body as inner loops. Loops in the else clause are ignored. */ - outer.getBody().contains(inner) and loop_variable(inner, v) and loop_variable(outer, v) + outer.getBody().contains(inner) and + loop_variable(inner, v) and + loop_variable(outer, v) and /* Ignore cases where there is no use of the variable or the only use is in the inner loop */ - and exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n)) + exists(Name n | n.uses(v) and outer.contains(n) and not inner.contains(n)) } from For inner, For outer, Variable v where variableUsedInNestedLoops(inner, outer, v) -select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.", - outer, "for statement" +select inner, "Nested for statement uses loop variable '" + v.getId() + "' of enclosing $@.", outer, + "for statement" diff --git a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql index 0082f8c3c1a..400c43d1d94 100644 --- a/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql +++ b/python/ql/src/Statements/NestedLoopsSameVariableWithReuse.ql @@ -19,18 +19,18 @@ predicate loop_variable_ssa(For f, Variable v, SsaVariable s) { predicate variableUsedInNestedLoops(For inner, For outer, Variable v, Name n) { /* Ignore cases where there is no use of the variable or the only use is in the inner loop. */ - outer.contains(n) - and not inner.contains(n) + outer.contains(n) and + not inner.contains(n) and /* Only treat loops in body as inner loops. Loops in the else clause are ignored. */ - and outer.getBody().contains(inner) - and exists(SsaVariable s | - loop_variable_ssa(inner, v, s.getAnUltimateDefinition()) - and loop_variable_ssa(outer, v, _) - and s.getAUse().getNode() = n + outer.getBody().contains(inner) and + exists(SsaVariable s | + loop_variable_ssa(inner, v, s.getAnUltimateDefinition()) and + loop_variable_ssa(outer, v, _) and + s.getAUse().getNode() = n ) } from For inner, For outer, Variable v, Name n where variableUsedInNestedLoops(inner, outer, v, n) -select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n, "uses", - outer, "for statement" \ No newline at end of file +select inner, "Nested for statement $@ loop variable '" + v.getId() + "' of enclosing $@.", n, + "uses", outer, "for statement" diff --git a/python/ql/src/Statements/NonIteratorInForLoop.ql b/python/ql/src/Statements/NonIteratorInForLoop.ql index 4c398c5e644..26c3005e5fc 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.ql +++ b/python/ql/src/Statements/NonIteratorInForLoop.ql @@ -14,10 +14,12 @@ import python from For loop, ControlFlowNode iter, Value v, ClassValue t, ControlFlowNode origin -where loop.getIter().getAFlowNode() = iter and -iter.pointsTo(_, v, origin) and v.getClass() = t and -not t.isIterable() and not t.failedInference(_) and -not v = Value::named("None") and -not t.isDescriptorType() - +where + loop.getIter().getAFlowNode() = iter and + iter.pointsTo(_, v, origin) and + v.getClass() = t and + not t.isIterable() and + not t.failedInference(_) and + not v = Value::named("None") and + not t.isDescriptorType() select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterator", t, t.getName() diff --git a/python/ql/src/Statements/RedundantAssignment.ql b/python/ql/src/Statements/RedundantAssignment.ql index 9395bf73141..9f9f41fa52b 100644 --- a/python/ql/src/Statements/RedundantAssignment.ql +++ b/python/ql/src/Statements/RedundantAssignment.ql @@ -12,8 +12,8 @@ */ import python -predicate assignment(AssignStmt a, Expr left, Expr right) -{ + +predicate assignment(AssignStmt a, Expr left, Expr right) { a.getATarget() = left and a.getValue() = right } @@ -23,7 +23,8 @@ predicate corresponding(Expr left, Expr right) { exists(Attribute la, Attribute ra | corresponding(la, ra) and left = la.getObject() and - right = ra.getObject()) + right = ra.getObject() + ) } predicate same_value(Expr left, Expr right) { @@ -33,9 +34,7 @@ predicate same_value(Expr left, Expr right) { } predicate maybe_defined_in_outer_scope(Name n) { - exists(SsaVariable v | v.getAUse().getNode() = n | - v.maybeUndefined() - ) + exists(SsaVariable v | v.getAUse().getNode() = n | v.maybeUndefined()) } Variable relevant_var(Name n) { @@ -50,17 +49,18 @@ predicate same_name(Name n1, Name n2) { not maybe_defined_in_outer_scope(n2) } -ClassObject value_type(Attribute a) { - a.getObject().refersTo(_, result, _) -} +ClassObject value_type(Attribute a) { a.getObject().refersTo(_, result, _) } predicate is_property_access(Attribute a) { value_type(a).lookupAttribute(a.getName()) instanceof PropertyObject } predicate same_attribute(Attribute a1, Attribute a2) { - corresponding(a1, a2) and a1.getName() = a2.getName() and same_value(a1.getObject(), a2.getObject()) and - exists(value_type(a1)) and not is_property_access(a1) + corresponding(a1, a2) and + a1.getName() = a2.getName() and + same_value(a1.getObject(), a2.getObject()) and + exists(value_type(a1)) and + not is_property_access(a1) } int pyflakes_commented_line(File file) { @@ -72,21 +72,24 @@ int pyflakes_commented_line(File file) { predicate pyflakes_commented(AssignStmt assignment) { exists(Location loc | assignment.getLocation() = loc and - loc.getStartLine() = pyflakes_commented_line(loc.getFile())) + loc.getStartLine() = pyflakes_commented_line(loc.getFile()) + ) } predicate side_effecting_lhs(Attribute lhs) { exists(ClassObject cls, ClassObject decl | lhs.getObject().refersTo(_, cls, _) and decl = cls.getAnImproperSuperType() and - not decl.isBuiltin() | + not decl.isBuiltin() + | decl.declaresAttribute("__setattr__") ) } from AssignStmt a, Expr left, Expr right -where assignment(a, left, right) - and same_value(left, right) - and not pyflakes_commented(a) and - not side_effecting_lhs(left) +where + assignment(a, left, right) and + same_value(left, right) and + not pyflakes_commented(a) and + not side_effecting_lhs(left) select a, "This assignment assigns a variable to itself." diff --git a/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql b/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql index a940dc60123..ff6b7f379d3 100644 --- a/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql +++ b/python/ql/src/Statements/ReturnOrYieldOutsideFunction.ql @@ -14,12 +14,12 @@ import python from AstNode node, string kind where - not node.getScope() instanceof Function and - ( - node instanceof Return and kind = "return" - or - node instanceof Yield and kind = "yield" - or - node instanceof YieldFrom and kind = "yield from" - ) + not node.getScope() instanceof Function and + ( + node instanceof Return and kind = "return" + or + node instanceof Yield and kind = "yield" + or + node instanceof YieldFrom and kind = "yield from" + ) select node, "'" + kind + "' is used outside a function." diff --git a/python/ql/src/Statements/ShouldUseWithStatement.ql b/python/ql/src/Statements/ShouldUseWithStatement.ql index 06b3b762db0..f86ba21b4b9 100644 --- a/python/ql/src/Statements/ShouldUseWithStatement.ql +++ b/python/ql/src/Statements/ShouldUseWithStatement.ql @@ -14,14 +14,12 @@ import python +predicate calls_close(Call c) { exists(Attribute a | c.getFunc() = a and a.getName() = "close") } -predicate calls_close(Call c) { - exists (Attribute a | c.getFunc() = a and a.getName() = "close") -} - -predicate -only_stmt_in_finally(Try t, Call c) { - exists(ExprStmt s | t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1) +predicate only_stmt_in_finally(Try t, Call c) { + exists(ExprStmt s | + t.getAFinalstmt() = s and s.getValue() = c and strictcount(t.getAFinalstmt()) = 1 + ) } predicate points_to_context_manager(ControlFlowNode f, ClassObject cls) { @@ -30,8 +28,12 @@ predicate points_to_context_manager(ControlFlowNode f, ClassObject cls) { } from Call close, Try t, ClassObject cls -where only_stmt_in_finally(t, close) and calls_close(close) and -exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() | - points_to_context_manager(f, cls)) -select close, "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", cls, cls.getName() - +where + only_stmt_in_finally(t, close) and + calls_close(close) and + exists(ControlFlowNode f | f = close.getFunc().getAFlowNode().(AttrNode).getObject() | + points_to_context_manager(f, cls) + ) +select close, + "Instance of context-manager class $@ is closed in a finally block. Consider using 'with' statement.", + cls, cls.getName() diff --git a/python/ql/src/Statements/SideEffectInAssert.ql b/python/ql/src/Statements/SideEffectInAssert.ql index e62685b7c33..faf10defcd1 100644 --- a/python/ql/src/Statements/SideEffectInAssert.ql +++ b/python/ql/src/Statements/SideEffectInAssert.ql @@ -14,11 +14,16 @@ import python predicate func_with_side_effects(Expr e) { - exists(string name | - name = ((Attribute)e).getName() or name = ((Name)e).getId() | - name = "print" or name = "write" or name = "append" or - name = "pop" or name = "remove" or name = "discard" or - name = "delete" or name = "close" or name = "open" or + exists(string name | name = e.(Attribute).getName() or name = e.(Name).getId() | + name = "print" or + name = "write" or + name = "append" or + name = "pop" or + name = "remove" or + name = "discard" or + name = "delete" or + name = "close" or + name = "open" or name = "exit" ) } @@ -29,7 +34,7 @@ predicate probable_side_effect(Expr e) { or e instanceof YieldFrom or - e instanceof Call and func_with_side_effects(((Call)e).getFunc()) + e instanceof Call and func_with_side_effects(e.(Call).getFunc()) } from Assert a, Expr e diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql index 2e5361bd946..2b46a6b8109 100644 --- a/python/ql/src/Statements/StatementNoEffect.ql +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -14,8 +14,7 @@ import python predicate understood_attribute(Attribute attr, ClassObject cls, ClassObject attr_cls) { - exists(string name | - attr.getName() = name | + exists(string name | attr.getName() = name | attr.getObject().refersTo(_, cls, _) and cls.attributeRefersTo(name, _, attr_cls, _) ) @@ -37,15 +36,19 @@ predicate maybe_side_effecting_attribute(Attribute attr) { predicate side_effecting_descriptor_type(ClassObject descriptor) { descriptor.isDescriptorType() and - /* Technically all descriptor gets have side effects, - * but some are indicative of a missing call and - * we want to treat them as having no effect. */ - not descriptor = thePyFunctionType() and - not descriptor = theStaticMethodType() and - not descriptor = theClassMethodType() + /* + * Technically all descriptor gets have side effects, + * but some are indicative of a missing call and + * we want to treat them as having no effect. + */ + + not descriptor = thePyFunctionType() and + not descriptor = theStaticMethodType() and + not descriptor = theClassMethodType() } -/** Side effecting binary operators are rare, so we assume they are not +/** + * Side effecting binary operators are rare, so we assume they are not * side-effecting unless we know otherwise. */ predicate side_effecting_binary(Expr b) { @@ -53,20 +56,22 @@ predicate side_effecting_binary(Expr b) { binary_operator_special_method(b, sub, cls, method_name) or comparison_special_method(b, sub, cls, method_name) - | + | method_name = special_method() and - cls.hasAttribute(method_name) - and + cls.hasAttribute(method_name) and not exists(ClassObject declaring | - declaring.declaresAttribute(method_name) - and declaring = cls.getAnImproperSuperType() and - declaring.isBuiltin() and not declaring = theObjectType() + declaring.declaresAttribute(method_name) and + declaring = cls.getAnImproperSuperType() and + declaring.isBuiltin() and + not declaring = theObjectType() ) ) } pragma[nomagic] -private predicate binary_operator_special_method(BinaryExpr b, Expr sub, ClassObject cls, string method_name) { +private predicate binary_operator_special_method( + BinaryExpr b, Expr sub, ClassObject cls, string method_name +) { method_name = special_method() and sub = b.getLeft() and method_name = b.getOp().getSpecialMethodName() and @@ -89,19 +94,17 @@ private string special_method() { } predicate is_notebook(File f) { - exists(Comment c | - c.getLocation().getFile() = f | + exists(Comment c | c.getLocation().getFile() = f | c.getText().regexpMatch("#\\s*.+\\s*") ) } /** Expression (statement) in a jupyter/ipython notebook */ -predicate in_notebook(Expr e) { - is_notebook(e.getScope().(Module).getFile()) -} +predicate in_notebook(Expr e) { is_notebook(e.getScope().(Module).getFile()) } FunctionObject assertRaises() { - result = ModuleObject::named("unittest").attr("TestCase").(ClassObject).lookupAttribute("assertRaises") + result = + ModuleObject::named("unittest").attr("TestCase").(ClassObject).lookupAttribute("assertRaises") } /** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */ @@ -122,13 +125,10 @@ predicate python2_print(Expr e) { predicate no_effect(Expr e) { not e instanceof StrConst and - not ((StrConst)e).isDocString() and + not e.(StrConst).isDocString() and not e.hasSideEffects() and - forall(Expr sub | - sub = e.getASubExpression*() - | - not side_effecting_binary(sub) - and + forall(Expr sub | sub = e.getASubExpression*() | + not side_effecting_binary(sub) and not maybe_side_effecting_attribute(sub) ) and not in_notebook(e) and @@ -139,4 +139,3 @@ predicate no_effect(Expr e) { from ExprStmt stmt where no_effect(stmt.getValue()) select stmt, "This statement has no effect." - diff --git a/python/ql/src/Statements/StringConcatenationInLoop.ql b/python/ql/src/Statements/StringConcatenationInLoop.ql index 5ca79a345a5..3e0f72f3356 100644 --- a/python/ql/src/Statements/StringConcatenationInLoop.ql +++ b/python/ql/src/Statements/StringConcatenationInLoop.ql @@ -13,17 +13,17 @@ import python predicate string_concat_in_loop(BinaryExpr b) { - b.getOp() instanceof Add - and + b.getOp() instanceof Add and exists(SsaVariable d, SsaVariable u, BinaryExprNode add, ClassObject str_type | - add.getNode() = b and d = u.getAnUltimateDefinition() | - d.getDefinition().(DefinitionNode).getValue() = add and u.getAUse() = add.getAnOperand() and - add.getAnOperand().refersTo(_, str_type, _) and - (str_type = theBytesType() or str_type = theUnicodeType()) + add.getNode() = b and d = u.getAnUltimateDefinition() + | + d.getDefinition().(DefinitionNode).getValue() = add and + u.getAUse() = add.getAnOperand() and + add.getAnOperand().refersTo(_, str_type, _) and + (str_type = theBytesType() or str_type = theUnicodeType()) ) } - from BinaryExpr b, Stmt s where string_concat_in_loop(b) and s.getASubExpression() = b select s, "String concatenation in a loop is quadratic in the number of iterations." diff --git a/python/ql/src/Statements/TopLevelPrint.ql b/python/ql/src/Statements/TopLevelPrint.ql index cc56902cd62..f295e1b0132 100644 --- a/python/ql/src/Statements/TopLevelPrint.ql +++ b/python/ql/src/Statements/TopLevelPrint.ql @@ -13,10 +13,10 @@ import python - predicate main_eq_name(If i) { exists(Name n, StrConst m, Compare c | - i.getTest() = c and c.getLeft() = n and + i.getTest() = c and + c.getLeft() = n and c.getAComparator() = m and n.getId() = "__name__" and m.getText() = "__main__" @@ -24,12 +24,16 @@ predicate main_eq_name(If i) { } predicate is_print_stmt(Stmt s) { - s instanceof Print or - exists(ExprStmt e, Call c, Name n | e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print") + s instanceof Print + or + exists(ExprStmt e, Call c, Name n | + e = s and c = e.getValue() and n = c.getFunc() and n.getId() = "print" + ) } from Stmt p -where is_print_stmt(p) and -exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and -not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) +where + is_print_stmt(p) and + exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and + not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) select p, "Print statement may execute during import." diff --git a/python/ql/src/Statements/UnnecessaryDelete.ql b/python/ql/src/Statements/UnnecessaryDelete.ql index fbe196e9fc1..ac11f782b32 100644 --- a/python/ql/src/Statements/UnnecessaryDelete.ql +++ b/python/ql/src/Statements/UnnecessaryDelete.ql @@ -12,7 +12,6 @@ * @id py/unnecessary-delete */ - import python from Delete del, Expr e, Function f @@ -23,11 +22,14 @@ where not e instanceof Subscript and not e instanceof Attribute and not exists(Stmt s | s.(While).contains(del) or s.(For).contains(del)) and - /* False positive: calling `sys.exc_info` within a function results in a - reference cycle,and an explicit call to `del` helps break this cycle. */ + /* + * False positive: calling `sys.exc_info` within a function results in a + * reference cycle, and an explicit call to `del` helps break this cycle. + */ + not exists(FunctionObject ex | ex.hasLongName("sys.exc_info") and ex.getACall().getScope() = f ) -select del, "Unnecessary deletion of local variable $@ in function $@.", - e.getLocation(), e.toString(), f.getLocation(), f.getName() \ No newline at end of file +select del, "Unnecessary deletion of local variable $@ in function $@.", e.getLocation(), + e.toString(), f.getLocation(), f.getName() diff --git a/python/ql/src/Statements/UnnecessaryElseClause.ql b/python/ql/src/Statements/UnnecessaryElseClause.ql index cfb93a7c0b7..8884b06e740 100644 --- a/python/ql/src/Statements/UnnecessaryElseClause.ql +++ b/python/ql/src/Statements/UnnecessaryElseClause.ql @@ -14,9 +14,11 @@ import python from Stmt loop, StmtList body, StmtList clause, string kind where -(exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for") - or - exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while") -) -and not exists(Break b | body.contains(b)) -select loop, "This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body." + ( + exists(For f | f = loop | clause = f.getOrelse() and body = f.getBody() and kind = "for") + or + exists(While w | w = loop | clause = w.getOrelse() and body = w.getBody() and kind = "while") + ) and + not exists(Break b | body.contains(b)) +select loop, + "This '" + kind + "' statement has a redundant 'else' as no 'break' is present in the body." diff --git a/python/ql/src/Statements/UnnecessaryPass.ql b/python/ql/src/Statements/UnnecessaryPass.ql index d98aa947236..fe0e0171930 100644 --- a/python/ql/src/Statements/UnnecessaryPass.ql +++ b/python/ql/src/Statements/UnnecessaryPass.ql @@ -13,21 +13,20 @@ import python predicate is_doc_string(ExprStmt s) { - s.getValue() instanceof Unicode or s.getValue() instanceof Bytes + s.getValue() instanceof Unicode or s.getValue() instanceof Bytes } predicate has_doc_string(StmtList stmts) { - stmts.getParent() instanceof Scope - and + stmts.getParent() instanceof Scope and is_doc_string(stmts.getItem(0)) } from Pass p, StmtList list -where list.getAnItem() = p and -( - strictcount(list.getAnItem()) = 2 and not has_doc_string(list) - or - strictcount(list.getAnItem()) > 2 -) +where + list.getAnItem() = p and + ( + strictcount(list.getAnItem()) = 2 and not has_doc_string(list) + or + strictcount(list.getAnItem()) > 2 + ) select p, "Unnecessary 'pass' statement." - diff --git a/python/ql/src/Statements/UnusedExceptionObject.ql b/python/ql/src/Statements/UnusedExceptionObject.ql index be848ad69c3..b1d20753b4a 100644 --- a/python/ql/src/Statements/UnusedExceptionObject.ql +++ b/python/ql/src/Statements/UnusedExceptionObject.ql @@ -13,7 +13,8 @@ import python from Call call, ClassObject ex -where call.getFunc().refersTo(ex) and ex.getAnImproperSuperType() = theExceptionType() -and exists(ExprStmt s | s.getValue() = call) - +where + call.getFunc().refersTo(ex) and + ex.getAnImproperSuperType() = theExceptionType() and + exists(ExprStmt s | s.getValue() = call) select call, "Instantiating an exception, but not raising it, has no effect" diff --git a/python/ql/src/Statements/UseOfExit.ql b/python/ql/src/Statements/UseOfExit.ql index c9f22605556..495a5d738da 100644 --- a/python/ql/src/Statements/UseOfExit.ql +++ b/python/ql/src/Statements/UseOfExit.ql @@ -13,4 +13,6 @@ import python from CallNode call, string name where call.getFunction().refersTo(Object::quitter(name)) -select call, "The '" + name + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified." +select call, + "The '" + name + + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified." From ae8dbd81f384cba08d064b8672f48d34dc2296eb Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 11 Feb 2020 13:41:51 +0100 Subject: [PATCH 021/236] Python: Update test-file for py/redundant-assignment now the test code can be pasted, and actually works ;) --- .../MismatchInMultipleAssignment.expected | 6 ++--- .../general/UnnecessaryDelete.expected | 2 +- .../Statements/general/statements_test.py | 22 +++++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected index 88b3dfbb008..4228f4970ea 100644 --- a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected +++ b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected @@ -1,4 +1,4 @@ | statements_test.py:19:5:19:18 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 2. | statements_test.py:19:15:19:18 | statements_test.py:19 | tuple | -| statements_test.py:163:5:163:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:163:13:163:23 | statements_test.py:163 | list | -| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:167:16:167:24 | statements_test.py:167 | tuple | -| statements_test.py:172:5:172:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:169:16:169:26 | statements_test.py:169 | tuple | +| statements_test.py:167:5:167:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:167:13:167:23 | statements_test.py:167 | list | +| statements_test.py:176:5:176:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:171:16:171:24 | statements_test.py:171 | tuple | +| statements_test.py:176:5:176:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:173:16:173:26 | statements_test.py:173 | tuple | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected index f6f7ca1bad2..6f3d9c11e2a 100644 --- a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected @@ -1 +1 @@ -| statements_test.py:181:5:181:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:181:9:181:9 | statements_test.py:181 | x | statements_test.py:179:1:179:31 | statements_test.py:179 | error_unnecessary_delete | +| statements_test.py:185:5:185:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:185:9:185:9 | statements_test.py:185 | x | statements_test.py:183:1:183:31 | statements_test.py:183 | error_unnecessary_delete | diff --git a/python/ql/test/query-tests/Statements/general/statements_test.py b/python/ql/test/query-tests/Statements/general/statements_test.py index 37ed94ec57e..897f95e0d44 100644 --- a/python/ql/test/query-tests/Statements/general/statements_test.py +++ b/python/ql/test/query-tests/Statements/general/statements_test.py @@ -64,19 +64,19 @@ for x in range(10): func(x) else: do_something() - + while x < 10: func(x) else: do_something() - + #OK else clauses: for x in range(10): if func(x): break else: do_something() - + while x < 10: if func(x): break @@ -95,24 +95,24 @@ else: #Not a redundant assignment if a property. class WithProp(object): - + @property def x(self): return self._x - + @prop.setter def set_x(self, x): side_effect(x) self._x = x - + def meth(self): self.x = self.x - + def maybe_property(x): x.y = x.y - + class WithoutProp(object): - + def meth(self): self.x = self.x @@ -143,8 +143,12 @@ for e in EnumDerived: class SideEffectingAttr(object): + def __init__(self): + self.foo = 'foo' + def __setattr__(self, name, val): print("hello!") + super().__setattr__(name, val) s = SideEffectingAttr() s.foo = s.foo From 6e349eb6e7f03f5e8f20bae859fb8aaae4e77d03 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 11 Feb 2020 14:42:12 +0100 Subject: [PATCH 022/236] Python: Make py/side-effect-in-assert handle example Also removed parantheses --- python/ql/src/Statements/SideEffectInAssert.py | 2 +- python/ql/src/Statements/SideEffectInAssert.ql | 10 ++++++++++ .../Statements/asserts/SideEffectInAssert.expected | 1 + .../ql/test/query-tests/Statements/asserts/assert.py | 6 +++--- 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/python/ql/src/Statements/SideEffectInAssert.py b/python/ql/src/Statements/SideEffectInAssert.py index 4aba0adc8de..f5a5cbdf807 100644 --- a/python/ql/src/Statements/SideEffectInAssert.py +++ b/python/ql/src/Statements/SideEffectInAssert.py @@ -1 +1 @@ -assert(subprocess.call(['run-backup']) == 0) +assert subprocess.call(['run-backup']) == 0 diff --git a/python/ql/src/Statements/SideEffectInAssert.ql b/python/ql/src/Statements/SideEffectInAssert.ql index faf10defcd1..a8ed146b16e 100644 --- a/python/ql/src/Statements/SideEffectInAssert.ql +++ b/python/ql/src/Statements/SideEffectInAssert.ql @@ -28,6 +28,14 @@ predicate func_with_side_effects(Expr e) { ) } +predicate call_with_side_effect(Call e) { + e.getAFlowNode() = Value::named("subprocess.call").getACall() + or + e.getAFlowNode() = Value::named("subprocess.check_call").getACall() + or + e.getAFlowNode() = Value::named("subprocess.check_output").getACall() +} + predicate probable_side_effect(Expr e) { // Only consider explicit yields, not artificial ones in comprehensions e instanceof Yield and not exists(Comp c | c.contains(e)) @@ -35,6 +43,8 @@ predicate probable_side_effect(Expr e) { e instanceof YieldFrom or e instanceof Call and func_with_side_effects(e.(Call).getFunc()) + or + e instanceof Call and call_with_side_effect(e) } from Assert a, Expr e diff --git a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected index 4f46cff1e76..f7425a3a429 100644 --- a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected +++ b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected @@ -1,2 +1,3 @@ | assert.py:5:5:5:20 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:5:13:5:19 | Yield | an expression | | assert.py:8:5:8:22 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:8:12:8:22 | Attribute() | an expression | +| assert.py:20:1:20:43 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:20:8:20:38 | Attribute() | an expression | diff --git a/python/ql/test/query-tests/Statements/asserts/assert.py b/python/ql/test/query-tests/Statements/asserts/assert.py index 854fffa3382..d6c1e27daba 100644 --- a/python/ql/test/query-tests/Statements/asserts/assert.py +++ b/python/ql/test/query-tests/Statements/asserts/assert.py @@ -16,8 +16,8 @@ def assert_tuple(x, y): assert () assert (x, y) - - +import subprocess +assert subprocess.call(['run-backup']) == 0 # TODO: FN @@ -103,4 +103,4 @@ def error_assert_in_intermediate_branch(x): elif yks(x): pass else: - pass \ No newline at end of file + pass From 83d40f167b995683afed0ec97ae98133db1b2f21 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 11 Feb 2020 16:31:48 +0100 Subject: [PATCH 023/236] Python: Update py/ineffectual-statement e.(StrConst).isDocString() can only hold if e instanceof StrConst, since we have that condition on the line above, we can safely remove this condition. --- python/ql/src/Statements/StatementNoEffect.ql | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql index 2b46a6b8109..a2bfdd5666f 100644 --- a/python/ql/src/Statements/StatementNoEffect.ql +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -125,7 +125,6 @@ predicate python2_print(Expr e) { predicate no_effect(Expr e) { not e instanceof StrConst and - not e.(StrConst).isDocString() and not e.hasSideEffects() and forall(Expr sub | sub = e.getASubExpression*() | not side_effecting_binary(sub) and From 13568b7b9f35dbe732d4635678b522d23f35ac0e Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 12 Feb 2020 13:01:05 +0100 Subject: [PATCH 024/236] Python: Modernise Statements/ queries Almost. Left out a few things marked with TODO --- python/ql/src/Statements/DocStrings.ql | 4 +- .../Statements/IterableStringOrSequence.ql | 20 ++--- .../MismatchInMultipleAssignment.ql | 6 +- .../ql/src/Statements/ModificationOfLocals.ql | 14 +--- .../src/Statements/NonIteratorInForLoop.qhelp | 2 +- .../ql/src/Statements/NonIteratorInForLoop.ql | 2 +- .../ql/src/Statements/RedundantAssignment.ql | 26 +++++-- .../src/Statements/ShouldUseWithStatement.ql | 8 +- python/ql/src/Statements/StatementNoEffect.ql | 40 +++++----- .../StringConcatenationInLoop.qhelp | 4 +- .../Statements/StringConcatenationInLoop.ql | 5 +- python/ql/src/Statements/TopLevelPrint.ql | 1 + python/ql/src/Statements/UnnecessaryDelete.ql | 4 +- .../src/Statements/UnusedExceptionObject.ql | 6 +- python/ql/src/Statements/UseOfExit.ql | 2 +- .../src/semmle/python/objects/ObjectAPI.qll | 73 ++++++++++++------- .../MismatchInMultipleAssignment.expected | 6 +- .../general/NonIteratorInForLoop.expected | 2 +- .../general/RedundantAssignment.expected | 2 +- .../general/UnnecessaryDelete.expected | 2 +- .../general/UnnecessaryElseClause.expected | 4 +- .../Statements/general/statements_test.py | 6 +- 22 files changed, 129 insertions(+), 110 deletions(-) diff --git a/python/ql/src/Statements/DocStrings.ql b/python/ql/src/Statements/DocStrings.ql index 7c55c65e204..d6a1d812300 100644 --- a/python/ql/src/Statements/DocStrings.ql +++ b/python/ql/src/Statements/DocStrings.ql @@ -27,8 +27,8 @@ predicate needs_docstring(Scope s) { } predicate function_needs_docstring(Function f) { - not exists(FunctionObject fo, FunctionObject base | fo.overrides(base) and fo.getFunction() = f | - not function_needs_docstring(base.getFunction()) + not exists(FunctionValue fo, FunctionValue base | fo.overrides(base) and fo.getScope() = f | + not function_needs_docstring(base.getScope()) ) and f.getName() != "lambda" and (f.getMetrics().getNumberOfLinesOfCode() - count(f.getADecorator())) > 2 and diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index 96872a9a556..fb7b2198bda 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -13,21 +13,15 @@ import python -predicate is_a_string_type(ClassObject seqtype) { - seqtype = theBytesType() and major_version() = 2 - or - seqtype = theUnicodeType() -} from - For loop, ControlFlowNode iter, Object str, Object seq, ControlFlowNode seq_origin, - ClassObject strtype, ClassObject seqtype, ControlFlowNode str_origin + For loop, ControlFlowNode iter, Value str, Value seq, ControlFlowNode seq_origin, ControlFlowNode str_origin where loop.getIter().getAFlowNode() = iter and - iter.refersTo(str, strtype, str_origin) and - iter.refersTo(seq, seqtype, seq_origin) and - is_a_string_type(strtype) and - seqtype.isIterable() and - not is_a_string_type(seqtype) -select loop, "Iteration over $@, of class " + seqtype.getName() + ", may also iterate over $@.", + iter.pointsTo(str, str_origin) and + iter.pointsTo(seq, seq_origin) and + str.getClass() = ClassValue::str() and + seq.getClass().isIterable() and + not seq.getClass() = ClassValue::str() +select loop, "Iteration over $@, of class " + seq.getClass().getName() + ", may also iterate over $@.", seq_origin, "sequence", str_origin, "string" diff --git a/python/ql/src/Statements/MismatchInMultipleAssignment.ql b/python/ql/src/Statements/MismatchInMultipleAssignment.ql index 2c619c78857..157ddf1270b 100644 --- a/python/ql/src/Statements/MismatchInMultipleAssignment.ql +++ b/python/ql/src/Statements/MismatchInMultipleAssignment.ql @@ -36,15 +36,15 @@ predicate mismatched(Assign a, int lcount, int rcount, Location loc, string sequ } predicate mismatched_tuple_rhs(Assign a, int lcount, int rcount, Location loc) { - exists(ExprList l, TupleObject r, AstNode origin | + exists(ExprList l, TupleValue r, AstNode origin | ( a.getATarget().(Tuple).getElts() = l or a.getATarget().(List).getElts() = l ) and - a.getValue().refersTo(r, origin) and + a.getValue().pointsTo(r, origin) and loc = origin.getLocation() and lcount = len(l) and - rcount = r.getLength() and + rcount = r.length() and lcount != rcount and not exists(Starred s | l.getAnItem() = s) ) diff --git a/python/ql/src/Statements/ModificationOfLocals.ql b/python/ql/src/Statements/ModificationOfLocals.ql index 38e5ccabcf5..2117748f3e2 100644 --- a/python/ql/src/Statements/ModificationOfLocals.ql +++ b/python/ql/src/Statements/ModificationOfLocals.ql @@ -12,23 +12,17 @@ import python -Object aFunctionLocalsObject() { - exists(Call c, Name n, GlobalVariable v | - c = result.getOrigin() and - n = c.getFunc() and - n.getVariable() = v and - v.getId() = "locals" and - c.getScope() instanceof FastLocalsFunction - ) +predicate originIsLocals(ControlFlowNode n) { + n.pointsTo(_, _, Value::named("locals").getACall()) } predicate modification_of_locals(ControlFlowNode f) { - f.(SubscriptNode).getObject().refersTo(aFunctionLocalsObject()) and + originIsLocals(f.(SubscriptNode).getObject()) and (f.isStore() or f.isDelete()) or exists(string mname, AttrNode attr | attr = f.(CallNode).getFunction() and - attr.getObject(mname).refersTo(aFunctionLocalsObject(), _) + originIsLocals(attr.getObject(mname)) | mname = "pop" or mname = "popitem" or diff --git a/python/ql/src/Statements/NonIteratorInForLoop.qhelp b/python/ql/src/Statements/NonIteratorInForLoop.qhelp index 0165db9fea9..5be3ff5d21f 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.qhelp +++ b/python/ql/src/Statements/NonIteratorInForLoop.qhelp @@ -17,7 +17,7 @@ for addressing the defect.

-In this example, the loop may attempt to iterate over None, which is not an iterator. +In this example, the loop may attempt to iterate over None, which is not an iterable. It is likely that the programmer forgot to test for None before the loop.

diff --git a/python/ql/src/Statements/NonIteratorInForLoop.ql b/python/ql/src/Statements/NonIteratorInForLoop.ql index 26c3005e5fc..85982ccc030 100644 --- a/python/ql/src/Statements/NonIteratorInForLoop.ql +++ b/python/ql/src/Statements/NonIteratorInForLoop.ql @@ -22,4 +22,4 @@ where not t.failedInference(_) and not v = Value::named("None") and not t.isDescriptorType() -select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterator", t, t.getName() +select loop, "$@ of class '$@' may be used in for-loop.", origin, "Non-iterable", t, t.getName() diff --git a/python/ql/src/Statements/RedundantAssignment.ql b/python/ql/src/Statements/RedundantAssignment.ql index 9f9f41fa52b..6688d248dcc 100644 --- a/python/ql/src/Statements/RedundantAssignment.ql +++ b/python/ql/src/Statements/RedundantAssignment.ql @@ -37,21 +37,30 @@ predicate maybe_defined_in_outer_scope(Name n) { exists(SsaVariable v | v.getAUse().getNode() = n | v.maybeUndefined()) } -Variable relevant_var(Name n) { - n.getVariable() = result and - (corresponding(n, _) or corresponding(_, n)) +/* Protection against FPs in projects that offer compatibility between Python 2 and 3, + * since many of them make assignments such as + * + * if PY2: + * bytes = str + * else: + * bytes = bytes + * + */ +predicate isBuiltin(string name) { + exists(Value v | v = Value::named(name) and v.isBuiltin()) } predicate same_name(Name n1, Name n2) { corresponding(n1, n2) and - relevant_var(n1) = relevant_var(n2) and - not exists(Object::builtin(n1.getId())) and + n1.getVariable() = n2.getVariable() and + not isBuiltin(n1.getId()) and not maybe_defined_in_outer_scope(n2) } ClassObject value_type(Attribute a) { a.getObject().refersTo(_, result, _) } predicate is_property_access(Attribute a) { + // TODO: We need something to model PropertyObject in the Value API value_type(a).lookupAttribute(a.getName()) instanceof PropertyObject } @@ -77,9 +86,9 @@ predicate pyflakes_commented(AssignStmt assignment) { } predicate side_effecting_lhs(Attribute lhs) { - exists(ClassObject cls, ClassObject decl | - lhs.getObject().refersTo(_, cls, _) and - decl = cls.getAnImproperSuperType() and + exists(ClassValue cls, ClassValue decl | + lhs.getObject().pointsTo().getClass() = cls and + decl = cls.getASuperType() and not decl.isBuiltin() | decl.declaresAttribute("__setattr__") @@ -90,6 +99,7 @@ from AssignStmt a, Expr left, Expr right where assignment(a, left, right) and same_value(left, right) and + // some people use self-assignment to shut Pyflakes up, such as `ok = ok # Pyflakes` not pyflakes_commented(a) and not side_effecting_lhs(left) select a, "This assignment assigns a variable to itself." diff --git a/python/ql/src/Statements/ShouldUseWithStatement.ql b/python/ql/src/Statements/ShouldUseWithStatement.ql index f86ba21b4b9..9be28d5ceec 100644 --- a/python/ql/src/Statements/ShouldUseWithStatement.ql +++ b/python/ql/src/Statements/ShouldUseWithStatement.ql @@ -22,12 +22,12 @@ predicate only_stmt_in_finally(Try t, Call c) { ) } -predicate points_to_context_manager(ControlFlowNode f, ClassObject cls) { - cls.isContextManager() and - forex(Object obj | f.refersTo(obj) | f.refersTo(obj, cls, _)) +predicate points_to_context_manager(ControlFlowNode f, ClassValue cls) { + forex(Value v | f.pointsTo(v) | v.getClass() = cls) and + cls.isContextManager() } -from Call close, Try t, ClassObject cls +from Call close, Try t, ClassValue cls where only_stmt_in_finally(t, close) and calls_close(close) and diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql index a2bfdd5666f..0a80f8f9aa4 100644 --- a/python/ql/src/Statements/StatementNoEffect.ql +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -13,28 +13,28 @@ import python -predicate understood_attribute(Attribute attr, ClassObject cls, ClassObject attr_cls) { +predicate understood_attribute(Attribute attr, ClassValue cls, ClassValue attr_cls) { exists(string name | attr.getName() = name | - attr.getObject().refersTo(_, cls, _) and - cls.attributeRefersTo(name, _, attr_cls, _) + attr.getObject().pointsTo().getClass() = cls and + cls.attr(name).getClass() = attr_cls ) } /* Conservative estimate of whether attribute lookup has a side effect */ predicate side_effecting_attribute(Attribute attr) { - exists(ClassObject cls, ClassObject attr_cls | + exists(ClassValue cls, ClassValue attr_cls | understood_attribute(attr, cls, attr_cls) and side_effecting_descriptor_type(attr_cls) ) } predicate maybe_side_effecting_attribute(Attribute attr) { - not understood_attribute(attr, _, _) and not attr.refersTo(_) + not understood_attribute(attr, _, _) and not attr.pointsTo(_) or side_effecting_attribute(attr) } -predicate side_effecting_descriptor_type(ClassObject descriptor) { +predicate side_effecting_descriptor_type(ClassValue descriptor) { descriptor.isDescriptorType() and /* * Technically all descriptor gets have side effects, @@ -42,9 +42,9 @@ predicate side_effecting_descriptor_type(ClassObject descriptor) { * we want to treat them as having no effect. */ - not descriptor = thePyFunctionType() and - not descriptor = theStaticMethodType() and - not descriptor = theClassMethodType() + not descriptor = ClassValue::function() and + not descriptor = ClassValue::staticmethod() and + not descriptor = ClassValue::classmethod() } /** @@ -52,39 +52,39 @@ predicate side_effecting_descriptor_type(ClassObject descriptor) { * side-effecting unless we know otherwise. */ predicate side_effecting_binary(Expr b) { - exists(Expr sub, ClassObject cls, string method_name | + exists(Expr sub, ClassValue cls, string method_name | binary_operator_special_method(b, sub, cls, method_name) or comparison_special_method(b, sub, cls, method_name) | method_name = special_method() and cls.hasAttribute(method_name) and - not exists(ClassObject declaring | + not exists(ClassValue declaring | declaring.declaresAttribute(method_name) and - declaring = cls.getAnImproperSuperType() and + declaring = cls.getASuperType() and declaring.isBuiltin() and - not declaring = theObjectType() + not declaring = ClassValue::object() ) ) } pragma[nomagic] private predicate binary_operator_special_method( - BinaryExpr b, Expr sub, ClassObject cls, string method_name + BinaryExpr b, Expr sub, ClassValue cls, string method_name ) { method_name = special_method() and sub = b.getLeft() and method_name = b.getOp().getSpecialMethodName() and - sub.refersTo(_, cls, _) + sub.pointsTo().getClass() = cls } pragma[nomagic] -private predicate comparison_special_method(Compare b, Expr sub, ClassObject cls, string method_name) { +private predicate comparison_special_method(Compare b, Expr sub, ClassValue cls, string method_name) { exists(Cmpop op | b.compares(sub, op, _) and method_name = op.getSpecialMethodName() ) and - sub.refersTo(_, cls, _) + sub.pointsTo().getClass() = cls } private string special_method() { @@ -102,9 +102,8 @@ predicate is_notebook(File f) { /** Expression (statement) in a jupyter/ipython notebook */ predicate in_notebook(Expr e) { is_notebook(e.getScope().(Module).getFile()) } -FunctionObject assertRaises() { - result = - ModuleObject::named("unittest").attr("TestCase").(ClassObject).lookupAttribute("assertRaises") +FunctionValue assertRaises() { + result = Value::named("unittest.TestCase").(ClassValue).lookup("assertRaises") } /** Holds if expression `e` is in a `with` block that tests for exceptions being raised. */ @@ -124,6 +123,7 @@ predicate python2_print(Expr e) { } predicate no_effect(Expr e) { + // strings can be used as comments not e instanceof StrConst and not e.hasSideEffects() and forall(Expr sub | sub = e.getASubExpression*() | diff --git a/python/ql/src/Statements/StringConcatenationInLoop.qhelp b/python/ql/src/Statements/StringConcatenationInLoop.qhelp index 8d8f494ddd0..b1350e70478 100644 --- a/python/ql/src/Statements/StringConcatenationInLoop.qhelp +++ b/python/ql/src/Statements/StringConcatenationInLoop.qhelp @@ -3,13 +3,13 @@ "qhelp.dtd"> -

If you concatenate strings in a loop then the time taken by the loop is quadratic in the number +

If you concatenate strings in a loop then the time taken by the loop is quadratic in the number of iterations.

-

Initialize an empty list before the start of the list. +

Initialize an empty list before the start of the loop. During the loop append the substrings to the list. At the end of the loop, convert the list to a string by using ''.join(list).

diff --git a/python/ql/src/Statements/StringConcatenationInLoop.ql b/python/ql/src/Statements/StringConcatenationInLoop.ql index 3e0f72f3356..f225e27cdcd 100644 --- a/python/ql/src/Statements/StringConcatenationInLoop.ql +++ b/python/ql/src/Statements/StringConcatenationInLoop.ql @@ -14,13 +14,12 @@ import python predicate string_concat_in_loop(BinaryExpr b) { b.getOp() instanceof Add and - exists(SsaVariable d, SsaVariable u, BinaryExprNode add, ClassObject str_type | + exists(SsaVariable d, SsaVariable u, BinaryExprNode add | add.getNode() = b and d = u.getAnUltimateDefinition() | d.getDefinition().(DefinitionNode).getValue() = add and u.getAUse() = add.getAnOperand() and - add.getAnOperand().refersTo(_, str_type, _) and - (str_type = theBytesType() or str_type = theUnicodeType()) + add.getAnOperand().pointsTo().getClass() = ClassValue::str() ) } diff --git a/python/ql/src/Statements/TopLevelPrint.ql b/python/ql/src/Statements/TopLevelPrint.ql index f295e1b0132..104af619049 100644 --- a/python/ql/src/Statements/TopLevelPrint.ql +++ b/python/ql/src/Statements/TopLevelPrint.ql @@ -34,6 +34,7 @@ predicate is_print_stmt(Stmt s) { from Stmt p where is_print_stmt(p) and + // TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) select p, "Print statement may execute during import." diff --git a/python/ql/src/Statements/UnnecessaryDelete.ql b/python/ql/src/Statements/UnnecessaryDelete.ql index ac11f782b32..48f167ca706 100644 --- a/python/ql/src/Statements/UnnecessaryDelete.ql +++ b/python/ql/src/Statements/UnnecessaryDelete.ql @@ -27,8 +27,8 @@ where * reference cycle, and an explicit call to `del` helps break this cycle. */ - not exists(FunctionObject ex | - ex.hasLongName("sys.exc_info") and + not exists(FunctionValue ex | + ex = Value::named("sys.exc_info") and ex.getACall().getScope() = f ) select del, "Unnecessary deletion of local variable $@ in function $@.", e.getLocation(), diff --git a/python/ql/src/Statements/UnusedExceptionObject.ql b/python/ql/src/Statements/UnusedExceptionObject.ql index b1d20753b4a..32b59113c5b 100644 --- a/python/ql/src/Statements/UnusedExceptionObject.ql +++ b/python/ql/src/Statements/UnusedExceptionObject.ql @@ -12,9 +12,9 @@ import python -from Call call, ClassObject ex +from Call call, ClassValue ex where - call.getFunc().refersTo(ex) and - ex.getAnImproperSuperType() = theExceptionType() and + call.getFunc().pointsTo(ex) and + ex.getASuperType() = ClassValue::exception() and exists(ExprStmt s | s.getValue() = call) select call, "Instantiating an exception, but not raising it, has no effect" diff --git a/python/ql/src/Statements/UseOfExit.ql b/python/ql/src/Statements/UseOfExit.ql index 495a5d738da..31e0e51ab39 100644 --- a/python/ql/src/Statements/UseOfExit.ql +++ b/python/ql/src/Statements/UseOfExit.ql @@ -12,7 +12,7 @@ import python from CallNode call, string name -where call.getFunction().refersTo(Object::quitter(name)) +where call.getFunction().pointsTo(Value::siteQuitter(name)) select call, "The '" + name + "' site.Quitter object may not exist if the 'site' module is not loaded or is modified." diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 1f1f7001da1..42e6bc95e7a 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -301,6 +301,19 @@ module Value { result = ObjectInternal::none_() } + /** + * Shorcuts added by the `site` module to exit your interactive session. + * + * see https://docs.python.org/3/library/constants.html#constants-added-by-the-site-module + */ + Value siteQuitter(string name) { + ( + name = "exit" + or + name = "quit" + ) and + result = Value::named(name) + } } /** Class representing callables in the Python program @@ -418,6 +431,12 @@ class ClassValue extends Value { this.hasAttribute("__get__") } + /** Holds if this class is a context manager. */ + predicate isContextManager() { + this.hasAttribute("__enter__") and + this.hasAttribute("__exit__") + } + /** Gets the qualified name for this class. * Should return the same name as the `__qualname__` attribute on classes in Python 3. */ @@ -698,34 +717,34 @@ module ClassValue { ClassValue bool() { result = TBuiltinClassObject(Builtin::special("bool")) } - + /** Get the `ClassValue` for the `tuple` class. */ ClassValue tuple() { result = TBuiltinClassObject(Builtin::special("tuple")) } - + /** Get the `ClassValue` for the `list` class. */ ClassValue list() { result = TBuiltinClassObject(Builtin::special("list")) } - + /** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */ ClassValue range() { major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange")) or major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range")) } - + /** Get the `ClassValue` for the `dict` class. */ ClassValue dict() { result = TBuiltinClassObject(Builtin::special("dict")) } - + /** Get the `ClassValue` for the `set` class. */ ClassValue set() { result = TBuiltinClassObject(Builtin::special("set")) } - + /** Get the `ClassValue` for the `object` class. */ ClassValue object() { result = TBuiltinClassObject(Builtin::special("object")) @@ -735,7 +754,7 @@ module ClassValue { ClassValue int_() { result = TBuiltinClassObject(Builtin::special("int")) } - + /** Get the `ClassValue` for the `long` class. */ ClassValue long() { result = TBuiltinClassObject(Builtin::special("long")) @@ -745,7 +764,7 @@ module ClassValue { ClassValue float_() { result = TBuiltinClassObject(Builtin::special("float")) } - + /** Get the `ClassValue` for the `complex` class. */ ClassValue complex() { result = TBuiltinClassObject(Builtin::special("complex")) @@ -770,12 +789,12 @@ module ClassValue { else result = unicode() } - + /** Get the `ClassValue` for the `property` class. */ ClassValue property() { result = TBuiltinClassObject(Builtin::special("property")) } - + /** Get the `ClassValue` for the class of Python functions. */ ClassValue functionType() { result = TBuiltinClassObject(Builtin::special("FunctionType")) @@ -785,32 +804,32 @@ module ClassValue { ClassValue builtinFunction() { result = Value::named("len").getClass() } - + /** Get the `ClassValue` for the `generatorType` class. */ ClassValue generator() { result = TBuiltinClassObject(Builtin::special("generator")) } - + /** Get the `ClassValue` for the `type` class. */ ClassValue type() { result = TType() } - + /** Get the `ClassValue` for `ClassType`. */ ClassValue classType() { result = TBuiltinClassObject(Builtin::special("ClassType")) } - + /** Get the `ClassValue` for `InstanceType`. */ ClassValue instanceType() { result = TBuiltinClassObject(Builtin::special("InstanceType")) } - + /** Get the `ClassValue` for `super`. */ ClassValue super_() { result = TBuiltinClassObject(Builtin::special("super")) } - + /** Get the `ClassValue` for the `classmethod` class. */ ClassValue classmethod() { result = TBuiltinClassObject(Builtin::special("ClassMethod")) @@ -820,23 +839,23 @@ module ClassValue { ClassValue staticmethod() { result = TBuiltinClassObject(Builtin::special("StaticMethod")) } - + /** Get the `ClassValue` for the `MethodType` class. */ pragma [noinline] ClassValue methodType() { result = TBuiltinClassObject(Builtin::special("MethodType")) } - + /** Get the `ClassValue` for the `MethodDescriptorType` class. */ ClassValue methodDescriptorType() { result = TBuiltinClassObject(Builtin::special("MethodDescriptorType")) } - + /** Get the `ClassValue` for the `GetSetDescriptorType` class. */ ClassValue getSetDescriptorType() { result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType")) } - + /** Get the `ClassValue` for the `StopIteration` class. */ ClassValue stopIteration() { result = TBuiltinClassObject(Builtin::special("StopIteration")) @@ -851,17 +870,17 @@ module ClassValue { ClassValue exception() { result = TBuiltinClassObject(Builtin::special("Exception")) } - + /** Get the `ClassValue` for the `BaseException` class. */ ClassValue baseException() { result = TBuiltinClassObject(Builtin::special("BaseException")) } - + /** Get the `ClassValue` for the `NoneType` class. */ ClassValue nonetype() { result = TBuiltinClassObject(Builtin::special("NoneType")) } - + /** Get the `ClassValue` for the `TypeError` class */ ClassValue typeError() { result = TBuiltinClassObject(Builtin::special("TypeError")) @@ -871,22 +890,22 @@ module ClassValue { ClassValue nameError() { result = TBuiltinClassObject(Builtin::builtin("NameError")) } - + /** Get the `ClassValue` for the `AttributeError` class. */ ClassValue attributeError() { result = TBuiltinClassObject(Builtin::builtin("AttributeError")) } - + /** Get the `ClassValue` for the `KeyError` class. */ ClassValue keyError() { result = TBuiltinClassObject(Builtin::builtin("KeyError")) } - + /** Get the `ClassValue` for the `IOError` class. */ ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) } - + /** Get the `ClassValue` for the `NotImplementedError` class. */ ClassValue notImplementedError() { result = TBuiltinClassObject(Builtin::builtin("NotImplementedError")) diff --git a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected index 4228f4970ea..b4755862d85 100644 --- a/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected +++ b/python/ql/test/query-tests/Statements/general/MismatchInMultipleAssignment.expected @@ -1,4 +1,4 @@ | statements_test.py:19:5:19:18 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 2. | statements_test.py:19:15:19:18 | statements_test.py:19 | tuple | -| statements_test.py:167:5:167:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:167:13:167:23 | statements_test.py:167 | list | -| statements_test.py:176:5:176:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:171:16:171:24 | statements_test.py:171 | tuple | -| statements_test.py:176:5:176:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:173:16:173:26 | statements_test.py:173 | tuple | +| statements_test.py:169:5:169:23 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:169:13:169:23 | statements_test.py:169 | list | +| statements_test.py:178:5:178:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 5. | statements_test.py:173:16:173:24 | statements_test.py:173 | tuple | +| statements_test.py:178:5:178:48 | AssignStmt | Left hand side of assignment contains 3 variables, but right hand side is a $@ of length 6. | statements_test.py:175:16:175:26 | statements_test.py:175 | tuple | diff --git a/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected index 308f0aa027e..38d063a8c0e 100644 --- a/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected +++ b/python/ql/test/query-tests/Statements/general/NonIteratorInForLoop.expected @@ -1 +1 @@ -| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterator | test.py:45:1:45:26 | class NonIterator | NonIterator | +| test.py:50:1:50:23 | For | $@ of class '$@' may be used in for-loop. | test.py:50:10:50:22 | ControlFlowNode for NonIterator() | Non-iterable | test.py:45:1:45:26 | class NonIterator | NonIterator | diff --git a/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected b/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected index f997ac54932..72e7116247a 100644 --- a/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected +++ b/python/ql/test/query-tests/Statements/general/RedundantAssignment.expected @@ -1,3 +1,3 @@ | statements_test.py:54:5:54:9 | AssignStmt | This assignment assigns a variable to itself. | | statements_test.py:57:9:57:19 | AssignStmt | This assignment assigns a variable to itself. | -| statements_test.py:117:9:117:23 | AssignStmt | This assignment assigns a variable to itself. | +| statements_test.py:119:9:119:23 | AssignStmt | This assignment assigns a variable to itself. | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected index 6f3d9c11e2a..d7dda673775 100644 --- a/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryDelete.expected @@ -1 +1 @@ -| statements_test.py:185:5:185:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:185:9:185:9 | statements_test.py:185 | x | statements_test.py:183:1:183:31 | statements_test.py:183 | error_unnecessary_delete | +| statements_test.py:187:5:187:9 | Delete | Unnecessary deletion of local variable $@ in function $@. | statements_test.py:187:9:187:9 | statements_test.py:187 | x | statements_test.py:185:1:185:31 | statements_test.py:185 | error_unnecessary_delete | diff --git a/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected index 6f333fc7bbf..830c2cd10ca 100644 --- a/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected +++ b/python/ql/test/query-tests/Statements/general/UnnecessaryElseClause.expected @@ -1,2 +1,2 @@ -| statements_test.py:63:1:63:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. | -| statements_test.py:68:1:68:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. | +| statements_test.py:65:1:65:19 | For | This 'for' statement has a redundant 'else' as no 'break' is present in the body. | +| statements_test.py:70:1:70:13 | While | This 'while' statement has a redundant 'else' as no 'break' is present in the body. | diff --git a/python/ql/test/query-tests/Statements/general/statements_test.py b/python/ql/test/query-tests/Statements/general/statements_test.py index 897f95e0d44..0a74bb31c10 100644 --- a/python/ql/test/query-tests/Statements/general/statements_test.py +++ b/python/ql/test/query-tests/Statements/general/statements_test.py @@ -56,8 +56,10 @@ class Redundant(object): def __init__(self, args): args = args # violation -#Non redundant assignment -len = len +if sys.version_info < (3,): + bytes = str +else: + bytes = bytes # Should not be flagged #Pointless else clauses for x in range(10): From 3f49aeecfecb12c196a0252aee120c168125587e Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 12 Feb 2020 14:45:15 +0100 Subject: [PATCH 025/236] Python: Descriptor tests fixup (1/3) --- .../test/library-tests/descriptors/Methods.expected | 12 ++++++------ .../library-tests/descriptors/Properties.expected | 2 +- python/ql/test/library-tests/descriptors/test.py | 7 ++----- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/python/ql/test/library-tests/descriptors/Methods.expected b/python/ql/test/library-tests/descriptors/Methods.expected index efd066e8b4c..a3e0443bd2f 100644 --- a/python/ql/test/library-tests/descriptors/Methods.expected +++ b/python/ql/test/library-tests/descriptors/Methods.expected @@ -1,6 +1,6 @@ -| 16 | classmethod() | 17 | Function c1 | -| 23 | classmethod() | 20 | Function c2 | -| 24 | classmethod() | 20 | Function c2 | -| 26 | staticmethod() | 27 | Function s1 | -| 33 | staticmethod() | 30 | Function s2 | -| 34 | staticmethod() | 30 | Function s2 | \ No newline at end of file +| 13 | classmethod() | 14 | Function c1 | +| 20 | classmethod() | 17 | Function c2 | +| 21 | classmethod() | 17 | Function c2 | +| 23 | staticmethod() | 24 | Function s1 | +| 30 | staticmethod() | 27 | Function s2 | +| 31 | staticmethod() | 27 | Function s2 | diff --git a/python/ql/test/library-tests/descriptors/Properties.expected b/python/ql/test/library-tests/descriptors/Properties.expected index 3eb736d618b..89504ea48f3 100644 --- a/python/ql/test/library-tests/descriptors/Properties.expected +++ b/python/ql/test/library-tests/descriptors/Properties.expected @@ -1 +1 @@ -| 6 | Property f | 7 | Function f | 11 | Function f | +| 3 | Property f | 4 | Function f | 8 | Function f | diff --git a/python/ql/test/library-tests/descriptors/test.py b/python/ql/test/library-tests/descriptors/test.py index 8d7f14198c7..51f75c5aaf1 100644 --- a/python/ql/test/library-tests/descriptors/test.py +++ b/python/ql/test/library-tests/descriptors/test.py @@ -1,6 +1,3 @@ - - - class C(object): @property @@ -8,8 +5,8 @@ class C(object): return self._f @f.setter - def f(self): - return self._f + def f(self, value): + self._f = value class D(object): From aed7bfb82016657ac68edf8f068dc48455e510b0 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 12 Feb 2020 14:53:24 +0100 Subject: [PATCH 026/236] Python: Descriptor tests fixup (2/3) Test format improved --- .../descriptors/Properties.expected | 3 ++- .../test/library-tests/descriptors/Properties.ql | 16 +++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/python/ql/test/library-tests/descriptors/Properties.expected b/python/ql/test/library-tests/descriptors/Properties.expected index 89504ea48f3..7dc9690ca1e 100644 --- a/python/ql/test/library-tests/descriptors/Properties.expected +++ b/python/ql/test/library-tests/descriptors/Properties.expected @@ -1 +1,2 @@ -| 3 | Property f | 4 | Function f | 8 | Function f | +| test.py:4:5:4:16 | Function f | getter | test.py:3:6:3:13 | Property f | +| test.py:8:5:8:23 | Function f | setter | test.py:3:6:3:13 | Property f | diff --git a/python/ql/test/library-tests/descriptors/Properties.ql b/python/ql/test/library-tests/descriptors/Properties.ql index e27ca6beb3c..a14731fbf91 100644 --- a/python/ql/test/library-tests/descriptors/Properties.ql +++ b/python/ql/test/library-tests/descriptors/Properties.ql @@ -1,13 +1,11 @@ - import python import semmle.python.types.Descriptors -int lineof(Object o) { - result = o.getOrigin().getLocation().getStartLine() -} - -from PropertyObject p, FunctionObject getter, FunctionObject setter +from PropertyObject p, string method_name, FunctionObject method where -getter = p.getGetter() and setter = p.getSetter() -select lineof(p), p.toString(), lineof(getter), getter.toString(), lineof(setter), setter.toString() - + method_name = "getter" and method = p.getGetter() + or + method_name = "setter" and method = p.getSetter() + or + method_name = "deleter" and method = p.getDeleter() +select method, method_name, p From e747add485d68811fd89aa80b1f3881738e3fd28 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 12 Feb 2020 16:32:50 +0100 Subject: [PATCH 027/236] Python: Descriptor tests fixup (3/3) Better tests for properties --- .../descriptors/Methods.expected | 12 ++--- .../descriptors/Properties.expected | 10 +++- .../ql/test/library-tests/descriptors/test.py | 54 ++++++++++++++++--- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/python/ql/test/library-tests/descriptors/Methods.expected b/python/ql/test/library-tests/descriptors/Methods.expected index a3e0443bd2f..cf21cd37ec5 100644 --- a/python/ql/test/library-tests/descriptors/Methods.expected +++ b/python/ql/test/library-tests/descriptors/Methods.expected @@ -1,6 +1,6 @@ -| 13 | classmethod() | 14 | Function c1 | -| 20 | classmethod() | 17 | Function c2 | -| 21 | classmethod() | 17 | Function c2 | -| 23 | staticmethod() | 24 | Function s1 | -| 30 | staticmethod() | 27 | Function s2 | -| 31 | staticmethod() | 27 | Function s2 | +| 55 | classmethod() | 56 | Function c1 | +| 62 | classmethod() | 59 | Function c2 | +| 63 | classmethod() | 59 | Function c2 | +| 65 | staticmethod() | 66 | Function s1 | +| 72 | staticmethod() | 69 | Function s2 | +| 73 | staticmethod() | 69 | Function s2 | diff --git a/python/ql/test/library-tests/descriptors/Properties.expected b/python/ql/test/library-tests/descriptors/Properties.expected index 7dc9690ca1e..f1e25679833 100644 --- a/python/ql/test/library-tests/descriptors/Properties.expected +++ b/python/ql/test/library-tests/descriptors/Properties.expected @@ -1,2 +1,8 @@ -| test.py:4:5:4:16 | Function f | getter | test.py:3:6:3:13 | Property f | -| test.py:8:5:8:23 | Function f | setter | test.py:3:6:3:13 | Property f | +| test.py:6:5:6:16 | Function x | getter | test.py:5:6:5:13 | Property x | +| test.py:11:5:11:23 | Function x | setter | test.py:5:6:5:13 | Property x | +| test.py:15:5:15:16 | Function x | deleter | test.py:5:6:5:13 | Property x | +| test.py:21:5:21:16 | Function x | getter | test.py:20:6:20:13 | Property x | +| test.py:28:5:28:19 | Function getx | getter | test.py:37:9:37:59 | Property getx | +| test.py:31:5:31:26 | Function setx | setter | test.py:37:9:37:59 | Property getx | +| test.py:34:5:34:19 | Function delx | deleter | test.py:37:9:37:59 | Property getx | +| test.py:41:5:41:19 | Function getx | getter | test.py:44:9:44:22 | Property getx | diff --git a/python/ql/test/library-tests/descriptors/test.py b/python/ql/test/library-tests/descriptors/test.py index 51f75c5aaf1..13f975daa3a 100644 --- a/python/ql/test/library-tests/descriptors/test.py +++ b/python/ql/test/library-tests/descriptors/test.py @@ -1,12 +1,54 @@ -class C(object): +class WithDecorator(object): + def __init__(self): + self._x = None @property - def f(self): - return self._f + def x(self): + """I'm the 'x' property.""" + return self._x - @f.setter - def f(self, value): - self._f = value + @x.setter + def x(self, value): + self._x = value + + @x.deleter + def x(self): + del self._x + +class WithDecoratorOnlyGetter(object): + + @property + def x(self): + return 42 + +class WithoutDecorator(object): + def __init__(self): + self._x = None + + def getx(self): + return self._x + + def setx(self, value): + self._x = value + + def delx(self): + del self._x + + x = property(getx, setx, delx, "I'm the 'x' property.") + +class WithoutDecoratorOnlyGetter(object): + + def getx(self): + return 42 + + x = property(getx) + +class WithoutDecoratorOnlySetter(object): + + def setx(self, value): + self._x = value + + x = property(fset=setx) # TODO: Not handled class D(object): From 67e9edb8203030ebc14d1737d0702ab81ae2790a Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 13 Feb 2020 12:04:46 +0100 Subject: [PATCH 028/236] Python: Add PropertyValue + Extend PropertyInternal.getSetter to handle non-decorator + Add PropertyInternal.getDeleter It seems like a bit hacky way to do things, since we're not using the PropertySetterOrDeleter class at all, but for now I'll leave it be. --- .../src/semmle/python/objects/Descriptors.qll | 28 ++++++++++++++++++- .../src/semmle/python/objects/ObjectAPI.qll | 28 +++++++++++++++++++ .../descriptors/Properties.expected | 16 +++++------ .../library-tests/descriptors/Properties.ql | 2 +- 4 files changed, 64 insertions(+), 10 deletions(-) diff --git a/python/ql/src/semmle/python/objects/Descriptors.qll b/python/ql/src/semmle/python/objects/Descriptors.qll index eb6859f0c0f..31353f92b10 100644 --- a/python/ql/src/semmle/python/objects/Descriptors.qll +++ b/python/ql/src/semmle/python/objects/Descriptors.qll @@ -20,13 +20,39 @@ class PropertyInternal extends ObjectInternal, TProperty { this = TProperty(_, _, result) } + private CallNode getCall() { this = TProperty(result, _, _) } + /** Gets the setter function of this property */ CallableObjectInternal getSetter() { + // @x.setter exists(CallNode call, AttrNode setter | - call.getFunction() = setter and + call.getFunction() = setter and PointsToInternal::pointsTo(setter.getObject("setter"), this.getContext(), this, _) and PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _) ) + or + // x = property(getter, setter, deleter) + exists(ControlFlowNode setter_arg | + setter_arg = getCall().getArg(1) or setter_arg = getCall().getArgByName("fset") + | + PointsToInternal::pointsTo(setter_arg, this.getContext(), result, _) + ) + } + + /** Gets the setter function of this property */ + CallableObjectInternal getDeleter() { + exists(CallNode call, AttrNode setter | + call.getFunction() = setter and + PointsToInternal::pointsTo(setter.getObject("deleter"), this.getContext(), this, _) and + PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _) + ) + or + // x = property(getter, setter, deleter) + exists(ControlFlowNode deleter_arg | + deleter_arg = getCall().getArg(2) or deleter_arg = getCall().getArgByName("fdel") + | + PointsToInternal::pointsTo(deleter_arg, this.getContext(), result, _) + ) } private Context getContext() { this = TProperty(_,result, _) } diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 42e6bc95e7a..716d1a1caa9 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -669,6 +669,34 @@ class NumericValue extends Value { } } +/** A Python property: + * @property + * def f(): + * .... + * + * https://docs.python.org/3/howto/descriptor.html#properties + * https://docs.python.org/3/library/functions.html#property + */ +class PropertyValue extends Value { + + PropertyValue() { + this instanceof PropertyInternal + } + + CallableValue getGetter(){ + result = this.(PropertyInternal).getGetter() + } + + CallableValue getSetter(){ + result = this.(PropertyInternal).getSetter() + } + + CallableValue getDeleter(){ + result = this.(PropertyInternal).getDeleter() + } + +} + /** A method-resolution-order sequence of classes */ class MRO extends TClassList { diff --git a/python/ql/test/library-tests/descriptors/Properties.expected b/python/ql/test/library-tests/descriptors/Properties.expected index f1e25679833..4dc1cf76321 100644 --- a/python/ql/test/library-tests/descriptors/Properties.expected +++ b/python/ql/test/library-tests/descriptors/Properties.expected @@ -1,8 +1,8 @@ -| test.py:6:5:6:16 | Function x | getter | test.py:5:6:5:13 | Property x | -| test.py:11:5:11:23 | Function x | setter | test.py:5:6:5:13 | Property x | -| test.py:15:5:15:16 | Function x | deleter | test.py:5:6:5:13 | Property x | -| test.py:21:5:21:16 | Function x | getter | test.py:20:6:20:13 | Property x | -| test.py:28:5:28:19 | Function getx | getter | test.py:37:9:37:59 | Property getx | -| test.py:31:5:31:26 | Function setx | setter | test.py:37:9:37:59 | Property getx | -| test.py:34:5:34:19 | Function delx | deleter | test.py:37:9:37:59 | Property getx | -| test.py:41:5:41:19 | Function getx | getter | test.py:44:9:44:22 | Property getx | +| test.py:6:5:6:16 | Function WithDecorator.x | getter | test.py:5:6:5:13 | property x | +| test.py:11:5:11:23 | Function WithDecorator.x | setter | test.py:5:6:5:13 | property x | +| test.py:15:5:15:16 | Function WithDecorator.x | deleter | test.py:5:6:5:13 | property x | +| test.py:21:5:21:16 | Function WithDecoratorOnlyGetter.x | getter | test.py:20:6:20:13 | property x | +| test.py:28:5:28:19 | Function WithoutDecorator.getx | getter | test.py:37:9:37:59 | property getx | +| test.py:31:5:31:26 | Function WithoutDecorator.setx | setter | test.py:37:9:37:59 | property getx | +| test.py:34:5:34:19 | Function WithoutDecorator.delx | deleter | test.py:37:9:37:59 | property getx | +| test.py:41:5:41:19 | Function WithoutDecoratorOnlyGetter.getx | getter | test.py:44:9:44:22 | property getx | diff --git a/python/ql/test/library-tests/descriptors/Properties.ql b/python/ql/test/library-tests/descriptors/Properties.ql index a14731fbf91..ed36fb4e5bc 100644 --- a/python/ql/test/library-tests/descriptors/Properties.ql +++ b/python/ql/test/library-tests/descriptors/Properties.ql @@ -1,7 +1,7 @@ import python import semmle.python.types.Descriptors -from PropertyObject p, string method_name, FunctionObject method +from PropertyValue p, string method_name, FunctionValue method where method_name = "getter" and method = p.getGetter() or From 79a4d7e9cc65ed8f244bf7c551fc70c5603a166d Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 13 Feb 2020 13:34:22 +0100 Subject: [PATCH 029/236] Python: Add some confusing (but valid) property tests --- .../descriptors/Methods.expected | 12 ++--- .../ql/test/library-tests/descriptors/test.py | 49 +++++++++++++++++++ 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/python/ql/test/library-tests/descriptors/Methods.expected b/python/ql/test/library-tests/descriptors/Methods.expected index cf21cd37ec5..eff2e730b4c 100644 --- a/python/ql/test/library-tests/descriptors/Methods.expected +++ b/python/ql/test/library-tests/descriptors/Methods.expected @@ -1,6 +1,6 @@ -| 55 | classmethod() | 56 | Function c1 | -| 62 | classmethod() | 59 | Function c2 | -| 63 | classmethod() | 59 | Function c2 | -| 65 | staticmethod() | 66 | Function s1 | -| 72 | staticmethod() | 69 | Function s2 | -| 73 | staticmethod() | 69 | Function s2 | +| 104 | classmethod() | 105 | Function c1 | +| 111 | classmethod() | 108 | Function c2 | +| 112 | classmethod() | 108 | Function c2 | +| 114 | staticmethod() | 115 | Function s1 | +| 121 | staticmethod() | 118 | Function s2 | +| 122 | staticmethod() | 118 | Function s2 | diff --git a/python/ql/test/library-tests/descriptors/test.py b/python/ql/test/library-tests/descriptors/test.py index 13f975daa3a..881350e665d 100644 --- a/python/ql/test/library-tests/descriptors/test.py +++ b/python/ql/test/library-tests/descriptors/test.py @@ -43,6 +43,13 @@ class WithoutDecoratorOnlyGetter(object): x = property(getx) +class WithoutDecoratorOnlyGetterKWArg(object): + + def getx(self): + return 42 + + x = property(fget=getx) + class WithoutDecoratorOnlySetter(object): def setx(self, value): @@ -50,6 +57,48 @@ class WithoutDecoratorOnlySetter(object): x = property(fset=setx) # TODO: Not handled +class WithDecoratorOnlySetter(object): + + x = property() + + @x.setter + def x(self, value): + print('{} setting value to {}'.format(self.__class__, value)) + +class FunkyButValid(object): + + def delx(self): + print("deleting x") + + x = property(fdel=delx) + + @x.setter + def y(self, value): + print('setting value to {}'.format(value)) + + @y.getter + def z(self): + return 42 + + +wat = FunkyButValid() +try: + wat.x +except AttributeError as e: + print("x can't be read") +del wat.x + +try: + wat.y +except AttributeError as e: + print("y can't be read") +wat.y = 1234 +del wat.y + +print(wat.z) +wat.z = 10 +del wat.z + class D(object): @classmethod From b4ab0b55be306e3662de264eaba929899ffa79ca Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 13 Feb 2020 13:52:03 +0100 Subject: [PATCH 030/236] Python: Modernise Statements/RedundantAssignment --- python/ql/src/Statements/RedundantAssignment.ql | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Statements/RedundantAssignment.ql b/python/ql/src/Statements/RedundantAssignment.ql index 6688d248dcc..0751e9b8253 100644 --- a/python/ql/src/Statements/RedundantAssignment.ql +++ b/python/ql/src/Statements/RedundantAssignment.ql @@ -57,11 +57,10 @@ predicate same_name(Name n1, Name n2) { not maybe_defined_in_outer_scope(n2) } -ClassObject value_type(Attribute a) { a.getObject().refersTo(_, result, _) } +ClassValue value_type(Attribute a) { a.getObject().pointsTo().getClass() = result } predicate is_property_access(Attribute a) { - // TODO: We need something to model PropertyObject in the Value API - value_type(a).lookupAttribute(a.getName()) instanceof PropertyObject + value_type(a).lookup(a.getName()) instanceof PropertyValue } predicate same_attribute(Attribute a1, Attribute a2) { From 3e7e9636eaa8b609b792f38d6a7326e71820928c Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 13 Feb 2020 17:14:20 +0100 Subject: [PATCH 031/236] Python: Add ModuleValue.{isUsedAsModule, isUsedAsScript} and a few test cases --- python/ql/src/Statements/TopLevelPrint.ql | 2 +- .../src/semmle/python/objects/ObjectAPI.qll | 26 +++++++++++++++++++ .../modules/usage/ModuleUsage.expected | 5 ++++ .../modules/usage/ModuleUsage.ql | 12 +++++++++ .../library-tests/modules/usage/imported.py | 6 +++++ .../test/library-tests/modules/usage/main.py | 5 ++++ .../test/library-tests/modules/usage/options | 1 + .../test/library-tests/modules/usage/script | 3 +++ 8 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 python/ql/test/library-tests/modules/usage/ModuleUsage.expected create mode 100644 python/ql/test/library-tests/modules/usage/ModuleUsage.ql create mode 100644 python/ql/test/library-tests/modules/usage/imported.py create mode 100644 python/ql/test/library-tests/modules/usage/main.py create mode 100644 python/ql/test/library-tests/modules/usage/options create mode 100755 python/ql/test/library-tests/modules/usage/script diff --git a/python/ql/src/Statements/TopLevelPrint.ql b/python/ql/src/Statements/TopLevelPrint.ql index 104af619049..d818d80a251 100644 --- a/python/ql/src/Statements/TopLevelPrint.ql +++ b/python/ql/src/Statements/TopLevelPrint.ql @@ -35,6 +35,6 @@ from Stmt p where is_print_stmt(p) and // TODO: Need to discuss how we would like to handle ModuleObject.getKind in the glorious future - exists(ModuleObject m | m.getModule() = p.getScope() and m.getKind() = "module") and + exists(ModuleValue m | m.getScope() = p.getScope() and m.isUsedAsModule()) and not exists(If i | main_eq_name(i) and i.getASubStatement().getASubStatement*() = p) select p, "Print statement may execute during import." diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 716d1a1caa9..86f1f305f44 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -187,6 +187,32 @@ class ModuleValue extends Value { result.importedAs(this.getScope().getAnImportedModuleName()) } + /** When used as a normal module (for example, imported and used by other modules) */ + predicate isUsedAsModule() { + this.isBuiltin() + or + this.isPackage() + or + exists(ImportingStmt i | this.importedAs(i.getAnImportedModuleName())) + or + this.getPath().getBaseName() = "__init__.py" + } + + /** When used (exclusively) as a script (will not include normal modules that can also be run as a script) */ + predicate isUsedAsScript() { + not isUsedAsModule() and + ( + not this.getPath().getExtension() = "py" + or + exists(If i, Name name, StrConst main, Cmpop op | + i.getScope() = this.getScope() and + op instanceof Eq and + i.getTest().(Compare).compares(name, op, main) and + name.getId() = "__name__" and main.getText() = "__main__" + ) + // TODO: Add she-bang handling + ) + } } module Module { diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected new file mode 100644 index 00000000000..0b6e68ee826 --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected @@ -0,0 +1,5 @@ +| file://:0:0:0:0 | Module builtins | isUsedAsModule | +| file://:0:0:0:0 | Module sys | isUsedAsModule | +| imported.py:0:0:0:0 | Module imported | isUsedAsModule | +| main.py:0:0:0:0 | Module main | isUsedAsScript | +| mybin:0:0:0:0 | Script script | isUsedAsScript | diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.ql b/python/ql/test/library-tests/modules/usage/ModuleUsage.ql new file mode 100644 index 00000000000..159447cf282 --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.ql @@ -0,0 +1,12 @@ +import python + +from ModuleValue mv, string usage +where + mv.isUsedAsModule() and usage = "isUsedAsModule" + or + mv.isUsedAsScript() and usage = "isUsedAsScript" + or + not mv.isUsedAsModule() and + not mv.isUsedAsScript() and + usage = "" +select mv, usage diff --git a/python/ql/test/library-tests/modules/usage/imported.py b/python/ql/test/library-tests/modules/usage/imported.py new file mode 100644 index 00000000000..98e3dfd364a --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/imported.py @@ -0,0 +1,6 @@ +def func(): + pass + +if __name__ == "__main__": + print("I could have done something interesting...") + print("but I didn't") diff --git a/python/ql/test/library-tests/modules/usage/main.py b/python/ql/test/library-tests/modules/usage/main.py new file mode 100644 index 00000000000..171fd01402f --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/main.py @@ -0,0 +1,5 @@ +import imported + +if __name__ == "__main__": + imported.func() + print('Done') diff --git a/python/ql/test/library-tests/modules/usage/options b/python/ql/test/library-tests/modules/usage/options new file mode 100644 index 00000000000..98459309caa --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/options @@ -0,0 +1 @@ +semmle-extractor-options: -F script diff --git a/python/ql/test/library-tests/modules/usage/script b/python/ql/test/library-tests/modules/usage/script new file mode 100755 index 00000000000..b5ec8d6f2aa --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/script @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +print('Under construction :)') From 01f5b3dc63b5a41057a0d41fb191ae4ebad9a40a Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 13 Feb 2020 17:19:07 +0100 Subject: [PATCH 032/236] Python: Add a script that we can't classify usage of --- .../ql/test/library-tests/modules/usage/ModuleUsage.expected | 3 ++- python/ql/test/library-tests/modules/usage/myscript.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100755 python/ql/test/library-tests/modules/usage/myscript.py diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected index 0b6e68ee826..c108a6a9fd8 100644 --- a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected @@ -2,4 +2,5 @@ | file://:0:0:0:0 | Module sys | isUsedAsModule | | imported.py:0:0:0:0 | Module imported | isUsedAsModule | | main.py:0:0:0:0 | Module main | isUsedAsScript | -| mybin:0:0:0:0 | Script script | isUsedAsScript | +| myscript.py:0:0:0:0 | Script myscript | | +| script:0:0:0:0 | Script script | isUsedAsScript | diff --git a/python/ql/test/library-tests/modules/usage/myscript.py b/python/ql/test/library-tests/modules/usage/myscript.py new file mode 100755 index 00000000000..0eb83a9dadb --- /dev/null +++ b/python/ql/test/library-tests/modules/usage/myscript.py @@ -0,0 +1,3 @@ +#!/usr/bin/env python + +print("I'm actually a script you see ;)") From 82b29b56988e206435b4e290fcc4a491248c27e3 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 13 Feb 2020 17:44:56 +0100 Subject: [PATCH 033/236] Python: Recognize shebangs in module usage detection --- python/ql/src/semmle/python/objects/ObjectAPI.qll | 7 ++++++- .../test/library-tests/modules/usage/ModuleUsage.expected | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 86f1f305f44..a7213455d3e 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -210,7 +210,12 @@ class ModuleValue extends Value { i.getTest().(Compare).compares(name, op, main) and name.getId() = "__name__" and main.getText() = "__main__" ) - // TODO: Add she-bang handling + or + exists(Comment c | + c.getLocation().getFile() = this.getPath() and + c.getLocation().getStartLine() = 1 and + c.getText().regexpMatch("^#!/.*python(2|3)?[ \\\\t]*$") + ) ) } } diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected index c108a6a9fd8..0f76ee35081 100644 --- a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected @@ -2,5 +2,5 @@ | file://:0:0:0:0 | Module sys | isUsedAsModule | | imported.py:0:0:0:0 | Module imported | isUsedAsModule | | main.py:0:0:0:0 | Module main | isUsedAsScript | -| myscript.py:0:0:0:0 | Script myscript | | +| myscript.py:0:0:0:0 | Script myscript | isUsedAsScript | | script:0:0:0:0 | Script script | isUsedAsScript | From 87eff7f062a5aa6fa5df4daae6153950cb9be1c2 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 14 Feb 2020 17:50:15 +0100 Subject: [PATCH 034/236] Python: More iterator => iterable renaming --- .../query-tests/Statements/iter/NonIteratorInForLoop.expected | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected b/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected index 4f6ed0fac55..1f807ddf5f0 100644 --- a/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected +++ b/python/ql/test/3/query-tests/Statements/iter/NonIteratorInForLoop.expected @@ -1,2 +1,2 @@ -| async_iterator.py:26:11:26:34 | For | $@ of class '$@' may be used in for-loop. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | Non-iterator | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter | -| statements_test.py:34:5:34:19 | For | $@ of class '$@' may be used in for-loop. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | Non-iterator | file://:0:0:0:0 | builtin-class int | int | +| async_iterator.py:26:11:26:34 | For | $@ of class '$@' may be used in for-loop. | async_iterator.py:26:20:26:33 | ControlFlowNode for MissingAiter() | Non-iterable | async_iterator.py:13:1:13:19 | class MissingAiter | MissingAiter | +| statements_test.py:34:5:34:19 | For | $@ of class '$@' may be used in for-loop. | statements_test.py:34:18:34:18 | ControlFlowNode for IntegerLiteral | Non-iterable | file://:0:0:0:0 | builtin-class int | int | From 0509228296f3136485fd7a6f4e8bcb14a0c845e0 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 14 Feb 2020 17:50:46 +0100 Subject: [PATCH 035/236] Python: Make ModelUsage test language agnostic --- .../modules/usage/ModuleUsage.expected | 1 - .../library-tests/modules/usage/ModuleUsage.ql | 18 +++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected index 0f76ee35081..ae241915998 100644 --- a/python/ql/test/library-tests/modules/usage/ModuleUsage.expected +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.expected @@ -1,4 +1,3 @@ -| file://:0:0:0:0 | Module builtins | isUsedAsModule | | file://:0:0:0:0 | Module sys | isUsedAsModule | | imported.py:0:0:0:0 | Module imported | isUsedAsModule | | main.py:0:0:0:0 | Module main | isUsedAsScript | diff --git a/python/ql/test/library-tests/modules/usage/ModuleUsage.ql b/python/ql/test/library-tests/modules/usage/ModuleUsage.ql index 159447cf282..3ff70adb69d 100644 --- a/python/ql/test/library-tests/modules/usage/ModuleUsage.ql +++ b/python/ql/test/library-tests/modules/usage/ModuleUsage.ql @@ -2,11 +2,15 @@ import python from ModuleValue mv, string usage where - mv.isUsedAsModule() and usage = "isUsedAsModule" - or - mv.isUsedAsScript() and usage = "isUsedAsScript" - or - not mv.isUsedAsModule() and - not mv.isUsedAsScript() and - usage = "" + // builtin module has different name in Python 2 and 3 + not mv = Module::builtinModule() and + ( + mv.isUsedAsModule() and usage = "isUsedAsModule" + or + mv.isUsedAsScript() and usage = "isUsedAsScript" + or + not mv.isUsedAsModule() and + not mv.isUsedAsScript() and + usage = "" + ) select mv, usage From d7b803a8593bd843d9ebd73e5de69b227c5fbcd2 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 14 Feb 2020 17:51:31 +0100 Subject: [PATCH 036/236] Python: Fix modernisation of py/iteration-string-and-sequence Introduced a regression, since the old code was: ``` predicate is_a_string_type(ClassObject seqtype) { seqtype = theBytesType() and major_version() = 2 or seqtype = theUnicodeType() } ``` but *now* we're good! --- python/ql/src/Statements/IterableStringOrSequence.ql | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index fb7b2198bda..7602f98bce7 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -13,6 +13,11 @@ import python +predicate has_string_type(Value v) { + v.getClass() = ClassValue::bytes() and major_version() = 2 + or + v.getClass() = ClassValue::unicode() +} from For loop, ControlFlowNode iter, Value str, Value seq, ControlFlowNode seq_origin, ControlFlowNode str_origin @@ -20,8 +25,8 @@ where loop.getIter().getAFlowNode() = iter and iter.pointsTo(str, str_origin) and iter.pointsTo(seq, seq_origin) and - str.getClass() = ClassValue::str() and + has_string_type(str) and seq.getClass().isIterable() and - not seq.getClass() = ClassValue::str() + not has_string_type(seq) select loop, "Iteration over $@, of class " + seq.getClass().getName() + ", may also iterate over $@.", seq_origin, "sequence", str_origin, "string" From e7fdfd3d3e162a12041f15d0487da710ad6ad543 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 17 Feb 2020 14:12:37 +0100 Subject: [PATCH 037/236] Python: Move subprocess.call so super-class detection works This is a temporary fix! Added minimal working example (MWE) as a regression, so it's easier to fix the real problem. only Python 3 is facing the problem -- and without --max-import-depth=1 the test times out at 10 minutes :O --- .../regressions/subprocess-assert/ClassValue.expected | 3 +++ .../regressions/subprocess-assert/ClassValue.ql | 10 ++++++++++ .../regressions/subprocess-assert/mwe_failure.py | 10 ++++++++++ .../regressions/subprocess-assert/mwe_failure_2.py | 8 ++++++++ .../regressions/subprocess-assert/mwe_success.py | 8 ++++++++ .../PointsTo/regressions/subprocess-assert/options | 1 + .../Statements/asserts/SideEffectInAssert.expected | 2 +- .../ql/test/query-tests/Statements/asserts/assert.py | 4 ++-- .../test/query-tests/Statements/asserts/side_effect.py | 5 +++++ 9 files changed, 48 insertions(+), 3 deletions(-) create mode 100644 python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected create mode 100644 python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql create mode 100644 python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py create mode 100644 python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py create mode 100644 python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py create mode 100644 python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options create mode 100644 python/ql/test/query-tests/Statements/asserts/side_effect.py diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected new file mode 100644 index 00000000000..1c02b5ee6c6 --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.expected @@ -0,0 +1,3 @@ +| mwe_failure.py:7:1:7:23 | class MyTest | | +| mwe_failure_2.py:7:1:7:23 | class MyTest | | +| mwe_success.py:7:1:7:23 | class MyTest | class TestCase | diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql new file mode 100644 index 00000000000..b6f1ce2da98 --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/ClassValue.ql @@ -0,0 +1,10 @@ +import python + +// as used in semmle.python.filters.Tests + +from ClassValue c, string base +where + c.getScope().getLocation().getFile().getShortName().matches("mwe%.py") and + c.getName() = "MyTest" and + if exists(c.getABaseType()) then base = c.getABaseType().toString() else base = "" +select c, base diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py new file mode 100644 index 00000000000..6c22b37b04b --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py @@ -0,0 +1,10 @@ +import subprocess +assert subprocess.call(['run-backup']) == 0 + +class TestCase: + pass + +class MyTest(TestCase): + pass + +# found by /home/rasmus/code/ql/python/ql/test/query-tests/Statements/asserts/AssertLiteralConstant.qlref diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py new file mode 100644 index 00000000000..84bed797f8e --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure_2.py @@ -0,0 +1,8 @@ +import subprocess +assert subprocess.call(['run-backup']) + +class TestCase: + pass + +class MyTest(TestCase): + pass diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py new file mode 100644 index 00000000000..fce8f9bd25c --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/mwe_success.py @@ -0,0 +1,8 @@ +import subprocess +subprocess.call(['run-backup']) + +class TestCase: + pass + +class MyTest(TestCase): + pass diff --git a/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options new file mode 100644 index 00000000000..89369a90996 --- /dev/null +++ b/python/ql/test/3/library-tests/PointsTo/regressions/subprocess-assert/options @@ -0,0 +1 @@ +semmle-extractor-options: --lang=3 --max-import-depth=1 diff --git a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected index f7425a3a429..f03252810d8 100644 --- a/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected +++ b/python/ql/test/query-tests/Statements/asserts/SideEffectInAssert.expected @@ -1,3 +1,3 @@ | assert.py:5:5:5:20 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:5:13:5:19 | Yield | an expression | | assert.py:8:5:8:22 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:8:12:8:22 | Attribute() | an expression | -| assert.py:20:1:20:43 | Assert | This 'assert' statement contains $@ which may have side effects. | assert.py:20:8:20:38 | Attribute() | an expression | +| side_effect.py:5:1:5:43 | Assert | This 'assert' statement contains $@ which may have side effects. | side_effect.py:5:8:5:38 | Attribute() | an expression | diff --git a/python/ql/test/query-tests/Statements/asserts/assert.py b/python/ql/test/query-tests/Statements/asserts/assert.py index d6c1e27daba..036f9b20a4e 100644 --- a/python/ql/test/query-tests/Statements/asserts/assert.py +++ b/python/ql/test/query-tests/Statements/asserts/assert.py @@ -16,8 +16,8 @@ def assert_tuple(x, y): assert () assert (x, y) -import subprocess -assert subprocess.call(['run-backup']) == 0 # TODO: FN + + diff --git a/python/ql/test/query-tests/Statements/asserts/side_effect.py b/python/ql/test/query-tests/Statements/asserts/side_effect.py new file mode 100644 index 00000000000..42b9f0a16a2 --- /dev/null +++ b/python/ql/test/query-tests/Statements/asserts/side_effect.py @@ -0,0 +1,5 @@ +# For now, this test lives in its own file, since including them in the top of assert.py +# messes up the results of the refers-to/points-to analysis +# see /home/rasmus/code/ql/python/ql/test/library-tests/PointsTo/regressions/subprocess-assert/mwe_failure.py +import subprocess +assert subprocess.call(['run-backup']) == 0 From e4b83855d9097c60e4784358809e5da4a97c8b4b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 19 Feb 2020 15:11:36 +0100 Subject: [PATCH 038/236] Python: Autoformat security/strings/External.qll --- .../python/security/strings/External.qll | 72 +++++++------------ 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index bd567f54f24..4dcbe0d17e3 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -2,45 +2,36 @@ import python import Basic private import Common -/** An extensible kind of taint representing an externally controlled string. +/** + * An extensible kind of taint representing an externally controlled string. */ abstract class ExternalStringKind extends StringKind { - bindingset[this] - ExternalStringKind() { - this = this - } + ExternalStringKind() { this = this } override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { result = StringKind.super.getTaintForFlowStep(fromnode, tonode) or - tonode.(SequenceNode).getElement(_) = fromnode and result.(ExternalStringSequenceKind).getItem() = this + tonode.(SequenceNode).getElement(_) = fromnode and + result.(ExternalStringSequenceKind).getItem() = this or json_load(fromnode, tonode) and result.(ExternalJsonKind).getValue() = this or tonode.(DictNode).getAValue() = fromnode and result.(ExternalStringDictKind).getValue() = this } - } /** A kind of "taint", representing a sequence, with a "taint" member */ class ExternalStringSequenceKind extends SequenceKind { - - ExternalStringSequenceKind() { - this.getItem() instanceof ExternalStringKind - } - + ExternalStringSequenceKind() { this.getItem() instanceof ExternalStringKind } } -/** An hierachical dictionary or list where the entire structure is externally controlled +/** + * An hierachical dictionary or list where the entire structure is externally controlled * This is typically a parsed JSON object. */ class ExternalJsonKind extends TaintKind { - - ExternalJsonKind() { - this = "json[" + any(ExternalStringKind key) + "]" - } - + ExternalJsonKind() { this = "json[" + any(ExternalStringKind key) + "]" } /** Gets the taint kind for item in this sequence */ TaintKind getValue() { @@ -54,65 +45,52 @@ class ExternalJsonKind extends TaintKind { json_subscript_taint(tonode, fromnode, this, result) or result = this and copy_call(fromnode, tonode) - } + } override TaintKind getTaintOfMethodResult(string name) { name = "get" and result = this.getValue() - } - + } } /** A kind of "taint", representing a dictionary mapping str->"taint" */ class ExternalStringDictKind extends DictKind { - - ExternalStringDictKind() { - this.getValue() instanceof ExternalStringKind - } - + ExternalStringDictKind() { this.getValue() instanceof ExternalStringKind } } -/** A kind of "taint", representing a dictionary mapping strings to sequences of - * tainted strings */ - +/** + * A kind of "taint", representing a dictionary mapping strings to sequences of + * tainted strings + */ class ExternalStringSequenceDictKind extends DictKind { - ExternalStringSequenceDictKind() { - this.getValue() instanceof ExternalStringSequenceKind - } + ExternalStringSequenceDictKind() { this.getValue() instanceof ExternalStringSequenceKind } } /* Helper for getTaintForStep() */ -pragma [noinline] -private predicate json_subscript_taint(SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key) { +pragma[noinline] +private predicate json_subscript_taint( + SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key +) { sub.isLoad() and sub.getValue() = obj and key = seq.getValue() } - private predicate json_load(ControlFlowNode fromnode, CallNode tonode) { exists(FunctionObject json_loads | ModuleObject::named("json").attr("loads") = json_loads and - json_loads.getACall() = tonode and tonode.getArg(0) = fromnode + json_loads.getACall() = tonode and + tonode.getArg(0) = fromnode ) } /** A kind of "taint", representing an open file-like object from an external source. */ class ExternalFileObject extends TaintKind { - - ExternalFileObject() { - this = "file[" + any(ExternalStringKind key) + "]" - } - + ExternalFileObject() { this = "file[" + any(ExternalStringKind key) + "]" } /** Gets the taint kind for the contents of this file */ - TaintKind getValue() { - this = "file[" + result + "]" - } + TaintKind getValue() { this = "file[" + result + "]" } override TaintKind getTaintOfMethodResult(string name) { name = "read" and result = this.getValue() } - } - - From 74345b1c05e496d4eb711f031b0890981841e77e Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 19 Feb 2020 15:33:53 +0100 Subject: [PATCH 039/236] Python: Make library-tests/taint/strings tests more transparent Following the setup I invented for library-tests/taint/unpacking. TestStep is still a bit annoying, since the output is not easy to eyeball; but for now I guess we can live with it :) I honestly didn't get the point of DistinctStringKinds.ql, other than showing we can handle multiple taint kinds --- .../strings/DistinctStringKinds.expected | 16 ---- .../taint/strings/DistinctStringKinds.ql | 37 -------- .../library-tests/taint/strings/Taint.qll | 16 +++- .../taint/strings/TestNode.expected | 43 ---------- .../library-tests/taint/strings/TestNode.ql | 8 -- .../taint/strings/TestStep.expected | 84 +++++++++++++------ .../taint/strings/TestTaint.expected | 22 +++++ .../library-tests/taint/strings/TestTaint.ql | 18 ++++ .../test/library-tests/taint/strings/test.py | 27 +++--- 9 files changed, 129 insertions(+), 142 deletions(-) delete mode 100644 python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected delete mode 100644 python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql delete mode 100644 python/ql/test/library-tests/taint/strings/TestNode.expected delete mode 100644 python/ql/test/library-tests/taint/strings/TestNode.ql create mode 100644 python/ql/test/library-tests/taint/strings/TestTaint.expected create mode 100644 python/ql/test/library-tests/taint/strings/TestTaint.ql diff --git a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected deleted file mode 100644 index e7c6590cb50..00000000000 --- a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.expected +++ /dev/null @@ -1,16 +0,0 @@ -| Taint exception.info | test.py:41 | test.py:41:22:41:26 | taint | p1 = exception.info | -| Taint exception.info | test.py:42 | test.py:42:12:42:22 | func() | p1 = exception.info | -| Taint exception.info | test.py:42 | test.py:42:17:42:21 | taint | p1 = exception.info | -| Taint exception.info | test.py:45 | test.py:45:12:45:33 | TAINTED_EXCEPTION_INFO | | -| Taint exception.info | test.py:46 | test.py:46:11:46:41 | cross_over() | | -| Taint exception.info | test.py:46 | test.py:46:37:46:40 | info | | -| Taint exception.info | test.py:48 | test.py:48:19:48:21 | arg | p0 = exception.info | -| Taint exception.info | test.py:49 | test.py:49:12:49:14 | arg | p0 = exception.info | -| Taint externally controlled string | test.py:41 | test.py:41:22:41:26 | taint | p1 = externally controlled string | -| Taint externally controlled string | test.py:42 | test.py:42:12:42:22 | func() | p1 = externally controlled string | -| Taint externally controlled string | test.py:42 | test.py:42:17:42:21 | taint | p1 = externally controlled string | -| Taint externally controlled string | test.py:48 | test.py:48:19:48:21 | arg | p0 = externally controlled string | -| Taint externally controlled string | test.py:49 | test.py:49:12:49:14 | arg | p0 = externally controlled string | -| Taint externally controlled string | test.py:52 | test.py:52:11:52:33 | TAINTED_EXTERNAL_STRING | | -| Taint externally controlled string | test.py:53 | test.py:53:11:53:41 | cross_over() | | -| Taint externally controlled string | test.py:53 | test.py:53:38:53:40 | ext | | diff --git a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql b/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql deleted file mode 100644 index a1bf5ea5e1f..00000000000 --- a/python/ql/test/library-tests/taint/strings/DistinctStringKinds.ql +++ /dev/null @@ -1,37 +0,0 @@ -import python -import semmle.python.security.TaintTracking - -import semmle.python.security.Exceptions -import semmle.python.security.strings.Untrusted - - -class ExceptionInfoSource extends TaintSource { - - ExceptionInfoSource() { this.(NameNode).getId() = "TAINTED_EXCEPTION_INFO" } - - override predicate isSourceOf(TaintKind kind) { - kind instanceof ExceptionInfo - } - - override string toString() { - result = "Exception info source" - } - -} - -class ExternalStringSource extends TaintSource { - - ExternalStringSource() { this.(NameNode).getId() = "TAINTED_EXTERNAL_STRING" } - - override predicate isSourceOf(TaintKind kind) { - kind instanceof ExternalStringKind - } - - override string toString() { - result = "Untrusted string source" - } - -} -from TaintedNode n -where n.getLocation().getFile().getShortName() = "test.py" -select "Taint " + n.getTaintKind(), n.getLocation().toString(), n.getAstNode(), n.getContext() diff --git a/python/ql/test/library-tests/taint/strings/Taint.qll b/python/ql/test/library-tests/taint/strings/Taint.qll index b5b2fb61a5e..d045534e0ef 100644 --- a/python/ql/test/library-tests/taint/strings/Taint.qll +++ b/python/ql/test/library-tests/taint/strings/Taint.qll @@ -1,11 +1,12 @@ import python import semmle.python.security.TaintTracking import semmle.python.security.strings.Untrusted +import semmle.python.security.Exceptions class SimpleSource extends TaintSource { - SimpleSource() { this.(NameNode).getId() = "TAINTED" } + SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" } override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind @@ -46,3 +47,16 @@ class DictSource extends TaintSource { } +class ExceptionInfoSource extends TaintSource { + + ExceptionInfoSource() { this.(NameNode).getId() = "TAINTED_EXCEPTION_INFO" } + + override predicate isSourceOf(TaintKind kind) { + kind instanceof ExceptionInfo + } + + override string toString() { + result = "Exception info source" + } + +} diff --git a/python/ql/test/library-tests/taint/strings/TestNode.expected b/python/ql/test/library-tests/taint/strings/TestNode.expected deleted file mode 100644 index 3e248be5f89..00000000000 --- a/python/ql/test/library-tests/taint/strings/TestNode.expected +++ /dev/null @@ -1,43 +0,0 @@ -| Taint externally controlled string | test.py:5 | test.py:5:22:5:28 | TAINTED | | -| Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | -| Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | -| Taint externally controlled string | test.py:8 | test.py:8:9:8:9 | a | | -| Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | -| Taint externally controlled string | test.py:9 | test.py:9:9:9:9 | b | | -| Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | -| Taint externally controlled string | test.py:13 | test.py:13:9:13:22 | tainted_string | | -| Taint externally controlled string | test.py:13 | test.py:13:9:13:31 | Attribute() | | -| Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | -| Taint externally controlled string | test.py:14 | test.py:14:9:14:29 | Attribute() | | -| Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | -| Taint externally controlled string | test.py:15 | test.py:15:9:15:25 | Subscript | | -| Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | -| Taint externally controlled string | test.py:16 | test.py:16:9:16:27 | Subscript | | -| Taint externally controlled string | test.py:17 | test.py:17:9:17:32 | reversed() | | -| Taint externally controlled string | test.py:17 | test.py:17:18:17:31 | tainted_string | | -| Taint externally controlled string | test.py:18 | test.py:18:9:18:28 | copy() | | -| Taint externally controlled string | test.py:18 | test.py:18:14:18:27 | tainted_string | | -| Taint externally controlled string | test.py:19 | test.py:19:9:19:22 | tainted_string | | -| Taint externally controlled string | test.py:19 | test.py:19:9:19:30 | Attribute() | | -| Taint externally controlled string | test.py:22 | test.py:22:22:22:28 | TAINTED | | -| Taint externally controlled string | test.py:23 | test.py:23:8:23:21 | tainted_string | | -| Taint externally controlled string | test.py:26 | test.py:26:23:26:36 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | -| Taint externally controlled string | test.py:30 | test.py:30:8:30:21 | tainted_string | | -| Taint externally controlled string | test.py:30 | test.py:30:34:30:47 | tainted_string | | -| Taint externally controlled string | test.py:33 | test.py:33:23:33:36 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | -| Taint externally controlled string | test.py:37 | test.py:37:9:37:27 | str() | | -| Taint externally controlled string | test.py:37 | test.py:37:13:37:26 | tainted_string | | -| Taint externally controlled string | test.py:38 | test.py:38:9:38:29 | bytes() | | -| Taint externally controlled string | test.py:38 | test.py:38:15:38:28 | tainted_string | | -| Taint externally controlled string | test.py:39 | test.py:39:9:39:46 | bytes() | | -| Taint externally controlled string | test.py:39 | test.py:39:15:39:28 | tainted_string | | -| Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | -| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | -| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | -| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | -| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | -| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | -| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | diff --git a/python/ql/test/library-tests/taint/strings/TestNode.ql b/python/ql/test/library-tests/taint/strings/TestNode.ql deleted file mode 100644 index 23b6643f86a..00000000000 --- a/python/ql/test/library-tests/taint/strings/TestNode.ql +++ /dev/null @@ -1,8 +0,0 @@ -import python -import semmle.python.security.TaintTracking -import Taint - - -from TaintedNode n -where n.getLocation().getFile().getShortName() = "test.py" -select "Taint " + n.getTaintKind(), n.getLocation().toString(), n.getCfgNode().getNode(), n.getContext() diff --git a/python/ql/test/library-tests/taint/strings/TestStep.expected b/python/ql/test/library-tests/taint/strings/TestStep.expected index 17373b5db79..eccb3efa010 100644 --- a/python/ql/test/library-tests/taint/strings/TestStep.expected +++ b/python/ql/test/library-tests/taint/strings/TestStep.expected @@ -1,38 +1,70 @@ -| Taint externally controlled string | test.py:5 | test.py:5:22:5:28 | TAINTED | | --> | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | +| Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | +| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:12:45:22 | func() | p1 = exception.info | +| Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info | +| Taint exception.info | test.py:48 | test.py:48:12:48:33 | TAINTED_EXCEPTION_INFO | | --> | Taint exception.info | test.py:49 | test.py:49:37:49:40 | info | | +| Taint exception.info | test.py:49 | test.py:49:11:49:41 | cross_over() | | --> | Taint exception.info | test.py:50 | test.py:50:10:50:12 | res | | +| Taint exception.info | test.py:49 | test.py:49:37:49:40 | info | | --> | Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | +| Taint exception.info | test.py:49 | test.py:49:37:49:40 | info | | --> | Taint exception.info | test.py:49 | test.py:49:11:49:41 | cross_over() | | +| Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info | --> | Taint exception.info | test.py:53 | test.py:53:12:53:14 | arg | p0 = exception.info | +| Taint externally controlled string | test.py:5 | test.py:5:22:5:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | | Taint externally controlled string | test.py:6 | test.py:6:31:6:44 | tainted_string | | --> | Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint externally controlled string | test.py:8 | test.py:8:9:8:9 | a | | +| Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint externally controlled string | test.py:10 | test.py:10:10:10:10 | a | | | Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint externally controlled string | test.py:9 | test.py:9:9:9:9 | b | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:13 | test.py:13:9:13:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:17 | test.py:17:18:17:31 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:18 | test.py:18:14:18:27 | tainted_string | | -| Taint externally controlled string | test.py:12 | test.py:12:22:12:28 | TAINTED | | --> | Taint externally controlled string | test.py:19 | test.py:19:9:19:22 | tainted_string | | -| Taint externally controlled string | test.py:13 | test.py:13:9:13:22 | tainted_string | | --> | Taint externally controlled string | test.py:13 | test.py:13:9:13:31 | Attribute() | | -| Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:29 | Attribute() | | -| Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:25 | Subscript | | -| Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:27 | Subscript | | -| Taint externally controlled string | test.py:17 | test.py:17:18:17:31 | tainted_string | | --> | Taint externally controlled string | test.py:17 | test.py:17:9:17:32 | reversed() | | -| Taint externally controlled string | test.py:18 | test.py:18:14:18:27 | tainted_string | | --> | Taint externally controlled string | test.py:18 | test.py:18:9:18:28 | copy() | | -| Taint externally controlled string | test.py:19 | test.py:19:9:19:22 | tainted_string | | --> | Taint externally controlled string | test.py:19 | test.py:19:9:19:30 | Attribute() | | -| Taint externally controlled string | test.py:22 | test.py:22:22:22:28 | TAINTED | | --> | Taint externally controlled string | test.py:23 | test.py:23:8:23:21 | tainted_string | | -| Taint externally controlled string | test.py:22 | test.py:22:22:22:28 | TAINTED | | --> | Taint externally controlled string | test.py:26 | test.py:26:23:26:36 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | --> | Taint externally controlled string | test.py:30 | test.py:30:8:30:21 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | --> | Taint externally controlled string | test.py:30 | test.py:30:34:30:47 | tainted_string | | -| Taint externally controlled string | test.py:29 | test.py:29:22:29:28 | TAINTED | | --> | Taint externally controlled string | test.py:33 | test.py:33:23:33:36 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | --> | Taint externally controlled string | test.py:37 | test.py:37:13:37:26 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | --> | Taint externally controlled string | test.py:38 | test.py:38:15:38:28 | tainted_string | | -| Taint externally controlled string | test.py:36 | test.py:36:22:36:28 | TAINTED | | --> | Taint externally controlled string | test.py:39 | test.py:39:15:39:28 | tainted_string | | -| Taint externally controlled string | test.py:37 | test.py:37:13:37:26 | tainted_string | | --> | Taint externally controlled string | test.py:37 | test.py:37:9:37:27 | str() | | -| Taint externally controlled string | test.py:38 | test.py:38:15:38:28 | tainted_string | | --> | Taint externally controlled string | test.py:38 | test.py:38:9:38:29 | bytes() | | -| Taint externally controlled string | test.py:39 | test.py:39:15:39:28 | tainted_string | | --> | Taint externally controlled string | test.py:39 | test.py:39:9:39:46 | bytes() | | +| Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint externally controlled string | test.py:10 | test.py:10:13:10:13 | b | | +| Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | --> | Taint externally controlled string | test.py:10 | test.py:10:16:10:16 | c | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:17 | test.py:17:9:17:22 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:18 | test.py:18:18:18:31 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:19 | test.py:19:14:19:27 | tainted_string | | +| Taint externally controlled string | test.py:13 | test.py:13:22:13:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:20 | test.py:20:9:20:22 | tainted_string | | +| Taint externally controlled string | test.py:14 | test.py:14:9:14:22 | tainted_string | | --> | Taint externally controlled string | test.py:14 | test.py:14:9:14:31 | Attribute() | | +| Taint externally controlled string | test.py:14 | test.py:14:9:14:31 | Attribute() | | --> | Taint externally controlled string | test.py:21 | test.py:21:10:21:10 | a | | +| Taint externally controlled string | test.py:15 | test.py:15:9:15:22 | tainted_string | | --> | Taint externally controlled string | test.py:15 | test.py:15:9:15:29 | Attribute() | | +| Taint externally controlled string | test.py:15 | test.py:15:9:15:29 | Attribute() | | --> | Taint externally controlled string | test.py:21 | test.py:21:13:21:13 | b | | +| Taint externally controlled string | test.py:16 | test.py:16:9:16:22 | tainted_string | | --> | Taint externally controlled string | test.py:16 | test.py:16:9:16:25 | Subscript | | +| Taint externally controlled string | test.py:16 | test.py:16:9:16:25 | Subscript | | --> | Taint externally controlled string | test.py:21 | test.py:21:16:21:16 | c | | +| Taint externally controlled string | test.py:17 | test.py:17:9:17:22 | tainted_string | | --> | Taint externally controlled string | test.py:17 | test.py:17:9:17:27 | Subscript | | +| Taint externally controlled string | test.py:17 | test.py:17:9:17:27 | Subscript | | --> | Taint externally controlled string | test.py:21 | test.py:21:19:21:19 | d | | +| Taint externally controlled string | test.py:18 | test.py:18:9:18:32 | reversed() | | --> | Taint externally controlled string | test.py:21 | test.py:21:22:21:22 | e | | +| Taint externally controlled string | test.py:18 | test.py:18:18:18:31 | tainted_string | | --> | Taint externally controlled string | test.py:18 | test.py:18:9:18:32 | reversed() | | +| Taint externally controlled string | test.py:19 | test.py:19:9:19:28 | copy() | | --> | Taint externally controlled string | test.py:21 | test.py:21:25:21:25 | f | | +| Taint externally controlled string | test.py:19 | test.py:19:14:19:27 | tainted_string | | --> | Taint externally controlled string | test.py:19 | test.py:19:9:19:28 | copy() | | +| Taint externally controlled string | test.py:20 | test.py:20:9:20:22 | tainted_string | | --> | Taint externally controlled string | test.py:20 | test.py:20:9:20:30 | Attribute() | | +| Taint externally controlled string | test.py:20 | test.py:20:9:20:30 | Attribute() | | --> | Taint externally controlled string | test.py:21 | test.py:21:28:21:28 | g | | +| Taint externally controlled string | test.py:24 | test.py:24:22:24:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:25 | test.py:25:8:25:21 | tainted_string | | +| Taint externally controlled string | test.py:24 | test.py:24:22:24:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:28 | test.py:28:14:28:27 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:22:31:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:32 | test.py:32:8:32:21 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:22:31:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:32 | test.py:32:34:32:47 | tainted_string | | +| Taint externally controlled string | test.py:31 | test.py:31:22:31:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:35 | test.py:35:14:35:27 | tainted_string | | +| Taint externally controlled string | test.py:38 | test.py:38:22:38:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:39 | test.py:39:13:39:26 | tainted_string | | +| Taint externally controlled string | test.py:38 | test.py:38:22:38:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:40 | test.py:40:15:40:28 | tainted_string | | +| Taint externally controlled string | test.py:38 | test.py:38:22:38:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:41 | test.py:41:15:41:28 | tainted_string | | +| Taint externally controlled string | test.py:39 | test.py:39:9:39:27 | str() | | --> | Taint externally controlled string | test.py:42 | test.py:42:10:42:10 | a | | +| Taint externally controlled string | test.py:39 | test.py:39:13:39:26 | tainted_string | | --> | Taint externally controlled string | test.py:39 | test.py:39:9:39:27 | str() | | +| Taint externally controlled string | test.py:40 | test.py:40:9:40:29 | bytes() | | --> | Taint externally controlled string | test.py:42 | test.py:42:13:42:13 | b | | +| Taint externally controlled string | test.py:40 | test.py:40:15:40:28 | tainted_string | | --> | Taint externally controlled string | test.py:40 | test.py:40:9:40:29 | bytes() | | +| Taint externally controlled string | test.py:41 | test.py:41:9:41:46 | bytes() | | --> | Taint externally controlled string | test.py:42 | test.py:42:16:42:16 | c | | +| Taint externally controlled string | test.py:41 | test.py:41:15:41:28 | tainted_string | | --> | Taint externally controlled string | test.py:41 | test.py:41:9:41:46 | bytes() | | +| Taint externally controlled string | test.py:44 | test.py:44:22:44:26 | taint | p1 = externally controlled string | --> | Taint externally controlled string | test.py:45 | test.py:45:17:45:21 | taint | p1 = externally controlled string | +| Taint externally controlled string | test.py:45 | test.py:45:17:45:21 | taint | p1 = externally controlled string | --> | Taint externally controlled string | test.py:45 | test.py:45:12:45:22 | func() | p1 = externally controlled string | +| Taint externally controlled string | test.py:45 | test.py:45:17:45:21 | taint | p1 = externally controlled string | --> | Taint externally controlled string | test.py:52 | test.py:52:19:52:21 | arg | p0 = externally controlled string | +| Taint externally controlled string | test.py:52 | test.py:52:19:52:21 | arg | p0 = externally controlled string | --> | Taint externally controlled string | test.py:53 | test.py:53:12:53:14 | arg | p0 = externally controlled string | +| Taint externally controlled string | test.py:56 | test.py:56:11:56:24 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | +| Taint externally controlled string | test.py:57 | test.py:57:11:57:41 | cross_over() | | --> | Taint externally controlled string | test.py:58 | test.py:58:10:58:12 | res | | +| Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | --> | Taint externally controlled string | test.py:44 | test.py:44:22:44:26 | taint | p1 = externally controlled string | +| Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | --> | Taint externally controlled string | test.py:57 | test.py:57:11:57:41 | cross_over() | | | Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | +| Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | --> | Taint json[externally controlled string] | test.py:10 | test.py:10:10:10:10 | a | | | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | --> | Taint externally controlled string | test.py:8 | test.py:8:9:8:18 | Attribute() | | | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:9 | a | | --> | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | | Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | +| Taint json[externally controlled string] | test.py:8 | test.py:8:9:8:18 | Attribute() | | --> | Taint json[externally controlled string] | test.py:10 | test.py:10:13:10:13 | b | | | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | --> | Taint externally controlled string | test.py:9 | test.py:9:9:9:14 | Subscript | | | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:9 | b | | --> | Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | +| Taint json[externally controlled string] | test.py:9 | test.py:9:9:9:14 | Subscript | | --> | Taint json[externally controlled string] | test.py:10 | test.py:10:16:10:16 | c | | diff --git a/python/ql/test/library-tests/taint/strings/TestTaint.expected b/python/ql/test/library-tests/taint/strings/TestTaint.expected new file mode 100644 index 00000000000..db6d601bb43 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestTaint.expected @@ -0,0 +1,22 @@ +| test.py:10 | test_json | a | externally controlled string | +| test.py:10 | test_json | a | json[externally controlled string] | +| test.py:10 | test_json | b | externally controlled string | +| test.py:10 | test_json | b | json[externally controlled string] | +| test.py:10 | test_json | c | externally controlled string | +| test.py:10 | test_json | c | json[externally controlled string] | +| test.py:21 | test_str | a | externally controlled string | +| test.py:21 | test_str | b | externally controlled string | +| test.py:21 | test_str | c | externally controlled string | +| test.py:21 | test_str | d | externally controlled string | +| test.py:21 | test_str | e | externally controlled string | +| test.py:21 | test_str | f | externally controlled string | +| test.py:21 | test_str | g | externally controlled string | +| test.py:26 | test_const_sanitizer1 | tainted_string | NO TAINT | +| test.py:28 | test_const_sanitizer1 | tainted_string | externally controlled string | +| test.py:33 | test_const_sanitizer2 | tainted_string | NO TAINT | +| test.py:35 | test_const_sanitizer2 | tainted_string | externally controlled string | +| test.py:42 | test_str2 | a | externally controlled string | +| test.py:42 | test_str2 | b | externally controlled string | +| test.py:42 | test_str2 | c | externally controlled string | +| test.py:50 | test_exc_info | res | exception.info | +| test.py:58 | test_untrusted | res | externally controlled string | diff --git a/python/ql/test/library-tests/taint/strings/TestTaint.ql b/python/ql/test/library-tests/taint/strings/TestTaint.ql new file mode 100644 index 00000000000..92657b1fef9 --- /dev/null +++ b/python/ql/test/library-tests/taint/strings/TestTaint.ql @@ -0,0 +1,18 @@ +import python +import semmle.python.security.TaintTracking +import Taint + +from Call call, Expr arg, string taint_string +where + call.getLocation().getFile().getShortName() = "test.py" and + call.getFunc().(Name).getId() = "test" and + arg = call.getAnArg() and + ( + not exists(TaintedNode tainted | tainted.getAstNode() = arg) and + taint_string = "NO TAINT" + or + exists(TaintedNode tainted | tainted.getAstNode() = arg | + taint_string = tainted.getTaintKind().toString() + ) + ) +select arg.getLocation().toString(), call.getScope().(Function).getName(), arg.toString(), taint_string diff --git a/python/ql/test/library-tests/taint/strings/test.py b/python/ql/test/library-tests/taint/strings/test.py index 1b2b439a648..878200d947c 100644 --- a/python/ql/test/library-tests/taint/strings/test.py +++ b/python/ql/test/library-tests/taint/strings/test.py @@ -2,41 +2,44 @@ import json from copy import copy def test_json(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING tainted_json = json.loads(tainted_string) a = tainted_json["x"] b = a.get("y") c = b["z"] + test(a, b, c) def test_str(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING a = tainted_string.ljust(8) b = tainted_string.copy() c = tainted_string[:] d = tainted_string[::2] e = reversed(tainted_string) f = copy(tainted_string) - h = tainted_string.strip() + g = tainted_string.strip() + test(a, b, c, d, e, f, g) def test_const_sanitizer1(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING if tainted_string == "OK": - not_tainted(tainted_string) + test(tainted_string) # not tainted else: - still_tainted(tainted_string) + test(tainted_string) # still tainted def test_const_sanitizer2(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING if tainted_string == "OK" or tainted_string == "ALSO_OK": - not_tainted(tainted_string) + test(tainted_string) # not tainted else: - still_tainted(tainted_string) + test(tainted_string) # still tainted def test_str2(): - tainted_string = TAINTED + tainted_string = TAINTED_STRING a = str(tainted_string) b = bytes(tainted_string) # This is an error in Python 3 c = bytes(tainted_string, encoding="utf8") # This is an error in Python 2 + test(a, b, c) def cross_over(func, taint): return func(taint) @@ -44,13 +47,15 @@ def cross_over(func, taint): def test_exc_info(): info = TAINTED_EXCEPTION_INFO res = cross_over(exc_info_call, info) + test(res) def exc_info_call(arg): return arg def test_untrusted(): - ext = TAINTED_EXTERNAL_STRING + ext = TAINTED_STRING res = cross_over(untrusted_call, ext) + test(res) def exc_untrusted_call(arg): return arg From 4f3149d865e702723f5354d62c19d5176c1463c2 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 19 Feb 2020 16:27:31 +0100 Subject: [PATCH 040/236] Python: Fix error after merge conflict --- python/ql/src/Statements/StatementNoEffect.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql index 0a80f8f9aa4..6e5fc9d88e8 100644 --- a/python/ql/src/Statements/StatementNoEffect.ql +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -42,7 +42,7 @@ predicate side_effecting_descriptor_type(ClassValue descriptor) { * we want to treat them as having no effect. */ - not descriptor = ClassValue::function() and + not descriptor = ClassValue::functionType() and not descriptor = ClassValue::staticmethod() and not descriptor = ClassValue::classmethod() } From fd270cc02cce94885f5f2ad1cdbecd14e716a277 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 19 Feb 2020 16:22:09 +0100 Subject: [PATCH 041/236] Python: Add basic taint support for urlsplit/urlparse --- .../python/security/strings/External.qll | 101 ++++++++++++++++++ .../library-tests/taint/namedtuple/Taint.qll | 27 +++++ .../taint/namedtuple/TestTaint.expected | 11 ++ .../taint/namedtuple/TestTaint.ql | 18 ++++ .../library-tests/taint/namedtuple/test.py | 33 ++++++ .../taint/strings/TestStep.expected | 6 ++ .../taint/strings/TestTaint.expected | 2 + .../test/library-tests/taint/strings/test.py | 8 ++ 8 files changed, 206 insertions(+) create mode 100644 python/ql/test/library-tests/taint/namedtuple/Taint.qll create mode 100644 python/ql/test/library-tests/taint/namedtuple/TestTaint.expected create mode 100644 python/ql/test/library-tests/taint/namedtuple/TestTaint.ql create mode 100644 python/ql/test/library-tests/taint/namedtuple/test.py diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index 4dcbe0d17e3..564580c2465 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -18,6 +18,10 @@ abstract class ExternalStringKind extends StringKind { json_load(fromnode, tonode) and result.(ExternalJsonKind).getValue() = this or tonode.(DictNode).getAValue() = fromnode and result.(ExternalStringDictKind).getValue() = this + or + urlsplit(fromnode, tonode) and result.(ExternalUrlSplitResult).getItem() = this + or + urlparse(fromnode, tonode) and result.(ExternalUrlParseResult).getItem() = this } } @@ -65,6 +69,65 @@ class ExternalStringSequenceDictKind extends DictKind { ExternalStringSequenceDictKind() { this.getValue() instanceof ExternalStringSequenceKind } } +/** TaintKind for the result of `urlsplit(tainted_string)` */ +class ExternalUrlSplitResult extends ExternalStringSequenceKind { + // https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlsplit + override TaintKind getTaintOfAttribute(string name) { + result = super.getTaintOfAttribute(name) + or + ( + // namedtuple field names + name = "scheme" or + name = "netloc" or + name = "path" or + name = "query" or + name = "fragment" or + // class methods + name = "username" or + name = "password" or + name = "hostname" + ) and + result instanceof ExternalStringKind + } + + override TaintKind getTaintOfMethodResult(string name) { + result = super.getTaintOfMethodResult(name) + or + name = "geturl" and + result instanceof ExternalStringKind + } +} + +/** TaintKind for the result of `urlparse(tainted_string)` */ +class ExternalUrlParseResult extends ExternalStringSequenceKind { + // https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse + override TaintKind getTaintOfAttribute(string name) { + result = super.getTaintOfAttribute(name) + or + ( + // namedtuple field names + name = "scheme" or + name = "netloc" or + name = "path" or + name = "params" or + name = "query" or + name = "fragment" or + // class methods + name = "username" or + name = "password" or + name = "hostname" + ) and + result instanceof ExternalStringKind + } + + override TaintKind getTaintOfMethodResult(string name) { + result = super.getTaintOfMethodResult(name) + or + name = "geturl" and + result instanceof ExternalStringKind + } +} + /* Helper for getTaintForStep() */ pragma[noinline] private predicate json_subscript_taint( @@ -83,6 +146,44 @@ private predicate json_load(ControlFlowNode fromnode, CallNode tonode) { ) } +private predicate urlsplit(ControlFlowNode fromnode, CallNode tonode) { + // This could be implemented as `exists(FunctionValue` without the explicit six part, + // but then our tests will need to import +100 modules, so for now this slightly + // altered version gets to live on. + exists(Value urlsplit | + ( + urlsplit = Value::named("six.moves.urllib.parse.urlsplit") + or + // Python 2 + urlsplit = Value::named("urlparse.urlsplit") + or + // Python 3 + urlsplit = Value::named("urllib.parse.urlsplit") + ) and + tonode = urlsplit.getACall() and + tonode.getArg(0) = fromnode + ) +} + +private predicate urlparse(ControlFlowNode fromnode, CallNode tonode) { + // This could be implemented as `exists(FunctionValue` without the explicit six part, + // but then our tests will need to import +100 modules, so for now this slightly + // altered version gets to live on. + exists(Value urlparse | + ( + urlparse = Value::named("six.moves.urllib.parse.urlparse") + or + // Python 2 + urlparse = Value::named("urlparse.urlparse") + or + // Python 3 + urlparse = Value::named("urllib.parse.urlparse") + ) and + tonode = urlparse.getACall() and + tonode.getArg(0) = fromnode + ) +} + /** A kind of "taint", representing an open file-like object from an external source. */ class ExternalFileObject extends TaintKind { ExternalFileObject() { this = "file[" + any(ExternalStringKind key) + "]" } diff --git a/python/ql/test/library-tests/taint/namedtuple/Taint.qll b/python/ql/test/library-tests/taint/namedtuple/Taint.qll new file mode 100644 index 00000000000..b97f65225f2 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/Taint.qll @@ -0,0 +1,27 @@ +import python +import semmle.python.security.TaintTracking +import semmle.python.security.strings.Untrusted + +class SimpleSource extends TaintSource { + SimpleSource() { this.(NameNode).getId() = "TAINTED_STRING" } + + override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringKind } + + override string toString() { result = "taint source" } +} + +class ListSource extends TaintSource { + ListSource() { this.(NameNode).getId() = "TAINTED_LIST" } + + override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringSequenceKind } + + override string toString() { result = "list taint source" } +} + +class DictSource extends TaintSource { + DictSource() { this.(NameNode).getId() = "TAINTED_DICT" } + + override predicate isSourceOf(TaintKind kind) { kind instanceof ExternalStringDictKind } + + override string toString() { result = "dict taint source" } +} diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected new file mode 100644 index 00000000000..c90d33199ba --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -0,0 +1,11 @@ +| test.py:13 | test_basic | a | externally controlled string | +| test.py:13 | test_basic | b | externally controlled string | +| test.py:13 | test_basic | c | externally controlled string | +| test.py:13 | test_basic | d | externally controlled string | +| test.py:13 | test_basic | urlsplit_res | [externally controlled string] | +| test.py:20 | test_sanitizer | Attribute | externally controlled string | +| test.py:23 | test_sanitizer | Subscript | externally controlled string | +| test.py:33 | test_namedtuple | a | NO TAINT | +| test.py:33 | test_namedtuple | b | NO TAINT | +| test.py:33 | test_namedtuple | c | NO TAINT | +| test.py:33 | test_namedtuple | d | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.ql b/python/ql/test/library-tests/taint/namedtuple/TestTaint.ql new file mode 100644 index 00000000000..92657b1fef9 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.ql @@ -0,0 +1,18 @@ +import python +import semmle.python.security.TaintTracking +import Taint + +from Call call, Expr arg, string taint_string +where + call.getLocation().getFile().getShortName() = "test.py" and + call.getFunc().(Name).getId() = "test" and + arg = call.getAnArg() and + ( + not exists(TaintedNode tainted | tainted.getAstNode() = arg) and + taint_string = "NO TAINT" + or + exists(TaintedNode tainted | tainted.getAstNode() = arg | + taint_string = tainted.getTaintKind().toString() + ) + ) +select arg.getLocation().toString(), call.getScope().(Function).getName(), arg.toString(), taint_string diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py new file mode 100644 index 00000000000..1c07bcfdc60 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -0,0 +1,33 @@ +from six.moves.urllib.parse import urlsplit + +# Currently we don't have support for namedtuples in general, but do have special support +# for `urlsplit` (and `urlparse`) + +def test_basic(): + tainted_string = TAINTED_STRING + urlsplit_res = urlsplit(tainted_string) + a = urlsplit_res.netloc # field access + b = urlsplit_res.hostname # property + c = urlsplit_res[3] # indexing + _, _, d, _, _ = urlsplit(tainted_string) # unpacking + test(a, b, c, d, urlsplit_res) + +def test_sanitizer(): + tainted_string = TAINTED_STRING + urlsplit_res = urlsplit(tainted_string) + + if urlsplit_res.netloc == "OK": + test(urlsplit_res.netloc) # TODO: FP, should not be tainted here + + if urlsplit_res[2] == "OK": + test(urlsplit_res[0]) # TODO: FP, should not be tainted here + +def test_namedtuple(): + tainted_string = TAINTED_STRING + Point = namedtuple('Point', ['x', 'y']) + p = Point('safe', tainted_string) + a = p.x + b = p.y + c = p[0] + d = p[1] + test(a, b, c, d) # TODO: FN, at least p.y and p[1] should be tainted diff --git a/python/ql/test/library-tests/taint/strings/TestStep.expected b/python/ql/test/library-tests/taint/strings/TestStep.expected index eccb3efa010..6efa8fb6edf 100644 --- a/python/ql/test/library-tests/taint/strings/TestStep.expected +++ b/python/ql/test/library-tests/taint/strings/TestStep.expected @@ -1,3 +1,5 @@ +| Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:10:69:21 | urlsplit_res | | +| Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | | --> | Taint [externally controlled string] | test.py:69 | test.py:69:24:69:35 | urlparse_res | | | Taint exception.info | test.py:44 | test.py:44:22:44:26 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:45 | test.py:45:12:45:22 | func() | p1 = exception.info | | Taint exception.info | test.py:45 | test.py:45:17:45:21 | taint | p1 = exception.info | --> | Taint exception.info | test.py:52 | test.py:52:19:52:21 | arg | p0 = exception.info | @@ -56,6 +58,10 @@ | Taint externally controlled string | test.py:57 | test.py:57:11:57:41 | cross_over() | | --> | Taint externally controlled string | test.py:58 | test.py:58:10:58:12 | res | | | Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | --> | Taint externally controlled string | test.py:44 | test.py:44:22:44:26 | taint | p1 = externally controlled string | | Taint externally controlled string | test.py:57 | test.py:57:38:57:40 | ext | | --> | Taint externally controlled string | test.py:57 | test.py:57:11:57:41 | cross_over() | | +| Taint externally controlled string | test.py:66 | test.py:66:22:66:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:67 | test.py:67:29:67:42 | tainted_string | | +| Taint externally controlled string | test.py:66 | test.py:66:22:66:35 | TAINTED_STRING | | --> | Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | | +| Taint externally controlled string | test.py:67 | test.py:67:29:67:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:67 | test.py:67:20:67:43 | urlsplit() | | +| Taint externally controlled string | test.py:68 | test.py:68:29:68:42 | tainted_string | | --> | Taint [externally controlled string] | test.py:68 | test.py:68:20:68:43 | urlparse() | | | Taint json[externally controlled string] | test.py:6 | test.py:6:20:6:45 | Attribute() | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint externally controlled string | test.py:7 | test.py:7:9:7:25 | Subscript | | | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:20 | tainted_json | | --> | Taint json[externally controlled string] | test.py:7 | test.py:7:9:7:25 | Subscript | | diff --git a/python/ql/test/library-tests/taint/strings/TestTaint.expected b/python/ql/test/library-tests/taint/strings/TestTaint.expected index db6d601bb43..ff7372a8ce6 100644 --- a/python/ql/test/library-tests/taint/strings/TestTaint.expected +++ b/python/ql/test/library-tests/taint/strings/TestTaint.expected @@ -20,3 +20,5 @@ | test.py:42 | test_str2 | c | externally controlled string | | test.py:50 | test_exc_info | res | exception.info | | test.py:58 | test_untrusted | res | externally controlled string | +| test.py:69 | test_urlsplit_urlparse | urlparse_res | [externally controlled string] | +| test.py:69 | test_urlsplit_urlparse | urlsplit_res | [externally controlled string] | diff --git a/python/ql/test/library-tests/taint/strings/test.py b/python/ql/test/library-tests/taint/strings/test.py index 878200d947c..207f52c807c 100644 --- a/python/ql/test/library-tests/taint/strings/test.py +++ b/python/ql/test/library-tests/taint/strings/test.py @@ -59,3 +59,11 @@ def test_untrusted(): def exc_untrusted_call(arg): return arg + +from six.moves.urllib.parse import urlsplit, urlparse + +def test_urlsplit_urlparse(): + tainted_string = TAINTED_STRING + urlsplit_res = urlsplit(tainted_string) + urlparse_res = urlparse(tainted_string) + test(urlsplit_res, urlparse_res) From bdab9ee12b44c303e086fb1bf950745a93dcaf6d Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Wed, 19 Feb 2020 14:31:03 +0100 Subject: [PATCH 042/236] change useless cat query to only flag instances that can be re-written to --- .../{UselessCat.ql => UselessUseOfCat.ql} | 3 +- .../javascript/security/UselessUseOfCat.qll | 322 ++++++++++++++++-- .../Security/CWE-078/UselessCat.expected | 3 - .../Security/CWE-078/UselessUseOfCat.expected | 20 ++ .../{UselessCat.ql => UselessUseOfCat.ql} | 2 + .../Security/CWE-078/uselesscat.js | 116 ++++--- 6 files changed, 377 insertions(+), 89 deletions(-) rename javascript/ql/src/Security/CWE-078/{UselessCat.ql => UselessUseOfCat.ql} (83%) delete mode 100644 javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected create mode 100644 javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected rename javascript/ql/test/query-tests/Security/CWE-078/{UselessCat.ql => UselessUseOfCat.ql} (89%) diff --git a/javascript/ql/src/Security/CWE-078/UselessCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql similarity index 83% rename from javascript/ql/src/Security/CWE-078/UselessCat.ql rename to javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index d2f0e13fe2e..f48ba6b8e95 100644 --- a/javascript/ql/src/Security/CWE-078/UselessCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -14,7 +14,8 @@ import javascript import semmle.javascript.security.UselessUseOfCat + from UselessCat cat -select cat.getCommand(), "Useless use of `cat` in $@.", cat, "command execution" +select cat, "Useless use of `cat`. Can be replaced with: " + createReadFileCall(cat) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index e5c5e8a0a1a..f67fa287685 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -6,54 +6,312 @@ import javascript import semmle.javascript.security.dataflow.IndirectCommandArgument /** - * Gets the first string from a string/string-concatenation. + * Gets a string representing an equivalent call to `fs.ReadFile` for a call to `cat`. */ -private string getStartingString(DataFlow::Node node) { - node.mayHaveStringValue(result) or - node.(StringOps::ConcatenationRoot).getFirstLeaf().mayHaveStringValue(result) +string createReadFileCall(UselsesCatCandidates::UselessCatCandicate cat) { + exists(string sync, string extraArg, string callback | + (if cat.isSync() then sync = "Sync" else sync = "") and + ( + if exists(cat.getOptionsArg()) + then extraArg = ", " + printOptionsArg(cat.getOptionsArg()) + ")" + else extraArg = "" + ) and + if exists(cat.getCallback()) + then callback = ", function(" + getCallbackArgs(cat.getCallback()) + ") {...}" + else callback = "" + | + result = "fs.readFile" + sync + "(" + cat.getFileArgument().trim() + extraArg + callback + ")" + ) } /** - * Gets a string from a string/string-concatenation. + * Gets a string concatenation of the parameters to a function. */ -private string getAString(DataFlow::Node node) { - node.mayHaveStringValue(result) or - node.(StringOps::ConcatenationRoot).getALeaf().mayHaveStringValue(result) +private string getCallbackArgs(DataFlow::FunctionNode func) { + result = concat(int i | i = [0 .. 2] | func.getParameter(i).getName(), ", ") } /** - * An command-line execution of `cat` that only reads a file. + * Gets a string representation of the options argument from an exec-like call. */ -class UselessCat extends DataFlow::Node { - DataFlow::Node command; +private string printOptionsArg(DataFlow::Node node) { + result = node.asExpr().(VarAccess).getVariable().getName() + or + // fall back to toString(), but ensure that we don't have dots in the middle. + result = node.(DataFlow::ObjectLiteralNode).toString() and not result.regexpMatch(".*\\.\\..*") +} + +/** + * A call to a useless use of `cat`. + */ +class UselessCat extends DataFlow::CallNode { + UselsesCatCandidates::UselessCatCandicate candidate; UselessCat() { - command = this.(SystemCommandExecution).getACommandArgument() and - exists(string cat | - cat = "cat" or cat = "/bin/cat" or cat = "sudo cat" or cat = "sudo /bin/cat" - | - exists(string commandString | - commandString = getStartingString(command).trim() and - (commandString = cat or commandString.regexpMatch(cat + " .*")) - ) and - // `cat` is OK in combination with pipes, wildcards, and redirections. - not getAString(command).regexpMatch(".*(\\*|\\||>|<).*") and - // It is OK just to spawn "cat" without any arguments. - not ( - command.mayHaveStringValue(cat) and - not exists( - this - .(SystemCommandExecution) - .getArgumentList() - .(DataFlow::ArrayCreationNode) - .getAnElement() + this = candidate and + // wildcards, pipes, redirections, and multiple files are OK. + // (The multiple files detection relies on the fileArgument not containing spaces anywhere) + not candidate.getFileArgument().regexpMatch(".*(\\*|\\||>|<| ).*") and + // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. + not exists(string prop | + not prop = "encoding" and + exists(candidate.getOptionsArg().getALocalSource().getAPropertyWrite(prop)) + ) and + exists(createReadFileCall(this)) and + // If there is a callback, then it must either have one or two arguments, or if there is a third argument it must be unused. + ( + not exists(candidate.getCallback()) + or + exists(DataFlow::FunctionNode func | func = candidate.getCallback() | + func.getNumParameter() = 1 + or + func.getNumParameter() = 2 + or + // `exec` can use 3 parameters, `readFile` can only use two, so it is OK to have a third parameter if it is unused, + func.getNumParameter() = 3 and + not exists(DataFlow::Node node | + not node = func.getParameter(2) and func.getParameter(2) = node.getALocalSource() + ) + ) + ) + } +} + +module UselsesCatCandidates { + /** + * A candidate for a useless use of `cat`. + * Subclasses of this class specify the structure of the `exec`-like call. + */ + abstract class UselessCatCandicate extends DataFlow::CallNode { + /** + * Holds if the call is synchronous (e.g. `execFileSync`). + */ + abstract predicate isSync(); + + /** + * Gets a string representation of the expression that determines what file is read. + */ + abstract string getFileArgument(); + + /** + * Gets the data-flow node for the options argument to the `exec`-like call. + */ + abstract DataFlow::Node getOptionsArg(); + + /** + * Gets the callback used for the `exec` like call (if it exists). + */ + abstract DataFlow::FunctionNode getCallback(); + } + + /** + * Create a string representation of a string concatenation. + */ + private string createConcatRepresentation(StringOps::ConcatenationRoot root) { + // String concat + not exists(root.getStringValue()) and + not root.asExpr() instanceof TemplateLiteral and + forall(Expr e | e = root.getALeaf().asExpr() | exists(createLeafRepresentation(e))) and + result = + concat(Expr leaf | + leaf = root.getALeaf().asExpr() + | + createLeafRepresentation(leaf), "+" order by leaf.getFirstToken().getIndex() + ) + or + // Template string + exists(TemplateLiteral template | template = root.asExpr() | + forall(Expr e | e = template.getAChild() | exists(createTemplateElementRepresentation(e))) and + result = + "`" + + concat(int i | + i = [0 .. template.getNumChild() - 1] + | + createTemplateElementRepresentation(template.getChild(i)) order by i + ) + "`" + ) + } + + /** + * Gets a string representing the expression needed to re-create the value for a leaf in a string-concatenation. + */ + private string createLeafRepresentation(Expr e) { + result = "\"" + e.getStringValue() + "\"" or + result = e.(VarAccess).getVariable().getName() + } + + /** + * Gets a string representing the expression needed to re-create the value for an element of a template string. + */ + private string createTemplateElementRepresentation(Expr e) { + result = "${" + e.(VarAccess).getVariable().getName() + "}" + or + result = e.(TemplateElement).getValue() + } + + /** + * Gets a string used to call `cat`. + */ + private string cat() { + result = "cat" or result = "/bin/cat" or result = "sudo cat" or result = "sudo /bin/cat" + } + + /** + * Gets a string representing an expression that gets the file read by a call to `cat`. + * The input `arg` is the node that determines the commandline where `cat` is invoked. + */ + private string getFileArgumentWithoutCat(DataFlow::Node arg) { + exists(string cat | cat = cat() | + exists(string command | arg.mayHaveStringValue(command) | + command.prefix(cat.length()) = cat and + result = "\"" + command.suffix(cat.length()).trim() + "\"" + ) + or + exists(StringOps::ConcatenationRoot root, string printed, string quote | + root = arg and printed = createConcatRepresentation(root).suffix(1) // remove initial quote + | + (if root.asExpr() instanceof TemplateLiteral then quote = "`" else quote = "\"") and + root.getFirstLeaf().getStringValue().prefix(cat.length()) = cat and + // Remove an initial ""+ (e.g. in `""+file`) + exists(string rawConcat | rawConcat = quote + printed.suffix(cat.length()).trim() | + if rawConcat.prefix(3) = "\"\"+" then result = rawConcat.suffix(3) else result = rawConcat ) ) ) } /** - * Gets the dataflow node determining the command executed. + * A call to child_process.exec that might be a useless call to cat. */ - DataFlow::Node getCommand() { result = command } + private class ExecCall extends UselessCatCandicate { + string fileArgument; + boolean hasOptions; + + ExecCall() { + this = DataFlow::moduleImport("child_process").getAMemberCall("exec") and + ( + this.getNumArgument() = 2 and hasOptions = false + or + this.getNumArgument() = 3 and hasOptions = true + ) and + fileArgument = getFileArgumentWithoutCat(getArgument(0)) + } + + override predicate isSync() { none() } + + override string getFileArgument() { result = fileArgument } + + override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(1) } + + override DataFlow::FunctionNode getCallback() { result = getLastArgument() } + } + + /** + * A call to child_process.execSync that might be a useless call to cat. + */ + private class ExecSyncCall extends UselessCatCandicate { + string fileArgument; + boolean hasOptions; + + ExecSyncCall() { + this = DataFlow::moduleImport("child_process").getAMemberCall("execSync") and + ( + this.getNumArgument() = 1 and hasOptions = false + or + this.getNumArgument() = 2 and hasOptions = true + ) and + fileArgument = getFileArgumentWithoutCat(getArgument(0)) + } + + override predicate isSync() { any() } + + override string getFileArgument() { result = fileArgument } + + override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(1) } + + override DataFlow::FunctionNode getCallback() { none() } + } + + // TODO: No! + bindingset[str, quote] + string surroundInQuotes(string str, string quote) { + if not str.prefix(1) = quote and not str.suffix(str.length() - 1) = quote + then result = quote + str + quote + else + if not str.prefix(1) = quote + then result = str + quote + else + if not str.suffix(str.length() - 1) = quote + then result = quote + str + else result = str + } + + /** + * Gets the file that is read for a call to child_process.execFile/execFileSync. + */ + string getFileThatIsRead(DataFlow::CallNode call) { + exists(DataFlow::ArrayCreationNode array, DataFlow::Node element | + array = call.getArgument(1).(DataFlow::ArrayCreationNode) and + array.getSize() = 1 and + element = array.getElement(0) + | + result = element.asExpr().(VarAccess).getVariable().getName() or + result = "\"" + element.getStringValue() + "\"" or + result = createConcatRepresentation(element) + ) + } + + /** + * A call to child_process.execFile that might be a useless call to cat. + */ + private class ExecFileCall extends UselessCatCandicate { + string fileArgument; + boolean hasOptions; + + ExecFileCall() { + this.getArgument(0).mayHaveStringValue(cat()) and + this = DataFlow::moduleImport("child_process").getAMemberCall("execFile") and + ( + this.getNumArgument() = 3 and hasOptions = false + or + this.getNumArgument() = 4 and hasOptions = true + ) and + fileArgument = getFileThatIsRead(this) + } + + override predicate isSync() { none() } + + override string getFileArgument() { result = fileArgument } + + override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(2) } + + override DataFlow::FunctionNode getCallback() { result = getLastArgument() } + } + + /** + * A call to child_process.execFileSync that might be a useless call to cat. + */ + private class ExecFileSyncCall extends UselessCatCandicate { + string fileArgument; + boolean hasOptions; + + ExecFileSyncCall() { + this.getArgument(0).mayHaveStringValue(cat()) and + this = DataFlow::moduleImport("child_process").getAMemberCall("execFileSync") and + ( + this.getNumArgument() = 2 and hasOptions = false + or + this.getNumArgument() = 3 and hasOptions = true + ) and + fileArgument = getFileThatIsRead(this) + } + + override predicate isSync() { any() } + + override string getFileArgument() { result = fileArgument } + + override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(2) } + + override DataFlow::FunctionNode getCallback() { none() } + } } diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected deleted file mode 100644 index 67b59aa9d6e..00000000000 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.expected +++ /dev/null @@ -1,3 +0,0 @@ -| False negative | uselesscat.js:69:42:69:69 | // NOT ... lagged] | -| False positive | uselesscat.js:18:70:18:118 | // OK [ ... jection | -| False positive | uselesscat.js:82:80:82:128 | // OK ( ... / gid)) | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected new file mode 100644 index 00000000000..f4b22cd0803 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -0,0 +1,20 @@ +readFile +| uselesscat.js:10:1:10:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | +| uselesscat.js:12:1:14:2 | exec("c ... ut);\\n}) | fs.readFile("/proc/"+id+"/status", function(err, out) {...}) | +| uselesscat.js:16:1:16:29 | execSyn ... uinfo') | fs.readFileSync("/proc/cpuinfo") | +| uselesscat.js:18:1:18:26 | execSyn ... path}`) | fs.readFileSync(`${newpath}`) | +| uselesscat.js:32:1:32:34 | execSyn ... path}`) | fs.readFileSync(`foo/bar/${newpath}`) | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | fs.readFileSync(`foo/bar/${newpath}`, {encoding: 'utf8'})) | +| uselesscat.js:51:9:51:31 | execSyn ... + file) | fs.readFileSync(file) | +| uselesscat.js:59:1:62:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", function(error, stderr, stdout) {...}) | +| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", {encoding: 'utf8'}), function(error, stderr, stdout) {...}) | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | fs.readFileSync("pom.xml", {encoding: 'utf8'})) | +| uselesscat.js:76:1:76:39 | execFil ... xml' ]) | fs.readFileSync("pom.xml") | +| uselesscat.js:79:1:79:46 | execFil ... opts) | fs.readFileSync("pom.xml", opts)) | +| uselesscat.js:82:1:82:90 | execFil ... String) | fs.readFileSync("pom.xml", anOptsFileNameThatIsTooLongToBePrintedByToString)) | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/"+newPath+"bar", {encoding: 'utf8'})) | +| uselesscat.js:88:1:88:35 | execSyn ... + foo) | fs.readFileSync("/proc/cpuinfo"+foo) | +| uselesscat.js:90:1:90:50 | execFil ... th}` ]) | fs.readFileSync(`foo/bar/${newpath}`) | +#select +| False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | +| False negative | uselesscat.js:84:118:84:144 | // NOT ... lagged] | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql similarity index 89% rename from javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql rename to javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql index bfb342f7ad1..edcf4987e3e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessCat.ql +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql @@ -16,3 +16,5 @@ where msg = "False positive" ) select msg, comment + +query string readFile(UselessCat cat) { result = createReadFileCall(cat) } diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index 4fe7ccd75d2..d35f3224d18 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -7,76 +7,86 @@ var spawnSync = child_process.spawnSync; var fs = require('fs'); var app = express(); -function readStatus(id) { - exec("cat /proc/" + id + "/status", function (err, out) { // NOT OK - console.log(out); - }); -}; +exec("cat foo/bar", function (err, out) {}); // NOT OK -var basePath = '/foo/bar'; -app.get('/:data', function (req, res) { - res.send(execSync('cat ' + basePath + req.params.data).toString()); // OK [but flagged] - caught by command-injection +exec("cat /proc/" + id + "/status", function (err, out) { // NOT OK + console.log(out); }); -spawn('cat', ['package.json']); // NOT OK - -spawn('/bin/cat', ['package.json']); // NOT - -spawn('/bin/cat', [someValue]); // NOT OK -spawn('sudo /bin/cat', [someValue]); // NOT OK -spawn('sudo cat', [someValue]); // NOT OK - -child_process.spawnSync('cat', [someValue]); // NOT OK - -child_process.execSync('cat package.json | wc -l'); // OK - pipes! - -const cat = child_process.spawn('cat') // OK - benign use (usually pipe to and from). - -spawn('cat', ['package.json']); // NOT OK. - -exec('cat *.js') // OK - wildcard use. -exec('cat *.js | wc -l') // OK - wildcard use and pipes! -exec('cat *.js | wc -l', { cwd: './' }, function () { }); // OK - wildcard and pipes - -spawnSync('cat', ['/proc/cpuinfo']) // NOT OK. - -exec(`cat ${path.join(__dirname, 'package.json')} | sort | uniq`, () => {}); // OK: pipes - execSync('cat /proc/cpuinfo').toString(); // NOT OK. -var cmd = "cat /proc/cpuinfo" -execSync(cmd); // NOT OK +execSync(`cat ${newpath}`) // NOT OK -execSync("cat /proc/cpuinfo | grep -c '" + someValue + "'"); // OK - pipes +child_process.execSync('cat package.json | wc -l'); // OK - pipes! -function cat(file) { - return execSync('cat ' + file).toString(); // NOT OK -} +execSync('cat /proc/cpuinfo /foo/bar').toString(); // OK multiple files. -execSync(`cat ${files.join(' ')} > ${outFile}`); // OK +execSync(`cat ${newpath} /foo/bar`).toString(); // OK multiple files. -var cmd = 'cat package.json | grep' -exec(cmd); // OK - pipes! +exec(`cat ${newpath} | grep foo`, function (err, out) { }) // OK - pipes -execSync("sudo cat " + newpath + "*.js | grep foo").toString(); // OK - wildcard and pipes +execSync(`cat ${newpath}`, {uid: 1000}) // OK - non trivial options -execSync(`cat ${newpath}`); // NOT OK +exec('cat *.js | wc -l', { cwd: './' }, function (err, out) { }); // OK - wildcard and pipes -exec("cat /proc/cpuinfo | grep name"); // OK - pipes +execSync(`cat foo/bar/${newpath}`); // NOT OK ("encoding" is used EXACTLY the same way in fs.readFileSync) -execSync(`cat ${newpath} | ${othertool}`); // OK - pipes +execSync(`cat foo/bar/${newpath}`, {encoding: 'utf8'}); // NOT OK ("encoding" is used EXACTLY the same way in fs.readFileSync) -execSync("sh -c 'cat " + newpath + "'"); // NOT OK. [but not flagged] - -exec(` cat ${newpath}`) // NOT OK - -exec(` cat ${newpath} | grep foo`) // OK - pipes +execSync("/bin/cat /proc/cpuinfo", { uid: 1000, gid: 1000, encoding: 'utf8'}); // OK (fs.readFileSync cannot emulate uid / gid)) execSync('cat /proc/cpuinfo > foo/bar/baz').toString(); // OK. execSync(`cat ${newpath} > ${destpath}`).toString(); // OK. -const Opts = {encoding: 'utf8'} -execSync(`cat foo/bar/${newpath}`, Opts).slice(0, 7); // NOT OK ("encoding" is used EXACTLY the same way in fs.readFileSync) +execSync(`cat ${files.join(' ')} > ${outFile}`); // OK -execSync("/bin/cat /proc/cpuinfo", { uid: 1000, gid: 1000, encoding: 'utf8'}); // OK (fs.readFileSync cannot emulate uid / gid)) \ No newline at end of file +execSync(`cat ${files.join(' ')}`); // OK - not just a simple file read + +exec("cat /proc/cpuinfo | grep name"); // OK - pipes + +execSync(`cat ${newpath} | ${othertool}`); // OK - pipes + +function cat(file) { + return execSync('cat ' + file).toString(); // NOT OK +} + +execSync("sh -c 'cat " + newpath + "'"); // NOT OK. [but not flagged] + +var execFile = child_process.execFile; +var execFileSync = child_process.execFileSync; + +execFile('/bin/cat', [ 'pom.xml' ], function(error, stdout, stderr ) { // NOT OK + // Not using stderr + console.log(stdout); +}); + +execFile('/bin/cat', [ 'pom.xml' ], function(error, stdout, stderr ) { // OK. - stderr is used. + console.log(stderr); +}); + + +execFile('/bin/cat', [ 'pom.xml' ], {encoding: 'utf8'}, function(error, stdout, stderr ) { // NOT OK + // Not using stderr + console.log(stdout); +}); + +execFileSync('/bin/cat', [ 'pom.xml' ], {encoding: 'utf8'}); // NOT OK + +execFileSync('/bin/cat', [ 'pom.xml' ]); // NOT OK + +var opts = {encoding: 'utf8'}; +execFileSync('/bin/cat', [ 'pom.xml' ], opts); // NOT OK + +var anOptsFileNameThatIsTooLongToBePrintedByToString = {encoding: 'utf8'}; +execFileSync('/bin/cat', [ 'pom.xml' ], anOptsFileNameThatIsTooLongToBePrintedByToString); // NOT OK + +execFileSync('/bin/cat', [ 'pom.xml' ], {encoding: 'someEncodingValueThatIsCompletelyBogusAndTooLongForToString'}); // NOT OK [but not flagged] + +execFileSync('/bin/cat', [ "foo/" + newPath + "bar" ], {encoding: 'utf8'}); // NOT OK + +execSync('cat /proc/cpuinfo' + foo).toString(); // NOT OK. + +execFileSync('/bin/cat', [ `foo/bar/${newpath}` ]); // NOT OK + +execFileSync('node', [ `foo/bar/${newpath}` ]); // OK - not a call to cat From 100a603a86a73d4be0f2b8325b7abb5ff676aeef Mon Sep 17 00:00:00 2001 From: james Date: Thu, 13 Feb 2020 18:06:44 +0000 Subject: [PATCH 043/236] docs: expand handbook entry on monotonic aggregates (cherry picked from commit b32683fc9ff98bcdad3c9889cc3da828585453de) --- docs/language/ql-handbook/expressions.rst | 73 ++++++++++++++++++++--- 1 file changed, 65 insertions(+), 8 deletions(-) diff --git a/docs/language/ql-handbook/expressions.rst b/docs/language/ql-handbook/expressions.rst index 9c77029d305..109235612d9 100644 --- a/docs/language/ql-handbook/expressions.rst +++ b/docs/language/ql-handbook/expressions.rst @@ -393,16 +393,73 @@ aggregation in a simpler form: Monotonic aggregates ==================== -In addition to the standard aggregates, QL also supports monotonic aggregates. -These are a slightly different way of computing aggregates which have some advantages. -For example, you can use monotonic aggregates :ref:`recursively `. -You can't do this with normal aggregates. +In addition to standard aggregates, QL also supports monotonic aggregates. +Monotonic aggregates differ from standard aggregates in the way that they deal with the +values generated by the ```` part of the formula: -For more information and examples, see `Monotonic aggregates in QL -`_. +- Standard aggregates take the ```` values for each ```` value and + flatten them into a list. A single aggregation function is applied to all the values. +- Monotonic aggregates take an ```` for each value given by the ````, + and create combinations of all the possible values. The aggregation + function is applied to each of the resulting combinations. -.. TODO: Eventually replace this link with just the relevant examples. - (Some of the content is a duplicate of the above discussion on aggregates.) +In general, if the ```` is total and functional, then monotonic aggregates are +equivalent to standard aggregates. Results differ when there is not precisely one ```` +value for each value generated by the ````: + +- If there are missing ```` values (that is, there is less than one + ```` value for each value generated by the ````), monotonic aggregates + won't compute a result, as you cannot create combinations of values + including exactly one ```` value for each value generated by the ````. + +- If there is more than one ```` per ```` result, you can create multiple + combinations of values including exactly one ```` value for each + value generated by the ````. Here, the aggregation function is applied to each of the + resulting combinations. + +Recursive monotonic aggregates +------------------------------ + +Monotonic aggregates may be used :ref:`recursively `, but the recursive call may only appear in the +expression, and not in the range. The recursive semantics for aggregates are the same as the +recursive semantics for the rest of QL. For example, we might define a predicate to calculate +the distance of a node in a graph from the leaves as follows: + +.. code-block:: ql + + int depth(Node n) { + if not exists(n.getAChild()) + then result = 0 + else result = 1 + max(Node child | child = n.getAChild() | depth(child)) + } + +Here the recursive call is in the expression, which is legal. The recursive semantics for aggregates +are the same as the recursive semantics for the rest of QL. If you understand how aggregates work in +the non-recursive case then you should not find it difficult to use them recursively. However, it is +worth seeing how the evaluation of a recursive aggregation proceeds. + +Consider the depth example we just saw with the following graph as input (arrows point from children to parents): + +.. |image0| image:: ../images/monotonic-aggregates-graph.png + +|image0| + +Then the evaluation of the ``depth`` predicate proceeds as follows: + ++-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Stage** | **depth** | **Comments** | ++===========+============================================+==========================================================================================================================================================================+ +| 0 |   | We always begin with the empty set. | ++-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| 1 | ``(0, b), (0, d), (0, e)`` | The nodes with no children have depth 0. The recursive step for **a** and **c** fails to produce a value, since some of their children do not have values for ``depth``. | ++-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| 2 | ``(0, b), (0, d), (0, e), (1, c)`` | The recursive step for **c** succeeds, since ``depth`` now has a value for all its children (**d** and **e**). The recursive step for **a** still fails. | ++-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +| 3 | ``(0, b), (0, d), (0, e), (1, c), (2, a)`` | The recursive step for **a** succeeds, since ``depth`` now has a value for all its children (**b** and **c**). | ++-----------+--------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +Here, we can see that at the intermediate stages it is very important for the aggregate to +fail if some of the children lack a value - this prevents erroneous values being added. .. index:: any From fc3d30c10b1ed1fd58e643112b3d3b95d28cd0b0 Mon Sep 17 00:00:00 2001 From: james Date: Mon, 17 Feb 2020 16:20:31 +0000 Subject: [PATCH 044/236] docs: technical feedback (cherry picked from commit d5ff8f2b8ee9204f99bff41e969a64899469132e) --- docs/language/ql-handbook/expressions.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/language/ql-handbook/expressions.rst b/docs/language/ql-handbook/expressions.rst index 109235612d9..27e8543e4f5 100644 --- a/docs/language/ql-handbook/expressions.rst +++ b/docs/language/ql-handbook/expressions.rst @@ -407,8 +407,8 @@ In general, if the ```` is total and functional, then monotonic aggr equivalent to standard aggregates. Results differ when there is not precisely one ```` value for each value generated by the ````: -- If there are missing ```` values (that is, there is less than one - ```` value for each value generated by the ````), monotonic aggregates +- If there are missing ```` values (that is, there is no + ```` value for a value generated by the ````), monotonic aggregates won't compute a result, as you cannot create combinations of values including exactly one ```` value for each value generated by the ````. From 56f3e431f934952efa50deba7d2f913fbef88377 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 10:28:53 +0100 Subject: [PATCH 045/236] update expected output --- .../Security/CWE-078/CommandInjection.expected | 9 --------- 1 file changed, 9 deletions(-) diff --git a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected index 3211c119645..589deda96c9 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/CommandInjection.expected @@ -89,10 +89,6 @@ nodes | third-party-command-injection.js:5:20:5:26 | command | | third-party-command-injection.js:6:21:6:27 | command | | third-party-command-injection.js:6:21:6:27 | command | -| uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | -| uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | -| uselesscat.js:18:40:18:54 | req.params.data | -| uselesscat.js:18:40:18:54 | req.params.data | edges | child_process-test.js:6:9:6:49 | cmd | child_process-test.js:17:13:17:15 | cmd | | child_process-test.js:6:9:6:49 | cmd | child_process-test.js:17:13:17:15 | cmd | @@ -182,10 +178,6 @@ edges | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | -| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | -| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | -| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | -| uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | #select | child_process-test.js:17:13:17:15 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:17:13:17:15 | cmd | This command depends on $@. | child_process-test.js:6:25:6:31 | req.url | a user-provided value | | child_process-test.js:18:17:18:19 | cmd | child_process-test.js:6:25:6:31 | req.url | child_process-test.js:18:17:18:19 | cmd | This command depends on $@. | child_process-test.js:6:25:6:31 | req.url | a user-provided value | @@ -219,4 +211,3 @@ edges | other.js:18:22:18:24 | cmd | other.js:5:25:5:31 | req.url | other.js:18:22:18:24 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value | | other.js:19:36:19:38 | cmd | other.js:5:25:5:31 | req.url | other.js:19:36:19:38 | cmd | This command depends on $@. | other.js:5:25:5:31 | req.url | a user-provided value | | third-party-command-injection.js:6:21:6:27 | command | third-party-command-injection.js:5:20:5:26 | command | third-party-command-injection.js:6:21:6:27 | command | This command depends on $@. | third-party-command-injection.js:5:20:5:26 | command | a server-provided value | -| uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | uselesscat.js:18:40:18:54 | req.params.data | uselesscat.js:18:20:18:54 | 'cat ' ... ms.data | This command depends on $@. | uselesscat.js:18:40:18:54 | req.params.data | a user-provided value | From d4e73df92f400fc6962363d594b1a4878d3aa87f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 10:39:16 +0100 Subject: [PATCH 046/236] remove dead predicate --- .../semmle/javascript/security/UselessUseOfCat.qll | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index f67fa287685..3afe8dda8e6 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -232,20 +232,6 @@ module UselsesCatCandidates { override DataFlow::FunctionNode getCallback() { none() } } - // TODO: No! - bindingset[str, quote] - string surroundInQuotes(string str, string quote) { - if not str.prefix(1) = quote and not str.suffix(str.length() - 1) = quote - then result = quote + str + quote - else - if not str.prefix(1) = quote - then result = str + quote - else - if not str.suffix(str.length() - 1) = quote - then result = quote + str - else result = str - } - /** * Gets the file that is read for a call to child_process.execFile/execFileSync. */ From a5fdcb67f9b2b96228b02caf59003f970ada2c46 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 10:43:41 +0100 Subject: [PATCH 047/236] restricts alerts to the first line --- javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index f48ba6b8e95..e3fde814d66 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -13,9 +13,8 @@ import javascript import semmle.javascript.security.UselessUseOfCat +import semmle.javascript.RestrictedLocations from UselessCat cat -select cat, "Useless use of `cat`. Can be replaced with: " + createReadFileCall(cat) - - +select cat.asExpr().(FirstLineOf), "Useless use of `cat`. Can be replaced with: " + createReadFileCall(cat) \ No newline at end of file From 558beb72559e8c6ee97609ffd3f29beaaf06bc0a Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 10:57:33 +0100 Subject: [PATCH 048/236] simplify the output file argument --- .../javascript/security/UselessUseOfCat.qll | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 3afe8dda8e6..02eacb08a86 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -172,14 +172,28 @@ module UselsesCatCandidates { | (if root.asExpr() instanceof TemplateLiteral then quote = "`" else quote = "\"") and root.getFirstLeaf().getStringValue().prefix(cat.length()) = cat and - // Remove an initial ""+ (e.g. in `""+file`) exists(string rawConcat | rawConcat = quote + printed.suffix(cat.length()).trim() | - if rawConcat.prefix(3) = "\"\"+" then result = rawConcat.suffix(3) else result = rawConcat + result = getSimplifiedStringConcat(rawConcat) ) ) ) } + /** + * Gets a simplified and equivalent string concatenation for a given string concatenation `str` + */ + bindingset[str] + private string getSimplifiedStringConcat(string str) { + // Remove an initial ""+ (e.g. in `""+file`) + if str.prefix(3) = "\"\"+" then + result = str.suffix(3) + // prettify `${newpath}` to just newpath + else if str.prefix(3) = "`${" and str.suffix(str.length() - 2) = "}`" and not str.suffix(3).matches("%{%") then + result = str.prefix(str.length() - 2).suffix(3) + else + result = str + } + /** * A call to child_process.exec that might be a useless call to cat. */ From a193cb110eb9960c56e93b9f3816e6d273077d88 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 11:13:39 +0100 Subject: [PATCH 049/236] support arrow functions in the callbacks --- .../javascript/security/UselessUseOfCat.qll | 28 ++++++++++++++----- .../Security/CWE-078/UselessUseOfCat.expected | 5 +++- .../Security/CWE-078/uselesscat.js | 6 ++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 02eacb08a86..ac5338eb27e 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -17,13 +17,24 @@ string createReadFileCall(UselsesCatCandidates::UselessCatCandicate cat) { else extraArg = "" ) and if exists(cat.getCallback()) - then callback = ", function(" + getCallbackArgs(cat.getCallback()) + ") {...}" + then callback = constructCallbackString(cat.getCallback()) else callback = "" | result = "fs.readFile" + sync + "(" + cat.getFileArgument().trim() + extraArg + callback + ")" ) } +string constructCallbackString(DataFlow::FunctionNode func) { + exists(string args | args = getCallbackArgs(func) | + if func.getFunction() instanceof ArrowFunctionExpr + then + if func.getFunction().getBody() instanceof Expr + then result = ", (" + args + ") => ..." + else result = ", (" + args + ") => {...}" + else result = ", function(" + args + ") {...}" + ) +} + /** * Gets a string concatenation of the parameters to a function. */ @@ -185,13 +196,16 @@ module UselsesCatCandidates { bindingset[str] private string getSimplifiedStringConcat(string str) { // Remove an initial ""+ (e.g. in `""+file`) - if str.prefix(3) = "\"\"+" then - result = str.suffix(3) - // prettify `${newpath}` to just newpath - else if str.prefix(3) = "`${" and str.suffix(str.length() - 2) = "}`" and not str.suffix(3).matches("%{%") then - result = str.prefix(str.length() - 2).suffix(3) + if str.prefix(3) = "\"\"+" + then result = str.suffix(3) else - result = str + // prettify `${newpath}` to just newpath + if + str.prefix(3) = "`${" and + str.suffix(str.length() - 2) = "}`" and + not str.suffix(3).matches("%{%") + then result = str.prefix(str.length() - 2).suffix(3) + else result = str } /** diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index f4b22cd0803..de1c6aa86fe 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -2,7 +2,7 @@ readFile | uselesscat.js:10:1:10:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | | uselesscat.js:12:1:14:2 | exec("c ... ut);\\n}) | fs.readFile("/proc/"+id+"/status", function(err, out) {...}) | | uselesscat.js:16:1:16:29 | execSyn ... uinfo') | fs.readFileSync("/proc/cpuinfo") | -| uselesscat.js:18:1:18:26 | execSyn ... path}`) | fs.readFileSync(`${newpath}`) | +| uselesscat.js:18:1:18:26 | execSyn ... path}`) | fs.readFileSync(newpath) | | uselesscat.js:32:1:32:34 | execSyn ... path}`) | fs.readFileSync(`foo/bar/${newpath}`) | | uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | fs.readFileSync(`foo/bar/${newpath}`, {encoding: 'utf8'})) | | uselesscat.js:51:9:51:31 | execSyn ... + file) | fs.readFileSync(file) | @@ -15,6 +15,9 @@ readFile | uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/"+newPath+"bar", {encoding: 'utf8'})) | | uselesscat.js:88:1:88:35 | execSyn ... + foo) | fs.readFileSync("/proc/cpuinfo"+foo) | | uselesscat.js:90:1:90:50 | execFil ... th}` ]) | fs.readFileSync(`foo/bar/${newpath}`) | +| uselesscat.js:94:1:94:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | +| uselesscat.js:96:1:96:53 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:98:1:98:55 | exec("c ... h(out)) | fs.readFile("foo/bar", (err, out) => ...) | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | | False negative | uselesscat.js:84:118:84:144 | // NOT ... lagged] | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index d35f3224d18..98c41513baa 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -90,3 +90,9 @@ execSync('cat /proc/cpuinfo' + foo).toString(); // NOT OK. execFileSync('/bin/cat', [ `foo/bar/${newpath}` ]); // NOT OK execFileSync('node', [ `foo/bar/${newpath}` ]); // OK - not a call to cat + +exec("cat foo/bar", function (err, out) {}); // NOT OK + +exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK + +exec("cat foo/bar", (err, out) => doSomethingWith(out)); // NOT OK \ No newline at end of file From b5ef45e6c232b163ea95dd8911f8b0a0c61d68ed Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 11:30:23 +0100 Subject: [PATCH 050/236] add isSync predicate to SystemCommandExecution --- .../ql/src/semmle/javascript/Concepts.qll | 3 ++ .../javascript/frameworks/NodeJSLib.qll | 4 ++ .../semmle/javascript/frameworks/ShellJS.qll | 2 + .../frameworks/SystemCommandExecutors.qll | 20 +++++++- .../Security/CWE-078/UselessUseOfCat.expected | 46 +++++++++++++++++++ .../Security/CWE-078/UselessUseOfCat.ql | 4 ++ 6 files changed, 78 insertions(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/Concepts.qll b/javascript/ql/src/semmle/javascript/Concepts.qll index 23bbbce3b16..4fef87f8585 100644 --- a/javascript/ql/src/semmle/javascript/Concepts.qll +++ b/javascript/ql/src/semmle/javascript/Concepts.qll @@ -22,6 +22,9 @@ abstract class SystemCommandExecution extends DataFlow::Node { * to the command. */ DataFlow::Node getArgumentList() { none() } + + /** Holds if the command execution happens synchronously. */ + abstract predicate isSync(); } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 0ee7c10fa7f..95600e072f6 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -623,6 +623,10 @@ module NodeJSLib { // all of the above methods take the argument list as their second argument result = getArgument(1) } + + override predicate isSync() { + "Sync" = methodName.suffix(methodName.length() - 4) + } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll index 22605479244..e459fe9089c 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll @@ -160,6 +160,8 @@ module ShellJS { override DataFlow::Node getACommandArgument() { result = getArgument(0) } override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() } + + override predicate isSync() {none ()} } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 7cea87bb3f3..e0664f93dd1 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -9,6 +9,7 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I int cmdArg; boolean shell; + boolean sync; SystemCommandExecutors() { exists(string mod, DataFlow::SourceNode callee | @@ -31,9 +32,11 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I ) and cmdArg = 0 | - callee = DataFlow::moduleMember(mod, method) + callee = DataFlow::moduleMember(mod, method) and + sync = getSync(method) ) or + sync = false and ( shell = false and ( @@ -65,4 +68,19 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() and shell = true } + + override predicate isSync() { + sync = true + } } + +/** + * Gets a boolean reflecting if the name ends with "sync" or "Sync". + */ +bindingset[name] +private boolean getSync(string name) { + if name.suffix(name.length() - 4) = "Sync" or name.suffix(name.length() - 4) = "sync" then + result = true + else + result = false +} \ No newline at end of file diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index de1c6aa86fe..3acd23863d0 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -18,6 +18,52 @@ readFile | uselesscat.js:94:1:94:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | | uselesscat.js:96:1:96:53 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | | uselesscat.js:98:1:98:55 | exec("c ... h(out)) | fs.readFile("foo/bar", (err, out) => ...) | +syncCommand +| child_process-test.js:9:5:9:22 | cp.execSync("foo") | +| child_process-test.js:11:5:11:26 | cp.exec ... ("foo") | +| child_process-test.js:13:5:13:23 | cp.spawnSync("foo") | +| child_process-test.js:18:5:18:20 | cp.execSync(cmd) | +| child_process-test.js:20:5:20:24 | cp.execFileSync(cmd) | +| child_process-test.js:22:5:22:21 | cp.spawnSync(cmd) | +| command-line-parameter-command-injection.js:11:2:11:21 | cp.execSync(args[0]) | +| command-line-parameter-command-injection.js:12:2:12:33 | cp.exec ... rgs[0]) | +| command-line-parameter-command-injection.js:15:2:15:26 | cp.exec ... rgs[0]) | +| command-line-parameter-command-injection.js:16:2:16:38 | cp.exec ... rgs[0]) | +| command-line-parameter-command-injection.js:19:2:19:18 | cp.execSync(arg0) | +| command-line-parameter-command-injection.js:20:2:20:30 | cp.exec ... + arg0) | +| command-line-parameter-command-injection.js:26:2:26:51 | cp.exec ... tion"`) | +| command-line-parameter-command-injection.js:27:2:27:58 | cp.exec ... tion"`) | +| other.js:7:5:7:36 | require ... nc(cmd) | +| other.js:9:5:9:35 | require ... nc(cmd) | +| other.js:12:5:12:30 | require ... nc(cmd) | +| third-party-command-injection.js:6:9:6:28 | cp.execSync(command) | +| tst_shell-command-injection-from-environment.js:4:2:4:62 | cp.exec ... emp")]) | +| tst_shell-command-injection-from-environment.js:5:2:5:54 | cp.exec ... temp")) | +| uselesscat.js:16:1:16:29 | execSyn ... uinfo') | +| uselesscat.js:18:1:18:26 | execSyn ... path}`) | +| uselesscat.js:20:1:20:50 | child_p ... wc -l') | +| uselesscat.js:22:1:22:38 | execSyn ... o/bar') | +| uselesscat.js:24:1:24:35 | execSyn ... o/bar`) | +| uselesscat.js:28:1:28:39 | execSyn ... 1000}) | +| uselesscat.js:32:1:32:34 | execSyn ... path}`) | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | +| uselesscat.js:36:1:36:77 | execSyn ... utf8'}) | +| uselesscat.js:38:1:38:43 | execSyn ... r/baz') | +| uselesscat.js:40:1:40:40 | execSyn ... path}`) | +| uselesscat.js:42:1:42:47 | execSyn ... File}`) | +| uselesscat.js:44:1:44:34 | execSyn ... ' ')}`) | +| uselesscat.js:48:1:48:41 | execSyn ... tool}`) | +| uselesscat.js:51:9:51:31 | execSyn ... + file) | +| uselesscat.js:54:1:54:39 | execSyn ... + "'") | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | +| uselesscat.js:76:1:76:39 | execFil ... xml' ]) | +| uselesscat.js:79:1:79:46 | execFil ... opts) | +| uselesscat.js:82:1:82:90 | execFil ... String) | +| uselesscat.js:84:1:84:115 | execFil ... ring'}) | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | +| uselesscat.js:88:1:88:35 | execSyn ... + foo) | +| uselesscat.js:90:1:90:50 | execFil ... th}` ]) | +| uselesscat.js:92:1:92:46 | execFil ... th}` ]) | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | | False negative | uselesscat.js:84:118:84:144 | // NOT ... lagged] | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql index edcf4987e3e..f9a4fd2ea8c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql @@ -18,3 +18,7 @@ where select msg, comment query string readFile(UselessCat cat) { result = createReadFileCall(cat) } + +query SystemCommandExecution syncCommand() { + result.isSync() +} \ No newline at end of file From 12c0291dde44e65f39db09e6918eba6ee09b86bd Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 11:35:11 +0100 Subject: [PATCH 051/236] require that an options object has a known set of properties --- .../semmle/javascript/security/UselessUseOfCat.qll | 13 +++++++++---- .../Security/CWE-078/UselessUseOfCat.expected | 1 + .../test/query-tests/Security/CWE-078/uselesscat.js | 4 +++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index ac5338eb27e..5558fdcb654 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -60,15 +60,20 @@ class UselessCat extends DataFlow::CallNode { UselessCat() { this = candidate and + exists(createReadFileCall(this)) and // wildcards, pipes, redirections, and multiple files are OK. // (The multiple files detection relies on the fileArgument not containing spaces anywhere) not candidate.getFileArgument().regexpMatch(".*(\\*|\\||>|<| ).*") and // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. - not exists(string prop | - not prop = "encoding" and - exists(candidate.getOptionsArg().getALocalSource().getAPropertyWrite(prop)) + ( + not exists(candidate.getOptionsArg()) + or + forex(string prop | + exists(candidate.getOptionsArg().getALocalSource().getAPropertyWrite(prop)) + | + prop = "encoding" + ) ) and - exists(createReadFileCall(this)) and // If there is a callback, then it must either have one or two arguments, or if there is a third argument it must be unused. ( not exists(candidate.getCallback()) diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index 3acd23863d0..61def6726b3 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -64,6 +64,7 @@ syncCommand | uselesscat.js:88:1:88:35 | execSyn ... + foo) | | uselesscat.js:90:1:90:50 | execFil ... th}` ]) | | uselesscat.js:92:1:92:46 | execFil ... th}` ]) | +| uselesscat.js:100:1:100:56 | execFil ... ptions) | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | | False negative | uselesscat.js:84:118:84:144 | // NOT ... lagged] | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index 98c41513baa..c860d21f654 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -95,4 +95,6 @@ exec("cat foo/bar", function (err, out) {}); // NOT OK exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK -exec("cat foo/bar", (err, out) => doSomethingWith(out)); // NOT OK \ No newline at end of file +exec("cat foo/bar", (err, out) => doSomethingWith(out)); // NOT OK + +execFileSync('/bin/cat', [ 'pom.xml' ], unknownOptions); // OK - unknown options. \ No newline at end of file From b1cbfce50bdc96b53ea7feb5c34ca3f64604892e Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 14:17:37 +0100 Subject: [PATCH 052/236] use SystemCommandExecution and a few small fixes --- .../javascript/security/UselessUseOfCat.qll | 180 ++++++------------ .../Security/CWE-078/UselessUseOfCat.expected | 4 +- 2 files changed, 57 insertions(+), 127 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 5558fdcb654..a77298f8376 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -3,10 +3,9 @@ */ import javascript -import semmle.javascript.security.dataflow.IndirectCommandArgument /** - * Gets a string representing an equivalent call to `fs.ReadFile` for a call to `cat`. + * Gets a string representation of an equivalent call to `fs.readFile` for a given command execution `cat`. */ string createReadFileCall(UselsesCatCandidates::UselessCatCandicate cat) { exists(string sync, string extraArg, string callback | @@ -24,6 +23,9 @@ string createReadFileCall(UselsesCatCandidates::UselessCatCandicate cat) { ) } +/** + * Constructs a string representing the callback `func`. + */ string constructCallbackString(DataFlow::FunctionNode func) { exists(string args | args = getCallbackArgs(func) | if func.getFunction() instanceof ArrowFunctionExpr @@ -36,20 +38,25 @@ string constructCallbackString(DataFlow::FunctionNode func) { } /** - * Gets a string concatenation of the parameters to a function. + * Gets a string concatenation of the parameter names in a function `func`. */ private string getCallbackArgs(DataFlow::FunctionNode func) { - result = concat(int i | i = [0 .. 2] | func.getParameter(i).getName(), ", ") + result = + concat(int i | + i = [0 .. func.getNumParameter()] + | + func.getParameter(i).getName(), ", " order by i + ) } /** - * Gets a string representation of the options argument from an exec-like call. + * Gets a string representation of the options argument `arg` from an exec-like call. */ -private string printOptionsArg(DataFlow::Node node) { - result = node.asExpr().(VarAccess).getVariable().getName() +private string printOptionsArg(DataFlow::Node arg) { + result = arg.asExpr().(VarAccess).getVariable().getName() or // fall back to toString(), but ensure that we don't have dots in the middle. - result = node.(DataFlow::ObjectLiteralNode).toString() and not result.regexpMatch(".*\\.\\..*") + result = arg.(DataFlow::ObjectLiteralNode).toString() and not result.regexpMatch(".*\\.\\..*") } /** @@ -60,6 +67,7 @@ class UselessCat extends DataFlow::CallNode { UselessCat() { this = candidate and + // We can create an equivalent `fs.readFile` call. exists(createReadFileCall(this)) and // wildcards, pipes, redirections, and multiple files are OK. // (The multiple files detection relies on the fileArgument not containing spaces anywhere) @@ -96,28 +104,56 @@ class UselessCat extends DataFlow::CallNode { module UselsesCatCandidates { /** * A candidate for a useless use of `cat`. - * Subclasses of this class specify the structure of the `exec`-like call. + * This class describes the structure of a call to cat + * This class does not determine whether it is a useless call, or even if it is a call to `cat`. */ - abstract class UselessCatCandicate extends DataFlow::CallNode { + class UselessCatCandicate extends DataFlow::CallNode { + SystemCommandExecution command; + + UselessCatCandicate() { this = command } + /** * Holds if the call is synchronous (e.g. `execFileSync`). */ - abstract predicate isSync(); + predicate isSync() { command.isSync() } /** - * Gets a string representation of the expression that determines what file is read. + * Holds if the executed command execution has an argument list as a separate argument. */ - abstract string getFileArgument(); + predicate hasArgumentList() { exists(command.getArgumentList()) } /** - * Gets the data-flow node for the options argument to the `exec`-like call. + * Gets a string representation of the expression that determines what file is read by `cat`. */ - abstract DataFlow::Node getOptionsArg(); + string getFileArgument() { + if hasArgumentList() + then + getArgument(0).mayHaveStringValue(cat()) and + result = getFileThatIsReadFromCommandList(this) + else result = getFileArgumentWithoutCat(getArgument(0)) + } /** - * Gets the callback used for the `exec` like call (if it exists). + * Gets the data-flow node (if it exists) for the options argument to the `exec`-like call. */ - abstract DataFlow::FunctionNode getCallback(); + DataFlow::Node getOptionsArg() { + exists(int n | + n >= 1 and + // If there is a command-list, then the options is at least the third argument. + (not exists(command.getArgumentList()) or n >= 2) and + // async calls have a callback as their last call. + if this.isSync() then n < getNumArgument() else n < getNumArgument() - 1 + | + result = getArgument(n) + ) + } + + /** + * Gets the callback (if it exists) for an async `exec` like call. + */ + DataFlow::FunctionNode getCallback() { + not this.isSync() and result = getLastArgument().getALocalSource() + } } /** @@ -214,61 +250,9 @@ module UselsesCatCandidates { } /** - * A call to child_process.exec that might be a useless call to cat. + * Gets the file that is read for a call with an explicit command list (e.g. `child_process.execFile/execFileSync`). */ - private class ExecCall extends UselessCatCandicate { - string fileArgument; - boolean hasOptions; - - ExecCall() { - this = DataFlow::moduleImport("child_process").getAMemberCall("exec") and - ( - this.getNumArgument() = 2 and hasOptions = false - or - this.getNumArgument() = 3 and hasOptions = true - ) and - fileArgument = getFileArgumentWithoutCat(getArgument(0)) - } - - override predicate isSync() { none() } - - override string getFileArgument() { result = fileArgument } - - override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(1) } - - override DataFlow::FunctionNode getCallback() { result = getLastArgument() } - } - - /** - * A call to child_process.execSync that might be a useless call to cat. - */ - private class ExecSyncCall extends UselessCatCandicate { - string fileArgument; - boolean hasOptions; - - ExecSyncCall() { - this = DataFlow::moduleImport("child_process").getAMemberCall("execSync") and - ( - this.getNumArgument() = 1 and hasOptions = false - or - this.getNumArgument() = 2 and hasOptions = true - ) and - fileArgument = getFileArgumentWithoutCat(getArgument(0)) - } - - override predicate isSync() { any() } - - override string getFileArgument() { result = fileArgument } - - override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(1) } - - override DataFlow::FunctionNode getCallback() { none() } - } - - /** - * Gets the file that is read for a call to child_process.execFile/execFileSync. - */ - string getFileThatIsRead(DataFlow::CallNode call) { + string getFileThatIsReadFromCommandList(DataFlow::CallNode call) { exists(DataFlow::ArrayCreationNode array, DataFlow::Node element | array = call.getArgument(1).(DataFlow::ArrayCreationNode) and array.getSize() = 1 and @@ -279,58 +263,4 @@ module UselsesCatCandidates { result = createConcatRepresentation(element) ) } - - /** - * A call to child_process.execFile that might be a useless call to cat. - */ - private class ExecFileCall extends UselessCatCandicate { - string fileArgument; - boolean hasOptions; - - ExecFileCall() { - this.getArgument(0).mayHaveStringValue(cat()) and - this = DataFlow::moduleImport("child_process").getAMemberCall("execFile") and - ( - this.getNumArgument() = 3 and hasOptions = false - or - this.getNumArgument() = 4 and hasOptions = true - ) and - fileArgument = getFileThatIsRead(this) - } - - override predicate isSync() { none() } - - override string getFileArgument() { result = fileArgument } - - override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(2) } - - override DataFlow::FunctionNode getCallback() { result = getLastArgument() } - } - - /** - * A call to child_process.execFileSync that might be a useless call to cat. - */ - private class ExecFileSyncCall extends UselessCatCandicate { - string fileArgument; - boolean hasOptions; - - ExecFileSyncCall() { - this.getArgument(0).mayHaveStringValue(cat()) and - this = DataFlow::moduleImport("child_process").getAMemberCall("execFileSync") and - ( - this.getNumArgument() = 2 and hasOptions = false - or - this.getNumArgument() = 3 and hasOptions = true - ) and - fileArgument = getFileThatIsRead(this) - } - - override predicate isSync() { any() } - - override string getFileArgument() { result = fileArgument } - - override DataFlow::Node getOptionsArg() { hasOptions = true and result = getArgument(2) } - - override DataFlow::FunctionNode getCallback() { none() } - } } diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index 61def6726b3..f4ebbd0848b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -6,8 +6,8 @@ readFile | uselesscat.js:32:1:32:34 | execSyn ... path}`) | fs.readFileSync(`foo/bar/${newpath}`) | | uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | fs.readFileSync(`foo/bar/${newpath}`, {encoding: 'utf8'})) | | uselesscat.js:51:9:51:31 | execSyn ... + file) | fs.readFileSync(file) | -| uselesscat.js:59:1:62:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", function(error, stderr, stdout) {...}) | -| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", {encoding: 'utf8'}), function(error, stderr, stdout) {...}) | +| uselesscat.js:59:1:62:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", function(error, stdout, stderr) {...}) | +| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", {encoding: 'utf8'}), function(error, stdout, stderr) {...}) | | uselesscat.js:74:1:74:60 | execFil ... utf8'}) | fs.readFileSync("pom.xml", {encoding: 'utf8'})) | | uselesscat.js:76:1:76:39 | execFil ... xml' ]) | fs.readFileSync("pom.xml") | | uselesscat.js:79:1:79:46 | execFil ... opts) | fs.readFileSync("pom.xml", opts)) | From b2ccec28e076c3b93c56da15cce11acd3577f748 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 14:34:50 +0100 Subject: [PATCH 053/236] require the file to be non-empty --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index a77298f8376..47a99b0bf4e 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -69,6 +69,8 @@ class UselessCat extends DataFlow::CallNode { this = candidate and // We can create an equivalent `fs.readFile` call. exists(createReadFileCall(this)) and + // There is a file to read, and not just a pair of quotes. + candidate.getFileArgument().length() >= 3 and // wildcards, pipes, redirections, and multiple files are OK. // (The multiple files detection relies on the fileArgument not containing spaces anywhere) not candidate.getFileArgument().regexpMatch(".*(\\*|\\||>|<| ).*") and From 924272a7a508567b3622a1a317e3649bc2424a2d Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 20 Feb 2020 14:35:26 +0100 Subject: [PATCH 054/236] insert placeholder qhelp --- .../Security/CWE-078/UselessUseOfCat.qhelp | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp new file mode 100644 index 00000000000..452f630712d --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -0,0 +1,30 @@ + + + +

+ Useless use of cat +

+ + +
+ +

+ TODO: This is a placeholder +

+ +
+ +

+

+
+ + +
  • + OWASP: + Command Injection. +
  • + +
    +
    From 96b8d786501c662bf2ba3880c6a78e944f5ea764 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 11 Feb 2020 21:35:30 -0800 Subject: [PATCH 055/236] Adds modernized files. --- .../ql/src/Expressions/TruncatedDivision.ql | 14 ++-- .../src/semmle/python/objects/ObjectAPI.qll | 66 +++++++++++++++++++ .../general/TruncatedDivision.qlref | 1 + 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index 3d4deb9ba54..d93c1ced353 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -18,18 +18,20 @@ where // Only relevant for Python 2, as all later versions implement true division major_version() = 2 and - exists(BinaryExprNode bin, Object lobj, Object robj | + exists(BinaryExprNode bin, Value lobj, Value robj | bin = div.getAFlowNode() and bin.getNode().getOp() instanceof Div - and bin.getLeft().refersTo(lobj, theIntType(), left) - and bin.getRight().refersTo(robj, theIntType(), right) + and bin.getLeft().pointsTo(lobj, left) + and lobj.getClass() = ClassValue::int_() + and bin.getRight().pointsTo(robj, right) + and robj.getClass() = ClassValue::int_() // Ignore instances where integer division leaves no remainder - and not lobj.(NumericObject).intValue() % robj.(NumericObject).intValue() = 0 + and not lobj.(NumericValue).intValue() % robj.(NumericValue).intValue() = 0 and not bin.getNode().getEnclosingModule().hasFromFuture("division") // Filter out results wrapped in `int(...)` - and not exists(CallNode c, ClassObject cls | + and not exists(CallNode c, ClassValue cls | c.getAnArg() = bin - and c.getFunction().refersTo(cls) + and c.getFunction().pointsTo(cls) and cls.getName() = "int" ) ) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 1f1f7001da1..a525c24e89c 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -78,6 +78,16 @@ class Value extends TObject { predicate isBuiltin() { this.(ObjectInternal).isBuiltin() } + + /** INTERNAL -- Do not use */ + Builtin asBuiltin() { + this = TBuiltinClassObject(result) or + this = TBuiltinFunctionObject(result) or + this = TBuiltinMethodObject(result) or + this = TBuiltinModuleObject(result) or + this = TBuiltinOpaqueObject(result) or + this = TBuiltinTuple(result) + } predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) @@ -134,6 +144,62 @@ class Value extends TObject { } } +/** Numeric values (ints and floats). + * Includes those occurring in the source as a literal + * or in a builtin module as a value. + */ +class NumericValue extends Value { + + NumericValue() { + this.asBuiltin().getClass() = theIntType().asBuiltin() or + this.asBuiltin().getClass() = theLongType().asBuiltin() or + this.asBuiltin().getClass() = theFloatType().asBuiltin() + } + + /** Gets the Boolean value that this object + * would evaluate to in a Boolean context, + * such as `bool(x)` or `if x: ...` + */ + override boolean booleanValue() { + this.intValue() != 0 and result = true + or + this.intValue() = 0 and result = false + or + this.floatValue() != 0 and result = true + or + this.floatValue() = 0 and result = false + } + + /** Gets the value of this object if it is a constant integer and it fits in a QL int */ + int intValue() { + ( + this.asBuiltin().getClass() = theIntType().asBuiltin() or + this.asBuiltin().getClass() = theLongType().asBuiltin() + ) + and + result = this.asBuiltin().getName().toInt() + } + + /** Gets the value of this object if it is a constant float */ + float floatValue() { + this.asBuiltin().getClass() = theFloatType().asBuiltin() + and + result = this.asBuiltin().getName().toFloat() + } + + /** Gets the string representation of this object, equivalent to calling repr() in Python */ + string repr() { + exists(string s | + s = this.asBuiltin().getName() | + if this.asBuiltin().getClass() = theLongType().asBuiltin() then + result = s + "L" + else + result = s + ) + } + +} + /** Class representing modules in the Python program * Each `ModuleValue` represents a module object in the Python program. */ diff --git a/python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref b/python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref new file mode 100644 index 00000000000..0bc5573c72f --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref @@ -0,0 +1 @@ +Expressions/TruncatedDivision.ql \ No newline at end of file From 115495450d863312f4ea01ea27380598aca3769f Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 11 Feb 2020 21:38:00 -0800 Subject: [PATCH 056/236] Adds test cases. --- .../Expressions/general/expressions_test.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/python/ql/test/query-tests/Expressions/general/expressions_test.py b/python/ql/test/query-tests/Expressions/general/expressions_test.py index 1d802f1bfd6..3de3e8b1639 100644 --- a/python/ql/test/query-tests/Expressions/general/expressions_test.py +++ b/python/ql/test/query-tests/Expressions/general/expressions_test.py @@ -242,3 +242,52 @@ from types import MappingProxyType def mpt_arg(d=MappingProxyType({})): return 1 in d + + + + + + + + + + + + +#### TruncatedDivision.ql + +# NOTE: The following test case will only work under Python 2. + +# Truncated division occurs when two integers are divided. This causes the +# fractional part, if there is one, to be discared. So for example, `2 / 3` will +# evaluate to `0` instead of `0.666...`. + +def truncated_division(): + + def average(l): + return sum(l) / len(l) + + + + ## Negative Cases + + # This case is good, and is a minimal obvious case that should be good. It + # SHOULD NOT be found by the query. + print(3.0 / 2.0) + + # This case is good, because the sum is `3.0`, which is a float, and will not + # truncate. This case SHOULD NOT be found by the query. + print(average([1.0, 2.0])) + + + + ## Positive Cases + + # This case is bad, and is a minimal obvious case that should be bad. It + # SHOULD be found by the query. + print(3 / 2) + + # This case is bad, because the sum is `3`, which is an integer, and will + # truncate when divided by the length `2`. This case SHOULD be found by the + # query. + print(average([1,2])) From 91ea46f5ee8ddf0dcf16fdfe837852593035188f Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 11 Feb 2020 22:03:23 -0800 Subject: [PATCH 057/236] Adds test output. --- .../query-tests/Expressions/general/TruncatedDivision.expected | 1 + 1 file changed, 1 insertion(+) create mode 100644 python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected diff --git a/python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected b/python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected new file mode 100644 index 00000000000..3707d6bc6fa --- /dev/null +++ b/python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected @@ -0,0 +1 @@ +| expressions_test.py:288:9:288:13 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | expressions_test.py:288:9:288:9 | expressions_test.py:288 | left | expressions_test.py:288:13:288:13 | expressions_test.py:288 | right | From 5acd982d596363169d16971f125efca86c1aea5e Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 17 Feb 2020 11:12:06 -0800 Subject: [PATCH 058/236] Swaps ...obj for ...val --- python/ql/src/Expressions/TruncatedDivision.ql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index d93c1ced353..b9ee85f0ea7 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -18,15 +18,15 @@ where // Only relevant for Python 2, as all later versions implement true division major_version() = 2 and - exists(BinaryExprNode bin, Value lobj, Value robj | + exists(BinaryExprNode bin, Value lval, Value rval | bin = div.getAFlowNode() and bin.getNode().getOp() instanceof Div - and bin.getLeft().pointsTo(lobj, left) - and lobj.getClass() = ClassValue::int_() - and bin.getRight().pointsTo(robj, right) - and robj.getClass() = ClassValue::int_() + and bin.getLeft().pointsTo(lval, left) + and lval.getClass() = ClassValue::int_() + and bin.getRight().pointsTo(rval, right) + and rval.getClass() = ClassValue::int_() // Ignore instances where integer division leaves no remainder - and not lobj.(NumericValue).intValue() % robj.(NumericValue).intValue() = 0 + and not lval.(NumericValue).intValue() % rval.(NumericValue).intValue() = 0 and not bin.getNode().getEnclosingModule().hasFromFuture("division") // Filter out results wrapped in `int(...)` and not exists(CallNode c, ClassValue cls | From 28be3b47fc9625078344b85b5e5c548982763d21 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 17 Feb 2020 11:15:37 -0800 Subject: [PATCH 059/236] Replaces name-reference to the class with canonical predicate. --- python/ql/src/Expressions/TruncatedDivision.ql | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index b9ee85f0ea7..ee6378534a4 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -29,10 +29,9 @@ where and not lval.(NumericValue).intValue() % rval.(NumericValue).intValue() = 0 and not bin.getNode().getEnclosingModule().hasFromFuture("division") // Filter out results wrapped in `int(...)` - and not exists(CallNode c, ClassValue cls | - c.getAnArg() = bin - and c.getFunction().pointsTo(cls) - and cls.getName() = "int" + and not exists(CallNode c | + c = ClassValue::int_().getACall() + and c.getAnArg() = bin ) ) select div, "Result of division may be truncated as its $@ and $@ arguments may both be integers.", From 5d9d724d43d588dd5fed025bb473cd56fc050d29 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 20 Feb 2020 16:17:33 -0800 Subject: [PATCH 060/236] Removes conflicting NumericValue definition --- .../src/semmle/python/objects/ObjectAPI.qll | 56 ------------------- 1 file changed, 56 deletions(-) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index a525c24e89c..e865d7d649f 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -144,62 +144,6 @@ class Value extends TObject { } } -/** Numeric values (ints and floats). - * Includes those occurring in the source as a literal - * or in a builtin module as a value. - */ -class NumericValue extends Value { - - NumericValue() { - this.asBuiltin().getClass() = theIntType().asBuiltin() or - this.asBuiltin().getClass() = theLongType().asBuiltin() or - this.asBuiltin().getClass() = theFloatType().asBuiltin() - } - - /** Gets the Boolean value that this object - * would evaluate to in a Boolean context, - * such as `bool(x)` or `if x: ...` - */ - override boolean booleanValue() { - this.intValue() != 0 and result = true - or - this.intValue() = 0 and result = false - or - this.floatValue() != 0 and result = true - or - this.floatValue() = 0 and result = false - } - - /** Gets the value of this object if it is a constant integer and it fits in a QL int */ - int intValue() { - ( - this.asBuiltin().getClass() = theIntType().asBuiltin() or - this.asBuiltin().getClass() = theLongType().asBuiltin() - ) - and - result = this.asBuiltin().getName().toInt() - } - - /** Gets the value of this object if it is a constant float */ - float floatValue() { - this.asBuiltin().getClass() = theFloatType().asBuiltin() - and - result = this.asBuiltin().getName().toFloat() - } - - /** Gets the string representation of this object, equivalent to calling repr() in Python */ - string repr() { - exists(string s | - s = this.asBuiltin().getName() | - if this.asBuiltin().getClass() = theLongType().asBuiltin() then - result = s + "L" - else - result = s - ) - } - -} - /** Class representing modules in the Python program * Each `ModuleValue` represents a module object in the Python program. */ From ab1fcb32ae93102b270f8312b8c941eb625d47ca Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 20 Feb 2020 16:17:43 -0800 Subject: [PATCH 061/236] autoformats --- .../src/semmle/python/objects/ObjectAPI.qll | 1290 +++++++---------- 1 file changed, 557 insertions(+), 733 deletions(-) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index e865d7d649f..91f7a2fed1e 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -3,9 +3,6 @@ * A `Value` is a static approximation to a set of runtime objects. */ - - - import python private import TObject private import semmle.python.objects.ObjectInternal @@ -14,897 +11,724 @@ private import semmle.python.pointsto.PointsToContext private import semmle.python.pointsto.MRO private import semmle.python.types.Builtins -/* Use the term `ObjectSource` to refer to DB entity. Either a CFG node +/* + * Use the term `ObjectSource` to refer to DB entity. Either a CFG node * for Python objects, or `@py_cobject` entity for built-in objects. */ + class ObjectSource = Object; /* Aliases for scopes */ class FunctionScope = Function; + class ClassScope = Class; + class ModuleScope = Module; -/** Class representing values in the Python program +/** + * Class representing values in the Python program * Each `Value` is a static approximation to a set of one or more real objects. */ class Value extends TObject { + Value() { + this != ObjectInternal::unknown() and + this != ObjectInternal::unknownClass() and + this != ObjectInternal::undefined() + } - Value() { - this != ObjectInternal::unknown() and - this != ObjectInternal::unknownClass() and - this != ObjectInternal::undefined() - } + string toString() { result = this.(ObjectInternal).toString() } - string toString() { - result = this.(ObjectInternal).toString() - } + /** Gets a `ControlFlowNode` that refers to this object. */ + ControlFlowNode getAReference() { PointsToInternal::pointsTo(result, _, this, _) } - /** Gets a `ControlFlowNode` that refers to this object. */ - ControlFlowNode getAReference() { - PointsToInternal::pointsTo(result, _, this, _) - } + /** + * Gets the class of this object. + * Strictly, the `Value` representing the class of the objects + * represented by this Value. + */ + ClassValue getClass() { result = this.(ObjectInternal).getClass() } - /** Gets the class of this object. - * Strictly, the `Value` representing the class of the objects - * represented by this Value. - */ - ClassValue getClass() { - result = this.(ObjectInternal).getClass() - } + /** Gets a call to this object */ + CallNode getACall() { result = this.getACall(_) } - /** Gets a call to this object */ - CallNode getACall() { - result = this.getACall(_) - } + /** Gets a call to this object with the given `caller` context. */ + CallNode getACall(PointsToContext caller) { + PointsToInternal::pointsTo(result.getFunction(), caller, this, _) + or + exists(BoundMethodObjectInternal bm | + PointsToInternal::pointsTo(result.getFunction(), caller, bm, _) and + bm.getFunction() = this + ) + } - /** Gets a call to this object with the given `caller` context. */ - CallNode getACall(PointsToContext caller) { - PointsToInternal::pointsTo(result.getFunction(), caller, this, _) - or - exists(BoundMethodObjectInternal bm | - PointsToInternal::pointsTo(result.getFunction(), caller, bm, _) and - bm.getFunction() = this - ) - } + /** Gets a `Value` that represents the attribute `name` of this object. */ + Value attr(string name) { this.(ObjectInternal).attribute(name, result, _) } - /** Gets a `Value` that represents the attribute `name` of this object. */ - Value attr(string name) { - this.(ObjectInternal).attribute(name, result, _) - } + /** + * Holds if this value is builtin. Applies to built-in functions and methods, + * but also integers and strings. + */ + predicate isBuiltin() { this.(ObjectInternal).isBuiltin() } - /** Holds if this value is builtin. Applies to built-in functions and methods, - * but also integers and strings. - */ - predicate isBuiltin() { - this.(ObjectInternal).isBuiltin() - } - - /** INTERNAL -- Do not use */ - Builtin asBuiltin() { - this = TBuiltinClassObject(result) or - this = TBuiltinFunctionObject(result) or - this = TBuiltinMethodObject(result) or - this = TBuiltinModuleObject(result) or - this = TBuiltinOpaqueObject(result) or - this = TBuiltinTuple(result) - } + /** INTERNAL -- Do not use */ + Builtin asBuiltin() { + this = TBuiltinClassObject(result) or + this = TBuiltinFunctionObject(result) or + this = TBuiltinMethodObject(result) or + this = TBuiltinModuleObject(result) or + this = TBuiltinOpaqueObject(result) or + this = TBuiltinTuple(result) + } - predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { - this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) - or - not exists(this.(ObjectInternal).getOrigin()) and - filepath = "" and bl = 0 and bc = 0 and el = 0 and ec = 0 - } + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) + or + not exists(this.(ObjectInternal).getOrigin()) and + filepath = "" and + bl = 0 and + bc = 0 and + el = 0 and + ec = 0 + } - /** Gets the name of this value, if it has one. - * Note this is the innate name of the - * object, not necessarily all the names by which it can be called. - */ - final string getName() { - result = this.(ObjectInternal).getName() - } + /** + * Gets the name of this value, if it has one. + * Note this is the innate name of the + * object, not necessarily all the names by which it can be called. + */ + final string getName() { result = this.(ObjectInternal).getName() } - /** Holds if this value has the attribute `name` */ - predicate hasAttribute(string name) { - this.(ObjectInternal).hasAttribute(name) - } + /** Holds if this value has the attribute `name` */ + predicate hasAttribute(string name) { this.(ObjectInternal).hasAttribute(name) } - /** Whether this value is absent from the database, but has been inferred to likely exist */ - predicate isAbsent() { - this instanceof AbsentModuleObjectInternal - or - this instanceof AbsentModuleAttributeObjectInternal - } + /** Whether this value is absent from the database, but has been inferred to likely exist */ + predicate isAbsent() { + this instanceof AbsentModuleObjectInternal + or + this instanceof AbsentModuleAttributeObjectInternal + } - /** Whether this overrides v. In this context, "overrides" means that this object - * is a named attribute of a some class C and `v` is a named attribute of another - * class S, both attributes having the same name, and S is a super class of C. - */ - predicate overrides(Value v) { - exists(ClassValue my_class, ClassValue other_class, string name | - my_class.declaredAttribute(name) = this and - other_class.declaredAttribute(name) = v and - my_class.getABaseType+() = other_class - ) - } + /** + * Whether this overrides v. In this context, "overrides" means that this object + * is a named attribute of a some class C and `v` is a named attribute of another + * class S, both attributes having the same name, and S is a super class of C. + */ + predicate overrides(Value v) { + exists(ClassValue my_class, ClassValue other_class, string name | + my_class.declaredAttribute(name) = this and + other_class.declaredAttribute(name) = v and + my_class.getABaseType+() = other_class + ) + } - /** Gets the boolean interpretation of this value. - * Could be both `true` and `false`, if we can't determine the result more precisely. - */ - boolean getABooleanValue() { - result = this.(ObjectInternal).booleanValue() - } + /** + * Gets the boolean interpretation of this value. + * Could be both `true` and `false`, if we can't determine the result more precisely. + */ + boolean getABooleanValue() { result = this.(ObjectInternal).booleanValue() } - /** Gets the boolean interpretation of this value, only if we can determine the result precisely. - * The result can be `none()`, but never both `true` and `false`. - */ - boolean getDefiniteBooleanValue() { - result = getABooleanValue() and - not (getABooleanValue() = true and getABooleanValue() = false) - } + /** + * Gets the boolean interpretation of this value, only if we can determine the result precisely. + * The result can be `none()`, but never both `true` and `false`. + */ + boolean getDefiniteBooleanValue() { + result = getABooleanValue() and + not (getABooleanValue() = true and getABooleanValue() = false) + } } -/** Class representing modules in the Python program +/** + * Class representing modules in the Python program * Each `ModuleValue` represents a module object in the Python program. */ class ModuleValue extends Value { + ModuleValue() { this instanceof ModuleObjectInternal } - ModuleValue() { - this instanceof ModuleObjectInternal - } + /** + * Holds if this module "exports" name. + * That is, does it define `name` in `__all__` or is + * `__all__` not defined and `name` a global variable that does not start with "_" + * This is the set of names imported by `from ... import *`. + */ + predicate exports(string name) { PointsTo::moduleExports(this, name) } - /** Holds if this module "exports" name. - * That is, does it define `name` in `__all__` or is - * `__all__` not defined and `name` a global variable that does not start with "_" - * This is the set of names imported by `from ... import *`. - */ - predicate exports(string name) { - PointsTo::moduleExports(this, name) - } + /** Gets the scope for this module, provided that it is a Python module. */ + ModuleScope getScope() { result = this.(ModuleObjectInternal).getSourceModule() } - /** Gets the scope for this module, provided that it is a Python module. */ - ModuleScope getScope() { - result = this.(ModuleObjectInternal).getSourceModule() - } + /** + * Gets the container path for this module. Will be the file for a Python module, + * the folder for a package and no result for a builtin module. + */ + Container getPath() { + result = this.(PackageObjectInternal).getFolder() + or + result = this.(PythonModuleObjectInternal).getSourceModule().getFile() + } - /** Gets the container path for this module. Will be the file for a Python module, - * the folder for a package and no result for a builtin module. - */ - Container getPath() { - result = this.(PackageObjectInternal).getFolder() - or - result = this.(PythonModuleObjectInternal).getSourceModule().getFile() - } + /** + * Whether this module is imported by 'import name'. For example on a linux system, + * the module 'posixpath' is imported as 'os.path' or as 'posixpath' + */ + predicate importedAs(string name) { PointsToInternal::module_imported_as(this, name) } - /** Whether this module is imported by 'import name'. For example on a linux system, - * the module 'posixpath' is imported as 'os.path' or as 'posixpath' */ - predicate importedAs(string name) { - PointsToInternal::module_imported_as(this, name) - } + /** Whether this module is a package. */ + predicate isPackage() { this instanceof PackageObjectInternal } - /** Whether this module is a package. */ - predicate isPackage() { - this instanceof PackageObjectInternal - } - - /** Whether the complete set of names "exported" by this module can be accurately determined */ - predicate hasCompleteExportInfo() { - this.(ModuleObjectInternal).hasCompleteExportInfo() - } - - /** Get a module that this module imports */ - ModuleValue getAnImportedModule() { - result.importedAs(this.getScope().getAnImportedModuleName()) - } + /** Whether the complete set of names "exported" by this module can be accurately determined */ + predicate hasCompleteExportInfo() { this.(ModuleObjectInternal).hasCompleteExportInfo() } + /** Get a module that this module imports */ + ModuleValue getAnImportedModule() { result.importedAs(this.getScope().getAnImportedModuleName()) } } module Module { + /** + * Gets the `ModuleValue` named `name`. + * + * Note that the name used to refer to a module is not + * necessarily its name. For example, + * there are modules referred to by the name `os.path`, + * but that are not named `os.path`, for example the module `posixpath`. + * Such that the following is true: + * `Module::named("os.path").getName() = "posixpath" + */ + ModuleValue named(string name) { + result.getName() = name + or + result = named(name, _) + } - /** Gets the `ModuleValue` named `name`. - * - * Note that the name used to refer to a module is not - * necessarily its name. For example, - * there are modules referred to by the name `os.path`, - * but that are not named `os.path`, for example the module `posixpath`. - * Such that the following is true: - * `Module::named("os.path").getName() = "posixpath" - */ - ModuleValue named(string name) { - result.getName() = name - or - result = named(name, _) - } + /* Prevent runaway recursion when a module has itself as an attribute. */ + private ModuleValue named(string name, int dots) { + dots = 0 and + not name.charAt(_) = "." and + result.getName() = name + or + dots <= 3 and + exists(string modname, string attrname | name = modname + "." + attrname | + result = named(modname, dots - 1).attr(attrname) + ) + } - /* Prevent runaway recursion when a module has itself as an attribute. */ - private ModuleValue named(string name, int dots) { - dots = 0 and not name.charAt(_) = "." and - result.getName() = name - or - dots <= 3 and - exists(string modname, string attrname | - name = modname + "." + attrname | - result = named(modname, dots-1).attr(attrname) - ) - } - - /** Get the `ModuleValue` for the `builtin` module. */ - ModuleValue builtinModule() { - result = TBuiltinModuleObject(Builtin::builtinModule()) - } + /** Get the `ModuleValue` for the `builtin` module. */ + ModuleValue builtinModule() { result = TBuiltinModuleObject(Builtin::builtinModule()) } } module Value { + /** + * Gets the `Value` named `name`. + * If there is at least one '.' in `name`, then the part of + * the name to the left of the rightmost '.' is interpreted as a module name + * and the part after the rightmost '.' as an attribute of that module. + * For example, `Value::named("os.path.join")` is the `Value` representing the + * `join` function of the `os.path` module. + * If there is no '.' in `name`, then the `Value` returned is the builtin + * object of that name. + * For example `Value::named("len")` is the `Value` representing the `len` built-in function. + */ + Value named(string name) { + exists(string modname, string attrname | name = modname + "." + attrname | + result = Module::named(modname).attr(attrname) + ) + or + result = ObjectInternal::builtin(name) + or + name = "None" and result = ObjectInternal::none_() + or + name = "True" and result = TTrue() + or + name = "False" and result = TFalse() + } - /** Gets the `Value` named `name`. - * If there is at least one '.' in `name`, then the part of - * the name to the left of the rightmost '.' is interpreted as a module name - * and the part after the rightmost '.' as an attribute of that module. - * For example, `Value::named("os.path.join")` is the `Value` representing the - * `join` function of the `os.path` module. - * If there is no '.' in `name`, then the `Value` returned is the builtin - * object of that name. - * For example `Value::named("len")` is the `Value` representing the `len` built-in function. - */ - Value named(string name) { - exists(string modname, string attrname | - name = modname + "." + attrname | - result = Module::named(modname).attr(attrname) - ) - or - result = ObjectInternal::builtin(name) - or - name = "None" and result = ObjectInternal::none_() - or - name = "True" and result = TTrue() - or - name = "False" and result = TFalse() - } + /** + * Gets the `NumericValue` for the integer constant `i`, if it exists. + * There will be no `NumericValue` for most integers, but the following are + * guaranteed to exist: + * * From zero to 511 inclusive. + * * All powers of 2 (up to 2**30) + * * Any integer explicitly mentioned in the source program. + */ + NumericValue forInt(int i) { result.(IntObjectInternal).intValue() = i } - /** Gets the `NumericValue` for the integer constant `i`, if it exists. - * There will be no `NumericValue` for most integers, but the following are - * guaranteed to exist: - * * From zero to 511 inclusive. - * * All powers of 2 (up to 2**30) - * * Any integer explicitly mentioned in the source program. - */ - NumericValue forInt(int i) { - result.(IntObjectInternal).intValue() = i - } + /** + * Gets the `Value` for the bytes constant `bytes`, if it exists. + * There will be no `Value` for most byte strings, unless it is explicitly + * declared in the source program. + */ + StringValue forBytes(string bytes) { result.(BytesObjectInternal).strValue() = bytes } - /** Gets the `Value` for the bytes constant `bytes`, if it exists. - * There will be no `Value` for most byte strings, unless it is explicitly - * declared in the source program. - */ - StringValue forBytes(string bytes) { - result.(BytesObjectInternal).strValue() = bytes - } + /** + * Gets the `Value` for the unicode constant `text`, if it exists. + * There will be no `Value` for most text strings, unless it is explicitly + * declared in the source program. + */ + StringValue forUnicode(string text) { result.(UnicodeObjectInternal).strValue() = text } - /** Gets the `Value` for the unicode constant `text`, if it exists. - * There will be no `Value` for most text strings, unless it is explicitly - * declared in the source program. - */ - StringValue forUnicode(string text) { - result.(UnicodeObjectInternal).strValue() = text - } + /** + * Gets a `Value` for the string `text`. May be a bytes or unicode string for Python 2. + * There will be no `Value` for most strings, unless it is explicitly + * declared in the source program. + */ + StringValue forString(string text) { + result.(UnicodeObjectInternal).strValue() = text + or + major_version() = 2 and + result.(BytesObjectInternal).strValue() = text + } - /** Gets a `Value` for the string `text`. May be a bytes or unicode string for Python 2. - * There will be no `Value` for most strings, unless it is explicitly - * declared in the source program. - */ - StringValue forString(string text) { - result.(UnicodeObjectInternal).strValue() = text - or - major_version() = 2 and - result.(BytesObjectInternal).strValue() = text - } - - /** Gets the `Value` for the bool constant `b`. */ - Value forBool(boolean b) { - b = true and result = TTrue() - or - b = false and result = TFalse() - } - - /** Gets the `Value` for `None`. */ - Value none_() { - result = ObjectInternal::none_() - } + /** Gets the `Value` for the bool constant `b`. */ + Value forBool(boolean b) { + b = true and result = TTrue() + or + b = false and result = TFalse() + } + /** Gets the `Value` for `None`. */ + Value none_() { result = ObjectInternal::none_() } } -/** Class representing callables in the Python program +/** + * Class representing callables in the Python program * Callables include Python functions, built-in functions and bound-methods, * but not classes. */ class CallableValue extends Value { + CallableValue() { this instanceof CallableObjectInternal } - CallableValue() { - this instanceof CallableObjectInternal - } + /** + * Holds if this callable never returns once called. + * For example, `sys.exit` + */ + predicate neverReturns() { this.(CallableObjectInternal).neverReturns() } - /** Holds if this callable never returns once called. - * For example, `sys.exit` - */ - predicate neverReturns() { - this.(CallableObjectInternal).neverReturns() - } + /** Gets the scope for this function, provided that it is a Python function. */ + FunctionScope getScope() { result = this.(PythonFunctionObjectInternal).getScope() } - /** Gets the scope for this function, provided that it is a Python function. */ - FunctionScope getScope() { - result = this.(PythonFunctionObjectInternal).getScope() - } + /** Gets the `n`th parameter node of this callable. */ + NameNode getParameter(int n) { result = this.(CallableObjectInternal).getParameter(n) } - /** Gets the `n`th parameter node of this callable. */ - NameNode getParameter(int n) { - result = this.(CallableObjectInternal).getParameter(n) - } + /** Gets the `name`d parameter node of this callable. */ + NameNode getParameterByName(string name) { + result = this.(CallableObjectInternal).getParameterByName(name) + } - /** Gets the `name`d parameter node of this callable. */ - NameNode getParameterByName(string name) { - result = this.(CallableObjectInternal).getParameterByName(name) - } - - /** Gets the argument corresponding to the `n'th parameter node of this callable. */ - cached ControlFlowNode getArgumentForCall(CallNode call, int n) { - exists(ObjectInternal called, int offset | - PointsToInternal::pointsTo(call.getFunction(), _, called, _) and - called.functionAndOffset(this, offset) - | - call.getArg(n-offset) = result - or - exists(string name | call.getArgByName(name) = result and this.(PythonFunctionObjectInternal).getScope().getArg(n+offset).getName() = name) - or - called instanceof BoundMethodObjectInternal and - offset = 1 and n = 0 and result = call.getFunction().(AttrNode).getObject() - ) - } - - - /** Gets the argument corresponding to the `name`d parameter node of this callable. */ - cached ControlFlowNode getNamedArgumentForCall(CallNode call, string name) { - exists(CallableObjectInternal called, int offset | - PointsToInternal::pointsTo(call.getFunction(), _, called, _) and - called.functionAndOffset(this, offset) - | - exists(int n | - call.getArg(n) = result and - this.(PythonFunctionObjectInternal).getScope().getArg(n+offset).getName() = name - ) - or - call.getArgByName(name) = result and - exists(this.(PythonFunctionObjectInternal).getScope().getArgByName(name)) - or - called instanceof BoundMethodObjectInternal and - offset = 1 and name = "self" and result = call.getFunction().(AttrNode).getObject() - ) - } + /** Gets the argument corresponding to the `n'th parameter node of this callable. */ + cached + ControlFlowNode getArgumentForCall(CallNode call, int n) { + exists(ObjectInternal called, int offset | + PointsToInternal::pointsTo(call.getFunction(), _, called, _) and + called.functionAndOffset(this, offset) + | + call.getArg(n - offset) = result + or + exists(string name | + call.getArgByName(name) = result and + this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name + ) + or + called instanceof BoundMethodObjectInternal and + offset = 1 and + n = 0 and + result = call.getFunction().(AttrNode).getObject() + ) + } + /** Gets the argument corresponding to the `name`d parameter node of this callable. */ + cached + ControlFlowNode getNamedArgumentForCall(CallNode call, string name) { + exists(CallableObjectInternal called, int offset | + PointsToInternal::pointsTo(call.getFunction(), _, called, _) and + called.functionAndOffset(this, offset) + | + exists(int n | + call.getArg(n) = result and + this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name + ) + or + call.getArgByName(name) = result and + exists(this.(PythonFunctionObjectInternal).getScope().getArgByName(name)) + or + called instanceof BoundMethodObjectInternal and + offset = 1 and + name = "self" and + result = call.getFunction().(AttrNode).getObject() + ) + } } -/** Class representing classes in the Python program, both Python and built-in. +/** + * Class representing classes in the Python program, both Python and built-in. */ class ClassValue extends Value { + ClassValue() { this.(ObjectInternal).isClass() = true } - ClassValue() { - this.(ObjectInternal).isClass() = true - } + /** Gets an improper super type of this class. */ + ClassValue getASuperType() { result = this.getABaseType*() } - /** Gets an improper super type of this class. */ - ClassValue getASuperType() { - result = this.getABaseType*() - } + /** + * Looks up the attribute `name` on this class. + * Note that this may be different from `this.attr(name)`. + * For example given the class: + * ```class C: + * @classmethod + * def f(cls): pass + * ``` + * `this.lookup("f")` is equivalent to `C.__dict__['f']`, which is the class-method + * whereas + * `this.attr("f") is equivalent to `C.f`, which is a bound-method. + */ + Value lookup(string name) { this.(ClassObjectInternal).lookup(name, result, _) } - /** Looks up the attribute `name` on this class. - * Note that this may be different from `this.attr(name)`. - * For example given the class: - * ```class C: - * @classmethod - * def f(cls): pass - * ``` - * `this.lookup("f")` is equivalent to `C.__dict__['f']`, which is the class-method - * whereas - * `this.attr("f") is equivalent to `C.f`, which is a bound-method. - */ - Value lookup(string name) { - this.(ClassObjectInternal).lookup(name, result, _) - } + predicate isCallable() { this.(ClassObjectInternal).lookup("__call__", _, _) } - predicate isCallable() { - this.(ClassObjectInternal).lookup("__call__", _, _) - } + /** Holds if this class is an iterable. */ + predicate isIterable() { + this.hasAttribute("__iter__") + or + this.hasAttribute("__aiter__") + or + this.hasAttribute("__getitem__") + } - /** Holds if this class is an iterable. */ - predicate isIterable() { - this.hasAttribute("__iter__") - or - this.hasAttribute("__aiter__") - or - this.hasAttribute("__getitem__") - } + /** Holds if this class is a descriptor. */ + predicate isDescriptorType() { this.hasAttribute("__get__") } - /** Holds if this class is a descriptor. */ - predicate isDescriptorType() { - this.hasAttribute("__get__") - } + /** + * Gets the qualified name for this class. + * Should return the same name as the `__qualname__` attribute on classes in Python 3. + */ + string getQualifiedName() { + result = this.(ClassObjectInternal).getBuiltin().getName() + or + result = this.(PythonClassObjectInternal).getScope().getQualifiedName() + } - /** Gets the qualified name for this class. - * Should return the same name as the `__qualname__` attribute on classes in Python 3. - */ - string getQualifiedName() { - result = this.(ClassObjectInternal).getBuiltin().getName() - or - result = this.(PythonClassObjectInternal).getScope().getQualifiedName() - } + /** Gets the MRO for this class */ + MRO getMro() { result = Types::getMro(this) } - /** Gets the MRO for this class */ - MRO getMro() { - result = Types::getMro(this) - } + predicate failedInference(string reason) { Types::failedInference(this, reason) } - predicate failedInference(string reason) { - Types::failedInference(this, reason) - } + /** Gets the nth immediate base type of this class. */ + ClassValue getBaseType(int n) { result = Types::getBase(this, n) } - /** Gets the nth immediate base type of this class. */ - ClassValue getBaseType(int n) { - result = Types::getBase(this, n) - } + /** Gets an immediate base type of this class. */ + ClassValue getABaseType() { result = Types::getBase(this, _) } - /** Gets an immediate base type of this class. */ - ClassValue getABaseType() { - result = Types::getBase(this, _) - } + /** + * Holds if this class is a new style class. + * A new style class is one that implicitly or explicitly inherits from `object`. + */ + predicate isNewStyle() { Types::isNewStyle(this) } - /** Holds if this class is a new style class. - A new style class is one that implicitly or explicitly inherits from `object`. */ - predicate isNewStyle() { - Types::isNewStyle(this) - } + /** + * Holds if this class is an old style class. + * An old style class is one that does not inherit from `object`. + */ + predicate isOldStyle() { Types::isOldStyle(this) } - /** Holds if this class is an old style class. - An old style class is one that does not inherit from `object`. */ - predicate isOldStyle() { - Types::isOldStyle(this) - } + /** Gets the scope associated with this class, if it is not a builtin class */ + ClassScope getScope() { result = this.(PythonClassObjectInternal).getScope() } - /** Gets the scope associated with this class, if it is not a builtin class */ - ClassScope getScope() { - result = this.(PythonClassObjectInternal).getScope() - } + /** Gets the attribute declared in this class */ + Value declaredAttribute(string name) { Types::declaredAttribute(this, name, result, _) } - /** Gets the attribute declared in this class */ - Value declaredAttribute(string name) { - Types::declaredAttribute(this, name, result, _) - } - - /** Holds if this class has the attribute `name`, including - * attributes declared by super classes. - */ - predicate hasAttribute(string name) { - this.getMro().declares(name) - } - - /** Holds if this class declares the attribute `name`, - * *not* including attributes declared by super classes. - */ - predicate declaresAttribute(string name) { - this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name) - } + /** + * Holds if this class has the attribute `name`, including + * attributes declared by super classes. + */ + predicate hasAttribute(string name) { this.getMro().declares(name) } + /** + * Holds if this class declares the attribute `name`, + * *not* including attributes declared by super classes. + */ + predicate declaresAttribute(string name) { + this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name) + } } - -/** Class representing functions in the Python program, both Python and built-in. +/** + * Class representing functions in the Python program, both Python and built-in. * Note that this does not include other callables such as bound-methods. */ abstract class FunctionValue extends CallableValue { + abstract string getQualifiedName(); - abstract string getQualifiedName(); + /** Gets the minimum number of parameters that can be correctly passed to this function */ + abstract int minParameters(); - /** Gets the minimum number of parameters that can be correctly passed to this function */ - abstract int minParameters(); + /** Gets the maximum number of parameters that can be correctly passed to this function */ + abstract int maxParameters(); - /** Gets the maximum number of parameters that can be correctly passed to this function */ - abstract int maxParameters(); + predicate isOverridingMethod() { exists(Value f | this.overrides(f)) } - predicate isOverridingMethod() { - exists(Value f | this.overrides(f)) - } - - predicate isOverriddenMethod() { - exists(Value f | f.overrides(this)) - } + predicate isOverriddenMethod() { exists(Value f | f.overrides(this)) } } /** Class representing Python functions */ class PythonFunctionValue extends FunctionValue { + PythonFunctionValue() { this instanceof PythonFunctionObjectInternal } - PythonFunctionValue() { - this instanceof PythonFunctionObjectInternal - } + override string getQualifiedName() { + result = this.(PythonFunctionObjectInternal).getScope().getQualifiedName() + } - override string getQualifiedName() { - result = this.(PythonFunctionObjectInternal).getScope().getQualifiedName() - } + override int minParameters() { + exists(Function f | + f = this.getScope() and + result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault()) + ) + } - override int minParameters() { - exists(Function f | - f = this.getScope() and - result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault()) - ) - } - - override int maxParameters() { - exists(Function f | - f = this.getScope() and - if exists(f.getVararg()) then - result = 2147483647 // INT_MAX - else - result = count(f.getAnArg()) - ) - } - - /** Gets a control flow node corresponding to a return statement in this function */ - ControlFlowNode getAReturnedNode() { - result = this.getScope().getAReturnValueFlowNode() - } + override int maxParameters() { + exists(Function f | + f = this.getScope() and + if exists(f.getVararg()) + then result = 2147483647 // INT_MAX + else result = count(f.getAnArg()) + ) + } + /** Gets a control flow node corresponding to a return statement in this function */ + ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() } } /** Class representing builtin functions, such as `len` or `print` */ class BuiltinFunctionValue extends FunctionValue { + BuiltinFunctionValue() { this instanceof BuiltinFunctionObjectInternal } - BuiltinFunctionValue() { - this instanceof BuiltinFunctionObjectInternal - } + override string getQualifiedName() { result = this.(BuiltinFunctionObjectInternal).getName() } - override string getQualifiedName() { - result = this.(BuiltinFunctionObjectInternal).getName() - } + override int minParameters() { none() } - override int minParameters() { - none() - } - - override int maxParameters() { - none() - } + override int maxParameters() { none() } } /** Class representing builtin methods, such as `list.append` or `set.add` */ class BuiltinMethodValue extends FunctionValue { + BuiltinMethodValue() { this instanceof BuiltinMethodObjectInternal } - BuiltinMethodValue() { - this instanceof BuiltinMethodObjectInternal - } + override string getQualifiedName() { + exists(Builtin cls | + cls.isClass() and + cls.getMember(_) = this.(BuiltinMethodObjectInternal).getBuiltin() and + result = cls.getName() + "." + this.getName() + ) + } - override string getQualifiedName() { - exists(Builtin cls | - cls.isClass() and - cls.getMember(_) = this.(BuiltinMethodObjectInternal).getBuiltin() and - result = cls.getName() + "." + this.getName() - ) - } - - override int minParameters() { - none() - } - - override int maxParameters() { - none() - } + override int minParameters() { none() } + override int maxParameters() { none() } } -/** A class representing sequence objects with a length and tracked items. +/** + * A class representing sequence objects with a length and tracked items. */ class SequenceValue extends Value { + SequenceValue() { this instanceof SequenceObjectInternal } - SequenceValue() { - this instanceof SequenceObjectInternal - } - - Value getItem(int n) { - result = this.(SequenceObjectInternal).getItem(n) - } - - int length() { - result = this.(SequenceObjectInternal).length() - } + Value getItem(int n) { result = this.(SequenceObjectInternal).getItem(n) } + int length() { result = this.(SequenceObjectInternal).length() } } /** A class representing tuple objects */ class TupleValue extends SequenceValue { - - TupleValue() { - this instanceof TupleObjectInternal - } - + TupleValue() { this instanceof TupleObjectInternal } } -/** A class representing strings, either present in the source as a literal, or -in a builtin as a value. */ - +/** + * A class representing strings, either present in the source as a literal, or + *in a builtin as a value. + */ class StringValue extends Value { - StringValue() { - this instanceof BytesObjectInternal or - this instanceof UnicodeObjectInternal - } + StringValue() { + this instanceof BytesObjectInternal or + this instanceof UnicodeObjectInternal + } - string getText() { - result = this.(BytesObjectInternal).strValue() - or - result = this.(UnicodeObjectInternal).strValue() - } + string getText() { + result = this.(BytesObjectInternal).strValue() + or + result = this.(UnicodeObjectInternal).strValue() + } } -/** A class representing numbers (ints and floats), either present in the source as a literal, +/** + * A class representing numbers (ints and floats), either present in the source as a literal, * or in a builtin as a value. */ class NumericValue extends Value { - NumericValue() { - this instanceof IntObjectInternal or - this instanceof FloatObjectInternal - } + NumericValue() { + this instanceof IntObjectInternal or + this instanceof FloatObjectInternal + } - /** Gets the integer-value if it is a constant integer, and it fits in a QL int */ - int getIntValue() { - result = this.(IntObjectInternal).intValue() - } + /** Gets the integer-value if it is a constant integer, and it fits in a QL int */ + int getIntValue() { result = this.(IntObjectInternal).intValue() } - /** Gets the float-value if it is a constant float */ - int getFloatValue() { - result = this.(FloatObjectInternal).floatValue() - } + /** Gets the float-value if it is a constant float */ + int getFloatValue() { result = this.(FloatObjectInternal).floatValue() } } /** A method-resolution-order sequence of classes */ class MRO extends TClassList { + string toString() { result = this.(ClassList).toString() } - string toString() { - result = this.(ClassList).toString() - } + /** Gets the `n`th class in this MRO */ + ClassValue getItem(int n) { result = this.(ClassList).getItem(n) } - /** Gets the `n`th class in this MRO */ - ClassValue getItem(int n) { - result = this.(ClassList).getItem(n) - } + /** Holds if any class in this MRO declares the attribute `name` */ + predicate declares(string name) { this.(ClassList).declares(name) } - /** Holds if any class in this MRO declares the attribute `name` */ - predicate declares(string name) { - this.(ClassList).declares(name) - } + /** Gets the length of this MRO */ + int length() { result = this.(ClassList).length() } - /** Gets the length of this MRO */ - int length() { - result = this.(ClassList).length() - } + /** Holds if this MRO contains `cls` */ + predicate contains(ClassValue cls) { this.(ClassList).contains(cls) } - /** Holds if this MRO contains `cls` */ - predicate contains(ClassValue cls) { - this.(ClassList).contains(cls) - } - - /** Gets the value from scanning for the attribute `name` in this MRO. */ - Value lookup(string name) { - this.(ClassList).lookup(name, result, _) - } - - /** Gets the MRO formed by removing all classes before `cls` - * from this MRO. - */ - MRO startingAt(ClassValue cls) { - result = this.(ClassList).startingAt(cls) - } + /** Gets the value from scanning for the attribute `name` in this MRO. */ + Value lookup(string name) { this.(ClassList).lookup(name, result, _) } + /** + * Gets the MRO formed by removing all classes before `cls` + * from this MRO. + */ + MRO startingAt(ClassValue cls) { result = this.(ClassList).startingAt(cls) } } - module ClassValue { + /** Get the `ClassValue` for the `bool` class. */ + ClassValue bool() { result = TBuiltinClassObject(Builtin::special("bool")) } - /** Get the `ClassValue` for the `bool` class. */ - ClassValue bool() { - result = TBuiltinClassObject(Builtin::special("bool")) - } - - /** Get the `ClassValue` for the `tuple` class. */ - ClassValue tuple() { - result = TBuiltinClassObject(Builtin::special("tuple")) - } - - /** Get the `ClassValue` for the `list` class. */ - ClassValue list() { - result = TBuiltinClassObject(Builtin::special("list")) - } - - /** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */ - ClassValue range() { - major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange")) - or - major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range")) - } - - /** Get the `ClassValue` for the `dict` class. */ - ClassValue dict() { - result = TBuiltinClassObject(Builtin::special("dict")) - } - - /** Get the `ClassValue` for the `set` class. */ - ClassValue set() { - result = TBuiltinClassObject(Builtin::special("set")) - } - - /** Get the `ClassValue` for the `object` class. */ - ClassValue object() { - result = TBuiltinClassObject(Builtin::special("object")) - } + /** Get the `ClassValue` for the `tuple` class. */ + ClassValue tuple() { result = TBuiltinClassObject(Builtin::special("tuple")) } - /** Get the `ClassValue` for the `int` class. */ - ClassValue int_() { - result = TBuiltinClassObject(Builtin::special("int")) - } - - /** Get the `ClassValue` for the `long` class. */ - ClassValue long() { - result = TBuiltinClassObject(Builtin::special("long")) - } + /** Get the `ClassValue` for the `list` class. */ + ClassValue list() { result = TBuiltinClassObject(Builtin::special("list")) } - /** Get the `ClassValue` for the `float` class. */ - ClassValue float_() { - result = TBuiltinClassObject(Builtin::special("float")) - } - - /** Get the `ClassValue` for the `complex` class. */ - ClassValue complex() { - result = TBuiltinClassObject(Builtin::special("complex")) - } + /** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */ + ClassValue range() { + major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange")) + or + major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range")) + } - /** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */ - ClassValue bytes() { - result = TBuiltinClassObject(Builtin::special("bytes")) - } + /** Get the `ClassValue` for the `dict` class. */ + ClassValue dict() { result = TBuiltinClassObject(Builtin::special("dict")) } - /** Get the `ClassValue` for the class of unicode strings. - * `str` in Python 3 and `unicode` in Python 2. */ - ClassValue unicode() { - result = TBuiltinClassObject(Builtin::special("unicode")) - } + /** Get the `ClassValue` for the `set` class. */ + ClassValue set() { result = TBuiltinClassObject(Builtin::special("set")) } - /** Get the `ClassValue` for the `str` class. This is `bytes` in Python 2, - and `str` in Python 3. */ - ClassValue str() { - if major_version() = 2 then - result = bytes() - else - result = unicode() - } - - /** Get the `ClassValue` for the `property` class. */ - ClassValue property() { - result = TBuiltinClassObject(Builtin::special("property")) - } - - /** Get the `ClassValue` for the class of Python functions. */ - ClassValue functionType() { - result = TBuiltinClassObject(Builtin::special("FunctionType")) - } + /** Get the `ClassValue` for the `object` class. */ + ClassValue object() { result = TBuiltinClassObject(Builtin::special("object")) } - /** Get the `ClassValue` for the class of builtin functions. */ - ClassValue builtinFunction() { - result = Value::named("len").getClass() - } - - /** Get the `ClassValue` for the `generatorType` class. */ - ClassValue generator() { - result = TBuiltinClassObject(Builtin::special("generator")) - } - - /** Get the `ClassValue` for the `type` class. */ - ClassValue type() { - result = TType() - } - - /** Get the `ClassValue` for `ClassType`. */ - ClassValue classType() { - result = TBuiltinClassObject(Builtin::special("ClassType")) - } - - /** Get the `ClassValue` for `InstanceType`. */ - ClassValue instanceType() { - result = TBuiltinClassObject(Builtin::special("InstanceType")) - } - - /** Get the `ClassValue` for `super`. */ - ClassValue super_() { - result = TBuiltinClassObject(Builtin::special("super")) - } - - /** Get the `ClassValue` for the `classmethod` class. */ - ClassValue classmethod() { - result = TBuiltinClassObject(Builtin::special("ClassMethod")) - } + /** Get the `ClassValue` for the `int` class. */ + ClassValue int_() { result = TBuiltinClassObject(Builtin::special("int")) } - /** Get the `ClassValue` for the `staticmethod` class. */ - ClassValue staticmethod() { - result = TBuiltinClassObject(Builtin::special("StaticMethod")) - } - - /** Get the `ClassValue` for the `MethodType` class. */ - pragma [noinline] - ClassValue methodType() { - result = TBuiltinClassObject(Builtin::special("MethodType")) - } - - /** Get the `ClassValue` for the `MethodDescriptorType` class. */ - ClassValue methodDescriptorType() { - result = TBuiltinClassObject(Builtin::special("MethodDescriptorType")) - } - - /** Get the `ClassValue` for the `GetSetDescriptorType` class. */ - ClassValue getSetDescriptorType() { - result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType")) - } - - /** Get the `ClassValue` for the `StopIteration` class. */ - ClassValue stopIteration() { - result = TBuiltinClassObject(Builtin::special("StopIteration")) - } + /** Get the `ClassValue` for the `long` class. */ + ClassValue long() { result = TBuiltinClassObject(Builtin::special("long")) } - /** Get the `ClassValue` for the class of modules. */ - ClassValue module_() { - result = TBuiltinClassObject(Builtin::special("ModuleType")) - } + /** Get the `ClassValue` for the `float` class. */ + ClassValue float_() { result = TBuiltinClassObject(Builtin::special("float")) } - /** Get the `ClassValue` for the `Exception` class. */ - ClassValue exception() { - result = TBuiltinClassObject(Builtin::special("Exception")) - } - - /** Get the `ClassValue` for the `BaseException` class. */ - ClassValue baseException() { - result = TBuiltinClassObject(Builtin::special("BaseException")) - } - - /** Get the `ClassValue` for the `NoneType` class. */ - ClassValue nonetype() { - result = TBuiltinClassObject(Builtin::special("NoneType")) - } - - /** Get the `ClassValue` for the `TypeError` class */ - ClassValue typeError() { - result = TBuiltinClassObject(Builtin::special("TypeError")) - } + /** Get the `ClassValue` for the `complex` class. */ + ClassValue complex() { result = TBuiltinClassObject(Builtin::special("complex")) } - /** Get the `ClassValue` for the `NameError` class. */ - ClassValue nameError() { - result = TBuiltinClassObject(Builtin::builtin("NameError")) - } - - /** Get the `ClassValue` for the `AttributeError` class. */ - ClassValue attributeError() { - result = TBuiltinClassObject(Builtin::builtin("AttributeError")) - } - - /** Get the `ClassValue` for the `KeyError` class. */ - ClassValue keyError() { - result = TBuiltinClassObject(Builtin::builtin("KeyError")) - } - - /** Get the `ClassValue` for the `IOError` class. */ - ClassValue ioError() { - result = TBuiltinClassObject(Builtin::builtin("IOError")) - } - - /** Get the `ClassValue` for the `NotImplementedError` class. */ - ClassValue notImplementedError() { - result = TBuiltinClassObject(Builtin::builtin("NotImplementedError")) - } + /** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */ + ClassValue bytes() { result = TBuiltinClassObject(Builtin::special("bytes")) } - /** Get the `ClassValue` for the `ImportError` class. */ - ClassValue importError() { - result = TBuiltinClassObject(Builtin::builtin("ImportError")) - } + /** + * Get the `ClassValue` for the class of unicode strings. + * `str` in Python 3 and `unicode` in Python 2. + */ + ClassValue unicode() { result = TBuiltinClassObject(Builtin::special("unicode")) } + /** + * Get the `ClassValue` for the `str` class. This is `bytes` in Python 2, + * and `str` in Python 3. + */ + ClassValue str() { if major_version() = 2 then result = bytes() else result = unicode() } + + /** Get the `ClassValue` for the `property` class. */ + ClassValue property() { result = TBuiltinClassObject(Builtin::special("property")) } + + /** Get the `ClassValue` for the class of Python functions. */ + ClassValue functionType() { result = TBuiltinClassObject(Builtin::special("FunctionType")) } + + /** Get the `ClassValue` for the class of builtin functions. */ + ClassValue builtinFunction() { result = Value::named("len").getClass() } + + /** Get the `ClassValue` for the `generatorType` class. */ + ClassValue generator() { result = TBuiltinClassObject(Builtin::special("generator")) } + + /** Get the `ClassValue` for the `type` class. */ + ClassValue type() { result = TType() } + + /** Get the `ClassValue` for `ClassType`. */ + ClassValue classType() { result = TBuiltinClassObject(Builtin::special("ClassType")) } + + /** Get the `ClassValue` for `InstanceType`. */ + ClassValue instanceType() { result = TBuiltinClassObject(Builtin::special("InstanceType")) } + + /** Get the `ClassValue` for `super`. */ + ClassValue super_() { result = TBuiltinClassObject(Builtin::special("super")) } + + /** Get the `ClassValue` for the `classmethod` class. */ + ClassValue classmethod() { result = TBuiltinClassObject(Builtin::special("ClassMethod")) } + + /** Get the `ClassValue` for the `staticmethod` class. */ + ClassValue staticmethod() { result = TBuiltinClassObject(Builtin::special("StaticMethod")) } + + /** Get the `ClassValue` for the `MethodType` class. */ + pragma[noinline] + ClassValue methodType() { result = TBuiltinClassObject(Builtin::special("MethodType")) } + + /** Get the `ClassValue` for the `MethodDescriptorType` class. */ + ClassValue methodDescriptorType() { + result = TBuiltinClassObject(Builtin::special("MethodDescriptorType")) + } + + /** Get the `ClassValue` for the `GetSetDescriptorType` class. */ + ClassValue getSetDescriptorType() { + result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType")) + } + + /** Get the `ClassValue` for the `StopIteration` class. */ + ClassValue stopIteration() { result = TBuiltinClassObject(Builtin::special("StopIteration")) } + + /** Get the `ClassValue` for the class of modules. */ + ClassValue module_() { result = TBuiltinClassObject(Builtin::special("ModuleType")) } + + /** Get the `ClassValue` for the `Exception` class. */ + ClassValue exception() { result = TBuiltinClassObject(Builtin::special("Exception")) } + + /** Get the `ClassValue` for the `BaseException` class. */ + ClassValue baseException() { result = TBuiltinClassObject(Builtin::special("BaseException")) } + + /** Get the `ClassValue` for the `NoneType` class. */ + ClassValue nonetype() { result = TBuiltinClassObject(Builtin::special("NoneType")) } + + /** Get the `ClassValue` for the `TypeError` class */ + ClassValue typeError() { result = TBuiltinClassObject(Builtin::special("TypeError")) } + + /** Get the `ClassValue` for the `NameError` class. */ + ClassValue nameError() { result = TBuiltinClassObject(Builtin::builtin("NameError")) } + + /** Get the `ClassValue` for the `AttributeError` class. */ + ClassValue attributeError() { result = TBuiltinClassObject(Builtin::builtin("AttributeError")) } + + /** Get the `ClassValue` for the `KeyError` class. */ + ClassValue keyError() { result = TBuiltinClassObject(Builtin::builtin("KeyError")) } + + /** Get the `ClassValue` for the `IOError` class. */ + ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) } + + /** Get the `ClassValue` for the `NotImplementedError` class. */ + ClassValue notImplementedError() { + result = TBuiltinClassObject(Builtin::builtin("NotImplementedError")) + } + + /** Get the `ClassValue` for the `ImportError` class. */ + ClassValue importError() { result = TBuiltinClassObject(Builtin::builtin("ImportError")) } } From 376638e9c086c05c29b290a0ad71ed41b5e2a71d Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 20 Feb 2020 16:18:54 -0800 Subject: [PATCH 062/236] Move query over to Rasmus's API for NumericValue --- python/ql/src/Expressions/TruncatedDivision.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Expressions/TruncatedDivision.ql b/python/ql/src/Expressions/TruncatedDivision.ql index ee6378534a4..471b5244de0 100644 --- a/python/ql/src/Expressions/TruncatedDivision.ql +++ b/python/ql/src/Expressions/TruncatedDivision.ql @@ -26,7 +26,7 @@ where and bin.getRight().pointsTo(rval, right) and rval.getClass() = ClassValue::int_() // Ignore instances where integer division leaves no remainder - and not lval.(NumericValue).intValue() % rval.(NumericValue).intValue() = 0 + and not lval.(NumericValue).getIntValue() % rval.(NumericValue).getIntValue() = 0 and not bin.getNode().getEnclosingModule().hasFromFuture("division") // Filter out results wrapped in `int(...)` and not exists(CallNode c | From 2f3ea10cf8526f52237dca7f81d89b85fab6382f Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 20 Feb 2020 16:31:58 -0800 Subject: [PATCH 063/236] Move the query and examples over to 2/query-tests --- .../Expressions/TruncatedDivision.py | 46 +++++++++++++++++ .../general/TruncatedDivision.expected | 1 - .../general/TruncatedDivision.qlref | 1 - .../Expressions/general/expressions_test.py | 49 ------------------- 4 files changed, 46 insertions(+), 51 deletions(-) create mode 100644 python/ql/test/2/query-tests/Expressions/TruncatedDivision.py delete mode 100644 python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected delete mode 100644 python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.py b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.py new file mode 100644 index 00000000000..ed2df62b68d --- /dev/null +++ b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.py @@ -0,0 +1,46 @@ +#### TruncatedDivision.ql + +# NOTE: The following test case will only work under Python 2. + +# NOTE: While there are other files that have the same matching examples, this +# example file tries to explain the motivation of each example. Hopefully it's +# fine to have multiple such files. + +# Truncated division occurs when two integers are divided. This causes the +# fractional part, if there is one, to be discared. So for example, `2 / 3` will +# evaluate to `0` instead of `0.666...`. + +def truncated_division(): + + def average(l): + return sum(l) / len(l) + + + + ## Negative Cases + + # This case is good, and is a minimal obvious case that should be good. It + # SHOULD NOT be found by the query. + print(3.0 / 2.0) + + # This case is good, because the sum is `3.0`, which is a float, and will not + # truncate. This case SHOULD NOT be found by the query. + print(average([1.0, 2.0])) + + + + ## Positive Cases + + # This case is bad, and is a minimal obvious case that should be bad. It + # SHOULD be found by the query. + print(3 / 2) + + # This case is bad, because the sum is `3`, which is an integer, and will + # truncate when divided by the length `2`. This case SHOULD be found by the + # query. + # + # NOTE (2020-02-20): + # The current version of the Value/pointsTo API doesn't permit this detection, + # unfortunately, but we preserve this example in the hopes that future + # versions will catch it. That will necessitate changing the expected results. + print(average([1,2])) diff --git a/python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected b/python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected deleted file mode 100644 index 3707d6bc6fa..00000000000 --- a/python/ql/test/query-tests/Expressions/general/TruncatedDivision.expected +++ /dev/null @@ -1 +0,0 @@ -| expressions_test.py:288:9:288:13 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | expressions_test.py:288:9:288:9 | expressions_test.py:288 | left | expressions_test.py:288:13:288:13 | expressions_test.py:288 | right | diff --git a/python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref b/python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref deleted file mode 100644 index 0bc5573c72f..00000000000 --- a/python/ql/test/query-tests/Expressions/general/TruncatedDivision.qlref +++ /dev/null @@ -1 +0,0 @@ -Expressions/TruncatedDivision.ql \ No newline at end of file diff --git a/python/ql/test/query-tests/Expressions/general/expressions_test.py b/python/ql/test/query-tests/Expressions/general/expressions_test.py index 3de3e8b1639..1d802f1bfd6 100644 --- a/python/ql/test/query-tests/Expressions/general/expressions_test.py +++ b/python/ql/test/query-tests/Expressions/general/expressions_test.py @@ -242,52 +242,3 @@ from types import MappingProxyType def mpt_arg(d=MappingProxyType({})): return 1 in d - - - - - - - - - - - - -#### TruncatedDivision.ql - -# NOTE: The following test case will only work under Python 2. - -# Truncated division occurs when two integers are divided. This causes the -# fractional part, if there is one, to be discared. So for example, `2 / 3` will -# evaluate to `0` instead of `0.666...`. - -def truncated_division(): - - def average(l): - return sum(l) / len(l) - - - - ## Negative Cases - - # This case is good, and is a minimal obvious case that should be good. It - # SHOULD NOT be found by the query. - print(3.0 / 2.0) - - # This case is good, because the sum is `3.0`, which is a float, and will not - # truncate. This case SHOULD NOT be found by the query. - print(average([1.0, 2.0])) - - - - ## Positive Cases - - # This case is bad, and is a minimal obvious case that should be bad. It - # SHOULD be found by the query. - print(3 / 2) - - # This case is bad, because the sum is `3`, which is an integer, and will - # truncate when divided by the length `2`. This case SHOULD be found by the - # query. - print(average([1,2])) From 210387a8bebeb618c1f8790b65ad55296c6f2c98 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 20 Feb 2020 17:32:42 -0800 Subject: [PATCH 064/236] Adds bulk of modernizations --- python/ql/src/Expressions/IsComparisons.qll | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index d310ed6782a..efd7effaf81 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -35,7 +35,7 @@ predicate invalid_to_use_is_portably(ClassValue c) { } predicate simple_constant(ControlFlowNode f) { - exists(Object obj | f.refersTo(obj) | obj = theTrueObject() or obj = theFalseObject() or obj = theNoneObject()) + exists(Value val | f.pointsTo(val) | val = Value::named("True") or val = Value::named("False") or val = Value::named("None")) } private predicate cpython_interned_value(Expr e) { @@ -66,14 +66,14 @@ private predicate universally_interned_value(Expr e) { predicate cpython_interned_constant(Expr e) { exists(Expr const | - e.refersTo(_, const) | + e.pointsTo(_, const) | cpython_interned_value(const) ) } predicate universally_interned_constant(Expr e) { exists(Expr const | - e.refersTo(_, const) | + e.pointsTo(_, const) | universally_interned_value(const) ) } @@ -108,14 +108,14 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) ) and /* OK to use 'is' when comparing items from a known set of objects */ - not exists(Expr left, Expr right, Object obj | + not exists(Expr left, Expr right, Value val | comp.compares(left, op, right) and - exists(ImmutableLiteral il | il.getLiteralObject() = obj) | - left.refersTo(obj) and right.refersTo(obj) + exists(ImmutableLiteral il | il.getLiteralObject() = val) | + left.pointsTo(val) and right.pointsTo(val) or /* Simple constant in module, probably some sort of sentinel */ exists(AstNode origin | - not left.refersTo(_) and right.refersTo(obj, origin) and + not left.pointsTo(_) and right.pointsTo(val, origin) and origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule() ) ) @@ -124,7 +124,7 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) not exists(Expr left, Expr right, AstNode origin | comp.compares(left, op, right) and enum_member(origin) | - left.refersTo(_, origin) or right.refersTo(_, origin) + left.pointsTo(_, origin) or right.pointsTo(_, origin) ) } From 2b1d9c8d16cb9f4f1ee6ed73f9979b7177def33e Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 20 Feb 2020 20:20:56 -0800 Subject: [PATCH 065/236] Updates last library difference I'm not entirely sure if `getLiteralObject` and `getLiteralValue` are equivalent, and there don't see to be library tests for this --- python/ql/src/Expressions/IsComparisons.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index efd7effaf81..d2f1e6b1c8f 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -110,7 +110,7 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) /* OK to use 'is' when comparing items from a known set of objects */ not exists(Expr left, Expr right, Value val | comp.compares(left, op, right) and - exists(ImmutableLiteral il | il.getLiteralObject() = val) | + exists(ImmutableLiteral il | il.getLiteralValue() = val) | left.pointsTo(val) and right.pointsTo(val) or /* Simple constant in module, probably some sort of sentinel */ From 6ea14532ab155a8d7cc0cb2ab2f7c8552212e3f6 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 21 Feb 2020 10:27:57 +0100 Subject: [PATCH 066/236] small changes based on review --- .../src/Security/CWE-078/UselessUseOfCat.ql | 5 ++--- .../javascript/security/UselessUseOfCat.qll | 19 +++++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index e3fde814d66..417b9d9f2e3 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -1,14 +1,13 @@ /** * @name Useless use of cat - * @description Using cat to simply read a file can lead to unintended bugs, and at worst security issues. + * @description Using `cat`-process to simply read a file is unnecessarily complex, inefficient, unportable, can lead to subtle bugs, or even security vulnerabilities. * @kind problem * @problem.severity error * @precision high * @id js/useless-use-of-cat * @tags correctness * security - * external/cwe/cwe-078 - * external/cwe/cwe-088 + * maintainability */ import javascript diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 47a99b0bf4e..ca21f5d0bc6 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -11,13 +11,15 @@ string createReadFileCall(UselsesCatCandidates::UselessCatCandicate cat) { exists(string sync, string extraArg, string callback | (if cat.isSync() then sync = "Sync" else sync = "") and ( - if exists(cat.getOptionsArg()) - then extraArg = ", " + printOptionsArg(cat.getOptionsArg()) + ")" - else extraArg = "" + extraArg = ", " + printOptionsArg(cat.getOptionsArg()) + ")" + or + extraArg = "" and not exists(cat.getOptionsArg()) ) and - if exists(cat.getCallback()) - then callback = constructCallbackString(cat.getCallback()) - else callback = "" + ( + callback = constructCallbackString(cat.getCallback()) + or + callback = "" and not exists(cat.getCallback()) + ) | result = "fs.readFile" + sync + "(" + cat.getFileArgument().trim() + extraArg + callback + ")" ) @@ -94,10 +96,7 @@ class UselessCat extends DataFlow::CallNode { func.getNumParameter() = 2 or // `exec` can use 3 parameters, `readFile` can only use two, so it is OK to have a third parameter if it is unused, - func.getNumParameter() = 3 and - not exists(DataFlow::Node node | - not node = func.getParameter(2) and func.getParameter(2) = node.getALocalSource() - ) + func.getNumParameter() = 3 and not exists(SSA::definition(func.getParameter(2).getParameter())) ) ) } From 75410e5760c47f77313715f07ec333ca0f7083d1 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 21 Feb 2020 14:26:42 +0100 Subject: [PATCH 067/236] big refactor of UselessUseOfCal --- .../src/Security/CWE-078/UselessUseOfCat.ql | 8 +- .../javascript/security/UselessUseOfCat.qll | 330 +++++++++++------- .../Security/CWE-078/UselessUseOfCat.expected | 11 +- .../Security/CWE-078/UselessUseOfCat.ql | 2 +- .../Security/CWE-078/uselesscat.js | 40 ++- 5 files changed, 249 insertions(+), 142 deletions(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index 417b9d9f2e3..4f080711719 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -15,5 +15,9 @@ import semmle.javascript.security.UselessUseOfCat import semmle.javascript.RestrictedLocations -from UselessCat cat -select cat.asExpr().(FirstLineOf), "Useless use of `cat`. Can be replaced with: " + createReadFileCall(cat) \ No newline at end of file +from UselessCat cat, string message +where + message = " Can be replaced with: " + PrettyPrintCatCall::createReadFileCall(cat) + or + not exists(PrettyPrintCatCall::createReadFileCall(cat)) and message = "" +select cat.asExpr().(FirstLineOf), "Useless use of `cat`." + message \ No newline at end of file diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index ca21f5d0bc6..2633c84ce70 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -1,160 +1,233 @@ /** - * Provides predicates and classes for working with useless uses of `cat`. + * Provides predicates and classes for working with useless uses of the unix command `cat`. */ import javascript +import Expressions.ExprHasNoEffect +import Declarations.UnusedVariable /** - * Gets a string representation of an equivalent call to `fs.readFile` for a given command execution `cat`. + * A call that executes a system command. + * This class provide utility predicates for reasoning about command execution calls. */ -string createReadFileCall(UselsesCatCandidates::UselessCatCandicate cat) { - exists(string sync, string extraArg, string callback | - (if cat.isSync() then sync = "Sync" else sync = "") and - ( - extraArg = ", " + printOptionsArg(cat.getOptionsArg()) + ")" - or - extraArg = "" and not exists(cat.getOptionsArg()) - ) and - ( - callback = constructCallbackString(cat.getCallback()) - or - callback = "" and not exists(cat.getCallback()) - ) - | - result = "fs.readFile" + sync + "(" + cat.getFileArgument().trim() + extraArg + callback + ")" - ) -} +private class CommandCall extends DataFlow::InvokeNode { + SystemCommandExecution command; -/** - * Constructs a string representing the callback `func`. - */ -string constructCallbackString(DataFlow::FunctionNode func) { - exists(string args | args = getCallbackArgs(func) | - if func.getFunction() instanceof ArrowFunctionExpr - then - if func.getFunction().getBody() instanceof Expr - then result = ", (" + args + ") => ..." - else result = ", (" + args + ") => {...}" - else result = ", function(" + args + ") {...}" - ) -} + CommandCall() { this = command } -/** - * Gets a string concatenation of the parameter names in a function `func`. - */ -private string getCallbackArgs(DataFlow::FunctionNode func) { - result = - concat(int i | - i = [0 .. func.getNumParameter()] + /** + * Holds if the call is synchronous (e.g. `execFileSync`). + */ + predicate isSync() { command.isSync() } + + /** + * Gets an argument to this command execution that specifies the argument list to the command. + */ + DataFlow::Node getArgumentList() { result = command.getArgumentList() } + + /** + * Gets the callback (if it exists) for an async `exec`-like call. + */ + DataFlow::FunctionNode getCallback() { + not this.isSync() and result = getLastArgument().getALocalSource() + } + + /** + * Holds if the executed command execution has an argument list as a separate argument. + */ + predicate hasArgumentList() { exists(command.getArgumentList()) } + + /** + * Gets the data-flow node (if it exists) for a options argument for an `exec`-like call. + */ + DataFlow::Node getOptionsArg() { + exists(int n | + n >= 1 and + // if there is a command-list, then the options is at least the third argument. + (not exists(command.getArgumentList()) or n >= 2) and + // async exec calls can have a callback as their last call. + if command.isSync() or not exists(getCallback()) + then n < getNumArgument() + else n < getNumArgument() - 1 | - func.getParameter(i).getName(), ", " order by i + result = getArgument(n) ) + or + // Fallback in case normal API conventions are broken. + result = getAnArgument() and + result.getALocalSource() instanceof DataFlow::ObjectLiteralNode + } + + /** + * Gets the constant-string parts that are not part of the command itself. + * E.g. for a command execution `exec("/bin/cat foo bar")` this predicate will have result `"foo bar"`. + */ + string getNonCommandConstantString() { + if this.hasArgumentList() + then + result = + getConstantStringParts(getArgumentList() + .getALocalSource() + .(DataFlow::ArrayCreationNode) + .getElement(_)) + else + exists(string commandString | commandString = getConstantStringParts(getArgument(0)) | + result = commandString.suffix(1 + commandString.indexOf(" ", 0, 0)) + ) + } + + /** + * Holds if this command execution invokes the executeable `name`. + */ + bindingset[name] + predicate isACallTo(string name) { + if this.hasArgumentList() + then getArgument(0).mayHaveStringValue(name) + else + exists(string arg | arg = getConstantStringParts(getArgument(0)) | + arg.prefix(name.length()) = name + ) + } } /** - * Gets a string representation of the options argument `arg` from an exec-like call. + * Gets the constant string parts from a data-flow node. + * Either the string is some constant */ -private string printOptionsArg(DataFlow::Node arg) { - result = arg.asExpr().(VarAccess).getVariable().getName() +private string getConstantStringParts(DataFlow::Node node) { + node.mayHaveStringValue(result) or - // fall back to toString(), but ensure that we don't have dots in the middle. - result = arg.(DataFlow::ObjectLiteralNode).toString() and not result.regexpMatch(".*\\.\\..*") + result = node.(StringOps::ConcatenationRoot).getConstantStringParts() } /** * A call to a useless use of `cat`. */ -class UselessCat extends DataFlow::CallNode { - UselsesCatCandidates::UselessCatCandicate candidate; - +class UselessCat extends CommandCall { UselessCat() { - this = candidate and - // We can create an equivalent `fs.readFile` call. - exists(createReadFileCall(this)) and + this = command and + isACallTo(getACatExecuteable()) and // There is a file to read, and not just a pair of quotes. - candidate.getFileArgument().length() >= 3 and - // wildcards, pipes, redirections, and multiple files are OK. - // (The multiple files detection relies on the fileArgument not containing spaces anywhere) - not candidate.getFileArgument().regexpMatch(".*(\\*|\\||>|<| ).*") and + ( + not exists(PrettyPrintCatCall::createFileArgument(this)) + or + exists(string fileArg | fileArg = PrettyPrintCatCall::createFileArgument(this) | + fileArg.length() >= 3 + ) + ) and + // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. + not getNonCommandConstantString().regexpMatch(".*(\\*|\\||>|<| |\\$|&|,|\\`).*") and // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. ( - not exists(candidate.getOptionsArg()) + not exists(getOptionsArg()) or - forex(string prop | - exists(candidate.getOptionsArg().getALocalSource().getAPropertyWrite(prop)) - | + forex(string prop | exists(getOptionsArg().getALocalSource().getAPropertyWrite(prop)) | prop = "encoding" ) ) and // If there is a callback, then it must either have one or two arguments, or if there is a third argument it must be unused. ( - not exists(candidate.getCallback()) + not exists(getCallback()) or - exists(DataFlow::FunctionNode func | func = candidate.getCallback() | + exists(DataFlow::FunctionNode func | func = getCallback() | func.getNumParameter() = 1 or func.getNumParameter() = 2 or // `exec` can use 3 parameters, `readFile` can only use two, so it is OK to have a third parameter if it is unused, - func.getNumParameter() = 3 and not exists(SSA::definition(func.getParameter(2).getParameter())) + func.getNumParameter() = 3 and + not exists(SSA::definition(func.getParameter(2).getParameter())) ) + ) and + // The process returned by an async call is unused. + ( + isSync() + or + inVoidContext(this.getEnclosingExpr()) + or + this.getEnclosingExpr() = any(UnusedLocal v).getAnAssignedExpr() ) } } -module UselsesCatCandidates { +/** + * Gets a string used to call `cat`. + */ +string getACatExecuteable() { + result = "cat" or result = "/bin/cat" or result = "sudo cat" or result = "sudo /bin/cat" +} + +/** + * Predicates for creating an equivalent call to `fs.readFile` from a command execution of `cat`. + */ +module PrettyPrintCatCall { /** - * A candidate for a useless use of `cat`. - * This class describes the structure of a call to cat - * This class does not determine whether it is a useless call, or even if it is a call to `cat`. + * Create a string representation of an equivalent call to `fs.readFile` for a given command execution `cat`. */ - class UselessCatCandicate extends DataFlow::CallNode { - SystemCommandExecution command; - - UselessCatCandicate() { this = command } - - /** - * Holds if the call is synchronous (e.g. `execFileSync`). - */ - predicate isSync() { command.isSync() } - - /** - * Holds if the executed command execution has an argument list as a separate argument. - */ - predicate hasArgumentList() { exists(command.getArgumentList()) } - - /** - * Gets a string representation of the expression that determines what file is read by `cat`. - */ - string getFileArgument() { - if hasArgumentList() - then - getArgument(0).mayHaveStringValue(cat()) and - result = getFileThatIsReadFromCommandList(this) - else result = getFileArgumentWithoutCat(getArgument(0)) - } - - /** - * Gets the data-flow node (if it exists) for the options argument to the `exec`-like call. - */ - DataFlow::Node getOptionsArg() { - exists(int n | - n >= 1 and - // If there is a command-list, then the options is at least the third argument. - (not exists(command.getArgumentList()) or n >= 2) and - // async calls have a callback as their last call. - if this.isSync() then n < getNumArgument() else n < getNumArgument() - 1 - | - result = getArgument(n) + string createReadFileCall(UselessCat cat) { + exists(string sync, string extraArg, string callback | + (if cat.isSync() then sync = "Sync" else sync = "") and + ( + extraArg = ", " + createOptionsArg(cat.getOptionsArg()) + ")" + or + extraArg = "" and not exists(cat.getOptionsArg()) + ) and + ( + callback = createCallbackString(cat.getCallback()) + or + callback = "" and not exists(cat.getCallback()) ) - } + | + result = + "fs.readFile" + sync + "(" + createFileArgument(cat).trim() + extraArg + callback + ")" + ) + } - /** - * Gets the callback (if it exists) for an async `exec` like call. - */ - DataFlow::FunctionNode getCallback() { - not this.isSync() and result = getLastArgument().getALocalSource() - } + /** + * Create a string representation of the expression that determines what file is read by `cat`. + */ + string createFileArgument(CommandCall cat) { + if cat.hasArgumentList() + then + cat.getArgument(0).mayHaveStringValue(getACatExecuteable()) and + result = createFileThatIsReadFromCommandList(cat) + else result = createFileArgumentWithoutCat(cat.getArgument(0)) + } + + /** + * Create a string representing the callback `func`. + */ + string createCallbackString(DataFlow::FunctionNode func) { + exists(string args | args = createCallbackArgs(func) | + if func.getFunction() instanceof ArrowFunctionExpr + then + if func.getFunction().getBody() instanceof Expr + then result = ", (" + args + ") => ..." + else result = ", (" + args + ") => {...}" + else result = ", function(" + args + ") {...}" + ) + } + + /** + * Create a string concatenation of the parameter names in a function `func`. + */ + private string createCallbackArgs(DataFlow::FunctionNode func) { + result = + concat(int i | + i = [0 .. func.getNumParameter()] + | + func.getParameter(i).getName(), ", " order by i + ) + } + + /** + * Create a string representation of the options argument `arg` from an exec-like call. + */ + private string createOptionsArg(DataFlow::Node arg) { + result = arg.asExpr().(VarAccess).getVariable().getName() + or + // fall back to toString(), but ensure that we don't have dots in the middle. + result = arg.(DataFlow::ObjectLiteralNode).toString() and not result.regexpMatch(".*\\.\\..*") } /** @@ -169,7 +242,7 @@ module UselsesCatCandidates { concat(Expr leaf | leaf = root.getALeaf().asExpr() | - createLeafRepresentation(leaf), "+" order by leaf.getFirstToken().getIndex() + createLeafRepresentation(leaf), " + " order by leaf.getFirstToken().getIndex() ) or // Template string @@ -186,7 +259,7 @@ module UselsesCatCandidates { } /** - * Gets a string representing the expression needed to re-create the value for a leaf in a string-concatenation. + * Create a string representing the expression needed to re-create the value for a leaf in a string-concatenation. */ private string createLeafRepresentation(Expr e) { result = "\"" + e.getStringValue() + "\"" or @@ -194,7 +267,7 @@ module UselsesCatCandidates { } /** - * Gets a string representing the expression needed to re-create the value for an element of a template string. + * Create a string representing the expression needed to re-create the value for an element of a template string. */ private string createTemplateElementRepresentation(Expr e) { result = "${" + e.(VarAccess).getVariable().getName() + "}" @@ -203,18 +276,11 @@ module UselsesCatCandidates { } /** - * Gets a string used to call `cat`. - */ - private string cat() { - result = "cat" or result = "/bin/cat" or result = "sudo cat" or result = "sudo /bin/cat" - } - - /** - * Gets a string representing an expression that gets the file read by a call to `cat`. + * Create a string representing an expression that gets the file read by a call to `cat`. * The input `arg` is the node that determines the commandline where `cat` is invoked. */ - private string getFileArgumentWithoutCat(DataFlow::Node arg) { - exists(string cat | cat = cat() | + private string createFileArgumentWithoutCat(DataFlow::Node arg) { + exists(string cat | cat = getACatExecuteable() | exists(string command | arg.mayHaveStringValue(command) | command.prefix(cat.length()) = cat and result = "\"" + command.suffix(cat.length()).trim() + "\"" @@ -226,20 +292,20 @@ module UselsesCatCandidates { (if root.asExpr() instanceof TemplateLiteral then quote = "`" else quote = "\"") and root.getFirstLeaf().getStringValue().prefix(cat.length()) = cat and exists(string rawConcat | rawConcat = quote + printed.suffix(cat.length()).trim() | - result = getSimplifiedStringConcat(rawConcat) + result = createSimplifiedStringConcat(rawConcat) ) ) ) } /** - * Gets a simplified and equivalent string concatenation for a given string concatenation `str` + * Create a simplified and equivalent string concatenation for a given string concatenation `str` */ bindingset[str] - private string getSimplifiedStringConcat(string str) { + private string createSimplifiedStringConcat(string str) { // Remove an initial ""+ (e.g. in `""+file`) - if str.prefix(3) = "\"\"+" - then result = str.suffix(3) + if str.prefix(5) = "\"\" + " + then result = str.suffix(5) else // prettify `${newpath}` to just newpath if @@ -251,11 +317,11 @@ module UselsesCatCandidates { } /** - * Gets the file that is read for a call with an explicit command list (e.g. `child_process.execFile/execFileSync`). + * Create the file that is read for a call with an explicit command list (e.g. `child_process.execFile/execFileSync`). */ - string getFileThatIsReadFromCommandList(DataFlow::CallNode call) { + string createFileThatIsReadFromCommandList(CommandCall call) { exists(DataFlow::ArrayCreationNode array, DataFlow::Node element | - array = call.getArgument(1).(DataFlow::ArrayCreationNode) and + array = call.getArgumentList().(DataFlow::ArrayCreationNode) and array.getSize() = 1 and element = array.getElement(0) | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index f4ebbd0848b..1490b8abb01 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -1,6 +1,6 @@ readFile | uselesscat.js:10:1:10:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | -| uselesscat.js:12:1:14:2 | exec("c ... ut);\\n}) | fs.readFile("/proc/"+id+"/status", function(err, out) {...}) | +| uselesscat.js:12:1:14:2 | exec("c ... ut);\\n}) | fs.readFile("/proc/" + id + "/status", function(err, out) {...}) | | uselesscat.js:16:1:16:29 | execSyn ... uinfo') | fs.readFileSync("/proc/cpuinfo") | | uselesscat.js:18:1:18:26 | execSyn ... path}`) | fs.readFileSync(newpath) | | uselesscat.js:32:1:32:34 | execSyn ... path}`) | fs.readFileSync(`foo/bar/${newpath}`) | @@ -12,12 +12,14 @@ readFile | uselesscat.js:76:1:76:39 | execFil ... xml' ]) | fs.readFileSync("pom.xml") | | uselesscat.js:79:1:79:46 | execFil ... opts) | fs.readFileSync("pom.xml", opts)) | | uselesscat.js:82:1:82:90 | execFil ... String) | fs.readFileSync("pom.xml", anOptsFileNameThatIsTooLongToBePrintedByToString)) | -| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/"+newPath+"bar", {encoding: 'utf8'})) | -| uselesscat.js:88:1:88:35 | execSyn ... + foo) | fs.readFileSync("/proc/cpuinfo"+foo) | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/" + newPath + "bar", {encoding: 'utf8'})) | +| uselesscat.js:88:1:88:35 | execSyn ... + foo) | fs.readFileSync("/proc/cpuinfo" + foo) | | uselesscat.js:90:1:90:50 | execFil ... th}` ]) | fs.readFileSync(`foo/bar/${newpath}`) | | uselesscat.js:94:1:94:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | | uselesscat.js:96:1:96:53 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | | uselesscat.js:98:1:98:55 | exec("c ... h(out)) | fs.readFile("foo/bar", (err, out) => ...) | +| uselesscat.js:121:12:121:64 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:127:14:127:66 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | syncCommand | child_process-test.js:9:5:9:22 | cp.execSync("foo") | | child_process-test.js:11:5:11:26 | cp.exec ... ("foo") | @@ -65,6 +67,7 @@ syncCommand | uselesscat.js:90:1:90:50 | execFil ... th}` ]) | | uselesscat.js:92:1:92:46 | execFil ... th}` ]) | | uselesscat.js:100:1:100:56 | execFil ... ptions) | +| uselesscat.js:104:1:104:31 | execFil ... cat` ]) | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | -| False negative | uselesscat.js:84:118:84:144 | // NOT ... lagged] | +| False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql index f9a4fd2ea8c..a287a57ff55 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql @@ -17,7 +17,7 @@ where ) select msg, comment -query string readFile(UselessCat cat) { result = createReadFileCall(cat) } +query string readFile(UselessCat cat) { result = PrettyPrintCatCall::createReadFileCall(cat) } query SystemCommandExecution syncCommand() { result.isSync() diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index c860d21f654..d64058c010d 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -41,7 +41,7 @@ execSync(`cat ${newpath} > ${destpath}`).toString(); // OK. execSync(`cat ${files.join(' ')} > ${outFile}`); // OK -execSync(`cat ${files.join(' ')}`); // OK - not just a simple file read +execSync(`cat ${files.join(' ')}`); // OK [but flagged] - not just a simple file read exec("cat /proc/cpuinfo | grep name"); // OK - pipes @@ -81,7 +81,7 @@ execFileSync('/bin/cat', [ 'pom.xml' ], opts); // NOT OK var anOptsFileNameThatIsTooLongToBePrintedByToString = {encoding: 'utf8'}; execFileSync('/bin/cat', [ 'pom.xml' ], anOptsFileNameThatIsTooLongToBePrintedByToString); // NOT OK -execFileSync('/bin/cat', [ 'pom.xml' ], {encoding: 'someEncodingValueThatIsCompletelyBogusAndTooLongForToString'}); // NOT OK [but not flagged] +execFileSync('/bin/cat', [ 'pom.xml' ], {encoding: 'someEncodingValueThatIsCompletelyBogusAndTooLongForToString'}); // NOT OK execFileSync('/bin/cat', [ "foo/" + newPath + "bar" ], {encoding: 'utf8'}); // NOT OK @@ -97,4 +97,38 @@ exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK exec("cat foo/bar", (err, out) => doSomethingWith(out)); // NOT OK -execFileSync('/bin/cat', [ 'pom.xml' ], unknownOptions); // OK - unknown options. \ No newline at end of file +execFileSync('/bin/cat', [ 'pom.xml' ], unknownOptions); // OK - unknown options. + +exec("node foo/bar", (err, out) => doSomethingWith(out)); // OK - Not a call to cat + +execFileSync('node', [ `cat` ]); // OK - not a call to cat + +exec("cat foo/bar&", function (err, out) {}); // OK - contains & +exec("cat foo/bar,", function (err, out) {}); // OK - contains , +exec("cat foo/bar$", function (err, out) {}); // OK - contains $ +exec("cat foo/bar`", function (err, out) {}); // OK - contains ` + +spawn('cat', { stdio: ['pipe', stdin, 'inherit'] }); // OK - Non trivial use. (But weird API use.) + +(function () { + const cat = spawn('cat', [filename]); // OK - non trivial use. + cat.stdout.on('data', (data) => { + res.write(data); + }); + cat.stdout.on('end', () => res.end()); +})(); + +var dead = exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK + +var notDead = exec("cat foo/bar", (err, out) => {console.log(out)}); // OK +console.log(notDead); + +(function () { + var dead = exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK + + someCall( + exec("cat foo/bar", (err, out) => {console.log(out)}) // OK - non-trivial use of returned proccess. + ); + + return exec("cat foo/bar", (err, out) => {console.log(out)}); // OK - non-trivial use of returned proccess. +})(); \ No newline at end of file From 31ff652cb39a1fd58fb08226942524bbf76ef2a8 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 21 Feb 2020 15:18:53 +0100 Subject: [PATCH 068/236] Python: Make Sanitizer available for urlsplit taint It isn't used by default, it has to *actively* be enabled. --- .../python/security/strings/External.qll | 59 +++++++++++++++++++ .../library-tests/taint/namedtuple/Taint.qll | 20 +++++++ .../taint/namedtuple/TestTaint.expected | 4 +- .../library-tests/taint/namedtuple/test.py | 4 +- 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index 564580c2465..dc2a64ec2a3 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -195,3 +195,62 @@ class ExternalFileObject extends TaintKind { name = "read" and result = this.getValue() } } + +/** + * Temporary sanitizer for the tainted result from `urlsplit` and `urlparse`. Can be used to reduce FPs until + * we have better support for namedtuples. + * + * Will clear **all** taint on a test of the kind. That is, on the true edge of any matching test, + * all fields/indexes will be cleared of taint. + * + * Handles: + * - `if splitres.netloc == "KNOWN_VALUE"` + * - `if splitres[0] == "KNOWN_VALUE"` + */ +class UrlsplitUrlparseTempSanitizer extends Sanitizer { + // TODO: remove this once we have better support for named tuples + + UrlsplitUrlparseTempSanitizer() { this = "UrlsplitUrlparseTempSanitizer" } + + override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) { + ( + taint instanceof ExternalUrlSplitResult + or + taint instanceof ExternalUrlParseResult + ) and + exists(ControlFlowNode foobar | + foobar.(SubscriptNode).getObject() = test.getInput().getAUse() + or + foobar.(AttrNode).getObject() = test.getInput().getAUse() + | + clears_taint(_, foobar, test.getTest(), test.getSense()) + ) + } + + private predicate clears_taint(ControlFlowNode final_test, ControlFlowNode tainted, ControlFlowNode test, boolean sense) { + test_equality_with_const(final_test, tainted, sense) + or + test.(UnaryExprNode).getNode().getOp() instanceof Not and + exists(ControlFlowNode nested_test | + nested_test = test.(UnaryExprNode).getOperand() and + clears_taint(final_test, tainted, nested_test, sense.booleanNot()) + ) + } + + /** holds for `== "KNOWN_VALUE"` on `true` edge, and `!= "KNOWN_VALUE"` on `false` edge */ + private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode operand, boolean sense) { + exists(ControlFlowNode const, Cmpop op | + const.getNode() instanceof StrConst + | + ( + cmp.operands(const, op, operand) + or + cmp.operands(operand, op, const) + ) and ( + op instanceof Eq and sense = true + or + op instanceof NotEq and sense = false + ) + ) + } +} diff --git a/python/ql/test/library-tests/taint/namedtuple/Taint.qll b/python/ql/test/library-tests/taint/namedtuple/Taint.qll index b97f65225f2..9d19a54488c 100644 --- a/python/ql/test/library-tests/taint/namedtuple/Taint.qll +++ b/python/ql/test/library-tests/taint/namedtuple/Taint.qll @@ -25,3 +25,23 @@ class DictSource extends TaintSource { override string toString() { result = "dict taint source" } } + +class TestConfig extends TaintTracking::Configuration { + TestConfig() { this = "TestConfig" } + + override predicate isSanitizer(Sanitizer sanitizer) { + sanitizer instanceof UrlsplitUrlparseTempSanitizer + } + + override predicate isSource(TaintTracking::Source source) { + source instanceof SimpleSource + or + source instanceof ListSource + or + source instanceof DictSource + } + + override predicate isSink(TaintTracking::Sink sink) { + none() + } +} diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected index c90d33199ba..72d4c5c4e17 100644 --- a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -3,8 +3,8 @@ | test.py:13 | test_basic | c | externally controlled string | | test.py:13 | test_basic | d | externally controlled string | | test.py:13 | test_basic | urlsplit_res | [externally controlled string] | -| test.py:20 | test_sanitizer | Attribute | externally controlled string | -| test.py:23 | test_sanitizer | Subscript | externally controlled string | +| test.py:20 | test_sanitizer | Attribute | NO TAINT | +| test.py:23 | test_sanitizer | Subscript | NO TAINT | | test.py:33 | test_namedtuple | a | NO TAINT | | test.py:33 | test_namedtuple | b | NO TAINT | | test.py:33 | test_namedtuple | c | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py index 1c07bcfdc60..7c7120d3749 100644 --- a/python/ql/test/library-tests/taint/namedtuple/test.py +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -17,10 +17,10 @@ def test_sanitizer(): urlsplit_res = urlsplit(tainted_string) if urlsplit_res.netloc == "OK": - test(urlsplit_res.netloc) # TODO: FP, should not be tainted here + test(urlsplit_res.netloc) if urlsplit_res[2] == "OK": - test(urlsplit_res[0]) # TODO: FP, should not be tainted here + test(urlsplit_res[0]) def test_namedtuple(): tainted_string = TAINTED_STRING From 44db0f4e5df8f0d1ae1d0c578583474c13487dde Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 21 Feb 2020 15:39:49 +0100 Subject: [PATCH 069/236] better printing of the options arg --- .../javascript/security/UselessUseOfCat.qll | 14 ++++++++++---- .../Security/CWE-078/UselessUseOfCat.expected | 15 +++++++++------ .../query-tests/Security/CWE-078/uselesscat.js | 6 +++++- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 2633c84ce70..1d2a13d960d 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -59,9 +59,9 @@ private class CommandCall extends DataFlow::InvokeNode { } /** - * Gets the constant-string parts that are not part of the command itself. - * E.g. for a command execution `exec("/bin/cat foo bar")` this predicate will have result `"foo bar"`. - */ + * Gets the constant-string parts that are not part of the command itself. + * E.g. for a command execution `exec("/bin/cat foo bar")` this predicate will have result `"foo bar"`. + */ string getNonCommandConstantString() { if this.hasArgumentList() then @@ -168,7 +168,13 @@ module PrettyPrintCatCall { exists(string sync, string extraArg, string callback | (if cat.isSync() then sync = "Sync" else sync = "") and ( - extraArg = ", " + createOptionsArg(cat.getOptionsArg()) + ")" + exists(cat.getOptionsArg()) and + ( + extraArg = ", " + createOptionsArg(cat.getOptionsArg()) + or + not exists(createOptionsArg(cat.getOptionsArg())) and + extraArg = ", ..." + ) or extraArg = "" and not exists(cat.getOptionsArg()) ) and diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index 1490b8abb01..c0de47ed995 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -4,15 +4,16 @@ readFile | uselesscat.js:16:1:16:29 | execSyn ... uinfo') | fs.readFileSync("/proc/cpuinfo") | | uselesscat.js:18:1:18:26 | execSyn ... path}`) | fs.readFileSync(newpath) | | uselesscat.js:32:1:32:34 | execSyn ... path}`) | fs.readFileSync(`foo/bar/${newpath}`) | -| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | fs.readFileSync(`foo/bar/${newpath}`, {encoding: 'utf8'})) | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | fs.readFileSync(`foo/bar/${newpath}`, {encoding: 'utf8'}) | | uselesscat.js:51:9:51:31 | execSyn ... + file) | fs.readFileSync(file) | | uselesscat.js:59:1:62:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", function(error, stdout, stderr) {...}) | -| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", {encoding: 'utf8'}), function(error, stdout, stderr) {...}) | -| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | fs.readFileSync("pom.xml", {encoding: 'utf8'})) | +| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | fs.readFile("pom.xml", {encoding: 'utf8'}, function(error, stdout, stderr) {...}) | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | fs.readFileSync("pom.xml", {encoding: 'utf8'}) | | uselesscat.js:76:1:76:39 | execFil ... xml' ]) | fs.readFileSync("pom.xml") | -| uselesscat.js:79:1:79:46 | execFil ... opts) | fs.readFileSync("pom.xml", opts)) | -| uselesscat.js:82:1:82:90 | execFil ... String) | fs.readFileSync("pom.xml", anOptsFileNameThatIsTooLongToBePrintedByToString)) | -| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/" + newPath + "bar", {encoding: 'utf8'})) | +| uselesscat.js:79:1:79:46 | execFil ... opts) | fs.readFileSync("pom.xml", opts) | +| uselesscat.js:82:1:82:90 | execFil ... String) | fs.readFileSync("pom.xml", anOptsFileNameThatIsTooLongToBePrintedByToString) | +| uselesscat.js:84:1:84:115 | execFil ... ring'}) | fs.readFileSync("pom.xml", ...) | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | fs.readFileSync("foo/" + newPath + "bar", {encoding: 'utf8'}) | | uselesscat.js:88:1:88:35 | execSyn ... + foo) | fs.readFileSync("/proc/cpuinfo" + foo) | | uselesscat.js:90:1:90:50 | execFil ... th}` ]) | fs.readFileSync(`foo/bar/${newpath}`) | | uselesscat.js:94:1:94:43 | exec("c ... ut) {}) | fs.readFile("foo/bar", function(err, out) {...}) | @@ -20,6 +21,7 @@ readFile | uselesscat.js:98:1:98:55 | exec("c ... h(out)) | fs.readFile("foo/bar", (err, out) => ...) | | uselesscat.js:121:12:121:64 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | | uselesscat.js:127:14:127:66 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | fs.readFileSync("/etc/dnsmasq.conf", ...) | syncCommand | child_process-test.js:9:5:9:22 | cp.execSync("foo") | | child_process-test.js:11:5:11:26 | cp.exec ... ("foo") | @@ -68,6 +70,7 @@ syncCommand | uselesscat.js:92:1:92:46 | execFil ... th}` ]) | | uselesscat.js:100:1:100:56 | execFil ... ptions) | | uselesscat.js:104:1:104:31 | execFil ... cat` ]) | +| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | | False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index d64058c010d..c50e0fc1f5e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -131,4 +131,8 @@ console.log(notDead); ); return exec("cat foo/bar", (err, out) => {console.log(out)}); // OK - non-trivial use of returned proccess. -})(); \ No newline at end of file +})(); + +const stdout2 = execSync('cat /etc/dnsmasq.conf', { // NOT OK. + encoding: 'utf8' +}); \ No newline at end of file From 798db91f7153fb225e0bd5a22c90dd5e1cb7f14d Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 21 Feb 2020 15:26:07 +0100 Subject: [PATCH 070/236] Python: Add more urlsplit tests --- .../library-tests/taint/namedtuple/TestTaint.expected | 11 +++++++---- python/ql/test/library-tests/taint/namedtuple/test.py | 9 +++++++++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected index 72d4c5c4e17..471801f775d 100644 --- a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -5,7 +5,10 @@ | test.py:13 | test_basic | urlsplit_res | [externally controlled string] | | test.py:20 | test_sanitizer | Attribute | NO TAINT | | test.py:23 | test_sanitizer | Subscript | NO TAINT | -| test.py:33 | test_namedtuple | a | NO TAINT | -| test.py:33 | test_namedtuple | b | NO TAINT | -| test.py:33 | test_namedtuple | c | NO TAINT | -| test.py:33 | test_namedtuple | d | NO TAINT | +| test.py:26 | test_sanitizer | Attribute | NO TAINT | +| test.py:29 | test_sanitizer | Attribute | externally controlled string | +| test.py:32 | test_sanitizer | Attribute | externally controlled string | +| test.py:42 | test_namedtuple | a | NO TAINT | +| test.py:42 | test_namedtuple | b | NO TAINT | +| test.py:42 | test_namedtuple | c | NO TAINT | +| test.py:42 | test_namedtuple | d | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py index 7c7120d3749..d0b9c1e9212 100644 --- a/python/ql/test/library-tests/taint/namedtuple/test.py +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -22,6 +22,15 @@ def test_sanitizer(): if urlsplit_res[2] == "OK": test(urlsplit_res[0]) + if urlsplit_res.netloc == "OK": + test(urlsplit_res.path) # FN + + if urlsplit_res.netloc in ["OK"]: + test(urlsplit_res.netloc) # FP + + if urlsplit_res.netloc in ["OK", non_constant()]: + test(urlsplit_res.netloc) # should be tainted + def test_namedtuple(): tainted_string = TAINTED_STRING Point = namedtuple('Point', ['x', 'y']) From bfa7553095947e0053c92f1f82378e2e2e829f62 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 21 Feb 2020 16:03:29 +0100 Subject: [PATCH 071/236] Python: urlsplit sanitizer handles `in [KNOWN_VALUE]` --- .../python/security/strings/External.qll | 25 ++++++++++++++++--- .../taint/namedtuple/TestTaint.expected | 2 +- .../library-tests/taint/namedtuple/test.py | 2 +- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index dc2a64ec2a3..a8b17b1f686 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -230,6 +230,8 @@ class UrlsplitUrlparseTempSanitizer extends Sanitizer { private predicate clears_taint(ControlFlowNode final_test, ControlFlowNode tainted, ControlFlowNode test, boolean sense) { test_equality_with_const(final_test, tainted, sense) or + test_in_const_seq(final_test, tainted, sense) + or test.(UnaryExprNode).getNode().getOp() instanceof Not and exists(ControlFlowNode nested_test | nested_test = test.(UnaryExprNode).getOperand() and @@ -238,19 +240,34 @@ class UrlsplitUrlparseTempSanitizer extends Sanitizer { } /** holds for `== "KNOWN_VALUE"` on `true` edge, and `!= "KNOWN_VALUE"` on `false` edge */ - private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode operand, boolean sense) { + private predicate test_equality_with_const(CompareNode cmp, ControlFlowNode tainted, boolean sense) { exists(ControlFlowNode const, Cmpop op | const.getNode() instanceof StrConst | ( - cmp.operands(const, op, operand) + cmp.operands(const, op, tainted) or - cmp.operands(operand, op, const) - ) and ( + cmp.operands(tainted, op, const) + ) and + ( op instanceof Eq and sense = true or op instanceof NotEq and sense = false ) ) } + + /** holds for `in ["KNOWN_VALUE", ...]` on `true` edge, and `not in ["KNOWN_VALUE", ...]` on `false` edge */ + private predicate test_in_const_seq(CompareNode cmp, ControlFlowNode tainted, boolean sense) { + exists(SequenceNode const_seq, Cmpop op | + forall(ControlFlowNode elem | elem = const_seq.getAnElement() | elem.getNode() instanceof StrConst) + | + cmp.operands(tainted, op, const_seq) and + ( + op instanceof In and sense = true + or + op instanceof NotIn and sense = false + ) + ) + } } diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected index 471801f775d..11c8b1268a7 100644 --- a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -6,7 +6,7 @@ | test.py:20 | test_sanitizer | Attribute | NO TAINT | | test.py:23 | test_sanitizer | Subscript | NO TAINT | | test.py:26 | test_sanitizer | Attribute | NO TAINT | -| test.py:29 | test_sanitizer | Attribute | externally controlled string | +| test.py:29 | test_sanitizer | Attribute | NO TAINT | | test.py:32 | test_sanitizer | Attribute | externally controlled string | | test.py:42 | test_namedtuple | a | NO TAINT | | test.py:42 | test_namedtuple | b | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py index d0b9c1e9212..f3d0b778e1b 100644 --- a/python/ql/test/library-tests/taint/namedtuple/test.py +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -26,7 +26,7 @@ def test_sanitizer(): test(urlsplit_res.path) # FN if urlsplit_res.netloc in ["OK"]: - test(urlsplit_res.netloc) # FP + test(urlsplit_res.netloc) if urlsplit_res.netloc in ["OK", non_constant()]: test(urlsplit_res.netloc) # should be tainted From 14273fc677366da17785111f64346dd215e14aa5 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Fri, 21 Feb 2020 11:25:03 -0800 Subject: [PATCH 072/236] Adds missing result to expected file --- .../ql/test/2/query-tests/Expressions/TruncatedDivision.expected | 1 + 1 file changed, 1 insertion(+) diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected index 67b76d0a86e..e85a047ad34 100644 --- a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected +++ b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected @@ -1,2 +1,3 @@ +| TruncatedDivision.py:36:9:36:13 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision.py:36:9:36:9 | TruncatedDivision.py:36 | left | TruncatedDivision.py:36:13:36:13 | TruncatedDivision.py:36 | right | | TruncatedDivision_test.py:8:12:8:16 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:8:12:8:12 | TruncatedDivision_test.py:8 | left | TruncatedDivision_test.py:8:16:8:16 | TruncatedDivision_test.py:8 | right | | TruncatedDivision_test.py:11:12:11:40 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:2:12:2:12 | TruncatedDivision_test.py:2 | left | TruncatedDivision_test.py:5:12:5:12 | TruncatedDivision_test.py:5 | right | From 170331b105e83ba25b0041a743ca429cd1a5325f Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Fri, 21 Feb 2020 18:46:32 -0700 Subject: [PATCH 073/236] C++: Better fix for `void` type on buffer access Fixes issue https://github.com/github/codeql-c-analysis-team/issues/20 This change undoes the workaround in https://github.com/Semmle/ql/pull/2736, and replaces it with a fix for the underlying cause. The problem was that the IR construction code for side effects incorrectly assumed that `BufferAccessOpcode` included `SizedBufferAccessOpcode`. I think that was actually a perfectly reasonable assumption to make, so I changed the `Opcode` hierarchy to make it true. --- .../code/cpp/ir/implementation/Opcode.qll | 22 +++++++++++-------- .../aliased_ssa/internal/AliasedSSA.qll | 4 ++-- .../raw/internal/TranslatedCall.qll | 2 +- .../code/csharp/ir/implementation/Opcode.qll | 22 +++++++++++-------- 4 files changed, 29 insertions(+), 21 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index 886183c32ba..3fb716d78e0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -213,23 +213,29 @@ abstract class IndirectReadOpcode extends IndirectMemoryAccessOpcode { } /** - * An opcode that accesses a memory buffer of unknown size. + * An opcode that accesses a memory buffer. */ abstract class BufferAccessOpcode extends Opcode { final override predicate hasAddressOperand() { any() } } +/** + * An opcode that accesses a memory buffer of unknown size. + */ +abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { +} + /** * An opcode that writes to a memory buffer of unknown size. */ -abstract class BufferWriteOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferWriteOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getWriteMemoryAccess() { result instanceof BufferMemoryAccess } } /** * An opcode that reads from a memory buffer of unknown size. */ -abstract class BufferReadOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferReadOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } @@ -261,9 +267,7 @@ abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ -abstract class SizedBufferAccessOpcode extends Opcode { - final override predicate hasAddressOperand() { any() } - +abstract class SizedBufferAccessOpcode extends BufferAccessOpcode { final override predicate hasBufferSizeOperand() { any() } } @@ -666,16 +670,16 @@ module Opcode { final override string toString() { result = "IndirectMayWriteSideEffect" } } - class BufferReadSideEffect extends ReadSideEffectOpcode, BufferReadOpcode, TBufferReadSideEffect { + class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, TBufferReadSideEffect { final override string toString() { result = "BufferReadSideEffect" } } - class BufferMustWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, + class BufferMustWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, TBufferMustWriteSideEffect { final override string toString() { result = "BufferMustWriteSideEffect" } } - class BufferMayWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, MayWriteOpcode, + class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, MayWriteOpcode, TBufferMayWriteSideEffect { final override string toString() { result = "BufferMayWriteSideEffect" } } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll index c65570c5999..2dfd3f60902 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasedSSA.qll @@ -26,7 +26,7 @@ private predicate hasResultMemoryAccess( type = languageType.getIRType() and isIndirectOrBufferMemoryAccess(instr.getResultMemoryAccess()) and (if instr.hasResultMayMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if type.getByteSize() > 0 + if exists(type.getByteSize()) then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) else endBitOffset = Ints::unknown() ) @@ -43,7 +43,7 @@ private predicate hasOperandMemoryAccess( type = languageType.getIRType() and isIndirectOrBufferMemoryAccess(operand.getMemoryAccess()) and (if operand.hasMayReadMemoryAccess() then isMayAccess = true else isMayAccess = false) and - if type.getByteSize() > 0 + if exists(type.getByteSize()) then endBitOffset = Ints::add(startBitOffset, Ints::mul(type.getByteSize(), 8)) else endBitOffset = Ints::unknown() ) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll index 93e5c43bf3b..25b1677958d 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/TranslatedCall.qll @@ -487,7 +487,7 @@ class TranslatedSideEffect extends TranslatedElement, TTranslatedArgumentSideEff } override CppType getInstructionOperandType(InstructionTag tag, TypedOperandTag operandTag) { - if hasSpecificReadSideEffect(any(Opcode::BufferReadSideEffect op)) + if hasSpecificReadSideEffect(any(BufferAccessOpcode op)) then result = getUnknownType() and tag instanceof OnlyInstructionTag and diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index 886183c32ba..3fb716d78e0 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -213,23 +213,29 @@ abstract class IndirectReadOpcode extends IndirectMemoryAccessOpcode { } /** - * An opcode that accesses a memory buffer of unknown size. + * An opcode that accesses a memory buffer. */ abstract class BufferAccessOpcode extends Opcode { final override predicate hasAddressOperand() { any() } } +/** + * An opcode that accesses a memory buffer of unknown size. + */ +abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { +} + /** * An opcode that writes to a memory buffer of unknown size. */ -abstract class BufferWriteOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferWriteOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getWriteMemoryAccess() { result instanceof BufferMemoryAccess } } /** * An opcode that reads from a memory buffer of unknown size. */ -abstract class BufferReadOpcode extends BufferAccessOpcode { +abstract class UnsizedBufferReadOpcode extends UnsizedBufferAccessOpcode { final override MemoryAccessKind getReadMemoryAccess() { result instanceof BufferMemoryAccess } } @@ -261,9 +267,7 @@ abstract class EntireAllocationReadOpcode extends EntireAllocationAccessOpcode { /** * An opcode that accesses a memory buffer whose size is determined by a `BufferSizeOperand`. */ -abstract class SizedBufferAccessOpcode extends Opcode { - final override predicate hasAddressOperand() { any() } - +abstract class SizedBufferAccessOpcode extends BufferAccessOpcode { final override predicate hasBufferSizeOperand() { any() } } @@ -666,16 +670,16 @@ module Opcode { final override string toString() { result = "IndirectMayWriteSideEffect" } } - class BufferReadSideEffect extends ReadSideEffectOpcode, BufferReadOpcode, TBufferReadSideEffect { + class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, TBufferReadSideEffect { final override string toString() { result = "BufferReadSideEffect" } } - class BufferMustWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, + class BufferMustWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, TBufferMustWriteSideEffect { final override string toString() { result = "BufferMustWriteSideEffect" } } - class BufferMayWriteSideEffect extends WriteSideEffectOpcode, BufferWriteOpcode, MayWriteOpcode, + class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, MayWriteOpcode, TBufferMayWriteSideEffect { final override string toString() { result = "BufferMayWriteSideEffect" } } From 75c1852ee49e04aa5ddd9d57c5b9613ebbf2628b Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 11:58:59 +0100 Subject: [PATCH 074/236] doc changes from review Co-Authored-By: Esben Sparre Andreasen --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 1d2a13d960d..cccf7937c55 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -8,7 +8,7 @@ import Declarations.UnusedVariable /** * A call that executes a system command. - * This class provide utility predicates for reasoning about command execution calls. + * This class provides utility predicates for reasoning about command execution calls. */ private class CommandCall extends DataFlow::InvokeNode { SystemCommandExecution command; @@ -38,7 +38,7 @@ private class CommandCall extends DataFlow::InvokeNode { predicate hasArgumentList() { exists(command.getArgumentList()) } /** - * Gets the data-flow node (if it exists) for a options argument for an `exec`-like call. + * Gets the data-flow node (if it exists) for an options argument for an `exec`-like call. */ DataFlow::Node getOptionsArg() { exists(int n | From 473787a4268a5e90b4a653ee8bcdb3681f7867ff Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 12:59:20 +0100 Subject: [PATCH 075/236] refactor the getOptionsArg predicate into the SystemCommandExecution class --- .../ql/src/semmle/javascript/Concepts.qll | 5 ++++ .../javascript/frameworks/NodeJSLib.qll | 12 ++++++++ .../semmle/javascript/frameworks/ShellJS.qll | 7 +++++ .../frameworks/SystemCommandExecutors.qll | 29 ++++++++++++------- .../javascript/security/UselessUseOfCat.qll | 16 +--------- .../Security/CWE-078/UselessUseOfCat.expected | 17 +++++++++++ .../Security/CWE-078/UselessUseOfCat.ql | 4 +++ 7 files changed, 65 insertions(+), 25 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/Concepts.qll b/javascript/ql/src/semmle/javascript/Concepts.qll index 4fef87f8585..798747af8b8 100644 --- a/javascript/ql/src/semmle/javascript/Concepts.qll +++ b/javascript/ql/src/semmle/javascript/Concepts.qll @@ -25,6 +25,11 @@ abstract class SystemCommandExecution extends DataFlow::Node { /** Holds if the command execution happens synchronously. */ abstract predicate isSync(); + + /** + * Gets the data-flow node (if it exists) for an options argument. + */ + abstract DataFlow::Node getOptionsArg(); } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 95600e072f6..2eed3f1586b 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -627,6 +627,18 @@ module NodeJSLib { override predicate isSync() { "Sync" = methodName.suffix(methodName.length() - 4) } + + override DataFlow::Node getOptionsArg() { + not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist + not result = getArgument(0) and + // fork/spawn and all sync methos always has options as the last argument + if methodName.regexpMatch("fork.*") or methodName.regexpMatch("spawn.*") or methodName.regexpMatch(".*Sync") then + result = getLastArgument() + else + // the rest (exec/execFile) has the options argument as their second last. + result = getArgument(this.getNumArgument() - 2) + } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll index e459fe9089c..6944b7e74a3 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll @@ -162,6 +162,13 @@ module ShellJS { override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() } override predicate isSync() {none ()} + + override DataFlow::Node getOptionsArg() { + result = getLastArgument() and + not result = getArgument(0) and + not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist + } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index e0664f93dd1..466ed29ae0a 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -7,6 +7,7 @@ import javascript private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode { int cmdArg; + int optionsArg; boolean shell; boolean sync; @@ -14,9 +15,9 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I SystemCommandExecutors() { exists(string mod, DataFlow::SourceNode callee | exists(string method | - mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false + mod = "cross-spawn" and method = "sync" and cmdArg = 0 and shell = false and optionsArg = -1 or - mod = "execa" and + mod = "execa" and optionsArg = -1 and ( shell = false and ( @@ -40,21 +41,21 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I ( shell = false and ( - mod = "cross-spawn" and cmdArg = 0 + mod = "cross-spawn" and cmdArg = 0 and optionsArg = -1 or - mod = "cross-spawn-async" and cmdArg = 0 + mod = "cross-spawn-async" and cmdArg = 0 and optionsArg = -1 or - mod = "exec-async" and cmdArg = 0 + mod = "exec-async" and cmdArg = 0 and optionsArg = -1 or - mod = "execa" and cmdArg = 0 + mod = "execa" and cmdArg = 0 and optionsArg = -1 ) or shell = true and ( - mod = "exec" and + mod = "exec" and optionsArg = -2 and cmdArg = 0 or - mod = "remote-exec" and cmdArg = 1 + mod = "remote-exec" and cmdArg = 1 and optionsArg = -1 ) ) and callee = DataFlow::moduleImport(mod) @@ -69,8 +70,16 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I arg = getACommandArgument() and shell = true } - override predicate isSync() { - sync = true + override predicate isSync() { sync = true } + + override DataFlow::Node getOptionsArg() { + (if optionsArg < 0 then + result = getArgument(getNumArgument() - optionsArg) + else + result = getArgument(optionsArg)) and + not result = getArgument(0) and + not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback + not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist } } diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index cccf7937c55..b40c0cc3a89 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -41,21 +41,7 @@ private class CommandCall extends DataFlow::InvokeNode { * Gets the data-flow node (if it exists) for an options argument for an `exec`-like call. */ DataFlow::Node getOptionsArg() { - exists(int n | - n >= 1 and - // if there is a command-list, then the options is at least the third argument. - (not exists(command.getArgumentList()) or n >= 2) and - // async exec calls can have a callback as their last call. - if command.isSync() or not exists(getCallback()) - then n < getNumArgument() - else n < getNumArgument() - 1 - | - result = getArgument(n) - ) - or - // Fallback in case normal API conventions are broken. - result = getAnArgument() and - result.getALocalSource() instanceof DataFlow::ObjectLiteralNode + result = command.getOptionsArg() } /** diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index c0de47ed995..f78680f30ed 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -71,6 +71,23 @@ syncCommand | uselesscat.js:100:1:100:56 | execFil ... ptions) | | uselesscat.js:104:1:104:31 | execFil ... cat` ]) | | uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | +options +| child_process-test.js:53:5:53:59 | cp.spaw ... cmd])) | child_process-test.js:53:25:53:58 | ['/C', ... , cmd]) | +| child_process-test.js:54:5:54:50 | cp.spaw ... t(cmd)) | child_process-test.js:54:25:54:49 | ['/C', ... at(cmd) | +| child_process-test.js:64:3:64:21 | cp.spawn(cmd, args) | child_process-test.js:64:17:64:20 | args | +| uselesscat.js:28:1:28:39 | execSyn ... 1000}) | uselesscat.js:28:28:28:38 | {uid: 1000} | +| uselesscat.js:30:1:30:64 | exec('c ... t) { }) | uselesscat.js:30:26:30:38 | { cwd: './' } | +| uselesscat.js:34:1:34:54 | execSyn ... utf8'}) | uselesscat.js:34:36:34:53 | {encoding: 'utf8'} | +| uselesscat.js:36:1:36:77 | execSyn ... utf8'}) | uselesscat.js:36:36:36:76 | { uid: ... 'utf8'} | +| uselesscat.js:69:1:72:2 | execFil ... ut);\\n}) | uselesscat.js:69:38:69:55 | {encoding: 'utf8'} | +| uselesscat.js:74:1:74:60 | execFil ... utf8'}) | uselesscat.js:74:42:74:59 | {encoding: 'utf8'} | +| uselesscat.js:79:1:79:46 | execFil ... opts) | uselesscat.js:79:42:79:45 | opts | +| uselesscat.js:82:1:82:90 | execFil ... String) | uselesscat.js:82:42:82:89 | anOptsF ... oString | +| uselesscat.js:84:1:84:115 | execFil ... ring'}) | uselesscat.js:84:42:84:114 | {encodi ... tring'} | +| uselesscat.js:86:1:86:75 | execFil ... utf8'}) | uselesscat.js:86:57:86:74 | {encoding: 'utf8'} | +| uselesscat.js:100:1:100:56 | execFil ... ptions) | uselesscat.js:100:42:100:55 | unknownOptions | +| uselesscat.js:111:1:111:51 | spawn(' ... it'] }) | uselesscat.js:111:14:111:50 | { stdio ... rit'] } | +| uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | uselesscat.js:136:51:138:1 | { // NO ... utf8'\\n} | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | | False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql index a287a57ff55..a70f53016bd 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql @@ -21,4 +21,8 @@ query string readFile(UselessCat cat) { result = PrettyPrintCatCall::createReadF query SystemCommandExecution syncCommand() { result.isSync() +} + +query DataFlow::Node options(SystemCommandExecution sys) { + result = sys.getOptionsArg() } \ No newline at end of file From a768e937f0c64052881f4c4ef389507dca5ac0e8 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 13:08:50 +0100 Subject: [PATCH 076/236] complete qldoc --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index b40c0cc3a89..80a0eb6398d 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -78,7 +78,7 @@ private class CommandCall extends DataFlow::InvokeNode { /** * Gets the constant string parts from a data-flow node. - * Either the string is some constant + * Either the result is a constant string value that the node can hold, or the node is a string-concatenation and the result is the string parts from the concatenation. */ private string getConstantStringParts(DataFlow::Node node) { node.mayHaveStringValue(result) From 051de247b0fa54119453e36b348a870d696c9cb5 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 13:11:30 +0100 Subject: [PATCH 077/236] change regexpMatch to regexpFind --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 80a0eb6398d..a19fce9027f 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -102,7 +102,7 @@ class UselessCat extends CommandCall { ) ) and // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. - not getNonCommandConstantString().regexpMatch(".*(\\*|\\||>|<| |\\$|&|,|\\`).*") and + not exists(getNonCommandConstantString().regexpFind("\\*|\\||>|<| |\\$|&|,|\\`", _, _)) and // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. ( not exists(getOptionsArg()) From fb94af976414aec823514f8d1ee3e1abbf507e1d Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 13:18:15 +0100 Subject: [PATCH 078/236] remove the last dependency on PrettyPrinting --- .../semmle/javascript/security/UselessUseOfCat.qll | 11 ++++------- .../Security/CWE-078/UselessUseOfCat.expected | 2 +- .../test/query-tests/Security/CWE-078/uselesscat.js | 8 ++++++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index a19fce9027f..bcdaa2eff4c 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -93,13 +93,10 @@ class UselessCat extends CommandCall { UselessCat() { this = command and isACallTo(getACatExecuteable()) and - // There is a file to read, and not just a pair of quotes. - ( - not exists(PrettyPrintCatCall::createFileArgument(this)) - or - exists(string fileArg | fileArg = PrettyPrintCatCall::createFileArgument(this) | - fileArg.length() >= 3 - ) + // There is a file to read, it's not just spawning `cat`. + not ( + not exists(getArgumentList()) and + getArgument(0).mayHaveStringValue(getACatExecuteable()) ) and // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. not exists(getNonCommandConstantString().regexpFind("\\*|\\||>|<| |\\$|&|,|\\`", _, _)) and diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index f78680f30ed..9e53b8cc422 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -45,7 +45,7 @@ syncCommand | tst_shell-command-injection-from-environment.js:5:2:5:54 | cp.exec ... temp")) | | uselesscat.js:16:1:16:29 | execSyn ... uinfo') | | uselesscat.js:18:1:18:26 | execSyn ... path}`) | -| uselesscat.js:20:1:20:50 | child_p ... wc -l') | +| uselesscat.js:20:1:20:36 | execSyn ... wc -l') | | uselesscat.js:22:1:22:38 | execSyn ... o/bar') | | uselesscat.js:24:1:24:35 | execSyn ... o/bar`) | | uselesscat.js:28:1:28:39 | execSyn ... 1000}) | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index c50e0fc1f5e..86647b9fc29 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -17,7 +17,7 @@ execSync('cat /proc/cpuinfo').toString(); // NOT OK. execSync(`cat ${newpath}`) // NOT OK -child_process.execSync('cat package.json | wc -l'); // OK - pipes! +execSync('cat package.json | wc -l'); // OK - pipes! execSync('cat /proc/cpuinfo /foo/bar').toString(); // OK multiple files. @@ -135,4 +135,8 @@ console.log(notDead); const stdout2 = execSync('cat /etc/dnsmasq.conf', { // NOT OK. encoding: 'utf8' -}); \ No newline at end of file +}); + +exec('/bin/cat', function (e, s) {}); // OK + +spawn("cat") // OK \ No newline at end of file From a779ae58a88b3307eec6dd9e26b7bc27a7476c22 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 14:03:41 +0100 Subject: [PATCH 079/236] add qhelp --- .../Security/CWE-078/UselessUseOfCat.qhelp | 47 +++++++++++++------ .../CWE-078/examples/useless-cat-fixed.js | 5 ++ .../Security/CWE-078/examples/useless-cat.js | 5 ++ 3 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js create mode 100644 javascript/ql/src/Security/CWE-078/examples/useless-cat.js diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp index 452f630712d..85a7a2e4949 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -3,28 +3,45 @@ "qhelp.dtd"> -

    - Useless use of cat -

    - - +

    Using the unix command cat to simply read a file is a +unnecessarily complex way to achieve something that can be done simpler and +safer using the Node.js fs.readFile API. +

    +

    +The use of cat for simple file reads leads to code that is +unportable, inefficient, complex, and can lead to subtle bugs or even +security vulnerabilities. +

    -

    - TODO: This is a placeholder -

    - +

    +Use fs.readFile or fs.readFileSync to read files +from the file system. +

    -

    -

    + +

    The following example shows code that reads a file using cat:

    + + + +

    The code in the example will break if the input name contain +special characters (including space), the code does not work on windows, +and if the input is user controlled a command injection attack can happen.

    + +

    To avoid these potential issues the fs.readFile API can be +used instead:

    + + +
    -
  • - OWASP: - Command Injection. -
  • +
  • +OWASP: Command Injection. +Node.js: File System API. +
  • +
    diff --git a/javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js b/javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js new file mode 100644 index 00000000000..225fa1f5869 --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/useless-cat-fixed.js @@ -0,0 +1,5 @@ +var fs = require('fs'); + +module.exports = function (name) { + return fs.readFileSync(name).toString(); +}; diff --git a/javascript/ql/src/Security/CWE-078/examples/useless-cat.js b/javascript/ql/src/Security/CWE-078/examples/useless-cat.js new file mode 100644 index 00000000000..78f099d0e4c --- /dev/null +++ b/javascript/ql/src/Security/CWE-078/examples/useless-cat.js @@ -0,0 +1,5 @@ +var child_process = require('child_process'); + +module.exports = function (name) { + return child_process.execSync("cat " + name).toString(); +}; From b72404dc9990c56896e1ae06707d4b20ba519aa6 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 14:07:49 +0100 Subject: [PATCH 080/236] add change note --- change-notes/1.24/analysis-javascript.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 47fb2dc590b..eaebf0c1251 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -39,6 +39,8 @@ | Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. | | Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | | Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | +| Useless use of cat (`js/useless-use-of-cat`) | correctness, security, maintainability | Highlights command executions of cat where the fs API should be used instead. Results are shown on LGTM by default. | + ## Changes to existing queries From b20e8520f6cfe15fe5217daeaaea37450b987817 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 14:52:08 +0100 Subject: [PATCH 081/236] add default message if not pretty printed call can be created --- .../ql/src/Security/CWE-078/UselessUseOfCat.ql | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index 4f080711719..9f28c5fb0cf 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -14,10 +14,12 @@ import javascript import semmle.javascript.security.UselessUseOfCat import semmle.javascript.RestrictedLocations - from UselessCat cat, string message -where - message = " Can be replaced with: " + PrettyPrintCatCall::createReadFileCall(cat) - or - not exists(PrettyPrintCatCall::createReadFileCall(cat)) and message = "" +where + message = " Can be replaced with: " + PrettyPrintCatCall::createReadFileCall(cat) + or + not exists(PrettyPrintCatCall::createReadFileCall(cat)) and + if cat.isSync() + then message = " Can be replaced with a call to fs.readFileSync(..)." + else message = " Can be replaced with a call to fs.readFile(..)." select cat.asExpr().(FirstLineOf), "Useless use of `cat`." + message \ No newline at end of file From 9d629aef95559daf7408fa8a51c5e8d73f5cf7cf Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 24 Feb 2020 11:10:56 +0100 Subject: [PATCH 082/236] Python: Highlight py/use-of-input is for Python 2 --- python/ql/src/Expressions/UseofInput.qhelp | 8 ++++---- python/ql/src/Expressions/UseofInput.ql | 6 +++--- .../ql/test/2/query-tests/Expressions/UseofInput.expected | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/python/ql/src/Expressions/UseofInput.qhelp b/python/ql/src/Expressions/UseofInput.qhelp index 44baace8c43..10aacce7e26 100644 --- a/python/ql/src/Expressions/UseofInput.qhelp +++ b/python/ql/src/Expressions/UseofInput.qhelp @@ -3,20 +3,20 @@ "qhelp.dtd"> -

    A call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    +

    In Python 2, a call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    -

    Get user input with raw_input(prompt) and then validate that input before evaluating. If the expected input is a number or +

    Get user input with raw_input(prompt) and then validate that input before evaluating. If the expected input is a number or string, then ast.literal_eval() can always be used safely.

    -
  • Python Standard Library: input, - ast.literal_eval.
  • +
  • Python Standard Library: input, + ast.literal_eval.
  • Wikipedia: Data validation.
  • diff --git a/python/ql/src/Expressions/UseofInput.ql b/python/ql/src/Expressions/UseofInput.ql index 8f601d5fafa..dc67458a083 100644 --- a/python/ql/src/Expressions/UseofInput.ql +++ b/python/ql/src/Expressions/UseofInput.ql @@ -1,6 +1,6 @@ /** - * @name 'input' function used - * @description The built-in function 'input' is used which can allow arbitrary code to be run. + * @name 'input' function used in Python 2 + * @description The built-in function 'input' is used which, in Python 2, can allow arbitrary code to be run. * @kind problem * @tags security * correctness @@ -18,4 +18,4 @@ where call.getFunction() = func and func.pointsTo(context, Value::named("input"), _) and not func.pointsTo(context, Value::named("raw_input"), _) -select call, "The unsafe built-in function 'input' is used." +select call, "The unsafe built-in function 'input' is used in Python 2." diff --git a/python/ql/test/2/query-tests/Expressions/UseofInput.expected b/python/ql/test/2/query-tests/Expressions/UseofInput.expected index 8f2b1fedd44..470c24df21f 100644 --- a/python/ql/test/2/query-tests/Expressions/UseofInput.expected +++ b/python/ql/test/2/query-tests/Expressions/UseofInput.expected @@ -1 +1 @@ -| expressions_test.py:6:12:6:18 | ControlFlowNode for input() | The unsafe built-in function 'input' is used. | +| expressions_test.py:6:12:6:18 | ControlFlowNode for input() | The unsafe built-in function 'input' is used in Python 2. | From afd6ea262840811512b0b5a7fe62b3adbc342d9c Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 24 Feb 2020 17:54:29 +0100 Subject: [PATCH 083/236] small correction in doc + autoformat --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index bcdaa2eff4c..29b18ecd2c9 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -40,9 +40,7 @@ private class CommandCall extends DataFlow::InvokeNode { /** * Gets the data-flow node (if it exists) for an options argument for an `exec`-like call. */ - DataFlow::Node getOptionsArg() { - result = command.getOptionsArg() - } + DataFlow::Node getOptionsArg() { result = command.getOptionsArg() } /** * Gets the constant-string parts that are not part of the command itself. @@ -78,7 +76,7 @@ private class CommandCall extends DataFlow::InvokeNode { /** * Gets the constant string parts from a data-flow node. - * Either the result is a constant string value that the node can hold, or the node is a string-concatenation and the result is the string parts from the concatenation. + * Either the result is a constant string value that the node can hold, or the node is a string-concatenation and the result is the string parts from the concatenation. */ private string getConstantStringParts(DataFlow::Node node) { node.mayHaveStringValue(result) @@ -108,7 +106,7 @@ class UselessCat extends CommandCall { prop = "encoding" ) ) and - // If there is a callback, then it must either have one or two arguments, or if there is a third argument it must be unused. + // If there is a callback, then it must either have one or two parameters, or if there is a third parameter it must be unused. ( not exists(getCallback()) or From 43bcd5b26c1cb9e6813c2db7c9afc3ecb770789c Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Mon, 24 Feb 2020 15:08:31 -0500 Subject: [PATCH 084/236] Add guidelines for experimental CodeQL queries and libraries --- CONTRIBUTING.md | 2 ++ cpp/ql/src/experimental/README.md | 1 + cpp/ql/test/experimental/README.md | 1 + csharp/ql/src/experimental/README.md | 1 + csharp/ql/test/experimental/README.md | 1 + docs/experimental.md | 42 +++++++++++++++++++++++ java/ql/src/experimental/README.md | 1 + java/ql/test/experimental/README.md | 1 + javascript/ql/src/experimental/README.md | 1 + javascript/ql/test/experimental/README.md | 1 + python/ql/src/experimental/README.md | 1 + python/ql/test/experimental/README.md | 1 + 12 files changed, 54 insertions(+) create mode 100644 cpp/ql/src/experimental/README.md create mode 100644 cpp/ql/test/experimental/README.md create mode 100644 csharp/ql/src/experimental/README.md create mode 100644 csharp/ql/test/experimental/README.md create mode 100644 docs/experimental.md create mode 100644 java/ql/src/experimental/README.md create mode 100644 java/ql/test/experimental/README.md create mode 100644 javascript/ql/src/experimental/README.md create mode 100644 javascript/ql/test/experimental/README.md create mode 100644 python/ql/src/experimental/README.md create mode 100644 python/ql/test/experimental/README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f22bc016c19..0583374555b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,8 @@ Follow the steps below to help other users understand what your query does, and Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query. For more information on writing query help, see the [Query help style guide](https://github.com/Semmle/ql/blob/master/docs/query-help-style-guide.md). +In addition to contributions to our standard queries and libraries, we also welcome contributions of a more experimental nature, which do not need to fulfill all the requirements listed above. See the guidelines for [experimental queries and libraries](docs/experimental.md) for details. + ## Using your personal data If you contribute to this project, we will record your name and email diff --git a/cpp/ql/src/experimental/README.md b/cpp/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/cpp/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/cpp/ql/test/experimental/README.md b/cpp/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/cpp/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/csharp/ql/src/experimental/README.md b/csharp/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/csharp/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/csharp/ql/test/experimental/README.md b/csharp/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/csharp/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/docs/experimental.md b/docs/experimental.md new file mode 100644 index 00000000000..5a15cfa3779 --- /dev/null +++ b/docs/experimental.md @@ -0,0 +1,42 @@ +# Experimental CodeQL queries and libraries + +In addition to our standard CodeQL queries and libraries, this repository may also contain queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our standard libraries and queries. + +## Requirements + +1. **Directory structure** + + - Experimental queries and libraries are stored in the `experimental` subdirectory within each language-specific directory in the [CodeQL repository](https://github.com/Semmle/ql). For example, experimental Java queries and libraries are stored in `ql/java/ql/src/experimental` and any corresponding tests in `ql/java/ql/test/experimental`. + - The structure of an `experimental` subdirectory mirrors the structure of standard queries and libraries (or tests) in the parent directory. + +2. **Query metadata** + + - The query `@id` must not clash with any other queries in the repository. + - The query must have a `@name` and `@description` to explain its purpose. + - The query must have a `@kind` and `@problem.severity` as required by CodeQL tools. + + For details, see the [guide on query metadata](https://github.com/Semmle/ql/blob/master/docs/query-metadata-style-guide.md). + +3. **Formatting** + + - The queries and libraries must be [autoformatted](https://help.semmle.com/codeql/codeql-for-vscode/reference/editor.html#autoformatting). + +4. **Compilation** + + - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot depend on the output of `getAQlClass` and it cannot use internal APIs. + - The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations). + +5. **Results** + + - The query must have at least one true positive result on some revision of a real project. + +6. **Contributor License Agreement** + + - The contributor can satisfy the [CLA](CONTRIBUTING.md#contributor-license-agreement). + +## Non-requirements + +Other criteria typically required for our standard queries and libraries are not required for experimental queries and libraries. In particular, fully disciplined query [metadata](docs/query-metadata-style-guide.md), query [help](docs/query-help-style-guide.md), tests, a low false positive rate and performance tuning are not required (but nonetheless recommended). + +Experimental queries and libraries may not be actively maintained as the standard libraries evolve and may be removed in the future. + diff --git a/java/ql/src/experimental/README.md b/java/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/java/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/java/ql/test/experimental/README.md b/java/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/java/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/javascript/ql/src/experimental/README.md b/javascript/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/javascript/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/javascript/ql/test/experimental/README.md b/javascript/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/javascript/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/python/ql/src/experimental/README.md b/python/ql/src/experimental/README.md new file mode 100644 index 00000000000..fc07363b24f --- /dev/null +++ b/python/ql/src/experimental/README.md @@ -0,0 +1 @@ +This directory contains [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. diff --git a/python/ql/test/experimental/README.md b/python/ql/test/experimental/README.md new file mode 100644 index 00000000000..2a9db304b9f --- /dev/null +++ b/python/ql/test/experimental/README.md @@ -0,0 +1 @@ +This directory contains tests for [experimental](../../../../docs/experimental.md) CodeQL queries and libraries. From d540caecdddc3b6bff300f5be6f2691394c954df Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 25 Feb 2020 10:04:51 +0100 Subject: [PATCH 085/236] Apply suggestions from code review Co-Authored-By: Esben Sparre Andreasen --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 29b18ecd2c9..1d6480f3b09 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -97,7 +97,7 @@ class UselessCat extends CommandCall { getArgument(0).mayHaveStringValue(getACatExecuteable()) ) and // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. - not exists(getNonCommandConstantString().regexpFind("\\*|\\||>|<| |\\$|&|,|\\`", _, _)) and + not exists(getNonCommandConstantString().regexpFind("\\*|\\||>|<| |\\$|&|,|\\`| ", _, _)) and // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. ( not exists(getOptionsArg()) @@ -135,7 +135,7 @@ class UselessCat extends CommandCall { * Gets a string used to call `cat`. */ string getACatExecuteable() { - result = "cat" or result = "/bin/cat" or result = "sudo cat" or result = "sudo /bin/cat" + result = "cat" or result = "/bin/cat" } /** From 87d283aa6c7089e1869c367c1c3d9941a326f976 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 25 Feb 2020 10:50:59 +0100 Subject: [PATCH 086/236] add tests for third party command execution libraries (and two small fixes) --- .../frameworks/SystemCommandExecutors.qll | 5 ++-- .../javascript/security/UselessUseOfCat.qll | 6 ++--- .../Security/CWE-078/UselessUseOfCat.expected | 19 ++++++++++++++ .../Security/CWE-078/uselesscat.js | 26 ++++++++++++++++++- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 466ed29ae0a..89a0929a182 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -70,14 +70,15 @@ private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::I arg = getACommandArgument() and shell = true } + override DataFlow::Node getArgumentList() { shell = false and result = getArgument(1) } + override predicate isSync() { sync = true } override DataFlow::Node getOptionsArg() { (if optionsArg < 0 then - result = getArgument(getNumArgument() - optionsArg) + result = getArgument(getNumArgument() + optionsArg) and getNumArgument() + optionsArg > cmdArg else result = getArgument(optionsArg)) and - not result = getArgument(0) and not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback not result.getALocalSource() instanceof DataFlow::ArrayCreationNode // looks like argumentlist } diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 1d6480f3b09..7cd66f7981b 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -21,9 +21,9 @@ private class CommandCall extends DataFlow::InvokeNode { predicate isSync() { command.isSync() } /** - * Gets an argument to this command execution that specifies the argument list to the command. + * Gets a list that specifies the arguments given to the command. */ - DataFlow::Node getArgumentList() { result = command.getArgumentList() } + DataFlow::ArrayCreationNode getArgumentList() { result = command.getArgumentList().getALocalSource() } /** * Gets the callback (if it exists) for an async `exec`-like call. @@ -35,7 +35,7 @@ private class CommandCall extends DataFlow::InvokeNode { /** * Holds if the executed command execution has an argument list as a separate argument. */ - predicate hasArgumentList() { exists(command.getArgumentList()) } + predicate hasArgumentList() { exists(getArgumentList()) } /** * Gets the data-flow node (if it exists) for an options argument for an `exec`-like call. diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected index 9e53b8cc422..4f2dae3d7b1 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.expected @@ -22,6 +22,18 @@ readFile | uselesscat.js:121:12:121:64 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | | uselesscat.js:127:14:127:66 | exec("c ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | | uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | fs.readFileSync("/etc/dnsmasq.conf", ...) | +| uselesscat.js:146:1:146:61 | shelljs ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:147:1:147:47 | shelljs ... utf8'}) | fs.readFile("foo/bar", {encoding: 'utf8'}) | +| uselesscat.js:148:1:148:81 | shelljs ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:151:1:151:48 | cspawn( ... tf8' }) | fs.readFile("foo/bar", { encoding: 'utf8' }) | +| uselesscat.js:152:1:152:82 | cspawn( ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:153:1:153:60 | cspawn( ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:154:1:154:26 | cspawn( ... /bar']) | fs.readFile("foo/bar") | +| uselesscat.js:158:16:158:46 | cspawn. ... /bar']) | fs.readFileSync("foo/bar") | +| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | fs.readFileSync("foo/bar", { encoding: 'utf8' }) | +| uselesscat.js:162:1:162:56 | execmod ... (out)}) | fs.readFile("foo/bar", (err, out) => {...}) | +| uselesscat.js:163:1:163:42 | execmod ... utf8'}) | fs.readFile("foo/bar") | +| uselesscat.js:164:1:164:76 | execmod ... (out)}) | fs.readFile("foo/bar", {encoding: 'utf8'}, (err, out) => {...}) | syncCommand | child_process-test.js:9:5:9:22 | cp.execSync("foo") | | child_process-test.js:11:5:11:26 | cp.exec ... ("foo") | @@ -71,6 +83,8 @@ syncCommand | uselesscat.js:100:1:100:56 | execFil ... ptions) | | uselesscat.js:104:1:104:31 | execFil ... cat` ]) | | uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | +| uselesscat.js:158:16:158:46 | cspawn. ... /bar']) | +| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | options | child_process-test.js:53:5:53:59 | cp.spaw ... cmd])) | child_process-test.js:53:25:53:58 | ['/C', ... , cmd]) | | child_process-test.js:54:5:54:50 | cp.spaw ... t(cmd)) | child_process-test.js:54:25:54:49 | ['/C', ... at(cmd) | @@ -88,6 +102,11 @@ options | uselesscat.js:100:1:100:56 | execFil ... ptions) | uselesscat.js:100:42:100:55 | unknownOptions | | uselesscat.js:111:1:111:51 | spawn(' ... it'] }) | uselesscat.js:111:14:111:50 | { stdio ... rit'] } | | uselesscat.js:136:17:138:2 | execSyn ... tf8'\\n}) | uselesscat.js:136:51:138:1 | { // NO ... utf8'\\n} | +| uselesscat.js:147:1:147:47 | shelljs ... utf8'}) | uselesscat.js:147:29:147:46 | {encoding: 'utf8'} | +| uselesscat.js:151:1:151:48 | cspawn( ... tf8' }) | uselesscat.js:151:28:151:47 | { encoding: 'utf8' } | +| uselesscat.js:156:1:156:35 | cspawn( ... tf8' }) | uselesscat.js:156:15:156:34 | { encoding: 'utf8' } | +| uselesscat.js:159:16:159:68 | cspawn. ... tf8' }) | uselesscat.js:159:48:159:67 | { encoding: 'utf8' } | +| uselesscat.js:164:1:164:76 | execmod ... (out)}) | uselesscat.js:164:24:164:41 | {encoding: 'utf8'} | #select | False negative | uselesscat.js:54:42:54:69 | // NOT ... lagged] | | False positive | uselesscat.js:44:37:44:85 | // OK [ ... le read | diff --git a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js index 86647b9fc29..ace4784b380 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js +++ b/javascript/ql/test/query-tests/Security/CWE-078/uselesscat.js @@ -139,4 +139,28 @@ const stdout2 = execSync('cat /etc/dnsmasq.conf', { // NOT OK. exec('/bin/cat', function (e, s) {}); // OK -spawn("cat") // OK \ No newline at end of file +spawn("cat") // OK + + +var shelljs = require("shelljs"); +shelljs.exec("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK +shelljs.exec("cat foo/bar", {encoding: 'utf8'}); // NOT OK +shelljs.exec("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // NOT OK + +let cspawn = require('cross-spawn'); +cspawn('cat', ['foo/bar'], { encoding: 'utf8' }); // NOT OK +cspawn('cat', ['foo/bar'], { encoding: 'utf8' }, (err, out) => {console.log(out)}); // NOT OK +cspawn('cat', ['foo/bar'], (err, out) => {console.log(out)}); // NOT OK +cspawn('cat', ['foo/bar']); // NOT OK +cspawn('cat', (err, out) => {console.log(out)}); // OK +cspawn('cat', { encoding: 'utf8' }); // OK + +let myResult = cspawn.sync('cat', ['foo/bar']); // NOT OK +let myResult = cspawn.sync('cat', ['foo/bar'], { encoding: 'utf8' }); // NOT OK + +var execmod = require('exec'); +execmod("cat foo/bar", (err, out) => {console.log(out)}); // NOT OK +execmod("cat foo/bar", {encoding: 'utf8'}); // NOT OK +execmod("cat foo/bar", {encoding: 'utf8'}, (err, out) => {console.log(out)}); // NOT OK + + \ No newline at end of file From 8d26f3219950f887ab3ccb27e7807e94deb17fce Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 25 Feb 2020 10:53:07 +0100 Subject: [PATCH 087/236] arg -> param --- .../src/semmle/javascript/security/UselessUseOfCat.qll | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 7cd66f7981b..674aa83e52b 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -185,20 +185,20 @@ module PrettyPrintCatCall { * Create a string representing the callback `func`. */ string createCallbackString(DataFlow::FunctionNode func) { - exists(string args | args = createCallbackArgs(func) | + exists(string params | params = createCallbackParams(func) | if func.getFunction() instanceof ArrowFunctionExpr then if func.getFunction().getBody() instanceof Expr - then result = ", (" + args + ") => ..." - else result = ", (" + args + ") => {...}" - else result = ", function(" + args + ") {...}" + then result = ", (" + params + ") => ..." + else result = ", (" + params + ") => {...}" + else result = ", function(" + params + ") {...}" ) } /** * Create a string concatenation of the parameter names in a function `func`. */ - private string createCallbackArgs(DataFlow::FunctionNode func) { + private string createCallbackParams(DataFlow::FunctionNode func) { result = concat(int i | i = [0 .. func.getNumParameter()] From c83c27cbc4bbc4d4f8f4d1e999625449534ab071 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 25 Feb 2020 11:11:58 +0100 Subject: [PATCH 088/236] add extra sanity-check that the output looks good --- .../javascript/security/UselessUseOfCat.qll | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 674aa83e52b..a0dfaa26fb6 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -74,6 +74,14 @@ private class CommandCall extends DataFlow::InvokeNode { } } +/** + * Holds if the input `str` contains some character that might be interpreted in a non-trivial way by a shell. + */ +bindingset[str] +predicate containsNonTrivialBashChar(string str) { + exists(str.regexpFind("\\*|\\||>|<| |\\$|&|,|\\`| ", _, _)) +} + /** * Gets the constant string parts from a data-flow node. * Either the result is a constant string value that the node can hold, or the node is a string-concatenation and the result is the string parts from the concatenation. @@ -97,7 +105,7 @@ class UselessCat extends CommandCall { getArgument(0).mayHaveStringValue(getACatExecuteable()) ) and // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. - not exists(getNonCommandConstantString().regexpFind("\\*|\\||>|<| |\\$|&|,|\\`| ", _, _)) and + not containsNonTrivialBashChar(getNonCommandConstantString()) and // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. ( not exists(getOptionsArg()) @@ -146,7 +154,7 @@ module PrettyPrintCatCall { * Create a string representation of an equivalent call to `fs.readFile` for a given command execution `cat`. */ string createReadFileCall(UselessCat cat) { - exists(string sync, string extraArg, string callback | + exists(string sync, string extraArg, string callback, string fileArg | (if cat.isSync() then sync = "Sync" else sync = "") and ( exists(cat.getOptionsArg()) and @@ -163,10 +171,12 @@ module PrettyPrintCatCall { callback = createCallbackString(cat.getCallback()) or callback = "" and not exists(cat.getCallback()) - ) + ) and + fileArg = createFileArgument(cat).trim() and + not(containsNonTrivialBashChar(fileArg.regexpReplaceAll("\\$|\\`| ", ""))) // string concat might contain " ", template strings might contain "$" or `, and that is OK. | result = - "fs.readFile" + sync + "(" + createFileArgument(cat).trim() + extraArg + callback + ")" + "fs.readFile" + sync + "(" + fileArg + extraArg + callback + ")" ) } From 8f7010157249cf2e0c5cea3002474f8e92e60d5b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 25 Feb 2020 15:39:07 +0100 Subject: [PATCH 089/236] Python: docs: Use tag consistently in UseofInput.qhelp --- python/ql/src/Expressions/UseofInput.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Expressions/UseofInput.qhelp b/python/ql/src/Expressions/UseofInput.qhelp index 10aacce7e26..f622a3c584b 100644 --- a/python/ql/src/Expressions/UseofInput.qhelp +++ b/python/ql/src/Expressions/UseofInput.qhelp @@ -3,7 +3,7 @@ "qhelp.dtd"> -

    In Python 2, a call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    +

    In Python 2, a call to the input() function, input(prompt) is equivalent to eval(raw_input(prompt)). Evaluating user input without any checking can be a serious security flaw.

    From f10a86d3ac16cad17cdf59a21ff1f9720407a82b Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 25 Feb 2020 15:52:00 +0100 Subject: [PATCH 090/236] Python: Remove `--optimize: true` from options files Tests will be run with optimizations on by default now. --- python/ql/test/2/library-tests/six/options | 1 - python/ql/test/3/library-tests/PointsTo/typehints/options | 1 - python/ql/test/3/library-tests/six/options | 1 - python/ql/test/library-tests/PointsTo/guarded/options | 1 - python/ql/test/library-tests/PointsTo/new/options | 1 - python/ql/test/library-tests/PointsTo/returns/options | 1 - python/ql/test/library-tests/modules/duplicate_name/options | 1 - python/ql/test/library-tests/regex/options | 1 - python/ql/test/library-tests/web/bottle/options | 1 - python/ql/test/library-tests/web/cherrypy/options | 1 - python/ql/test/library-tests/web/django/options | 1 - python/ql/test/library-tests/web/falcon/options | 1 - python/ql/test/library-tests/web/flask/options | 1 - python/ql/test/library-tests/web/pyramid/options | 1 - python/ql/test/library-tests/web/tornado/options | 1 - python/ql/test/library-tests/web/turbogears/options | 1 - python/ql/test/library-tests/web/twisted/options | 1 - python/ql/test/query-tests/Security/CWE-022/options | 1 - python/ql/test/query-tests/Security/CWE-078/options | 1 - python/ql/test/query-tests/Security/CWE-094/options | 1 - python/ql/test/query-tests/Security/CWE-326/options | 1 - python/ql/test/query-tests/Security/CWE-327/options | 1 - python/ql/test/query-tests/Security/CWE-377/options | 1 - python/ql/test/query-tests/Security/CWE-502/options | 1 - python/ql/test/query-tests/Security/CWE-732/options | 1 - python/ql/test/query-tests/Security/options | 1 - 26 files changed, 26 deletions(-) delete mode 100644 python/ql/test/library-tests/PointsTo/returns/options delete mode 100644 python/ql/test/query-tests/Security/options diff --git a/python/ql/test/2/library-tests/six/options b/python/ql/test/2/library-tests/six/options index 55e896b67f2..573a285f101 100644 --- a/python/ql/test/2/library-tests/six/options +++ b/python/ql/test/2/library-tests/six/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=2 --max-import-depth=4 -optimize: true diff --git a/python/ql/test/3/library-tests/PointsTo/typehints/options b/python/ql/test/3/library-tests/PointsTo/typehints/options index b3e60ec219e..666fe9447a1 100644 --- a/python/ql/test/3/library-tests/PointsTo/typehints/options +++ b/python/ql/test/3/library-tests/PointsTo/typehints/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=3 -p ../../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/3/library-tests/six/options b/python/ql/test/3/library-tests/six/options index 02026a7dcc5..61838b00376 100644 --- a/python/ql/test/3/library-tests/six/options +++ b/python/ql/test/3/library-tests/six/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=3 --max-import-depth=4 -optimize: true diff --git a/python/ql/test/library-tests/PointsTo/guarded/options b/python/ql/test/library-tests/PointsTo/guarded/options index be048160aeb..e7a518df45b 100644 --- a/python/ql/test/library-tests/PointsTo/guarded/options +++ b/python/ql/test/library-tests/PointsTo/guarded/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -optimize: true diff --git a/python/ql/test/library-tests/PointsTo/new/options b/python/ql/test/library-tests/PointsTo/new/options index 8e16f310b52..2e240a38e85 100644 --- a/python/ql/test/library-tests/PointsTo/new/options +++ b/python/ql/test/library-tests/PointsTo/new/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=4 -optimize: true diff --git a/python/ql/test/library-tests/PointsTo/returns/options b/python/ql/test/library-tests/PointsTo/returns/options deleted file mode 100644 index 58ad829f5a8..00000000000 --- a/python/ql/test/library-tests/PointsTo/returns/options +++ /dev/null @@ -1 +0,0 @@ -optimize: true diff --git a/python/ql/test/library-tests/modules/duplicate_name/options b/python/ql/test/library-tests/modules/duplicate_name/options index ebe93df725a..3819071b01c 100644 --- a/python/ql/test/library-tests/modules/duplicate_name/options +++ b/python/ql/test/library-tests/modules/duplicate_name/options @@ -1,2 +1 @@ semmle-extractor-options: -R . -optimize: true diff --git a/python/ql/test/library-tests/regex/options b/python/ql/test/library-tests/regex/options index be048160aeb..e7a518df45b 100644 --- a/python/ql/test/library-tests/regex/options +++ b/python/ql/test/library-tests/regex/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -optimize: true diff --git a/python/ql/test/library-tests/web/bottle/options b/python/ql/test/library-tests/web/bottle/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/bottle/options +++ b/python/ql/test/library-tests/web/bottle/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/cherrypy/options b/python/ql/test/library-tests/web/cherrypy/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/cherrypy/options +++ b/python/ql/test/library-tests/web/cherrypy/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/django/options b/python/ql/test/library-tests/web/django/options index d7a40c71d46..a87f995c396 100644 --- a/python/ql/test/library-tests/web/django/options +++ b/python/ql/test/library-tests/web/django/options @@ -1,2 +1 @@ semmle-extractor-options: --lang=3 --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/falcon/options b/python/ql/test/library-tests/web/falcon/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/falcon/options +++ b/python/ql/test/library-tests/web/falcon/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/flask/options b/python/ql/test/library-tests/web/flask/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/flask/options +++ b/python/ql/test/library-tests/web/flask/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/pyramid/options b/python/ql/test/library-tests/web/pyramid/options index 0267f80a6d6..1d132442a3b 100644 --- a/python/ql/test/library-tests/web/pyramid/options +++ b/python/ql/test/library-tests/web/pyramid/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/tornado/options b/python/ql/test/library-tests/web/tornado/options index 0267f80a6d6..1d132442a3b 100644 --- a/python/ql/test/library-tests/web/tornado/options +++ b/python/ql/test/library-tests/web/tornado/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/turbogears/options b/python/ql/test/library-tests/web/turbogears/options index e6bd3a732dc..7fb713d5924 100644 --- a/python/ql/test/library-tests/web/turbogears/options +++ b/python/ql/test/library-tests/web/turbogears/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=3 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/library-tests/web/twisted/options b/python/ql/test/library-tests/web/twisted/options index 6979a743e58..4084b102b55 100644 --- a/python/ql/test/library-tests/web/twisted/options +++ b/python/ql/test/library-tests/web/twisted/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=1 -p ../../../query-tests/Security/lib/ -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-022/options b/python/ql/test/query-tests/Security/CWE-022/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-022/options +++ b/python/ql/test/query-tests/Security/CWE-022/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-078/options b/python/ql/test/query-tests/Security/CWE-078/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-078/options +++ b/python/ql/test/query-tests/Security/CWE-078/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-094/options b/python/ql/test/query-tests/Security/CWE-094/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-094/options +++ b/python/ql/test/query-tests/Security/CWE-094/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-326/options b/python/ql/test/query-tests/Security/CWE-326/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-326/options +++ b/python/ql/test/query-tests/Security/CWE-326/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-327/options b/python/ql/test/query-tests/Security/CWE-327/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-327/options +++ b/python/ql/test/query-tests/Security/CWE-327/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-377/options b/python/ql/test/query-tests/Security/CWE-377/options index dce78f52325..492768b3481 100644 --- a/python/ql/test/query-tests/Security/CWE-377/options +++ b/python/ql/test/query-tests/Security/CWE-377/options @@ -1,2 +1 @@ semmle-extractor-options: -p ../lib/ --max-import-depth=3 -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-502/options b/python/ql/test/query-tests/Security/CWE-502/options index b63c517ee29..84717fe64cf 100644 --- a/python/ql/test/query-tests/Security/CWE-502/options +++ b/python/ql/test/query-tests/Security/CWE-502/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../lib -optimize: true diff --git a/python/ql/test/query-tests/Security/CWE-732/options b/python/ql/test/query-tests/Security/CWE-732/options index b63c517ee29..84717fe64cf 100644 --- a/python/ql/test/query-tests/Security/CWE-732/options +++ b/python/ql/test/query-tests/Security/CWE-732/options @@ -1,2 +1 @@ semmle-extractor-options: --max-import-depth=2 -p ../lib -optimize: true diff --git a/python/ql/test/query-tests/Security/options b/python/ql/test/query-tests/Security/options deleted file mode 100644 index 58ad829f5a8..00000000000 --- a/python/ql/test/query-tests/Security/options +++ /dev/null @@ -1 +0,0 @@ -optimize: true From d06caefd8e9872f138bd8eeb3c8c5ea7686ac295 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Tue, 25 Feb 2020 11:17:42 -0500 Subject: [PATCH 091/236] Address code review comments for `experimental.md` --- docs/experimental.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/experimental.md b/docs/experimental.md index 5a15cfa3779..fef4729cfd2 100644 --- a/docs/experimental.md +++ b/docs/experimental.md @@ -2,6 +2,8 @@ In addition to our standard CodeQL queries and libraries, this repository may also contain queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our standard libraries and queries. +Experimental queries and libraries may not be actively maintained as the standard libraries evolve, may be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings. + ## Requirements 1. **Directory structure** @@ -23,7 +25,7 @@ In addition to our standard CodeQL queries and libraries, this repository may al 4. **Compilation** - - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot depend on the output of `getAQlClass` and it cannot use internal APIs. + - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot use internal APIs, cannot depend on the output of `getAQlClass` and cannot make use of regexp matching on `toString`. - The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations). 5. **Results** @@ -38,5 +40,3 @@ In addition to our standard CodeQL queries and libraries, this repository may al Other criteria typically required for our standard queries and libraries are not required for experimental queries and libraries. In particular, fully disciplined query [metadata](docs/query-metadata-style-guide.md), query [help](docs/query-help-style-guide.md), tests, a low false positive rate and performance tuning are not required (but nonetheless recommended). -Experimental queries and libraries may not be actively maintained as the standard libraries evolve and may be removed in the future. - From 04951faf860697d0b31d4cbed7b1ea72de9598b7 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 25 Feb 2020 09:43:51 -0800 Subject: [PATCH 092/236] autoformat --- python/ql/src/Expressions/IsComparisons.qll | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index d2f1e6b1c8f..0eed3bd9552 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -28,9 +28,9 @@ predicate probablySingleton(ClassValue cls) { predicate invalid_to_use_is_portably(ClassValue c) { overrides_eq_or_cmp(c) and - /* Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ */ + // Exclude type/builtin-function/bool as it is legitimate to compare them using 'is' but they implement __eq__ not c = Value::named("type") and not c = ClassValue::builtinFunction() and not c = Value::named("bool") and - /* OK to compare with 'is' if a singleton */ + // OK to compare with 'is' if a singleton not probablySingleton(c) } @@ -95,7 +95,7 @@ private predicate comparison_one_type(Compare comp, Cmpop op, ClassValue cls) { } predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) { - /* OK to use 'is' when defining '__eq__' */ + // OK to use 'is' when defining '__eq__' not exists(Function eq | eq.getName() = "__eq__" or eq.getName() = "__ne__" | eq = comp.getScope().getScope*()) and ( @@ -107,20 +107,20 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) ) ) and - /* OK to use 'is' when comparing items from a known set of objects */ + // OK to use 'is' when comparing items from a known set of objects not exists(Expr left, Expr right, Value val | comp.compares(left, op, right) and exists(ImmutableLiteral il | il.getLiteralValue() = val) | left.pointsTo(val) and right.pointsTo(val) or - /* Simple constant in module, probably some sort of sentinel */ + // Simple constant in module, probably some sort of sentinel exists(AstNode origin | not left.pointsTo(_) and right.pointsTo(val, origin) and origin.getScope().getEnclosingModule() = comp.getScope().getEnclosingModule() ) ) and - /* OK to use 'is' when comparing with a member of an enum */ + // OK to use 'is' when comparing with a member of an enumÏ not exists(Expr left, Expr right, AstNode origin | comp.compares(left, op, right) and enum_member(origin) | From 930228acc55a5e452d8b4872182c2b7f216e6339 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 25 Feb 2020 09:52:46 -0800 Subject: [PATCH 093/236] Un-autoformats --- .../src/semmle/python/objects/ObjectAPI.qll | 1290 ++++++++++------- 1 file changed, 733 insertions(+), 557 deletions(-) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 91f7a2fed1e..e865d7d649f 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -3,6 +3,9 @@ * A `Value` is a static approximation to a set of runtime objects. */ + + + import python private import TObject private import semmle.python.objects.ObjectInternal @@ -11,724 +14,897 @@ private import semmle.python.pointsto.PointsToContext private import semmle.python.pointsto.MRO private import semmle.python.types.Builtins -/* - * Use the term `ObjectSource` to refer to DB entity. Either a CFG node +/* Use the term `ObjectSource` to refer to DB entity. Either a CFG node * for Python objects, or `@py_cobject` entity for built-in objects. */ - class ObjectSource = Object; /* Aliases for scopes */ class FunctionScope = Function; - class ClassScope = Class; - class ModuleScope = Module; -/** - * Class representing values in the Python program +/** Class representing values in the Python program * Each `Value` is a static approximation to a set of one or more real objects. */ class Value extends TObject { - Value() { - this != ObjectInternal::unknown() and - this != ObjectInternal::unknownClass() and - this != ObjectInternal::undefined() - } - string toString() { result = this.(ObjectInternal).toString() } + Value() { + this != ObjectInternal::unknown() and + this != ObjectInternal::unknownClass() and + this != ObjectInternal::undefined() + } - /** Gets a `ControlFlowNode` that refers to this object. */ - ControlFlowNode getAReference() { PointsToInternal::pointsTo(result, _, this, _) } + string toString() { + result = this.(ObjectInternal).toString() + } - /** - * Gets the class of this object. - * Strictly, the `Value` representing the class of the objects - * represented by this Value. - */ - ClassValue getClass() { result = this.(ObjectInternal).getClass() } + /** Gets a `ControlFlowNode` that refers to this object. */ + ControlFlowNode getAReference() { + PointsToInternal::pointsTo(result, _, this, _) + } - /** Gets a call to this object */ - CallNode getACall() { result = this.getACall(_) } + /** Gets the class of this object. + * Strictly, the `Value` representing the class of the objects + * represented by this Value. + */ + ClassValue getClass() { + result = this.(ObjectInternal).getClass() + } - /** Gets a call to this object with the given `caller` context. */ - CallNode getACall(PointsToContext caller) { - PointsToInternal::pointsTo(result.getFunction(), caller, this, _) - or - exists(BoundMethodObjectInternal bm | - PointsToInternal::pointsTo(result.getFunction(), caller, bm, _) and - bm.getFunction() = this - ) - } + /** Gets a call to this object */ + CallNode getACall() { + result = this.getACall(_) + } - /** Gets a `Value` that represents the attribute `name` of this object. */ - Value attr(string name) { this.(ObjectInternal).attribute(name, result, _) } + /** Gets a call to this object with the given `caller` context. */ + CallNode getACall(PointsToContext caller) { + PointsToInternal::pointsTo(result.getFunction(), caller, this, _) + or + exists(BoundMethodObjectInternal bm | + PointsToInternal::pointsTo(result.getFunction(), caller, bm, _) and + bm.getFunction() = this + ) + } - /** - * Holds if this value is builtin. Applies to built-in functions and methods, - * but also integers and strings. - */ - predicate isBuiltin() { this.(ObjectInternal).isBuiltin() } + /** Gets a `Value` that represents the attribute `name` of this object. */ + Value attr(string name) { + this.(ObjectInternal).attribute(name, result, _) + } - /** INTERNAL -- Do not use */ - Builtin asBuiltin() { - this = TBuiltinClassObject(result) or - this = TBuiltinFunctionObject(result) or - this = TBuiltinMethodObject(result) or - this = TBuiltinModuleObject(result) or - this = TBuiltinOpaqueObject(result) or - this = TBuiltinTuple(result) - } + /** Holds if this value is builtin. Applies to built-in functions and methods, + * but also integers and strings. + */ + predicate isBuiltin() { + this.(ObjectInternal).isBuiltin() + } + + /** INTERNAL -- Do not use */ + Builtin asBuiltin() { + this = TBuiltinClassObject(result) or + this = TBuiltinFunctionObject(result) or + this = TBuiltinMethodObject(result) or + this = TBuiltinModuleObject(result) or + this = TBuiltinOpaqueObject(result) or + this = TBuiltinTuple(result) + } - predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { - this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) - or - not exists(this.(ObjectInternal).getOrigin()) and - filepath = "" and - bl = 0 and - bc = 0 and - el = 0 and - ec = 0 - } + predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { + this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) + or + not exists(this.(ObjectInternal).getOrigin()) and + filepath = "" and bl = 0 and bc = 0 and el = 0 and ec = 0 + } - /** - * Gets the name of this value, if it has one. - * Note this is the innate name of the - * object, not necessarily all the names by which it can be called. - */ - final string getName() { result = this.(ObjectInternal).getName() } + /** Gets the name of this value, if it has one. + * Note this is the innate name of the + * object, not necessarily all the names by which it can be called. + */ + final string getName() { + result = this.(ObjectInternal).getName() + } - /** Holds if this value has the attribute `name` */ - predicate hasAttribute(string name) { this.(ObjectInternal).hasAttribute(name) } + /** Holds if this value has the attribute `name` */ + predicate hasAttribute(string name) { + this.(ObjectInternal).hasAttribute(name) + } - /** Whether this value is absent from the database, but has been inferred to likely exist */ - predicate isAbsent() { - this instanceof AbsentModuleObjectInternal - or - this instanceof AbsentModuleAttributeObjectInternal - } + /** Whether this value is absent from the database, but has been inferred to likely exist */ + predicate isAbsent() { + this instanceof AbsentModuleObjectInternal + or + this instanceof AbsentModuleAttributeObjectInternal + } - /** - * Whether this overrides v. In this context, "overrides" means that this object - * is a named attribute of a some class C and `v` is a named attribute of another - * class S, both attributes having the same name, and S is a super class of C. - */ - predicate overrides(Value v) { - exists(ClassValue my_class, ClassValue other_class, string name | - my_class.declaredAttribute(name) = this and - other_class.declaredAttribute(name) = v and - my_class.getABaseType+() = other_class - ) - } + /** Whether this overrides v. In this context, "overrides" means that this object + * is a named attribute of a some class C and `v` is a named attribute of another + * class S, both attributes having the same name, and S is a super class of C. + */ + predicate overrides(Value v) { + exists(ClassValue my_class, ClassValue other_class, string name | + my_class.declaredAttribute(name) = this and + other_class.declaredAttribute(name) = v and + my_class.getABaseType+() = other_class + ) + } - /** - * Gets the boolean interpretation of this value. - * Could be both `true` and `false`, if we can't determine the result more precisely. - */ - boolean getABooleanValue() { result = this.(ObjectInternal).booleanValue() } + /** Gets the boolean interpretation of this value. + * Could be both `true` and `false`, if we can't determine the result more precisely. + */ + boolean getABooleanValue() { + result = this.(ObjectInternal).booleanValue() + } - /** - * Gets the boolean interpretation of this value, only if we can determine the result precisely. - * The result can be `none()`, but never both `true` and `false`. - */ - boolean getDefiniteBooleanValue() { - result = getABooleanValue() and - not (getABooleanValue() = true and getABooleanValue() = false) - } + /** Gets the boolean interpretation of this value, only if we can determine the result precisely. + * The result can be `none()`, but never both `true` and `false`. + */ + boolean getDefiniteBooleanValue() { + result = getABooleanValue() and + not (getABooleanValue() = true and getABooleanValue() = false) + } } -/** - * Class representing modules in the Python program +/** Class representing modules in the Python program * Each `ModuleValue` represents a module object in the Python program. */ class ModuleValue extends Value { - ModuleValue() { this instanceof ModuleObjectInternal } - /** - * Holds if this module "exports" name. - * That is, does it define `name` in `__all__` or is - * `__all__` not defined and `name` a global variable that does not start with "_" - * This is the set of names imported by `from ... import *`. - */ - predicate exports(string name) { PointsTo::moduleExports(this, name) } + ModuleValue() { + this instanceof ModuleObjectInternal + } - /** Gets the scope for this module, provided that it is a Python module. */ - ModuleScope getScope() { result = this.(ModuleObjectInternal).getSourceModule() } + /** Holds if this module "exports" name. + * That is, does it define `name` in `__all__` or is + * `__all__` not defined and `name` a global variable that does not start with "_" + * This is the set of names imported by `from ... import *`. + */ + predicate exports(string name) { + PointsTo::moduleExports(this, name) + } - /** - * Gets the container path for this module. Will be the file for a Python module, - * the folder for a package and no result for a builtin module. - */ - Container getPath() { - result = this.(PackageObjectInternal).getFolder() - or - result = this.(PythonModuleObjectInternal).getSourceModule().getFile() - } + /** Gets the scope for this module, provided that it is a Python module. */ + ModuleScope getScope() { + result = this.(ModuleObjectInternal).getSourceModule() + } - /** - * Whether this module is imported by 'import name'. For example on a linux system, - * the module 'posixpath' is imported as 'os.path' or as 'posixpath' - */ - predicate importedAs(string name) { PointsToInternal::module_imported_as(this, name) } + /** Gets the container path for this module. Will be the file for a Python module, + * the folder for a package and no result for a builtin module. + */ + Container getPath() { + result = this.(PackageObjectInternal).getFolder() + or + result = this.(PythonModuleObjectInternal).getSourceModule().getFile() + } - /** Whether this module is a package. */ - predicate isPackage() { this instanceof PackageObjectInternal } + /** Whether this module is imported by 'import name'. For example on a linux system, + * the module 'posixpath' is imported as 'os.path' or as 'posixpath' */ + predicate importedAs(string name) { + PointsToInternal::module_imported_as(this, name) + } - /** Whether the complete set of names "exported" by this module can be accurately determined */ - predicate hasCompleteExportInfo() { this.(ModuleObjectInternal).hasCompleteExportInfo() } + /** Whether this module is a package. */ + predicate isPackage() { + this instanceof PackageObjectInternal + } + + /** Whether the complete set of names "exported" by this module can be accurately determined */ + predicate hasCompleteExportInfo() { + this.(ModuleObjectInternal).hasCompleteExportInfo() + } + + /** Get a module that this module imports */ + ModuleValue getAnImportedModule() { + result.importedAs(this.getScope().getAnImportedModuleName()) + } - /** Get a module that this module imports */ - ModuleValue getAnImportedModule() { result.importedAs(this.getScope().getAnImportedModuleName()) } } module Module { - /** - * Gets the `ModuleValue` named `name`. - * - * Note that the name used to refer to a module is not - * necessarily its name. For example, - * there are modules referred to by the name `os.path`, - * but that are not named `os.path`, for example the module `posixpath`. - * Such that the following is true: - * `Module::named("os.path").getName() = "posixpath" - */ - ModuleValue named(string name) { - result.getName() = name - or - result = named(name, _) - } - /* Prevent runaway recursion when a module has itself as an attribute. */ - private ModuleValue named(string name, int dots) { - dots = 0 and - not name.charAt(_) = "." and - result.getName() = name - or - dots <= 3 and - exists(string modname, string attrname | name = modname + "." + attrname | - result = named(modname, dots - 1).attr(attrname) - ) - } + /** Gets the `ModuleValue` named `name`. + * + * Note that the name used to refer to a module is not + * necessarily its name. For example, + * there are modules referred to by the name `os.path`, + * but that are not named `os.path`, for example the module `posixpath`. + * Such that the following is true: + * `Module::named("os.path").getName() = "posixpath" + */ + ModuleValue named(string name) { + result.getName() = name + or + result = named(name, _) + } - /** Get the `ModuleValue` for the `builtin` module. */ - ModuleValue builtinModule() { result = TBuiltinModuleObject(Builtin::builtinModule()) } + /* Prevent runaway recursion when a module has itself as an attribute. */ + private ModuleValue named(string name, int dots) { + dots = 0 and not name.charAt(_) = "." and + result.getName() = name + or + dots <= 3 and + exists(string modname, string attrname | + name = modname + "." + attrname | + result = named(modname, dots-1).attr(attrname) + ) + } + + /** Get the `ModuleValue` for the `builtin` module. */ + ModuleValue builtinModule() { + result = TBuiltinModuleObject(Builtin::builtinModule()) + } } module Value { - /** - * Gets the `Value` named `name`. - * If there is at least one '.' in `name`, then the part of - * the name to the left of the rightmost '.' is interpreted as a module name - * and the part after the rightmost '.' as an attribute of that module. - * For example, `Value::named("os.path.join")` is the `Value` representing the - * `join` function of the `os.path` module. - * If there is no '.' in `name`, then the `Value` returned is the builtin - * object of that name. - * For example `Value::named("len")` is the `Value` representing the `len` built-in function. - */ - Value named(string name) { - exists(string modname, string attrname | name = modname + "." + attrname | - result = Module::named(modname).attr(attrname) - ) - or - result = ObjectInternal::builtin(name) - or - name = "None" and result = ObjectInternal::none_() - or - name = "True" and result = TTrue() - or - name = "False" and result = TFalse() - } - /** - * Gets the `NumericValue` for the integer constant `i`, if it exists. - * There will be no `NumericValue` for most integers, but the following are - * guaranteed to exist: - * * From zero to 511 inclusive. - * * All powers of 2 (up to 2**30) - * * Any integer explicitly mentioned in the source program. - */ - NumericValue forInt(int i) { result.(IntObjectInternal).intValue() = i } + /** Gets the `Value` named `name`. + * If there is at least one '.' in `name`, then the part of + * the name to the left of the rightmost '.' is interpreted as a module name + * and the part after the rightmost '.' as an attribute of that module. + * For example, `Value::named("os.path.join")` is the `Value` representing the + * `join` function of the `os.path` module. + * If there is no '.' in `name`, then the `Value` returned is the builtin + * object of that name. + * For example `Value::named("len")` is the `Value` representing the `len` built-in function. + */ + Value named(string name) { + exists(string modname, string attrname | + name = modname + "." + attrname | + result = Module::named(modname).attr(attrname) + ) + or + result = ObjectInternal::builtin(name) + or + name = "None" and result = ObjectInternal::none_() + or + name = "True" and result = TTrue() + or + name = "False" and result = TFalse() + } - /** - * Gets the `Value` for the bytes constant `bytes`, if it exists. - * There will be no `Value` for most byte strings, unless it is explicitly - * declared in the source program. - */ - StringValue forBytes(string bytes) { result.(BytesObjectInternal).strValue() = bytes } + /** Gets the `NumericValue` for the integer constant `i`, if it exists. + * There will be no `NumericValue` for most integers, but the following are + * guaranteed to exist: + * * From zero to 511 inclusive. + * * All powers of 2 (up to 2**30) + * * Any integer explicitly mentioned in the source program. + */ + NumericValue forInt(int i) { + result.(IntObjectInternal).intValue() = i + } - /** - * Gets the `Value` for the unicode constant `text`, if it exists. - * There will be no `Value` for most text strings, unless it is explicitly - * declared in the source program. - */ - StringValue forUnicode(string text) { result.(UnicodeObjectInternal).strValue() = text } + /** Gets the `Value` for the bytes constant `bytes`, if it exists. + * There will be no `Value` for most byte strings, unless it is explicitly + * declared in the source program. + */ + StringValue forBytes(string bytes) { + result.(BytesObjectInternal).strValue() = bytes + } - /** - * Gets a `Value` for the string `text`. May be a bytes or unicode string for Python 2. - * There will be no `Value` for most strings, unless it is explicitly - * declared in the source program. - */ - StringValue forString(string text) { - result.(UnicodeObjectInternal).strValue() = text - or - major_version() = 2 and - result.(BytesObjectInternal).strValue() = text - } + /** Gets the `Value` for the unicode constant `text`, if it exists. + * There will be no `Value` for most text strings, unless it is explicitly + * declared in the source program. + */ + StringValue forUnicode(string text) { + result.(UnicodeObjectInternal).strValue() = text + } - /** Gets the `Value` for the bool constant `b`. */ - Value forBool(boolean b) { - b = true and result = TTrue() - or - b = false and result = TFalse() - } + /** Gets a `Value` for the string `text`. May be a bytes or unicode string for Python 2. + * There will be no `Value` for most strings, unless it is explicitly + * declared in the source program. + */ + StringValue forString(string text) { + result.(UnicodeObjectInternal).strValue() = text + or + major_version() = 2 and + result.(BytesObjectInternal).strValue() = text + } + + /** Gets the `Value` for the bool constant `b`. */ + Value forBool(boolean b) { + b = true and result = TTrue() + or + b = false and result = TFalse() + } + + /** Gets the `Value` for `None`. */ + Value none_() { + result = ObjectInternal::none_() + } - /** Gets the `Value` for `None`. */ - Value none_() { result = ObjectInternal::none_() } } -/** - * Class representing callables in the Python program +/** Class representing callables in the Python program * Callables include Python functions, built-in functions and bound-methods, * but not classes. */ class CallableValue extends Value { - CallableValue() { this instanceof CallableObjectInternal } - /** - * Holds if this callable never returns once called. - * For example, `sys.exit` - */ - predicate neverReturns() { this.(CallableObjectInternal).neverReturns() } + CallableValue() { + this instanceof CallableObjectInternal + } - /** Gets the scope for this function, provided that it is a Python function. */ - FunctionScope getScope() { result = this.(PythonFunctionObjectInternal).getScope() } + /** Holds if this callable never returns once called. + * For example, `sys.exit` + */ + predicate neverReturns() { + this.(CallableObjectInternal).neverReturns() + } - /** Gets the `n`th parameter node of this callable. */ - NameNode getParameter(int n) { result = this.(CallableObjectInternal).getParameter(n) } + /** Gets the scope for this function, provided that it is a Python function. */ + FunctionScope getScope() { + result = this.(PythonFunctionObjectInternal).getScope() + } - /** Gets the `name`d parameter node of this callable. */ - NameNode getParameterByName(string name) { - result = this.(CallableObjectInternal).getParameterByName(name) - } + /** Gets the `n`th parameter node of this callable. */ + NameNode getParameter(int n) { + result = this.(CallableObjectInternal).getParameter(n) + } - /** Gets the argument corresponding to the `n'th parameter node of this callable. */ - cached - ControlFlowNode getArgumentForCall(CallNode call, int n) { - exists(ObjectInternal called, int offset | - PointsToInternal::pointsTo(call.getFunction(), _, called, _) and - called.functionAndOffset(this, offset) - | - call.getArg(n - offset) = result - or - exists(string name | - call.getArgByName(name) = result and - this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name - ) - or - called instanceof BoundMethodObjectInternal and - offset = 1 and - n = 0 and - result = call.getFunction().(AttrNode).getObject() - ) - } + /** Gets the `name`d parameter node of this callable. */ + NameNode getParameterByName(string name) { + result = this.(CallableObjectInternal).getParameterByName(name) + } + + /** Gets the argument corresponding to the `n'th parameter node of this callable. */ + cached ControlFlowNode getArgumentForCall(CallNode call, int n) { + exists(ObjectInternal called, int offset | + PointsToInternal::pointsTo(call.getFunction(), _, called, _) and + called.functionAndOffset(this, offset) + | + call.getArg(n-offset) = result + or + exists(string name | call.getArgByName(name) = result and this.(PythonFunctionObjectInternal).getScope().getArg(n+offset).getName() = name) + or + called instanceof BoundMethodObjectInternal and + offset = 1 and n = 0 and result = call.getFunction().(AttrNode).getObject() + ) + } + + + /** Gets the argument corresponding to the `name`d parameter node of this callable. */ + cached ControlFlowNode getNamedArgumentForCall(CallNode call, string name) { + exists(CallableObjectInternal called, int offset | + PointsToInternal::pointsTo(call.getFunction(), _, called, _) and + called.functionAndOffset(this, offset) + | + exists(int n | + call.getArg(n) = result and + this.(PythonFunctionObjectInternal).getScope().getArg(n+offset).getName() = name + ) + or + call.getArgByName(name) = result and + exists(this.(PythonFunctionObjectInternal).getScope().getArgByName(name)) + or + called instanceof BoundMethodObjectInternal and + offset = 1 and name = "self" and result = call.getFunction().(AttrNode).getObject() + ) + } - /** Gets the argument corresponding to the `name`d parameter node of this callable. */ - cached - ControlFlowNode getNamedArgumentForCall(CallNode call, string name) { - exists(CallableObjectInternal called, int offset | - PointsToInternal::pointsTo(call.getFunction(), _, called, _) and - called.functionAndOffset(this, offset) - | - exists(int n | - call.getArg(n) = result and - this.(PythonFunctionObjectInternal).getScope().getArg(n + offset).getName() = name - ) - or - call.getArgByName(name) = result and - exists(this.(PythonFunctionObjectInternal).getScope().getArgByName(name)) - or - called instanceof BoundMethodObjectInternal and - offset = 1 and - name = "self" and - result = call.getFunction().(AttrNode).getObject() - ) - } } -/** - * Class representing classes in the Python program, both Python and built-in. +/** Class representing classes in the Python program, both Python and built-in. */ class ClassValue extends Value { - ClassValue() { this.(ObjectInternal).isClass() = true } - /** Gets an improper super type of this class. */ - ClassValue getASuperType() { result = this.getABaseType*() } + ClassValue() { + this.(ObjectInternal).isClass() = true + } - /** - * Looks up the attribute `name` on this class. - * Note that this may be different from `this.attr(name)`. - * For example given the class: - * ```class C: - * @classmethod - * def f(cls): pass - * ``` - * `this.lookup("f")` is equivalent to `C.__dict__['f']`, which is the class-method - * whereas - * `this.attr("f") is equivalent to `C.f`, which is a bound-method. - */ - Value lookup(string name) { this.(ClassObjectInternal).lookup(name, result, _) } + /** Gets an improper super type of this class. */ + ClassValue getASuperType() { + result = this.getABaseType*() + } - predicate isCallable() { this.(ClassObjectInternal).lookup("__call__", _, _) } + /** Looks up the attribute `name` on this class. + * Note that this may be different from `this.attr(name)`. + * For example given the class: + * ```class C: + * @classmethod + * def f(cls): pass + * ``` + * `this.lookup("f")` is equivalent to `C.__dict__['f']`, which is the class-method + * whereas + * `this.attr("f") is equivalent to `C.f`, which is a bound-method. + */ + Value lookup(string name) { + this.(ClassObjectInternal).lookup(name, result, _) + } - /** Holds if this class is an iterable. */ - predicate isIterable() { - this.hasAttribute("__iter__") - or - this.hasAttribute("__aiter__") - or - this.hasAttribute("__getitem__") - } + predicate isCallable() { + this.(ClassObjectInternal).lookup("__call__", _, _) + } - /** Holds if this class is a descriptor. */ - predicate isDescriptorType() { this.hasAttribute("__get__") } + /** Holds if this class is an iterable. */ + predicate isIterable() { + this.hasAttribute("__iter__") + or + this.hasAttribute("__aiter__") + or + this.hasAttribute("__getitem__") + } - /** - * Gets the qualified name for this class. - * Should return the same name as the `__qualname__` attribute on classes in Python 3. - */ - string getQualifiedName() { - result = this.(ClassObjectInternal).getBuiltin().getName() - or - result = this.(PythonClassObjectInternal).getScope().getQualifiedName() - } + /** Holds if this class is a descriptor. */ + predicate isDescriptorType() { + this.hasAttribute("__get__") + } - /** Gets the MRO for this class */ - MRO getMro() { result = Types::getMro(this) } + /** Gets the qualified name for this class. + * Should return the same name as the `__qualname__` attribute on classes in Python 3. + */ + string getQualifiedName() { + result = this.(ClassObjectInternal).getBuiltin().getName() + or + result = this.(PythonClassObjectInternal).getScope().getQualifiedName() + } - predicate failedInference(string reason) { Types::failedInference(this, reason) } + /** Gets the MRO for this class */ + MRO getMro() { + result = Types::getMro(this) + } - /** Gets the nth immediate base type of this class. */ - ClassValue getBaseType(int n) { result = Types::getBase(this, n) } + predicate failedInference(string reason) { + Types::failedInference(this, reason) + } - /** Gets an immediate base type of this class. */ - ClassValue getABaseType() { result = Types::getBase(this, _) } + /** Gets the nth immediate base type of this class. */ + ClassValue getBaseType(int n) { + result = Types::getBase(this, n) + } - /** - * Holds if this class is a new style class. - * A new style class is one that implicitly or explicitly inherits from `object`. - */ - predicate isNewStyle() { Types::isNewStyle(this) } + /** Gets an immediate base type of this class. */ + ClassValue getABaseType() { + result = Types::getBase(this, _) + } - /** - * Holds if this class is an old style class. - * An old style class is one that does not inherit from `object`. - */ - predicate isOldStyle() { Types::isOldStyle(this) } + /** Holds if this class is a new style class. + A new style class is one that implicitly or explicitly inherits from `object`. */ + predicate isNewStyle() { + Types::isNewStyle(this) + } - /** Gets the scope associated with this class, if it is not a builtin class */ - ClassScope getScope() { result = this.(PythonClassObjectInternal).getScope() } + /** Holds if this class is an old style class. + An old style class is one that does not inherit from `object`. */ + predicate isOldStyle() { + Types::isOldStyle(this) + } - /** Gets the attribute declared in this class */ - Value declaredAttribute(string name) { Types::declaredAttribute(this, name, result, _) } + /** Gets the scope associated with this class, if it is not a builtin class */ + ClassScope getScope() { + result = this.(PythonClassObjectInternal).getScope() + } - /** - * Holds if this class has the attribute `name`, including - * attributes declared by super classes. - */ - predicate hasAttribute(string name) { this.getMro().declares(name) } + /** Gets the attribute declared in this class */ + Value declaredAttribute(string name) { + Types::declaredAttribute(this, name, result, _) + } + + /** Holds if this class has the attribute `name`, including + * attributes declared by super classes. + */ + predicate hasAttribute(string name) { + this.getMro().declares(name) + } + + /** Holds if this class declares the attribute `name`, + * *not* including attributes declared by super classes. + */ + predicate declaresAttribute(string name) { + this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name) + } - /** - * Holds if this class declares the attribute `name`, - * *not* including attributes declared by super classes. - */ - predicate declaresAttribute(string name) { - this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name) - } } -/** - * Class representing functions in the Python program, both Python and built-in. + +/** Class representing functions in the Python program, both Python and built-in. * Note that this does not include other callables such as bound-methods. */ abstract class FunctionValue extends CallableValue { - abstract string getQualifiedName(); - /** Gets the minimum number of parameters that can be correctly passed to this function */ - abstract int minParameters(); + abstract string getQualifiedName(); - /** Gets the maximum number of parameters that can be correctly passed to this function */ - abstract int maxParameters(); + /** Gets the minimum number of parameters that can be correctly passed to this function */ + abstract int minParameters(); - predicate isOverridingMethod() { exists(Value f | this.overrides(f)) } + /** Gets the maximum number of parameters that can be correctly passed to this function */ + abstract int maxParameters(); - predicate isOverriddenMethod() { exists(Value f | f.overrides(this)) } + predicate isOverridingMethod() { + exists(Value f | this.overrides(f)) + } + + predicate isOverriddenMethod() { + exists(Value f | f.overrides(this)) + } } /** Class representing Python functions */ class PythonFunctionValue extends FunctionValue { - PythonFunctionValue() { this instanceof PythonFunctionObjectInternal } - override string getQualifiedName() { - result = this.(PythonFunctionObjectInternal).getScope().getQualifiedName() - } + PythonFunctionValue() { + this instanceof PythonFunctionObjectInternal + } - override int minParameters() { - exists(Function f | - f = this.getScope() and - result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault()) - ) - } + override string getQualifiedName() { + result = this.(PythonFunctionObjectInternal).getScope().getQualifiedName() + } - override int maxParameters() { - exists(Function f | - f = this.getScope() and - if exists(f.getVararg()) - then result = 2147483647 // INT_MAX - else result = count(f.getAnArg()) - ) - } + override int minParameters() { + exists(Function f | + f = this.getScope() and + result = count(f.getAnArg()) - count(f.getDefinition().getArgs().getADefault()) + ) + } + + override int maxParameters() { + exists(Function f | + f = this.getScope() and + if exists(f.getVararg()) then + result = 2147483647 // INT_MAX + else + result = count(f.getAnArg()) + ) + } + + /** Gets a control flow node corresponding to a return statement in this function */ + ControlFlowNode getAReturnedNode() { + result = this.getScope().getAReturnValueFlowNode() + } - /** Gets a control flow node corresponding to a return statement in this function */ - ControlFlowNode getAReturnedNode() { result = this.getScope().getAReturnValueFlowNode() } } /** Class representing builtin functions, such as `len` or `print` */ class BuiltinFunctionValue extends FunctionValue { - BuiltinFunctionValue() { this instanceof BuiltinFunctionObjectInternal } - override string getQualifiedName() { result = this.(BuiltinFunctionObjectInternal).getName() } + BuiltinFunctionValue() { + this instanceof BuiltinFunctionObjectInternal + } - override int minParameters() { none() } + override string getQualifiedName() { + result = this.(BuiltinFunctionObjectInternal).getName() + } - override int maxParameters() { none() } + override int minParameters() { + none() + } + + override int maxParameters() { + none() + } } /** Class representing builtin methods, such as `list.append` or `set.add` */ class BuiltinMethodValue extends FunctionValue { - BuiltinMethodValue() { this instanceof BuiltinMethodObjectInternal } - override string getQualifiedName() { - exists(Builtin cls | - cls.isClass() and - cls.getMember(_) = this.(BuiltinMethodObjectInternal).getBuiltin() and - result = cls.getName() + "." + this.getName() - ) - } + BuiltinMethodValue() { + this instanceof BuiltinMethodObjectInternal + } - override int minParameters() { none() } + override string getQualifiedName() { + exists(Builtin cls | + cls.isClass() and + cls.getMember(_) = this.(BuiltinMethodObjectInternal).getBuiltin() and + result = cls.getName() + "." + this.getName() + ) + } + + override int minParameters() { + none() + } + + override int maxParameters() { + none() + } - override int maxParameters() { none() } } -/** - * A class representing sequence objects with a length and tracked items. +/** A class representing sequence objects with a length and tracked items. */ class SequenceValue extends Value { - SequenceValue() { this instanceof SequenceObjectInternal } - Value getItem(int n) { result = this.(SequenceObjectInternal).getItem(n) } + SequenceValue() { + this instanceof SequenceObjectInternal + } + + Value getItem(int n) { + result = this.(SequenceObjectInternal).getItem(n) + } + + int length() { + result = this.(SequenceObjectInternal).length() + } - int length() { result = this.(SequenceObjectInternal).length() } } /** A class representing tuple objects */ class TupleValue extends SequenceValue { - TupleValue() { this instanceof TupleObjectInternal } + + TupleValue() { + this instanceof TupleObjectInternal + } + } -/** - * A class representing strings, either present in the source as a literal, or - *in a builtin as a value. - */ +/** A class representing strings, either present in the source as a literal, or +in a builtin as a value. */ + class StringValue extends Value { - StringValue() { - this instanceof BytesObjectInternal or - this instanceof UnicodeObjectInternal - } + StringValue() { + this instanceof BytesObjectInternal or + this instanceof UnicodeObjectInternal + } - string getText() { - result = this.(BytesObjectInternal).strValue() - or - result = this.(UnicodeObjectInternal).strValue() - } + string getText() { + result = this.(BytesObjectInternal).strValue() + or + result = this.(UnicodeObjectInternal).strValue() + } } -/** - * A class representing numbers (ints and floats), either present in the source as a literal, +/** A class representing numbers (ints and floats), either present in the source as a literal, * or in a builtin as a value. */ class NumericValue extends Value { - NumericValue() { - this instanceof IntObjectInternal or - this instanceof FloatObjectInternal - } + NumericValue() { + this instanceof IntObjectInternal or + this instanceof FloatObjectInternal + } - /** Gets the integer-value if it is a constant integer, and it fits in a QL int */ - int getIntValue() { result = this.(IntObjectInternal).intValue() } + /** Gets the integer-value if it is a constant integer, and it fits in a QL int */ + int getIntValue() { + result = this.(IntObjectInternal).intValue() + } - /** Gets the float-value if it is a constant float */ - int getFloatValue() { result = this.(FloatObjectInternal).floatValue() } + /** Gets the float-value if it is a constant float */ + int getFloatValue() { + result = this.(FloatObjectInternal).floatValue() + } } /** A method-resolution-order sequence of classes */ class MRO extends TClassList { - string toString() { result = this.(ClassList).toString() } - /** Gets the `n`th class in this MRO */ - ClassValue getItem(int n) { result = this.(ClassList).getItem(n) } + string toString() { + result = this.(ClassList).toString() + } - /** Holds if any class in this MRO declares the attribute `name` */ - predicate declares(string name) { this.(ClassList).declares(name) } + /** Gets the `n`th class in this MRO */ + ClassValue getItem(int n) { + result = this.(ClassList).getItem(n) + } - /** Gets the length of this MRO */ - int length() { result = this.(ClassList).length() } + /** Holds if any class in this MRO declares the attribute `name` */ + predicate declares(string name) { + this.(ClassList).declares(name) + } - /** Holds if this MRO contains `cls` */ - predicate contains(ClassValue cls) { this.(ClassList).contains(cls) } + /** Gets the length of this MRO */ + int length() { + result = this.(ClassList).length() + } - /** Gets the value from scanning for the attribute `name` in this MRO. */ - Value lookup(string name) { this.(ClassList).lookup(name, result, _) } + /** Holds if this MRO contains `cls` */ + predicate contains(ClassValue cls) { + this.(ClassList).contains(cls) + } + + /** Gets the value from scanning for the attribute `name` in this MRO. */ + Value lookup(string name) { + this.(ClassList).lookup(name, result, _) + } + + /** Gets the MRO formed by removing all classes before `cls` + * from this MRO. + */ + MRO startingAt(ClassValue cls) { + result = this.(ClassList).startingAt(cls) + } - /** - * Gets the MRO formed by removing all classes before `cls` - * from this MRO. - */ - MRO startingAt(ClassValue cls) { result = this.(ClassList).startingAt(cls) } } + module ClassValue { - /** Get the `ClassValue` for the `bool` class. */ - ClassValue bool() { result = TBuiltinClassObject(Builtin::special("bool")) } - /** Get the `ClassValue` for the `tuple` class. */ - ClassValue tuple() { result = TBuiltinClassObject(Builtin::special("tuple")) } + /** Get the `ClassValue` for the `bool` class. */ + ClassValue bool() { + result = TBuiltinClassObject(Builtin::special("bool")) + } + + /** Get the `ClassValue` for the `tuple` class. */ + ClassValue tuple() { + result = TBuiltinClassObject(Builtin::special("tuple")) + } + + /** Get the `ClassValue` for the `list` class. */ + ClassValue list() { + result = TBuiltinClassObject(Builtin::special("list")) + } + + /** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */ + ClassValue range() { + major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange")) + or + major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range")) + } + + /** Get the `ClassValue` for the `dict` class. */ + ClassValue dict() { + result = TBuiltinClassObject(Builtin::special("dict")) + } + + /** Get the `ClassValue` for the `set` class. */ + ClassValue set() { + result = TBuiltinClassObject(Builtin::special("set")) + } + + /** Get the `ClassValue` for the `object` class. */ + ClassValue object() { + result = TBuiltinClassObject(Builtin::special("object")) + } - /** Get the `ClassValue` for the `list` class. */ - ClassValue list() { result = TBuiltinClassObject(Builtin::special("list")) } + /** Get the `ClassValue` for the `int` class. */ + ClassValue int_() { + result = TBuiltinClassObject(Builtin::special("int")) + } + + /** Get the `ClassValue` for the `long` class. */ + ClassValue long() { + result = TBuiltinClassObject(Builtin::special("long")) + } - /** Get the `ClassValue` for `xrange` (Python 2), or `range` (only Python 3) */ - ClassValue range() { - major_version() = 2 and result = TBuiltinClassObject(Builtin::special("xrange")) - or - major_version() = 3 and result = TBuiltinClassObject(Builtin::special("range")) - } + /** Get the `ClassValue` for the `float` class. */ + ClassValue float_() { + result = TBuiltinClassObject(Builtin::special("float")) + } + + /** Get the `ClassValue` for the `complex` class. */ + ClassValue complex() { + result = TBuiltinClassObject(Builtin::special("complex")) + } - /** Get the `ClassValue` for the `dict` class. */ - ClassValue dict() { result = TBuiltinClassObject(Builtin::special("dict")) } + /** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */ + ClassValue bytes() { + result = TBuiltinClassObject(Builtin::special("bytes")) + } - /** Get the `ClassValue` for the `set` class. */ - ClassValue set() { result = TBuiltinClassObject(Builtin::special("set")) } + /** Get the `ClassValue` for the class of unicode strings. + * `str` in Python 3 and `unicode` in Python 2. */ + ClassValue unicode() { + result = TBuiltinClassObject(Builtin::special("unicode")) + } - /** Get the `ClassValue` for the `object` class. */ - ClassValue object() { result = TBuiltinClassObject(Builtin::special("object")) } + /** Get the `ClassValue` for the `str` class. This is `bytes` in Python 2, + and `str` in Python 3. */ + ClassValue str() { + if major_version() = 2 then + result = bytes() + else + result = unicode() + } + + /** Get the `ClassValue` for the `property` class. */ + ClassValue property() { + result = TBuiltinClassObject(Builtin::special("property")) + } + + /** Get the `ClassValue` for the class of Python functions. */ + ClassValue functionType() { + result = TBuiltinClassObject(Builtin::special("FunctionType")) + } - /** Get the `ClassValue` for the `int` class. */ - ClassValue int_() { result = TBuiltinClassObject(Builtin::special("int")) } + /** Get the `ClassValue` for the class of builtin functions. */ + ClassValue builtinFunction() { + result = Value::named("len").getClass() + } + + /** Get the `ClassValue` for the `generatorType` class. */ + ClassValue generator() { + result = TBuiltinClassObject(Builtin::special("generator")) + } + + /** Get the `ClassValue` for the `type` class. */ + ClassValue type() { + result = TType() + } + + /** Get the `ClassValue` for `ClassType`. */ + ClassValue classType() { + result = TBuiltinClassObject(Builtin::special("ClassType")) + } + + /** Get the `ClassValue` for `InstanceType`. */ + ClassValue instanceType() { + result = TBuiltinClassObject(Builtin::special("InstanceType")) + } + + /** Get the `ClassValue` for `super`. */ + ClassValue super_() { + result = TBuiltinClassObject(Builtin::special("super")) + } + + /** Get the `ClassValue` for the `classmethod` class. */ + ClassValue classmethod() { + result = TBuiltinClassObject(Builtin::special("ClassMethod")) + } - /** Get the `ClassValue` for the `long` class. */ - ClassValue long() { result = TBuiltinClassObject(Builtin::special("long")) } + /** Get the `ClassValue` for the `staticmethod` class. */ + ClassValue staticmethod() { + result = TBuiltinClassObject(Builtin::special("StaticMethod")) + } + + /** Get the `ClassValue` for the `MethodType` class. */ + pragma [noinline] + ClassValue methodType() { + result = TBuiltinClassObject(Builtin::special("MethodType")) + } + + /** Get the `ClassValue` for the `MethodDescriptorType` class. */ + ClassValue methodDescriptorType() { + result = TBuiltinClassObject(Builtin::special("MethodDescriptorType")) + } + + /** Get the `ClassValue` for the `GetSetDescriptorType` class. */ + ClassValue getSetDescriptorType() { + result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType")) + } + + /** Get the `ClassValue` for the `StopIteration` class. */ + ClassValue stopIteration() { + result = TBuiltinClassObject(Builtin::special("StopIteration")) + } - /** Get the `ClassValue` for the `float` class. */ - ClassValue float_() { result = TBuiltinClassObject(Builtin::special("float")) } + /** Get the `ClassValue` for the class of modules. */ + ClassValue module_() { + result = TBuiltinClassObject(Builtin::special("ModuleType")) + } - /** Get the `ClassValue` for the `complex` class. */ - ClassValue complex() { result = TBuiltinClassObject(Builtin::special("complex")) } + /** Get the `ClassValue` for the `Exception` class. */ + ClassValue exception() { + result = TBuiltinClassObject(Builtin::special("Exception")) + } + + /** Get the `ClassValue` for the `BaseException` class. */ + ClassValue baseException() { + result = TBuiltinClassObject(Builtin::special("BaseException")) + } + + /** Get the `ClassValue` for the `NoneType` class. */ + ClassValue nonetype() { + result = TBuiltinClassObject(Builtin::special("NoneType")) + } + + /** Get the `ClassValue` for the `TypeError` class */ + ClassValue typeError() { + result = TBuiltinClassObject(Builtin::special("TypeError")) + } - /** Get the `ClassValue` for the `bytes` class (also called `str` in Python 2). */ - ClassValue bytes() { result = TBuiltinClassObject(Builtin::special("bytes")) } + /** Get the `ClassValue` for the `NameError` class. */ + ClassValue nameError() { + result = TBuiltinClassObject(Builtin::builtin("NameError")) + } + + /** Get the `ClassValue` for the `AttributeError` class. */ + ClassValue attributeError() { + result = TBuiltinClassObject(Builtin::builtin("AttributeError")) + } + + /** Get the `ClassValue` for the `KeyError` class. */ + ClassValue keyError() { + result = TBuiltinClassObject(Builtin::builtin("KeyError")) + } + + /** Get the `ClassValue` for the `IOError` class. */ + ClassValue ioError() { + result = TBuiltinClassObject(Builtin::builtin("IOError")) + } + + /** Get the `ClassValue` for the `NotImplementedError` class. */ + ClassValue notImplementedError() { + result = TBuiltinClassObject(Builtin::builtin("NotImplementedError")) + } - /** - * Get the `ClassValue` for the class of unicode strings. - * `str` in Python 3 and `unicode` in Python 2. - */ - ClassValue unicode() { result = TBuiltinClassObject(Builtin::special("unicode")) } + /** Get the `ClassValue` for the `ImportError` class. */ + ClassValue importError() { + result = TBuiltinClassObject(Builtin::builtin("ImportError")) + } - /** - * Get the `ClassValue` for the `str` class. This is `bytes` in Python 2, - * and `str` in Python 3. - */ - ClassValue str() { if major_version() = 2 then result = bytes() else result = unicode() } - - /** Get the `ClassValue` for the `property` class. */ - ClassValue property() { result = TBuiltinClassObject(Builtin::special("property")) } - - /** Get the `ClassValue` for the class of Python functions. */ - ClassValue functionType() { result = TBuiltinClassObject(Builtin::special("FunctionType")) } - - /** Get the `ClassValue` for the class of builtin functions. */ - ClassValue builtinFunction() { result = Value::named("len").getClass() } - - /** Get the `ClassValue` for the `generatorType` class. */ - ClassValue generator() { result = TBuiltinClassObject(Builtin::special("generator")) } - - /** Get the `ClassValue` for the `type` class. */ - ClassValue type() { result = TType() } - - /** Get the `ClassValue` for `ClassType`. */ - ClassValue classType() { result = TBuiltinClassObject(Builtin::special("ClassType")) } - - /** Get the `ClassValue` for `InstanceType`. */ - ClassValue instanceType() { result = TBuiltinClassObject(Builtin::special("InstanceType")) } - - /** Get the `ClassValue` for `super`. */ - ClassValue super_() { result = TBuiltinClassObject(Builtin::special("super")) } - - /** Get the `ClassValue` for the `classmethod` class. */ - ClassValue classmethod() { result = TBuiltinClassObject(Builtin::special("ClassMethod")) } - - /** Get the `ClassValue` for the `staticmethod` class. */ - ClassValue staticmethod() { result = TBuiltinClassObject(Builtin::special("StaticMethod")) } - - /** Get the `ClassValue` for the `MethodType` class. */ - pragma[noinline] - ClassValue methodType() { result = TBuiltinClassObject(Builtin::special("MethodType")) } - - /** Get the `ClassValue` for the `MethodDescriptorType` class. */ - ClassValue methodDescriptorType() { - result = TBuiltinClassObject(Builtin::special("MethodDescriptorType")) - } - - /** Get the `ClassValue` for the `GetSetDescriptorType` class. */ - ClassValue getSetDescriptorType() { - result = TBuiltinClassObject(Builtin::special("GetSetDescriptorType")) - } - - /** Get the `ClassValue` for the `StopIteration` class. */ - ClassValue stopIteration() { result = TBuiltinClassObject(Builtin::special("StopIteration")) } - - /** Get the `ClassValue` for the class of modules. */ - ClassValue module_() { result = TBuiltinClassObject(Builtin::special("ModuleType")) } - - /** Get the `ClassValue` for the `Exception` class. */ - ClassValue exception() { result = TBuiltinClassObject(Builtin::special("Exception")) } - - /** Get the `ClassValue` for the `BaseException` class. */ - ClassValue baseException() { result = TBuiltinClassObject(Builtin::special("BaseException")) } - - /** Get the `ClassValue` for the `NoneType` class. */ - ClassValue nonetype() { result = TBuiltinClassObject(Builtin::special("NoneType")) } - - /** Get the `ClassValue` for the `TypeError` class */ - ClassValue typeError() { result = TBuiltinClassObject(Builtin::special("TypeError")) } - - /** Get the `ClassValue` for the `NameError` class. */ - ClassValue nameError() { result = TBuiltinClassObject(Builtin::builtin("NameError")) } - - /** Get the `ClassValue` for the `AttributeError` class. */ - ClassValue attributeError() { result = TBuiltinClassObject(Builtin::builtin("AttributeError")) } - - /** Get the `ClassValue` for the `KeyError` class. */ - ClassValue keyError() { result = TBuiltinClassObject(Builtin::builtin("KeyError")) } - - /** Get the `ClassValue` for the `IOError` class. */ - ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) } - - /** Get the `ClassValue` for the `NotImplementedError` class. */ - ClassValue notImplementedError() { - result = TBuiltinClassObject(Builtin::builtin("NotImplementedError")) - } - - /** Get the `ClassValue` for the `ImportError` class. */ - ClassValue importError() { result = TBuiltinClassObject(Builtin::builtin("ImportError")) } } From 15aeeb1e50fc6c73085c3ec51edfbfa7493e3207 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 25 Feb 2020 14:54:52 -0800 Subject: [PATCH 094/236] Removes erroneous expected result for py3 --- .../3/query-tests/Expressions/UseofApply/UseofApply.expected | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected index 4c4d84b6ce5..e69de29bb2d 100644 --- a/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected +++ b/python/ql/test/3/query-tests/Expressions/UseofApply/UseofApply.expected @@ -1 +0,0 @@ -| UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | From 2fb722b04e964ff0adb13474e34b7b251a284a0c Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 25 Feb 2020 14:55:55 -0800 Subject: [PATCH 095/236] Removes the general versions of the query. --- .../ql/test/query-tests/Expressions/general/UseofApply.expected | 1 - python/ql/test/query-tests/Expressions/general/UseofApply.qlref | 1 - 2 files changed, 2 deletions(-) delete mode 100644 python/ql/test/query-tests/Expressions/general/UseofApply.expected delete mode 100644 python/ql/test/query-tests/Expressions/general/UseofApply.qlref diff --git a/python/ql/test/query-tests/Expressions/general/UseofApply.expected b/python/ql/test/query-tests/Expressions/general/UseofApply.expected deleted file mode 100644 index 1f21475f56a..00000000000 --- a/python/ql/test/query-tests/Expressions/general/UseofApply.expected +++ /dev/null @@ -1 +0,0 @@ -| expressions_test.py:270:3:270:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | diff --git a/python/ql/test/query-tests/Expressions/general/UseofApply.qlref b/python/ql/test/query-tests/Expressions/general/UseofApply.qlref deleted file mode 100644 index abf684e3918..00000000000 --- a/python/ql/test/query-tests/Expressions/general/UseofApply.qlref +++ /dev/null @@ -1 +0,0 @@ -Expressions/UseofApply.ql From 21dd8757dd8b7d54dde9f0a5afd3ad6e3b9da16e Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Tue, 25 Feb 2020 23:11:29 -0500 Subject: [PATCH 096/236] Update docs/experimental.md Co-Authored-By: Felicity Chapman --- docs/experimental.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/experimental.md b/docs/experimental.md index fef4729cfd2..b606a791d9d 100644 --- a/docs/experimental.md +++ b/docs/experimental.md @@ -25,7 +25,7 @@ Experimental queries and libraries may not be actively maintained as the standar 4. **Compilation** - - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot use internal APIs, cannot depend on the output of `getAQlClass` and cannot make use of regexp matching on `toString`. + - Compilation of the query and any associated libraries and tests must be resilient to future development of the standard libraries. This means that the functionality cannot use internal APIs, cannot depend on the output of `getAQlClass`, and cannot make use of regexp matching on `toString`. - The query and any associated libraries and tests must not cause any compiler warnings to be emitted (such as use of deprecated functionality or missing `override` annotations). 5. **Results** @@ -39,4 +39,3 @@ Experimental queries and libraries may not be actively maintained as the standar ## Non-requirements Other criteria typically required for our standard queries and libraries are not required for experimental queries and libraries. In particular, fully disciplined query [metadata](docs/query-metadata-style-guide.md), query [help](docs/query-help-style-guide.md), tests, a low false positive rate and performance tuning are not required (but nonetheless recommended). - From d942a3b54ae8c6417de8b012c9c8bbded2ef021d Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 26 Feb 2020 10:21:27 +0100 Subject: [PATCH 097/236] C++: Change definition of isChiForAllAliasedMemory to recurse through inexact PhiInstructions --- cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index 638e0e0269c..c5f58e81a19 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -275,7 +275,7 @@ private predicate isChiForAllAliasedMemory(Instruction instr) { or isChiForAllAliasedMemory(instr.(ChiInstruction).getTotal()) or - isChiForAllAliasedMemory(instr.(PhiInstruction).getAnInput()) + isChiForAllAliasedMemory(instr.(PhiInstruction).getAnInputOperand().getAnyDef() ) } private predicate modelTaintToReturnValue(Function f, int parameterIn) { From 5f6d07dd57e965747722cf7c34e689aec74fc40b Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Wed, 26 Feb 2020 11:32:41 +0100 Subject: [PATCH 098/236] C++: Fix performance of UnsignedGEZero.ql This query used two fastTC operations that were already somewhat inefficient on their own but could send the evaluator into an OOM loop when run in parallel without enough RAM. The fix is to recurse manually, starting just from the expressions that are potential candidates for alerts. --- .../Likely Bugs/Arithmetic/UnsignedGEZero.qll | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll b/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll index 4ca68fb27c4..e0cc92b6ce8 100644 --- a/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll +++ b/cpp/ql/src/Likely Bugs/Arithmetic/UnsignedGEZero.qll @@ -15,24 +15,33 @@ class ConstantZero extends Expr { } } +/** + * Holds if `candidate` is an expression such that if it's unsigned then we + * want an alert at `ge`. + */ +private predicate lookForUnsignedAt(GEExpr ge, Expr candidate) { + // Base case: `candidate >= 0` + ge.getRightOperand() instanceof ConstantZero and + candidate = ge.getLeftOperand().getFullyConverted() and + // left operand was a signed or unsigned IntegralType before conversions + // (not a pointer, checking a pointer >= 0 is an entirely different mistake) + // (not an enum, as the fully converted type of an enum is compiler dependent + // so checking an enum >= 0 is always reasonable) + ge.getLeftOperand().getUnderlyingType() instanceof IntegralType + or + // Recursive case: `...(largerType)candidate >= 0` + exists(Conversion conversion | + lookForUnsignedAt(ge, conversion) and + candidate = conversion.getExpr() and + conversion.getType().getSize() > candidate.getType().getSize() + ) +} + class UnsignedGEZero extends GEExpr { UnsignedGEZero() { - this.getRightOperand() instanceof ConstantZero and - // left operand was a signed or unsigned IntegralType before conversions - // (not a pointer, checking a pointer >= 0 is an entirely different mistake) - // (not an enum, as the fully converted type of an enum is compiler dependent - // so checking an enum >= 0 is always reasonable) - getLeftOperand().getUnderlyingType() instanceof IntegralType and exists(Expr ue | - // ue is some conversion of the left operand - ue = getLeftOperand().getConversion*() and - // ue is unsigned - ue.getUnderlyingType().(IntegralType).isUnsigned() and - // ue may be converted to zero or more strictly larger possibly signed types - // before it is fully converted - forall(Expr following | following = ue.getConversion+() | - following.getType().getSize() > ue.getType().getSize() - ) + lookForUnsignedAt(this, ue) and + ue.getUnderlyingType().(IntegralType).isUnsigned() ) } } From 1bee0ffe3b20ca766b11cc080da0adffc56ace16 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 26 Feb 2020 12:09:21 +0100 Subject: [PATCH 099/236] C++: Autoformat --- cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index c5f58e81a19..34251d0805b 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -275,7 +275,7 @@ private predicate isChiForAllAliasedMemory(Instruction instr) { or isChiForAllAliasedMemory(instr.(ChiInstruction).getTotal()) or - isChiForAllAliasedMemory(instr.(PhiInstruction).getAnInputOperand().getAnyDef() ) + isChiForAllAliasedMemory(instr.(PhiInstruction).getAnInputOperand().getAnyDef()) } private predicate modelTaintToReturnValue(Function f, int parameterIn) { From 508b6050a813aba8ea29555117772e8002deb811 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Wed, 26 Feb 2020 13:51:25 +0100 Subject: [PATCH 100/236] Java: Remove some irrelevant bounds from TypeFlow. --- .../semmle/code/java/dataflow/TypeFlow.qll | 41 +++++++++++++++++-- .../src/semmle/code/java/dispatch/ObjFlow.qll | 1 + 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll index e9f32aadbbe..24cf918a9b7 100644 --- a/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll +++ b/java/ql/src/semmle/code/java/dataflow/TypeFlow.qll @@ -364,13 +364,45 @@ private predicate typeFlow(TypeFlowNode n, RefType t) { typeFlowJoin(lastRank(n), n, t) } +pragma[nomagic] +predicate erasedTypeBound(RefType t) { + exists(RefType t0 | typeFlow(_, t0) and t = t0.getErasure()) +} + +pragma[nomagic] +predicate typeBound(RefType t) { typeFlow(_, t) } + +/** + * Holds if we have a bound for `n` that is better than `t`, taking only erased + * types into account. + */ +pragma[nomagic] +private predicate irrelevantErasedBound(TypeFlowNode n, RefType t) { + exists(RefType bound | + typeFlow(n, bound) + or + n.getType() = bound and typeFlow(n, _) + | + t = bound.getErasure().(RefType).getASourceSupertype+() and + erasedTypeBound(t) + ) +} + /** * Holds if we have a bound for `n` that is better than `t`. */ -pragma[noinline] +pragma[nomagic] private predicate irrelevantBound(TypeFlowNode n, RefType t) { - exists(RefType bound | typeFlow(n, bound) or n.getType() = bound | - t = bound.getErasure().(RefType).getASourceSupertype+() + exists(RefType bound | + typeFlow(n, bound) and + t = bound.getASupertype+() and + typeBound(t) and + typeFlow(n, t) and + not t.getASupertype*() = bound + or + n.getType() = bound and + typeFlow(n, t) and + t = bound.getASupertype*() ) } @@ -380,7 +412,8 @@ private predicate irrelevantBound(TypeFlowNode n, RefType t) { */ private predicate bestTypeFlow(TypeFlowNode n, RefType t) { typeFlow(n, t) and - not irrelevantBound(n, t.getErasure()) + not irrelevantErasedBound(n, t.getErasure()) and + not irrelevantBound(n, t) } cached diff --git a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll index 4d97bfdc3cc..11537ac144b 100644 --- a/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll +++ b/java/ql/src/semmle/code/java/dispatch/ObjFlow.qll @@ -305,6 +305,7 @@ private module Unification { arg2 = t2.getTypeArgument(pos) } + pragma[nomagic] predicate failsUnification(Type t1, Type t2) { unificationTargets(t1, t2) and ( From 400a8ffae5339c19af66ad90ae9b26643df0c991 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 26 Feb 2020 14:08:10 +0100 Subject: [PATCH 101/236] Python: Use slightly better name than foobar I intended to rename before committing, but woops --- python/ql/src/semmle/python/security/strings/External.qll | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index a8b17b1f686..40dafff0f62 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -218,12 +218,12 @@ class UrlsplitUrlparseTempSanitizer extends Sanitizer { or taint instanceof ExternalUrlParseResult ) and - exists(ControlFlowNode foobar | - foobar.(SubscriptNode).getObject() = test.getInput().getAUse() + exists(ControlFlowNode full_use | + full_use.(SubscriptNode).getObject() = test.getInput().getAUse() or - foobar.(AttrNode).getObject() = test.getInput().getAUse() + full_use.(AttrNode).getObject() = test.getInput().getAUse() | - clears_taint(_, foobar, test.getTest(), test.getSense()) + clears_taint(_, full_use, test.getTest(), test.getSense()) ) } From 0b31cb17167ab23d3e95eb2f2f3056d67750cc36 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 26 Feb 2020 14:08:47 +0100 Subject: [PATCH 102/236] Python: Show that we have initial taint in urlsplit test --- .../taint/namedtuple/TestTaint.expected | 19 ++++++++++--------- .../library-tests/taint/namedtuple/test.py | 2 ++ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected index 11c8b1268a7..62b589299dd 100644 --- a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -3,12 +3,13 @@ | test.py:13 | test_basic | c | externally controlled string | | test.py:13 | test_basic | d | externally controlled string | | test.py:13 | test_basic | urlsplit_res | [externally controlled string] | -| test.py:20 | test_sanitizer | Attribute | NO TAINT | -| test.py:23 | test_sanitizer | Subscript | NO TAINT | -| test.py:26 | test_sanitizer | Attribute | NO TAINT | -| test.py:29 | test_sanitizer | Attribute | NO TAINT | -| test.py:32 | test_sanitizer | Attribute | externally controlled string | -| test.py:42 | test_namedtuple | a | NO TAINT | -| test.py:42 | test_namedtuple | b | NO TAINT | -| test.py:42 | test_namedtuple | c | NO TAINT | -| test.py:42 | test_namedtuple | d | NO TAINT | +| test.py:19 | test_sanitizer | Attribute | externally controlled string | +| test.py:22 | test_sanitizer | Attribute | NO TAINT | +| test.py:25 | test_sanitizer | Subscript | NO TAINT | +| test.py:28 | test_sanitizer | Attribute | NO TAINT | +| test.py:31 | test_sanitizer | Attribute | NO TAINT | +| test.py:34 | test_sanitizer | Attribute | externally controlled string | +| test.py:44 | test_namedtuple | a | NO TAINT | +| test.py:44 | test_namedtuple | b | NO TAINT | +| test.py:44 | test_namedtuple | c | NO TAINT | +| test.py:44 | test_namedtuple | d | NO TAINT | diff --git a/python/ql/test/library-tests/taint/namedtuple/test.py b/python/ql/test/library-tests/taint/namedtuple/test.py index f3d0b778e1b..ec6304f5073 100644 --- a/python/ql/test/library-tests/taint/namedtuple/test.py +++ b/python/ql/test/library-tests/taint/namedtuple/test.py @@ -16,6 +16,8 @@ def test_sanitizer(): tainted_string = TAINTED_STRING urlsplit_res = urlsplit(tainted_string) + test(urlsplit_res.netloc) # should be tainted + if urlsplit_res.netloc == "OK": test(urlsplit_res.netloc) From 771dfecf6d2658796fd00c10a3d44b53f9787ef8 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Wed, 26 Feb 2020 14:10:30 +0100 Subject: [PATCH 103/236] Python: Add sanitized edges for urlsplit test --- .../library-tests/taint/namedtuple/SanitizedEdges.expected | 7 +++++++ .../test/library-tests/taint/namedtuple/SanitizedEdges.ql | 6 ++++++ 2 files changed, 13 insertions(+) create mode 100644 python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected create mode 100644 python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql diff --git a/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected new file mode 100644 index 00000000000..0adf64dfd5d --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.expected @@ -0,0 +1,7 @@ +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:21 | Pi(urlsplit_res_0) [true] | +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:24 | Pi(urlsplit_res_3) [true] | +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:27 | Pi(urlsplit_res_6) [true] | +| UrlsplitUrlparseTempSanitizer | [externally controlled string] | test.py:30 | Pi(urlsplit_res_9) [true] | +| string equality sanitizer | externally controlled string | test.py:21 | Pi(urlsplit_res_0) [true] | +| string equality sanitizer | externally controlled string | test.py:24 | Pi(urlsplit_res_3) [true] | +| string equality sanitizer | externally controlled string | test.py:27 | Pi(urlsplit_res_6) [true] | diff --git a/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql new file mode 100644 index 00000000000..d523f79a963 --- /dev/null +++ b/python/ql/test/library-tests/taint/namedtuple/SanitizedEdges.ql @@ -0,0 +1,6 @@ +import python +import Taint + +from Sanitizer s, TaintKind taint, PyEdgeRefinement test +where s.sanitizingEdge(taint, test) +select s, taint, test.getTest().getLocation().toString(), test.getRepresentation() From ba1f3c46b8e582a99e89c83e89c57cd3a0e53f81 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Wed, 26 Feb 2020 08:17:45 -0800 Subject: [PATCH 104/236] Removes obsolete `asBuiltin` predicate --- python/ql/src/semmle/python/objects/ObjectAPI.qll | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index e865d7d649f..107269bd817 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -79,16 +79,6 @@ class Value extends TObject { this.(ObjectInternal).isBuiltin() } - /** INTERNAL -- Do not use */ - Builtin asBuiltin() { - this = TBuiltinClassObject(result) or - this = TBuiltinFunctionObject(result) or - this = TBuiltinMethodObject(result) or - this = TBuiltinModuleObject(result) or - this = TBuiltinOpaqueObject(result) or - this = TBuiltinTuple(result) - } - predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) { this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec) or From b0493458d68877e0a5564ac2b6f67535dafdbebf Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Wed, 26 Feb 2020 09:04:14 -0800 Subject: [PATCH 105/236] Combine and clean up the test files --- .../Expressions/TruncatedDivision.expected | 5 +- .../Expressions/TruncatedDivision.py | 46 ---------- .../Expressions/TruncatedDivision_test.py | 84 ++++++++++++++++--- 3 files changed, 76 insertions(+), 59 deletions(-) delete mode 100644 python/ql/test/2/query-tests/Expressions/TruncatedDivision.py diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected index e85a047ad34..b22b9b5a2f2 100644 --- a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected +++ b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.expected @@ -1,3 +1,2 @@ -| TruncatedDivision.py:36:9:36:13 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision.py:36:9:36:9 | TruncatedDivision.py:36 | left | TruncatedDivision.py:36:13:36:13 | TruncatedDivision.py:36 | right | -| TruncatedDivision_test.py:8:12:8:16 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:8:12:8:12 | TruncatedDivision_test.py:8 | left | TruncatedDivision_test.py:8:16:8:16 | TruncatedDivision_test.py:8 | right | -| TruncatedDivision_test.py:11:12:11:40 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:2:12:2:12 | TruncatedDivision_test.py:2 | left | TruncatedDivision_test.py:5:12:5:12 | TruncatedDivision_test.py:5 | right | +| TruncatedDivision_test.py:65:7:65:11 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:65:7:65:7 | TruncatedDivision_test.py:65 | left | TruncatedDivision_test.py:65:11:65:11 | TruncatedDivision_test.py:65 | right | +| TruncatedDivision_test.py:72:7:72:35 | BinaryExpr | Result of division may be truncated as its $@ and $@ arguments may both be integers. | TruncatedDivision_test.py:25:12:25:12 | TruncatedDivision_test.py:25 | left | TruncatedDivision_test.py:28:12:28:12 | TruncatedDivision_test.py:28 | right | diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.py b/python/ql/test/2/query-tests/Expressions/TruncatedDivision.py deleted file mode 100644 index ed2df62b68d..00000000000 --- a/python/ql/test/2/query-tests/Expressions/TruncatedDivision.py +++ /dev/null @@ -1,46 +0,0 @@ -#### TruncatedDivision.ql - -# NOTE: The following test case will only work under Python 2. - -# NOTE: While there are other files that have the same matching examples, this -# example file tries to explain the motivation of each example. Hopefully it's -# fine to have multiple such files. - -# Truncated division occurs when two integers are divided. This causes the -# fractional part, if there is one, to be discared. So for example, `2 / 3` will -# evaluate to `0` instead of `0.666...`. - -def truncated_division(): - - def average(l): - return sum(l) / len(l) - - - - ## Negative Cases - - # This case is good, and is a minimal obvious case that should be good. It - # SHOULD NOT be found by the query. - print(3.0 / 2.0) - - # This case is good, because the sum is `3.0`, which is a float, and will not - # truncate. This case SHOULD NOT be found by the query. - print(average([1.0, 2.0])) - - - - ## Positive Cases - - # This case is bad, and is a minimal obvious case that should be bad. It - # SHOULD be found by the query. - print(3 / 2) - - # This case is bad, because the sum is `3`, which is an integer, and will - # truncate when divided by the length `2`. This case SHOULD be found by the - # query. - # - # NOTE (2020-02-20): - # The current version of the Value/pointsTo API doesn't permit this detection, - # unfortunately, but we preserve this example in the hopes that future - # versions will catch it. That will necessitate changing the expected results. - print(average([1,2])) diff --git a/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py b/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py index 1c9d85e0fbd..6f265b824fb 100644 --- a/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py +++ b/python/ql/test/2/query-tests/Expressions/TruncatedDivision_test.py @@ -1,24 +1,88 @@ +#### TruncatedDivision.ql + +# NOTE: The following test case will only work under Python 2. + +# Truncated division occurs when two integers are divided. This causes the +# fractional part, if there is one, to be discared. So for example, `2 / 3` will +# evaluate to `0` instead of `0.666...`. + + + + + +## Negative Cases + + + +# This case is good, and is a minimal obvious case that should be good. It +# SHOULD NOT be found by the query. +print(3.0 / 2.0) + +# This case is good, because it explicitly converts the possibly-truncated +# value to an integer. It SHOULD NOT be found by the query. + def return_three(): return 3 def return_two(): return 2 -def f1(): - return 3 / 2 +print(int(return_three() / return_two())) -def f2(): - return return_three() / return_two() -def f3(x): + +# These cases are good, because `halve` checks the type, and if the type would +# truncate, it explicitly converts to a float first before doing the division. +# These SHOULD NOT be found by the query. + +def halve(x): if isinstance(x, float): return x / 2 else: return (1.0 * x) / 2 -def f4(): - do_stuff(f3(1)) - do_stuff(f3(1.0)) +print(halve(1)) +print(halve(1.0)) -def f5(): - return int(return_three() / return_two()) + + +# This case is good, because the sum is `3.0`, which is a float, and will not +# truncate. This case SHOULD NOT be found by the query. + +print(average([1.0, 2.0])) + + + + + +## Positive Cases + + + +# This case is bad, and is a minimal obvious case that should be bad. It +# SHOULD be found by the query. + +print(3 / 2) + + + +# This case is bad. It uses indirect returns of integers through function calls +# to produce the problem. I + +print(return_three() / return_two()) + + + +# This case is bad, because the sum is `3`, which is an integer, and will +# truncate when divided by the length `2`. This case SHOULD be found by the +# query. + +# NOTE (2020-02-20): +# The current version of the Value/pointsTo API doesn't permit this detection, +# unfortunately, but we preserve this example in the hopes that future +# versions will catch it. That will necessitate changing the expected results. + +def average(l): + return sum(l) / len(l) + +print(average([1,2])) From bd91bc0b290ed13ef6435cca8151e2362a06d3c8 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Wed, 26 Feb 2020 13:18:13 -0500 Subject: [PATCH 106/236] Java: add `Customizations.qll` --- java/ql/src/Customizations.qll | 12 ++++++++++++ java/ql/src/java.qll | 1 + 2 files changed, 13 insertions(+) create mode 100644 java/ql/src/Customizations.qll diff --git a/java/ql/src/Customizations.qll b/java/ql/src/Customizations.qll new file mode 100644 index 00000000000..1f5716726e3 --- /dev/null +++ b/java/ql/src/Customizations.qll @@ -0,0 +1,12 @@ +/** + * Contains customizations to the standard library. + * + * This module is imported by `java.qll`, so any customizations defined here automatically + * apply to all queries. + * + * Typical examples of customizations include adding new subclasses of abstract classes such as + * the `RemoteFlowSource` and `AdditionalTaintStep` classes associated with the security queries + * to model frameworks that are not covered by the standard library. + */ + +import java diff --git a/java/ql/src/java.qll b/java/ql/src/java.qll index c8e3b71f18b..6454a0d282b 100644 --- a/java/ql/src/java.qll +++ b/java/ql/src/java.qll @@ -1,5 +1,6 @@ /** Provides all default Java QL imports. */ +import Customizations import semmle.code.FileSystem import semmle.code.Location import semmle.code.java.Annotation From 62f8bf2b2e78b7ae9413f8ffd82f0f356773158c Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Wed, 26 Feb 2020 14:36:27 -0500 Subject: [PATCH 107/236] Java: add release note for `Customizations.qll` --- change-notes/1.24/analysis-java.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.24/analysis-java.md b/change-notes/1.24/analysis-java.md index a7b9b66998f..44448ffd3d0 100644 --- a/change-notes/1.24/analysis-java.md +++ b/change-notes/1.24/analysis-java.md @@ -5,6 +5,7 @@ The following changes in version 1.24 affect Java analysis in all applications. ## General improvements * Alert suppression can now be done with single-line block comments (`/* ... */`) as well as line comments (`// ...`). +* A `Customizations.qll` file has been added to allow customizations of the standard library that apply to all queries. ## New queries From aeb87931976ba36417c10ffb454260de90e64359 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Wed, 26 Feb 2020 18:38:42 -0500 Subject: [PATCH 108/236] Update docs/experimental.md Break sentence down into shorter ones, as per review comment. --- docs/experimental.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/experimental.md b/docs/experimental.md index b606a791d9d..e1f56bcf7d4 100644 --- a/docs/experimental.md +++ b/docs/experimental.md @@ -2,7 +2,7 @@ In addition to our standard CodeQL queries and libraries, this repository may also contain queries and libraries of a more experimental nature. Experimental queries and libraries can be improved incrementally and may eventually reach a sufficient maturity to be included in our standard libraries and queries. -Experimental queries and libraries may not be actively maintained as the standard libraries evolve, may be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings. +Experimental queries and libraries may not be actively maintained as the standard libraries evolve. They may also be changed in backwards-incompatible ways or may be removed entirely in the future without deprecation warnings. ## Requirements From 63adc635971e63b6f20540dd2dd276d9cb536a08 Mon Sep 17 00:00:00 2001 From: yo-h <55373593+yo-h@users.noreply.github.com> Date: Wed, 26 Feb 2020 18:35:16 -0500 Subject: [PATCH 109/236] `CONTRIBUTING.md`: add paragraph on maintaining backwards compatibility --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0583374555b..649af08b996 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,10 @@ Follow the steps below to help other users understand what your query does, and Query help files explain the purpose of your query to other users. Write your query help in a `.qhelp` file and save it in the same directory as your new query. For more information on writing query help, see the [Query help style guide](https://github.com/Semmle/ql/blob/master/docs/query-help-style-guide.md). +7. **Maintain backwards compatibility** + +The standard CodeQL libraries must evolve in a backwards compatible manner. If any backwards incompatible changes need to be made, the existing API must first be marked as deprecated. This is done by adding a `deprecated` annotation along with a QLDoc reference to the replacement API. Only after at least one full release cycle has elapsed may the old API be removed. + In addition to contributions to our standard queries and libraries, we also welcome contributions of a more experimental nature, which do not need to fulfill all the requirements listed above. See the guidelines for [experimental queries and libraries](docs/experimental.md) for details. ## Using your personal data From 057fed2cb85d26efa3012a4ad0edd90760eb5998 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Wed, 26 Feb 2020 21:55:02 -0800 Subject: [PATCH 110/236] Fixes erroneus naming --- python/ql/src/Expressions/CallArgs.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Expressions/CallArgs.qll b/python/ql/src/Expressions/CallArgs.qll index 72326a1c30c..fa9822dd6e1 100644 --- a/python/ql/src/Expressions/CallArgs.qll +++ b/python/ql/src/Expressions/CallArgs.qll @@ -26,7 +26,7 @@ private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { * plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs). */ -private int positional_arg_count_objectapi_for_call_objectapi(Call call, Object callable) { +private int positional_arg_count_for_call_objectapi(Call call, Object callable) { call = get_a_call_objectapi(callable).getNode() and exists(int positional_keywords | exists(FunctionObject func | func = get_function_or_initializer_objectapi(callable) | @@ -103,7 +103,7 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) { callable instanceof ClassObject and call.getAFlowNode() = get_a_call_objectapi(callable) and limit = func.maxParameters() - 1 ) and - positional_arg_count_objectapi_for_call_objectapi(call, callable) > limit + positional_arg_count_for_call_objectapi(call, callable) > limit } /** Holds if `call` has too many or too few arguments for `func` */ From fe2bb8fb4b9585a4d90850c7425a1fe3b87a1868 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Wed, 26 Feb 2020 22:01:31 -0800 Subject: [PATCH 111/236] Adds preliminary modernization --- python/ql/src/Expressions/CallArgs.qll | 127 ++++++++++++++++++ .../src/semmle/python/objects/ObjectAPI.qll | 22 +++ 2 files changed, 149 insertions(+) diff --git a/python/ql/src/Expressions/CallArgs.qll b/python/ql/src/Expressions/CallArgs.qll index fa9822dd6e1..5dcf50ac8f4 100644 --- a/python/ql/src/Expressions/CallArgs.qll +++ b/python/ql/src/Expressions/CallArgs.qll @@ -13,6 +13,17 @@ private int varargs_length_objectapi(Call call) { result = count(call.getStarargs().(List).getAnElt()) } +private int varargs_length(Call call) { + not exists(call.getStarargs()) and result = 0 + or + exists(TupleValue t | + call.getStarargs().pointsTo(t) | + result = t.length() + ) + or + result = count(call.getStarargs().(List).getAnElt()) +} + /** Gets a keyword argument that is not a keyword-only parameter. */ private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { func.getACall().getNode() = call and @@ -20,6 +31,13 @@ private Keyword not_keyword_only_arg_objectapi(Call call, FunctionObject func) { not func.getFunction().getAKeywordOnlyArg().getId() = result.getArg() } +/** Gets a keyword argument that is not a keyword-only parameter. */ +private Keyword not_keyword_only_arg(Call call, FunctionValue func) { + func.getACall().getNode() = call and + result = call.getAKeyword() and + not func.getScope().getAKeywordOnlyArg().getId() = result.getArg() +} + /** Gets the count of arguments that are passed as positional parameters even if they * are named in the call. * This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg @@ -40,10 +58,34 @@ private int positional_arg_count_for_call_objectapi(Call call, Object callable) ) } +/** Gets the count of arguments that are passed as positional parameters even if they + * are named in the call. + * This is the sum of the number of positional arguments, the number of elements in any explicit tuple passed as *arg + * plus the number of keyword arguments that do not match keyword-only arguments (if the function does not take **kwargs). + */ + +private int positional_arg_count_for_call(Call call, Value callable) { + call = get_a_call(callable).getNode() and + exists(int positional_keywords | + exists(FunctionValue func | func = get_function_or_initializer(callable) | + not func.getScope().hasKwArg() and + positional_keywords = count(not_keyword_only_arg(call, func)) + or + func.getScope().hasKwArg() and positional_keywords = 0 + ) + | + result = count(call.getAnArg()) + varargs_length_objectapi(call) + positional_keywords + ) +} + int arg_count_objectapi(Call call) { result = count(call.getAnArg()) + varargs_length_objectapi(call) + count(call.getAKeyword()) } +int arg_count(Call call) { + result = count(call.getAnArg()) + varargs_length(call) + count(call.getAKeyword()) +} + /* Gets a call corresponding to the given class or function*/ private ControlFlowNode get_a_call_objectapi(Object callable) { result = callable.(ClassObject).getACall() @@ -51,6 +93,13 @@ private ControlFlowNode get_a_call_objectapi(Object callable) { result = callable.(FunctionObject).getACall() } +/* Gets a call corresponding to the given class or function*/ +private ControlFlowNode get_a_call(Value callable) { + result = callable.(ClassValue).getACall() + or + result = callable.(FunctionValue).getACall() +} + /* Gets the function object corresponding to the given class or function*/ FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) { result = func_or_cls.(FunctionObject) @@ -58,6 +107,13 @@ FunctionObject get_function_or_initializer_objectapi(Object func_or_cls) { result = func_or_cls.(ClassObject).declaredAttribute("__init__") } +/* Gets the function object corresponding to the given class or function*/ +FunctionValue get_function_or_initializer(Value func_or_cls) { + result = func_or_cls.(FunctionValue) + or + result = func_or_cls.(ClassValue).declaredAttribute("__init__") +} + /**Whether there is an illegally named parameter called `name` in the `call` to `func` */ predicate illegally_named_parameter_objectapi(Call call, Object func, string name) { @@ -67,6 +123,14 @@ predicate illegally_named_parameter_objectapi(Call call, Object func, string nam not get_function_or_initializer_objectapi(func).isLegalArgumentName(name) } +/**Whether there is an illegally named parameter called `name` in the `call` to `func` */ +predicate illegally_named_parameter(Call call, Value func, string name) { + not func.isBuiltin() and + name = call.getANamedArgumentName() and + call.getAFlowNode() = get_a_call(func) and + not get_function_or_initializer(func).isLegalArgumentName(name) +} + /**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */ predicate too_few_args_objectapi(Call call, Object callable, int limit) { // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' @@ -88,6 +152,27 @@ predicate too_few_args_objectapi(Call call, Object callable, int limit) { ) } +/**Whether there are too few arguments in the `call` to `callable` where `limit` is the lowest number of legal arguments */ +predicate too_few_args(Call call, Value callable, int limit) { + // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' + not illegally_named_parameter(call, callable, _) and + not exists(call.getStarargs()) and not exists(call.getKwargs()) and + arg_count(call) < limit and + exists(FunctionValue func | func = get_function_or_initializer(callable) | + call = func.getACall().getNode() and limit = func.minParameters() and + /* The combination of misuse of `mox.Mox().StubOutWithMock()` + * and a bug in mox's implementation of methods results in having to + * pass 1 too few arguments to the mocked function. + */ + not (useOfMoxInModule(call.getEnclosingModule()) and func.isNormalMethod()) + or + call = func.getACall().getNode() and limit = func.minParameters() - 1 + or + callable instanceof ClassValue and + call.getAFlowNode() = get_a_call(callable) and limit = func.minParameters() - 1 + ) +} + /**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */ predicate too_many_args_objectapi(Call call, Object callable, int limit) { // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' @@ -106,6 +191,24 @@ predicate too_many_args_objectapi(Call call, Object callable, int limit) { positional_arg_count_for_call_objectapi(call, callable) > limit } +/**Whether there are too many arguments in the `call` to `func` where `limit` is the highest number of legal arguments */ +predicate too_many_args(Call call, Value callable, int limit) { + // Exclude cases where an incorrect name is used as that is covered by 'Wrong name for an argument in a call' + not illegally_named_parameter(call, callable, _) and + exists(FunctionValue func | + func = get_function_or_initializer(callable) and + not func.getScope().hasVarArg() and limit >= 0 + | + call = func.getACall().getNode() and limit = func.maxParameters() + or + call = func.getACall().getNode() and limit = func.maxParameters() - 1 + or + callable instanceof ClassValue and + call.getAFlowNode() = get_a_call(callable) and limit = func.maxParameters() - 1 + ) and + positional_arg_count_for_call(call, callable) > limit +} + /** Holds if `call` has too many or too few arguments for `func` */ predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too) { too_few_args_objectapi(call, func, limit) and too = "too few" @@ -113,6 +216,13 @@ predicate wrong_args_objectapi(Call call, FunctionObject func, int limit, string too_many_args_objectapi(call, func, limit) and too = "too many" } +/** Holds if `call` has too many or too few arguments for `func` */ +predicate wrong_args(Call call, FunctionValue func, int limit, string too) { + too_few_args(call, func, limit) and too = "too few" + or + too_many_args(call, func, limit) and too = "too many" +} + /** Holds if `call` has correct number of arguments for `func`. * Implies nothing about whether `call` could call `func`. */ @@ -123,8 +233,25 @@ predicate correct_args_if_called_as_method_objectapi(Call call, FunctionObject f arg_count_objectapi(call) < func.maxParameters() } +/** Holds if `call` has correct number of arguments for `func`. + * Implies nothing about whether `call` could call `func`. + */ + bindingset[call, func] +predicate correct_args_if_called_as_method(Call call, FunctionValue func) { + arg_count(call)+1 >= func.minParameters() + and + arg_count(call) < func.maxParameters() +} + /** Holds if `call` is a call to `overriding`, which overrides `func`. */ predicate overridden_call_objectapi(FunctionObject func, FunctionObject overriding, Call call) { overriding.overrides(func) and overriding.getACall().getNode() = call } + +/** Holds if `call` is a call to `overriding`, which overrides `func`. */ +predicate overridden_call(FunctionValue func, FunctionValue overriding, Call call) { + overriding.overrides(func) and + overriding.getACall().getNode() = call +} + diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 1f1f7001da1..844d366b07f 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -505,6 +505,28 @@ abstract class FunctionValue extends CallableValue { predicate isOverriddenMethod() { exists(Value f | f.overrides(this)) } + + /** Whether `name` is a legal argument name for this function */ + bindingset[name] + predicate isLegalArgumentName(string name) { + this.getScope().getAnArg().asName().getId() = name + or + this.getScope().getAKeywordOnlyArg().getId() = name + or + this.getScope().hasKwArg() + } + + /** Whether this is a "normal" method, that is, it is exists as a class attribute + * which is not wrapped and not the __new__ method. */ + predicate isNormalMethod() { + exists(ClassValue cls, string name | + cls.declaredAttribute(name) = this and + name != "__new__" and + exists(Expr expr, AstNode origin | expr.pointsTo(this, origin) | + not origin instanceof Lambda + ) + ) + } } /** Class representing Python functions */ From 1b73cee6928a19566d128bf2daa26ff9e9da4cd7 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 12 Feb 2020 12:56:55 +0100 Subject: [PATCH 112/236] JS: add js/exploitable-polynomial-redos --- change-notes/1.24/analysis-javascript.md | 1 + .../ql/src/Performance/PolynomialReDoS.qhelp | 108 +++++++++++++ .../ql/src/Performance/PolynomialReDoS.ql | 23 +++ javascript/ql/src/Performance/ReDoS.qhelp | 90 ++++------- .../src/Performance/ReDoSIntroduction.qhelp | 55 +++++++ .../ql/src/Performance/ReDoSReferences.qhelp | 16 ++ .../security/performance/PolynomialReDoS.qll | 31 ++++ .../PolynomialReDoSCustomizations.qll | 103 +++++++++++++ .../performance/SuperlinearBackTracking.qll | 142 ++++++++++++++++++ .../ReDoS/PolynomialBackTracking.expected | 139 +++++++++++++++++ .../ReDoS/PolynomialBackTracking.ql | 4 + .../ReDoS/PolynomialReDoS.expected | 74 +++++++++ .../Performance/ReDoS/PolynomialReDoS.qlref | 1 + .../Performance/ReDoS/ReDoS.expected | 2 + .../Performance/ReDoS/polynomial-redos.js | 49 ++++++ 15 files changed, 775 insertions(+), 63 deletions(-) create mode 100644 javascript/ql/src/Performance/PolynomialReDoS.qhelp create mode 100644 javascript/ql/src/Performance/PolynomialReDoS.ql create mode 100644 javascript/ql/src/Performance/ReDoSIntroduction.qhelp create mode 100644 javascript/ql/src/Performance/ReDoSReferences.qhelp create mode 100644 javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll create mode 100644 javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll create mode 100644 javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll create mode 100644 javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected create mode 100644 javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql create mode 100644 javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected create mode 100644 javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref create mode 100644 javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 47fb2dc590b..9268ab6a5c8 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -37,6 +37,7 @@ | Cross-site scripting through exception (`js/xss-through-exception`) | security, external/cwe/cwe-079, external/cwe/cwe-116 | Highlights potential XSS vulnerabilities where an exception is written to the DOM. Results are not shown on LGTM by default. | | Regular expression always matches (`js/regex/always-matches`) | correctness, regular-expressions | Highlights regular expression checks that trivially succeed by matching an empty substring. Results are shown on LGTM by default. | | Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. | +| Polynomial regular expression used on uncontrolled data (`js/polynomial-redos`) | security, external/cwe/cwe-730, external/cwe/cwe-400 | Highlights expensive regular expressions that may be used on malicious input. Results are shown on LGTM by default. | | Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | | Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | diff --git a/javascript/ql/src/Performance/PolynomialReDoS.qhelp b/javascript/ql/src/Performance/PolynomialReDoS.qhelp new file mode 100644 index 00000000000..b0e92d4ea5f --- /dev/null +++ b/javascript/ql/src/Performance/PolynomialReDoS.qhelp @@ -0,0 +1,108 @@ + + + + + + + +

    + + Consider this use of a regular expression, which removes + all leading and trailing whitespace in a string: + +

    + + + text.replace(/^\s+|\s+$/g, ''); // BAD + + +

    + + The sub-expression "\s+$" will match the + whitespace characters in text from left to right, but it + can start matching anywhere within a whitespace sequence. This is + problematic for strings that do not end with a whitespace + character. Such a string will force the regular expression engine to + process each whitespace sequence once per whitespace character in the + sequence. + +

    + +

    + + This ultimately means that the time cost of trimming a + string is quadratic in the length of the string. So a string like + "a b" will take milliseconds to process, but a similar + string with a million spaces instead of just one will take several + minutes. + +

    + +

    + + Avoid this problem by rewriting the regular expression to + not contain the ambiguity about when to start matching whitespace + sequences. For instance, by using a negative look-behind + (/^\s+|(?<!\s)\s+$/g), or just by using the built-in trim + method (text.trim()). + +

    + +

    + + Note that the sub-expression "^\s+" is + not problematic as the ^ anchor restricts + when that sub-expression can start matching, and as the regular + expression engine matches from left to right. + +

    + +
    + + + +

    + + As a similar, but slightly subtler problem, consider the + regular expression that matches lines with numbers, possibly written + using scientific notation: +

    + + + ^0\.\d+E?\d+$ // BAD + + +

    + + The problem with this regular expression is in the + sub-expression \d+E?\d+ because the second + \d+ can start matching digits anywhere after the first + match of the first \d+ if there is no E in + the input string. + +

    + +

    + + This is problematic for strings that do not + end with a digit. Such a string will force the regular expression + engine to process each digit sequence once per digit in the sequence, + again leading to a quadratic time complexity. + +

    + +

    + + To make the processing faster, the regular expression + should be rewritten such that the two \d+ sub-expressions + do not have overlapping matches: ^0\.\d+(E\d+)?$. + +

    + +
    + + + +
    diff --git a/javascript/ql/src/Performance/PolynomialReDoS.ql b/javascript/ql/src/Performance/PolynomialReDoS.ql new file mode 100644 index 00000000000..0d8fdb4990c --- /dev/null +++ b/javascript/ql/src/Performance/PolynomialReDoS.ql @@ -0,0 +1,23 @@ +/** + * @name Polynomial regular expression used on uncontrolled data + * @description A regular expression that can require polynomial time + * to match user-provided values may be + * vulnerable to denial-of-service attacks. + * @kind path-problem + * @problem.severity warning + * @precision high + * @id js/polynomial-redos + * @tags security + * external/cwe/cwe-730 + * external/cwe/cwe-400 + */ + +import javascript +import semmle.javascript.security.performance.PolynomialReDoS::PolynomialReDoS +import DataFlow::PathGraph + + +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) +select sink.getNode(), source, sink, "This expensive $@ use depends on $@.", + sink.getNode().(Sink).getRegExp(), "regular expression", source.getNode(), "a user-provided value" diff --git a/javascript/ql/src/Performance/ReDoS.qhelp b/javascript/ql/src/Performance/ReDoS.qhelp index b226eef11b8..48fa8393cf4 100644 --- a/javascript/ql/src/Performance/ReDoS.qhelp +++ b/javascript/ql/src/Performance/ReDoS.qhelp @@ -1,70 +1,34 @@ +"-//Semmle//qhelp//EN" +"qhelp.dtd"> + - -

    -Some regular expressions take a very long time to match certain input strings to the point where -the time it takes to match a string of length n is proportional to 2n. -Such regular expressions can negatively affect performance, or even allow a malicious user to -perform a Denial of Service ("DoS") attack by crafting an expensive input string for the regular -expression to match. -

    -

    -The regular expression engines provided by many popular JavaScript platforms use backtracking -non-deterministic finite automata to implement regular expression matching. While this approach -is space-efficient and allows supporting advanced features like capture groups, it is not -time-efficient in general. The worst-case time complexity of such an automaton can be exponential, -meaning that for strings of a certain shape, increasing the input length by ten characters may -make the automaton about 1000 times slower. -

    -

    -Typically, a regular expression is affected by this problem if it contains a repetition of the -form r* or r+ where the sub-expression r is ambiguous in -the sense that it can match some string in multiple ways. More information about the precise -circumstances can be found in the references. -

    -
    + - -

    -Modify the regular expression to remove the ambiguity. -

    -
    + +

    + Consider this regular expression: +

    + + /^_(__|.)+_$/ + +

    + Its sub-expression "(__|.)+?" can match the string "__" either by the + first alternative "__" to the left of the "|" operator, or by two + repetitions of the second alternative "." to the right. Thus, a string consisting + of an odd number of underscores followed by some other character will cause the regular + expression engine to run for an exponential amount of time before rejecting the input. +

    +

    + This problem can be avoided by rewriting the regular expression to remove the ambiguity between + the two branches of the alternative inside the repetition: +

    + + /^_(__|[^_])+_$/ + +
    - -

    -Consider this regular expression: -

    - -/^_(__|.)+_$/ - -

    -Its sub-expression "(__|.)+?" can match the string "__" either by the -first alternative "__" to the left of the "|" operator, or by two -repetitions of the second alternative "." to the right. Thus, a string consisting -of an odd number of underscores followed by some other character will cause the regular -expression engine to run for an exponential amount of time before rejecting the input. -

    -

    -This problem can be avoided by rewriting the regular expression to remove the ambiguity between -the two branches of the alternative inside the repetition: -

    - -/^_(__|[^_])+_$/ - -
    + - -
  • -OWASP: -Regular expression Denial of Service - ReDoS. -
  • -
  • Wikipedia: ReDoS.
  • -
  • Wikipedia: Time complexity.
  • -
  • James Kirrage, Asiri Rathnayake, Hayo Thielecke: -Static Analysis for Regular Expression Denial-of-Service Attack. -
  • -
    diff --git a/javascript/ql/src/Performance/ReDoSIntroduction.qhelp b/javascript/ql/src/Performance/ReDoSIntroduction.qhelp new file mode 100644 index 00000000000..4e3d3a06f65 --- /dev/null +++ b/javascript/ql/src/Performance/ReDoSIntroduction.qhelp @@ -0,0 +1,55 @@ + + + +

    + + Some regular expressions take a long time to match certain + input strings to the point where the time it takes to match a string + of length n is proportional to nk or even + 2n. Such regular expressions can negatively affect + performance, or even allow a malicious user to perform a Denial of + Service ("DoS") attack by crafting an expensive input string for the + regular expression to match. + +

    + +

    + + The regular expression engines provided by many popular + JavaScript platforms use backtracking non-deterministic finite + automata to implement regular expression matching. While this approach + is space-efficient and allows supporting advanced features like + capture groups, it is not time-efficient in general. The worst-case + time complexity of such an automaton can be polynomial or even + exponential, meaning that for strings of a certain shape, increasing + the input length by ten characters may make the automaton about 1000 + times slower. + +

    + +

    + + Typically, a regular expression is affected by this + problem if it contains a repetition of the form r* or + r+ where the sub-expression r is ambiguous + in the sense that it can match some string in multiple ways. More + information about the precise circumstances can be found in the + references. + +

    +
    + + + +

    + + Modify the regular expression to remove the ambiguity, or + ensure that the strings matched with the regular expression are short + enough that the time-complexity does not matter. + +

    + +
    +
    diff --git a/javascript/ql/src/Performance/ReDoSReferences.qhelp b/javascript/ql/src/Performance/ReDoSReferences.qhelp new file mode 100644 index 00000000000..2b3e5f17c62 --- /dev/null +++ b/javascript/ql/src/Performance/ReDoSReferences.qhelp @@ -0,0 +1,16 @@ + + + +
  • + OWASP: + Regular expression Denial of Service - ReDoS. +
  • +
  • Wikipedia: ReDoS.
  • +
  • Wikipedia: Time complexity.
  • +
  • James Kirrage, Asiri Rathnayake, Hayo Thielecke: + Static Analysis for Regular Expression Denial-of-Service Attack. +
  • +
    +
    diff --git a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll new file mode 100644 index 00000000000..2ae0fcc749d --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll @@ -0,0 +1,31 @@ +/** + * Provides a taint tracking configuration for reasoning about + * polynomial regular expression denial-of-service attacks. + * + * Note, for performance reasons: only import this file if + * `PolynomialReDoS::Configuration` is needed, otherwise + * `PolynomialReDoSCustomizations` should be imported instead. + */ +import javascript + +module PolynomialReDoS { + import PolynomialReDoSCustomizations::PolynomialReDoS + + class Configuration extends TaintTracking::Configuration { + Configuration() { this = "PolynomialReDoS" } + + override predicate isSource(DataFlow::Node source) { source instanceof Source } + + override predicate isSink(DataFlow::Node sink) { sink instanceof Sink } + + override predicate isSanitizerGuard(TaintTracking::SanitizerGuardNode node) { + super.isSanitizerGuard(node) or + node instanceof LengthGuard + } + + override predicate isSanitizer(DataFlow::Node node) { + super.isSanitizer(node) or + node instanceof Sanitizer + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll new file mode 100644 index 00000000000..d4b8a5a8317 --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll @@ -0,0 +1,103 @@ +/** + * Provides default sources, sinks and sanitizers for reasoning about + * polynomial regular expression denial-of-service attacks, as well + * as extension points for adding your own. + */ + +import javascript +import SuperlinearBackTracking + +module PolynomialReDoS { + /** + * A data flow source node for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Source extends DataFlow::Node { } + + /** + * A data flow sink node for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Sink extends DataFlow::Node { + abstract RegExpTerm getRegExp(); + } + + /** + * A sanitizer for polynomial regular expression denial-of-service vulnerabilities. + */ + abstract class Sanitizer extends DataFlow::Node { } + + /** + * A remote input to a server, seen as a source for polynomial + * regular expression denial-of-service vulnerabilities. + */ + class RequestInputAccessAsSource extends Source { + RequestInputAccessAsSource() { this instanceof HTTP::RequestInputAccess } + } + + /** + * A use of a superlinear backtracking term, seen as a sink for polynomial + * regular expression denial-of-service vulnerabilities. + */ + class PolynomialBackTrackingTermUse extends Sink { + PolynomialBackTrackingTerm term; + + PolynomialBackTrackingTermUse() { + exists(DataFlow::MethodCallNode mcn, DataFlow::Node regexp, string name | + term.getRootTerm() = RegExp::getRegExpFromNode(regexp) + | + this = mcn.getArgument(0) and + regexp = mcn.getReceiver() and + ( + name = "match" or + name = "split" or + name = "matchAll" or + name = "replace" or + name = "search" + ) + or + this = mcn.getReceiver() and + regexp = mcn.getArgument(0) and + (name = "test" or name = "exec") + ) + } + + override RegExpTerm getRegExp() { result = term } + } + + + /** + * An operation that limits the length of a string, seen as a sanitizer. + */ + class StringLengthLimiter extends Sanitizer { + StringLengthLimiter() { + this.(StringReplaceCall).isGlobal() + or + exists(string name | name = "slice" or name = "substring" or name = "substr" | + this.(DataFlow::MethodCallNode).getMethodName() = name + ) + } + } + + /** + * An check on the length of a string, seen as a sanitizer guard. + */ + class LengthGuard extends TaintTracking::SanitizerGuardNode, DataFlow::ValueNode { + DataFlow::Node input; + boolean polarity; + + LengthGuard() { + exists(RelationalComparison cmp, DataFlow::PropRead length | + this.asExpr() = cmp and + length.accesses(input, "length") + | + length.flowsTo(cmp.getLesserOperand().flow()) and polarity = true + or + length.flowsTo(cmp.getGreaterOperand().flow()) and polarity = false + ) + } + + override predicate sanitizes(boolean outcome, Expr e) { + outcome = polarity and + e = input.asExpr() + } + } +} diff --git a/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll b/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll new file mode 100644 index 00000000000..d1bdd0bc92c --- /dev/null +++ b/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll @@ -0,0 +1,142 @@ +/** + * Provides classes for working with regular expressions that can + * perform backtracking in superlinear time. + */ + +import javascript + +/** + * A regular expression term that permits unlimited repetitions. + */ +private class InfiniteRepetitionQuantifier extends RegExpQuantifier { + InfiniteRepetitionQuantifier() { + this instanceof RegExpPlus + or + this instanceof RegExpStar + or + this instanceof RegExpRange and not exists(this.(RegExpRange).getUpperBound()) + } +} + +/** + * Holds if `t` matches at least an epsilon symbol. + * + * That is, this term does not restrict the language of the enclosing regular expression. + * + * This is implemented as an under-approximation, and this predicate does not hold for sub-patterns in particular. + */ +private predicate matchesEpsilon(RegExpTerm t) { + t instanceof RegExpStar + or + t instanceof RegExpOpt + or + t.(RegExpRange).getLowerBound() = 0 + or + exists(RegExpTerm child | + child = t.getAChild() and + matchesEpsilon(child) + | + t instanceof RegExpAlt or + t instanceof RegExpGroup or + t instanceof RegExpPlus or + t instanceof RegExpRange + ) + or + matchesEpsilon(t.(RegExpBackRef).getGroup()) + or + forex(RegExpTerm child | child = t.(RegExpSequence).getAChild() | matchesEpsilon(child)) +} + +/** + * Gets a term that matches the symbol immediately before `t` is done matching. + * + * Examples: + * + * - For `d` in `abc?de` this gets `b`, `c`, `c?` (in addition to `d`). + * - For `(bc|de)` in `a(bc|de)f` this gets `c` and `e` (in addition to `bc|de` and `(bc|de)`). + */ +private RegExpTerm getAMatchPredecessor(RegExpTerm t) { + result = t + or + exists(RegExpTerm recurse | result = getAMatchPredecessor(recurse) | + // wrappers depend on their children + recurse = t.getAChild() and + ( + t instanceof RegExpAlt + or + t instanceof RegExpGroup + or + t instanceof RegExpQuantifier + ) + or + recurse = t.(RegExpSequence).getLastChild() + or + recurse = t.(RegExpBackRef).getGroup() + or + // recurse past epsilon terms + matchesEpsilon(t) and recurse = t.getPredecessor() + ) +} + +private RegExpCharacterClassEscape unwrapCharacterClassEscape(RegExpTerm t) { + t = result or + t.(RegExpCharacterClass).getAChild() = result +} + +pragma[inline] +private predicate compatibleConstants(RegExpTerm t1, RegExpTerm t2) { + exists(string s1, string s2 | + s1 = t1.getAMatchedString() and s2 = t2.getAMatchedString() + or + unwrapCharacterClassEscape(t1).getValue() = s1 and + unwrapCharacterClassEscape(t2).getValue() = s2 + | + s1 = s2 + ) +} + +/** + * Holds if `s1` and `s2` possibly have a non-empty intersection. + * + * This is a simple, and under-approximate, version of + * ReDoS::compatible/2, as this predicate only handles some character + * classes and constant values. + */ +pragma[inline] +private predicate compatible(RegExpTerm s1, RegExpTerm s2) { + not s1.(RegExpCharacterClass).isInverted() and + not s2.(RegExpCharacterClass).isInverted() and + compatibleConstants(s1, s2) +} + +/** + * A term that may cause a regular expression engine to perform a + * polynomial number of match attempts, relative to the input length. + */ +class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier { + string reason; + + PolynomialBackTrackingTerm() { + // the regexp may fail to match ... + exists(RegExpTerm succ | this.getSuccessor+() = succ | not matchesEpsilon(succ)) and + ( + // ... and while failing, it will try to start matching at all positions of a long string + forall(RegExpTerm pred | pred = this.getPredecessor+() | matchesEpsilon(pred)) and + reason = "it can start matching anywhere" + or + exists(InfiniteRepetitionQuantifier pred | + pred = getAMatchPredecessor(this.getPredecessor()) and + compatible(pred.getAChild(), this.getAChild()) + | + reason = "it can start matching anywhere after the start of the preceeding '" + + pred.toString() + "'" + ) + ) and + not this.getParent*() instanceof RegExpSubPattern // too many corner cases + } + + /** + * Gets the reason for the number of match attempts. + */ + string getReason() { result = reason } +} diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected new file mode 100644 index 00000000000..98f5cb00ed4 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.expected @@ -0,0 +1,139 @@ +| polynomial-redos.js:7:24:7:26 | \\s+ | it can start matching anywhere | +| polynomial-redos.js:8:17:8:18 | * | it can start matching anywhere | +| polynomial-redos.js:9:19:9:21 | \\s* | it can start matching anywhere | +| polynomial-redos.js:11:19:11:20 | .* | it can start matching anywhere | +| polynomial-redos.js:12:19:12:20 | .* | it can start matching anywhere | +| polynomial-redos.js:15:28:15:35 | [\\s\\S]*? | it can start matching anywhere after the start of the preceeding '\\s*' | +| polynomial-redos.js:18:17:18:22 | [0-9]* | it can start matching anywhere | +| polynomial-redos.js:18:83:18:100 | [\\u0600-\\u06FF\\/]+ | it can start matching anywhere | +| polynomial-redos.js:19:17:19:22 | [0-9]* | it can start matching anywhere | +| polynomial-redos.js:20:56:20:58 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d*' | +| polynomial-redos.js:20:56:20:58 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d+' | +| polynomial-redos.js:22:57:22:59 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d*' | +| polynomial-redos.js:22:57:22:59 | \\d+ | it can start matching anywhere after the start of the preceeding '\\d+' | +| polynomial-redos.js:25:37:25:56 | [a-zA-Z0-9+\\/ \\t\\n]+ | it can start matching anywhere after the start of the preceeding '[ \\t]+' | +| polynomial-redos.js:27:14:27:22 | [A-Z]{2,} | it can start matching anywhere | +| polynomial-redos.js:30:19:30:22 | [?]+ | it can start matching anywhere | +| polynomial-redos.js:31:42:31:43 | -+ | it can start matching anywhere | +| polynomial-redos.js:32:45:32:47 | \\n* | it can start matching anywhere | +| polynomial-redos.js:33:17:33:20 | (.)* | it can start matching anywhere | +| regexplib/address.js:18:26:18:31 | [ \\w]* | it can start matching anywhere after the start of the preceeding '[ \\w]{3,}' | +| regexplib/address.js:20:144:20:147 | [ ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9 \\-.]{6,}' | +| regexplib/address.js:24:26:24:31 | [ \\w]* | it can start matching anywhere after the start of the preceeding '[ \\w]{3,}' | +| regexplib/address.js:27:3:27:5 | \\s* | it can start matching anywhere | +| regexplib/address.js:27:48:27:50 | \\s* | it can start matching anywhere | +| regexplib/address.js:27:93:27:95 | \\s* | it can start matching anywhere | +| regexplib/address.js:38:39:38:45 | [ 0-9]* | it can start matching anywhere after the start of the preceeding '[ \|\\.]*' | +| regexplib/address.js:51:235:51:239 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:51:631:51:635 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:51:796:51:798 | \\s+ | it can start matching anywhere after the start of the preceeding '\\s+' | +| regexplib/address.js:67:379:67:755 | [a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\' ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\']+' | +| regexplib/address.js:69:3:69:5 | \\s* | it can start matching anywhere | +| regexplib/address.js:69:48:69:50 | \\s* | it can start matching anywhere | +| regexplib/address.js:69:93:69:95 | \\s* | it can start matching anywhere | +| regexplib/address.js:75:235:75:239 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:75:631:75:635 | \\x20* | it can start matching anywhere after the start of the preceeding '\\x20*' | +| regexplib/address.js:75:796:75:798 | \\s+ | it can start matching anywhere after the start of the preceeding '\\s+' | +| regexplib/address.js:85:15:85:49 | ([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])+ | it can start matching anywhere | +| regexplib/address.js:85:51:85:67 | ([ ]\|[:]\|\\t\|[-])* | it can start matching anywhere after the start of the preceeding '([0-9]\|[ ]\|[-]\|[\\(]\|[\\)]\|ext.\|[,])+' | +| regexplib/address.js:93:3:93:5 | \\s* | it can start matching anywhere | +| regexplib/address.js:93:48:93:50 | \\s* | it can start matching anywhere | +| regexplib/address.js:93:93:93:95 | \\s* | it can start matching anywhere | +| regexplib/address.js:95:379:95:755 | [a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\' ]+ | it can start matching anywhere after the start of the preceeding '[a-zA-Z0-9ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïñòóôõöøùúûüýÿ\\.\\,\\-\\/\\']+' | +| regexplib/email.js:8:16:8:49 | [^ \\t\\(\\)\\<\\>@,;\\:\\\\\\"\\.\\[\\]\\r\\n]+ | it can start matching anywhere | +| regexplib/email.js:12:2:12:4 | \\w+ | it can start matching anywhere | +| regexplib/email.js:15:28:15:30 | \\w* | it can start matching anywhere after the start of the preceeding '\\w+' | +| regexplib/email.js:20:3:20:6 | \\w+? | it can start matching anywhere | +| regexplib/email.js:28:2:28:4 | \\w+ | it can start matching anywhere | +| regexplib/email.js:28:27:28:29 | \\w* | it can start matching anywhere after the start of the preceeding '\\w+' | +| regexplib/email.js:28:73:28:87 | [0-9a-zA-Z'\\.]+ | it can start matching anywhere | +| regexplib/email.js:28:125:28:139 | [0-9a-zA-Z'\\.]+ | it can start matching anywhere | +| regexplib/email.js:29:2:29:7 | [\\w-]+ | it can start matching anywhere | +| regexplib/markup.js:6:99:6:113 | [\\s\\w\\d\\)\\(\\,]* | it can start matching anywhere after the start of the preceeding '[\\d\\w]+' | +| regexplib/markup.js:19:2:19:12 | (+()\\s-]+\|\\*\|\\[.*?\\])+ | it can start matching anywhere | +| tst.js:66:16:66:31 | [\\w#:.~>+()\\s-]+ | it can start matching anywhere | +| tst.js:66:46:66:48 | \\s* | it can start matching anywhere after the start of the preceeding '[\\w#:.~>+()\\s-]+' | +| tst.js:74:14:74:21 | (b\|a?b)* | it can start matching anywhere | +| tst.js:77:14:77:21 | (a\|aa?)* | it can start matching anywhere | +| tst.js:80:14:80:20 | (.\|\\n)* | it can start matching anywhere | +| tst.js:83:14:83:20 | (.\|\\n)* | it can start matching anywhere | +| tst.js:89:25:89:32 | (a\|aa?)* | it can start matching anywhere | +| tst.js:92:14:92:21 | (a\|aa?)* | it can start matching anywhere | diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql new file mode 100644 index 00000000000..592e5aad7a8 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialBackTracking.ql @@ -0,0 +1,4 @@ +import semmle.javascript.security.performance.SuperlinearBackTracking + +from PolynomialBackTrackingTerm t +select t, t.getReason() diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected new file mode 100644 index 00000000000..4d3a9a12da6 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.expected @@ -0,0 +1,74 @@ +nodes +| polynomial-redos.js:5:6:5:32 | tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | +| polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:33:2:33:8 | tainted | +| polynomial-redos.js:33:2:33:8 | tainted | +edges +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:7:2:7:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:8:2:8:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:9:2:9:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:11:2:11:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:12:2:12:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:15:2:15:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:18:2:18:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:19:2:19:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:20:2:20:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:25:2:25:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:27:77:27:83 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:30:2:30:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:33:2:33:8 | tainted | +| polynomial-redos.js:5:6:5:32 | tainted | polynomial-redos.js:33:2:33:8 | tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:5:6:5:32 | tainted | +| polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:5:6:5:32 | tainted | +#select +| polynomial-redos.js:7:2:7:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:7:2:7:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:7:24:7:26 | \\s+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:8:2:8:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:8:2:8:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:8:17:8:18 | * | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:9:2:9:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:9:2:9:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:9:19:9:21 | \\s* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:11:2:11:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:11:2:11:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:11:19:11:20 | .* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:12:2:12:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:12:2:12:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:12:19:12:20 | .* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:15:2:15:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:15:2:15:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:15:28:15:35 | [\\s\\S]*? | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:18:2:18:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:18:2:18:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:18:17:18:22 | [0-9]* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:18:2:18:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:18:2:18:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:18:83:18:100 | [\\u0600-\\u06FF\\/]+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:19:2:19:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:19:2:19:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:19:17:19:22 | [0-9]* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:20:2:20:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:20:2:20:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:20:56:20:58 | \\d+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:25:2:25:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:25:2:25:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:25:37:25:56 | [a-zA-Z0-9+\\/ \\t\\n]+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:27:77:27:83 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:27:77:27:83 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:27:14:27:22 | [A-Z]{2,} | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:30:2:30:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:30:2:30:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:30:19:30:22 | [?]+ | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | +| polynomial-redos.js:33:2:33:8 | tainted | polynomial-redos.js:5:16:5:32 | req.query.tainted | polynomial-redos.js:33:2:33:8 | tainted | This expensive $@ use depends on $@. | polynomial-redos.js:33:17:33:20 | (.)* | regular expression | polynomial-redos.js:5:16:5:32 | req.query.tainted | a user-provided value | diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref new file mode 100644 index 00000000000..e26045522d1 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/PolynomialReDoS.qlref @@ -0,0 +1 @@ +Performance/PolynomialReDoS.ql diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected b/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected index 6601731045f..f31ed3e638b 100644 --- a/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected +++ b/javascript/ql/test/query-tests/Performance/ReDoS/ReDoS.expected @@ -1,3 +1,5 @@ +| polynomial-redos.js:17:5:17:6 | .* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of ','. | +| polynomial-redos.js:41:52:41:63 | [\\x21-\\x7E]* | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of '?'. | | regexplib/address.js:51:803:51:811 | [A-Za-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'A'. | | regexplib/address.js:75:803:75:811 | [A-Za-z]+ | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'A'. | | regexplib/dates.js:66:133:66:139 | JANUARY | This part of the regular expression may cause exponential backtracking on strings containing many repetitions of 'JANUARY'. | diff --git a/javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js b/javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js new file mode 100644 index 00000000000..4175bd48a50 --- /dev/null +++ b/javascript/ql/test/query-tests/Performance/ReDoS/polynomial-redos.js @@ -0,0 +1,49 @@ +var express = require('express'); +var app = express(); + +app.use(function(req, res) { + let tainted = req.query.tainted; + + tainted.replace(/^\s+|\s+$/g, ''); // NOT OK + tainted.split(/ *, */); // NOT OK + tainted.replace(/\s*\n\s*/g, ' '); // NOT OK + tainted.split('\n'); // OK + tainted.replace(/.*[/\\]/, ''); // NOT OK + tainted.replace(/.*\./, ''); // NOT OK + tainted.replace(/^.*[/\\]/, ''); // OK + tainted.replace(/^.*\./, ''); // OK + tainted.replace(/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/); // NOT OK + tainted.replace(/^(`+)([\s\S]*?[^`])\1(?!`)/); // OK + /^(.*,)+(.+)?$/.test(tainted); // NOT OK - but only flagged by js/redos + tainted.match(/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i); // NOT OK + tainted.match(/[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i); // NOT OK (even though it is a proposed fix for the above) + tainted.match(/^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/); // NOT OK + if (tainted.length < 7000) { + tainted.match(/^(\+|-)?(\d+|(\d*\.\d*))?(E|e)?([-+])?(\d+)?$/); // OK - but flagged + } + + tainted.match(/^([a-z0-9-]+)[ \t]+([a-zA-Z0-9+\/ \t\n]+[=]*)(.*)$/); // NOT OK + tainted.match(/^([a-z0-9-]+)[ \t\n]+([a-zA-Z0-9+\/][a-zA-Z0-9+\/ \t\n=]*)([^a-zA-Z0-9+\/ \t\n=].*)?$/); // OK + /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/.test(tainted); // NOT OK + /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/.test(tainted); // OK + + tainted.replace(/[?]+.*$/g, ""); // OK - can not fail - but still flagged + tainted.replace(/\-\-+/g, "-").replace(/-+$/, ""); // OK - indirectly sanitized + tainted.replace(/\n\n\n+/g, "\n").replace(/\n*$/g, ""); // OK - indirectly sanitized + tainted.match(/(.)*solve\/challenges\/server-side(.)*/); // NOT OK + tainted.match(/(?![\s\S]*)/i); // OK + + tainted.match(/<.*class="([^"]+)".*>/); // NOT OK - but not flagged + tainted.match(/<.*style="([^"]+)".*>/); // NOT OK - but not flagged + tainted.match(/<.*href="([^"]+)".*>/); // NOT OK - but not flagged + + tainted.match(/^([^-]+)-([A-Za-z0-9+/]+(?:=?=?))([?\x21-\x7E]*)$/); // NOT OK - but not flagged + tainted.match(/^([^-]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)*$/); // NOT OK (it is a fix for the above, but it introduces exponential complexity elsewhere) + + tainted.match(/^([a-z0-9-]+)[ \t]+([a-zA-Z0-9+\/]+[=]*)([\n \t]+([^\n]+))?$/); // NOT OK - but not flagged due to lack of support for inverted character classes + tainted.match(/^([a-z0-9-]+)[ \t]+([a-zA-Z0-9+\/]+[=]*)([ \t]+([^ \t][^\n]*[\n]*)?)?$/); // OK + + tainted.match(/^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)+$/); // NOT OK - but not flagged + tainted.match(/^(?:\.?[a-zA-Z_][a-zA-Z_0-9]*)(?:\.[a-zA-Z_][a-zA-Z_0-9]*)*$/); // OK + +}); From 0c30d7ccedbc7fce794c253e17bde82dd1cc5170 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 27 Feb 2020 10:28:12 +0100 Subject: [PATCH 113/236] Java: Update test output. --- java/ql/test/library-tests/typeflow/typeflow.expected | 2 -- 1 file changed, 2 deletions(-) diff --git a/java/ql/test/library-tests/typeflow/typeflow.expected b/java/ql/test/library-tests/typeflow/typeflow.expected index 0a8947fc39d..a9bf42bc5ad 100644 --- a/java/ql/test/library-tests/typeflow/typeflow.expected +++ b/java/ql/test/library-tests/typeflow/typeflow.expected @@ -1,9 +1,7 @@ | A.java:9:10:9:11 | f1 | ArrayList | true | -| A.java:10:10:10:10 | l | List | false | | A.java:11:19:11:20 | f2 | List | false | | A.java:12:16:12:16 | x | List | false | | A.java:14:9:14:9 | y | A | false | -| A.java:18:11:18:12 | l2 | List | false | | A.java:19:9:19:9 | y | List | false | | A.java:26:14:26:14 | o | List | false | | A.java:34:11:34:11 | x | Integer | false | From fefcf1a7a68e396a233e68756699ea8c7608828d Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 27 Feb 2020 09:41:01 +0000 Subject: [PATCH 114/236] JS: Autoformat everything --- .../ql/src/AngularJS/DependencyMismatch.ql | 5 +- .../ql/src/AngularJS/DoubleCompilation.ql | 3 +- .../ql/src/AngularJS/IncompatibleService.ql | 3 +- .../src/AngularJS/UnusedAngularDependency.ql | 5 +- .../ql/src/Declarations/Declarations.qll | 6 +- .../ql/src/Declarations/MissingVarDecl.ql | 3 +- .../SuspiciousMethodNameDeclaration.ql | 32 +++---- .../UnreachableMethodOverloads.ql | 69 +++++++-------- .../src/Declarations/UnstableCyclicImport.ql | 10 ++- .../ql/src/Expressions/ExprHasNoEffect.ql | 1 - .../Expressions/HeterogeneousComparison.ql | 10 ++- .../src/Expressions/MissingSpaceInAppend.ql | 35 +++----- javascript/ql/src/Expressions/Misspelling.qll | 3 +- .../UnboundEventHandlerReceiver.ql | 7 +- .../src/LanguageFeatures/IllegalInvocation.ql | 3 +- .../src/LanguageFeatures/InconsistentNew.ql | 6 +- .../ql/src/Metrics/FCyclomaticComplexity.ql | 3 +- .../ql/src/Performance/PolynomialReDoS.ql | 1 - javascript/ql/src/Performance/ReDoS.ql | 11 +-- javascript/ql/src/RegExp/BackspaceEscape.ql | 5 +- .../DuplicateCharacterInCharacterClass.ql | 3 +- .../ql/src/RegExp/IdentityReplacement.ql | 3 +- .../ql/src/RegExp/RegExpAlwaysMatches.ql | 18 ++-- .../Security/CWE-020/HostnameRegexpShared.qll | 15 ++-- .../CWE-020/IncompleteHostnameRegExp.ql | 28 +++--- .../CWE-020/IncompleteUrlSchemeCheck.ql | 4 +- .../Security/CWE-020/MissingRegExpAnchor.ql | 28 +++--- .../CWE-020/UselessRegExpCharacterEscape.ql | 16 ++-- .../src/Security/CWE-078/CommandInjection.ql | 15 ++-- .../ql/src/Security/CWE-079/ExceptionXss.ql | 10 +-- .../ql/src/Security/CWE-116/DoubleEscaping.ql | 16 +--- .../CWE-116/IncompleteSanitization.ql | 4 +- .../Security/CWE-352/MissingCsrfMiddleware.ql | 17 +--- .../CWE-400/PrototypePollutionUtility.ql | 16 +--- .../Summaries/ExtractFlowStepSummaries.ql | 5 +- .../Summaries/ExtractSinkSummaries.ql | 3 +- .../Summaries/ExtractSourceSummaries.ql | 3 +- .../src/Security/Summaries/ImportFromCsv.qll | 3 +- .../ql/src/Statements/IgnoreArrayResult.ql | 4 +- .../ql/src/Statements/ImplicitReturn.ql | 3 +- .../src/Statements/UseOfReturnlessFunction.ql | 87 +++++++++---------- .../ql/src/Statements/UselessConditional.qll | 8 +- .../ql/src/external/CodeDuplication.qll | 12 ++- javascript/ql/src/external/DefectFilter.qll | 5 +- javascript/ql/src/external/MetricFilter.qll | 5 +- .../analysis-quality/CallGraphQuality.qll | 8 +- .../analysis-quality/UnresolvableImports.ql | 4 +- .../meta/extraction-metrics/MissingMetrics.ql | 9 +- .../meta/extraction-metrics/PhaseTimings.ql | 3 +- .../ql/src/semmle/javascript/BasicBlocks.qll | 3 +- .../semmle/javascript/CharacterEscapes.qll | 3 +- .../ql/src/semmle/javascript/Classes.qll | 24 ++--- .../ql/src/semmle/javascript/DefUse.qll | 3 +- .../src/semmle/javascript/ES2015Modules.qll | 20 ++--- .../ql/src/semmle/javascript/EmailClients.qll | 5 +- javascript/ql/src/semmle/javascript/Expr.qll | 7 +- javascript/ql/src/semmle/javascript/Files.qll | 7 +- .../ql/src/semmle/javascript/Functions.qll | 7 +- .../src/semmle/javascript/GeneratedCode.qll | 6 +- .../semmle/javascript/GlobalAccessPaths.qll | 22 ++--- .../src/semmle/javascript/HtmlSanitizers.qll | 5 +- .../src/semmle/javascript/InclusionTests.qll | 1 + .../ql/src/semmle/javascript/Locations.qll | 1 + .../ql/src/semmle/javascript/Modules.qll | 3 +- .../ql/src/semmle/javascript/NodeJS.qll | 3 +- .../javascript/NodeModuleResolutionImpl.qll | 2 +- .../ql/src/semmle/javascript/Promises.qll | 61 +++++-------- .../ql/src/semmle/javascript/Regexp.qll | 18 ++-- .../semmle/javascript/RestrictedLocations.qll | 5 +- javascript/ql/src/semmle/javascript/SSA.qll | 8 +- .../src/semmle/javascript/StandardLibrary.qll | 3 +- javascript/ql/src/semmle/javascript/Stmt.qll | 10 +-- .../ql/src/semmle/javascript/StringOps.qll | 13 ++- .../ql/src/semmle/javascript/TypeScript.qll | 20 ++--- .../ql/src/semmle/javascript/Variables.qll | 10 +-- .../dataflow/BackwardExploration.qll | 4 +- .../javascript/dataflow/Configuration.qll | 10 ++- .../semmle/javascript/dataflow/DataFlow.qll | 21 ++--- .../dataflow/ForwardExploration.qll | 4 +- .../javascript/dataflow/InferredTypes.qll | 3 +- .../src/semmle/javascript/dataflow/Nodes.qll | 58 +++++-------- .../javascript/dataflow/TaintTracking.qll | 15 ++-- .../javascript/dataflow/TypeTracking.qll | 5 +- .../dataflow/internal/AccessPaths.qll | 5 +- .../dataflow/internal/CallGraphs.qll | 32 ++++--- .../dataflow/internal/FlowSteps.qll | 13 +-- .../dependencies/FrameworkLibraries.qll | 18 ++-- .../frameworks/AngularJS/AngularJSCore.qll | 3 +- .../AngularJS/AngularJSExpressions.qll | 16 ++-- .../javascript/frameworks/ClientRequests.qll | 8 +- .../javascript/frameworks/CryptoLibraries.qll | 3 +- .../semmle/javascript/frameworks/Electron.qll | 6 +- .../javascript/frameworks/EventEmitter.qll | 13 +-- .../semmle/javascript/frameworks/Express.qll | 30 +++---- .../semmle/javascript/frameworks/Files.qll | 9 +- .../src/semmle/javascript/frameworks/HTTP.qll | 3 +- .../javascript/frameworks/Handlebars.qll | 2 +- .../src/semmle/javascript/frameworks/Koa.qll | 2 +- .../semmle/javascript/frameworks/Logging.qll | 18 ++-- .../semmle/javascript/frameworks/NoSQL.qll | 4 +- .../javascript/frameworks/NodeJSLib.qll | 70 +++++++-------- .../semmle/javascript/frameworks/React.qll | 11 +-- .../semmle/javascript/frameworks/SocketIO.qll | 20 ++--- .../frameworks/SystemCommandExecutors.qll | 1 - .../javascript/frameworks/Typeahead.qll | 3 +- .../javascript/frameworks/WebSocket.qll | 21 ++--- .../semmle/javascript/frameworks/jQuery.qll | 10 +-- .../src/semmle/javascript/linters/JSLint.qll | 5 +- .../security/dataflow/CleartextLogging.qll | 2 +- .../CleartextLoggingCustomizations.qll | 45 ++++------ .../security/dataflow/CommandInjection.qll | 1 - .../javascript/security/dataflow/DOM.qll | 9 +- .../dataflow/DomBasedXssCustomizations.qll | 3 +- .../security/dataflow/ExceptionXss.qll | 3 +- .../dataflow/IndirectCommandArgument.qll | 8 +- ...IndirectCommandInjectionCustomizations.qll | 9 +- .../LoopBoundInjectionCustomizations.qll | 7 +- .../security/dataflow/MissingRateLimiting.qll | 5 +- .../dataflow/TaintedPathCustomizations.qll | 10 +-- .../security/dataflow/UnsafeJQueryPlugin.qll | 1 - .../javascript/security/dataflow/Xss.qll | 6 +- .../security/performance/PolynomialReDoS.qll | 1 + .../PolynomialReDoSCustomizations.qll | 1 - .../performance/SuperlinearBackTracking.qll | 5 +- .../test/library-tests/Classes/ClassFlow.qll | 2 +- .../library-tests/Classes/PrivateField.qll | 7 +- .../Promises/ResolvedPromiseDefinition.qll | 4 +- .../ql/test/library-tests/Promises/flow.qll | 2 +- .../TypeAnnotations/PredicateTypeExpr.qll | 4 +- .../SocketIO/ClientSendNode_getAck.qll | 4 +- 130 files changed, 657 insertions(+), 774 deletions(-) diff --git a/javascript/ql/src/AngularJS/DependencyMismatch.ql b/javascript/ql/src/AngularJS/DependencyMismatch.ql index 314c0a12320..bbdb042bf19 100644 --- a/javascript/ql/src/AngularJS/DependencyMismatch.ql +++ b/javascript/ql/src/AngularJS/DependencyMismatch.ql @@ -24,8 +24,9 @@ where exists(string n | p = f.getDependencyParameter(n) | p.getName() != n and exists(f.getDependencyParameter(p.getName())) and - msg = "This parameter is named '" + p.getName() + "', " + - "but actually refers to dependency '" + n + "'." + msg = + "This parameter is named '" + p.getName() + "', " + "but actually refers to dependency '" + + n + "'." ) ) select p, msg diff --git a/javascript/ql/src/AngularJS/DoubleCompilation.ql b/javascript/ql/src/AngularJS/DoubleCompilation.ql index 74624e51c6e..a281ef5500f 100644 --- a/javascript/ql/src/AngularJS/DoubleCompilation.ql +++ b/javascript/ql/src/AngularJS/DoubleCompilation.ql @@ -15,7 +15,8 @@ import javascript from AngularJS::ServiceReference compile, SimpleParameter elem, CallExpr c where compile.getName() = "$compile" and - elem = any(AngularJS::CustomDirective d) + elem = + any(AngularJS::CustomDirective d) .getALinkFunction() .(AngularJS::LinkFunction) .getElementParameter() and diff --git a/javascript/ql/src/AngularJS/IncompatibleService.ql b/javascript/ql/src/AngularJS/IncompatibleService.ql index dcbc0d2ceaf..7e6daafd72b 100644 --- a/javascript/ql/src/AngularJS/IncompatibleService.ql +++ b/javascript/ql/src/AngularJS/IncompatibleService.ql @@ -130,7 +130,8 @@ where kind = getServiceKind(request, name) and exists(request.getAServiceDefinition(name)) and // ignore unknown/undefined services not isCompatibleRequestedService(request, kind) and - compatibleWithString = concat(string compatibleKind | + compatibleWithString = + concat(string compatibleKind | isCompatibleRequestedService(request, compatibleKind) and not isWildcardKind(compatibleKind) | diff --git a/javascript/ql/src/AngularJS/UnusedAngularDependency.ql b/javascript/ql/src/AngularJS/UnusedAngularDependency.ql index 8358b2382c0..8e0baa00fab 100644 --- a/javascript/ql/src/AngularJS/UnusedAngularDependency.ql +++ b/javascript/ql/src/AngularJS/UnusedAngularDependency.ql @@ -34,8 +34,9 @@ predicate isMissingParameter(AngularJS::InjectableFunction f, string msg, ASTNod then dependenciesAreString = "dependency is" else dependenciesAreString = "dependencies are" ) and - msg = "This function has " + paramCount + " " + parametersString + ", but " + injectionCount + - " " + dependenciesAreString + " injected into it." + msg = + "This function has " + paramCount + " " + parametersString + ", but " + injectionCount + " " + + dependenciesAreString + " injected into it." ) ) } diff --git a/javascript/ql/src/Declarations/Declarations.qll b/javascript/ql/src/Declarations/Declarations.qll index 1ebf94f711a..4fb60140af0 100644 --- a/javascript/ql/src/Declarations/Declarations.qll +++ b/javascript/ql/src/Declarations/Declarations.qll @@ -31,7 +31,8 @@ VarRef refInContainer(Variable var, RefKind kind, StmtContainer sc) { * declaration of `var` (if `kind` is `Decl()`) in `sc`. */ VarRef firstRefInContainer(Variable var, RefKind kind, StmtContainer sc) { - result = min(refInContainer(var, kind, sc) as ref + result = + min(refInContainer(var, kind, sc) as ref order by ref.getLocation().getStartLine(), ref.getLocation().getStartColumn() ) @@ -52,7 +53,8 @@ VarRef refInTopLevel(Variable var, RefKind kind, TopLevel tl) { * declaration of `var` (if `kind` is `Decl()`) in `tl`. */ VarRef firstRefInTopLevel(Variable var, RefKind kind, TopLevel tl) { - result = min(refInTopLevel(var, kind, tl) as ref + result = + min(refInTopLevel(var, kind, tl) as ref order by ref.getLocation().getStartLine(), ref.getLocation().getStartColumn() ) diff --git a/javascript/ql/src/Declarations/MissingVarDecl.ql b/javascript/ql/src/Declarations/MissingVarDecl.ql index 8b72adcd618..8dd40cb064a 100644 --- a/javascript/ql/src/Declarations/MissingVarDecl.ql +++ b/javascript/ql/src/Declarations/MissingVarDecl.ql @@ -57,7 +57,8 @@ GlobalVarAccess getAccessIn(GlobalVariable v, Function f) { * Gets the (lexically) first access to variable `v` in function `f`. */ GlobalVarAccess getFirstAccessIn(GlobalVariable v, Function f) { - result = min(getAccessIn(v, f) as gva + result = + min(getAccessIn(v, f) as gva order by gva.getLocation().getStartLine(), gva.getLocation().getStartColumn() ) diff --git a/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql b/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql index febdd7ab5ea..efa824ae7b0 100644 --- a/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql +++ b/javascript/ql/src/Declarations/SuspiciousMethodNameDeclaration.ql @@ -1,7 +1,7 @@ /** * @name Suspicious method name declaration - * @description A method declaration with a name that is a special keyword in another - * context is suspicious. + * @description A method declaration with a name that is a special keyword in another + * context is suspicious. * @kind problem * @problem.severity warning * @id js/suspicious-method-name-declaration @@ -19,7 +19,7 @@ import javascript predicate isSuspiciousMethodName(string name, ClassOrInterface container) { name = "function" or - // "constructor" is only suspicious outside a class. + // "constructor" is only suspicious outside a class. name = "constructor" and not container instanceof ClassDefinition or // "new" is only suspicious inside a class. @@ -31,7 +31,6 @@ where container.getLocation().getFile().getFileType().isTypeScript() and container.getMember(name) = member and isSuspiciousMethodName(name, container) and - // Cases to ignore. not ( // Assume that a "new" method is intentional if the class has an explicit constructor. @@ -39,31 +38,34 @@ where container instanceof ClassDefinition and exists(ConstructorDeclaration constructor | container.getMember("constructor") = constructor and - not constructor.isSynthetic() - ) + not constructor.isSynthetic() + ) or // Explicitly declared static methods are fine. container instanceof ClassDefinition and member.isStatic() or - // Only looking for declared methods. Methods with a body are OK. + // Only looking for declared methods. Methods with a body are OK. exists(member.getBody().getBody()) or // The developer was not confused about "function" when there are other methods in the interface. - name = "function" and + name = "function" and exists(MethodDeclaration other | other = container.getAMethod() | other.getName() != "function" and not other.(ConstructorDeclaration).isSynthetic() ) - ) - - and - + ) and ( - name = "constructor" and msg = "The member name 'constructor' does not declare a constructor in interfaces, but it does in classes." + name = "constructor" and + msg = + "The member name 'constructor' does not declare a constructor in interfaces, but it does in classes." or - name = "new" and msg = "The member name 'new' does not declare a constructor, but 'constructor' does in class declarations." + name = "new" and + msg = + "The member name 'new' does not declare a constructor, but 'constructor' does in class declarations." or - name = "function" and msg = "The member name 'function' does not declare a function, it declares a method named 'function'." + name = "function" and + msg = + "The member name 'function' does not declare a function, it declares a method named 'function'." ) select member, msg diff --git a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql index bcfd5d810e7..befe4914032 100644 --- a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql +++ b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql @@ -44,43 +44,39 @@ string getKind(MemberDeclaration m) { * A call-signature that originates from a MethodSignature in the AST. */ private class MethodCallSig extends CallSignatureType { - string name; - - MethodCallSig() { - exists(MethodSignature sig | - this = sig.getBody().getCallSignature() and - name = sig.getName() - ) - } - - /** - * Gets the name of any member that has this signature. - */ - string getName() { - result = name - } + string name; + + MethodCallSig() { + exists(MethodSignature sig | + this = sig.getBody().getCallSignature() and + name = sig.getName() + ) + } + + /** + * Gets the name of any member that has this signature. + */ + string getName() { result = name } } /** * Holds if the two call signatures could be overloads of each other and have the same parameter types. */ predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) { - method.getName() = other.getName() and - + method.getName() = other.getName() and method.getNumOptionalParameter() = other.getNumOptionalParameter() and method.getNumParameter() = other.getNumParameter() and method.getNumRequiredParameter() = other.getNumRequiredParameter() and - // purposely not looking at number of type arguments. - - method.getKind() = other.getKind() and - - - forall(int i | i in [0 .. -1 + method.getNumParameter()] | + // purposely not looking at number of type arguments. + method.getKind() = other.getKind() and + forall(int i | i in [0 .. -1 + method.getNumParameter()] | method.getParameter(i) = other.getParameter(i) // This is sometimes imprecise, so it is still a good idea to compare type annotations. - ) and - - // shared type parameters are equal. - forall(int i | i in [0 .. -1 + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] | + ) and + // shared type parameters are equal. + forall(int i | + i in [0 .. -1 + + min(int num | num = method.getNumTypeParameter() or num = other.getNumTypeParameter())] + | method.getTypeParameterBound(i) = other.getTypeParameterBound(i) ) } @@ -89,7 +85,7 @@ predicate matchingCallSignature(MethodCallSig method, MethodCallSig other) { * Gets which overload index the MethodSignature has among the overloads of the same name. */ int getOverloadIndex(MethodSignature sig) { - sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig + sig.getDeclaringType().getMethodOverload(sig.getName(), result) = sig } /** @@ -100,18 +96,14 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) { method.getDeclaringType() = other.getDeclaringType() and // same static modifier. getKind(method) = getKind(other) and - // same name. method.getName() = other.getName() and - // same number of parameters. method.getBody().getNumParameter() = other.getBody().getNumParameter() and - // The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar. forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] | - getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i) + getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i) ) and - matchingCallSignature(method.getBody().getCallSignature(), other.getBody().getCallSignature()) } @@ -119,22 +111,19 @@ from ClassOrInterface decl, string name, MethodSignature previous, MethodSignatu where previous = decl.getMethod(name) and unreachable = getOtherMatchingSignatures(previous) and - // If the method is part of inheritance between classes/interfaces, then there can sometimes be reasons for having this pattern. - not exists(decl.getASuperTypeDeclaration().getMethod(name)) and - not exists(ClassOrInterface sub | + not exists(decl.getASuperTypeDeclaration().getMethod(name)) and + not exists(ClassOrInterface sub | decl = sub.getASuperTypeDeclaration() and exists(sub.getMethod(name)) ) and - - // If a later method overload has more type parameters, then that overload can be selected by explicitly declaring the type arguments at the callsite. // This comparison removes those cases. unreachable.getBody().getNumTypeParameter() <= previous.getBody().getNumTypeParameter() and - // We always select the first of the overloaded methods. not exists(MethodSignature later | later = getOtherMatchingSignatures(previous) | getOverloadIndex(later) < getOverloadIndex(previous) ) select unreachable, - "This overload of " + name + "() is unreachable, the $@ overload will always be selected.", previous, "previous" + "This overload of " + name + "() is unreachable, the $@ overload will always be selected.", + previous, "previous" diff --git a/javascript/ql/src/Declarations/UnstableCyclicImport.ql b/javascript/ql/src/Declarations/UnstableCyclicImport.ql index 51006e42ad7..2e4ee9bdbf9 100644 --- a/javascript/ql/src/Declarations/UnstableCyclicImport.ql +++ b/javascript/ql/src/Declarations/UnstableCyclicImport.ql @@ -74,7 +74,8 @@ class CandidateVarAccess extends VarAccess { * We use this to avoid duplicate alerts about the same underlying cyclic import. */ VarAccess getFirstCandidateAccess(ImportDeclaration decl) { - result = min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p + result = + min(decl.getASpecifier().getLocal().getVariable().getAnAccess().(CandidateVarAccess) as p order by p.getFirstToken().getIndex() ) @@ -133,14 +134,15 @@ string pathToModule(Module source, Module destination, int steps) { steps > 1 and exists(Module next | // Only extend the path to one of the potential successors, as we only need one example. - next = min(Module mod | + next = + min(Module mod | isImportedAtRuntime(source, mod) and numberOfStepsToModule(mod, destination, steps - 1) | mod order by mod.getName() ) and - result = repr(getARuntimeImport(source, next)) + " => " + - pathToModule(next, destination, steps - 1) + result = + repr(getARuntimeImport(source, next)) + " => " + pathToModule(next, destination, steps - 1) ) ) } diff --git a/javascript/ql/src/Expressions/ExprHasNoEffect.ql b/javascript/ql/src/Expressions/ExprHasNoEffect.ql index 917ab81a3e7..5ef6be7ba14 100644 --- a/javascript/ql/src/Expressions/ExprHasNoEffect.ql +++ b/javascript/ql/src/Expressions/ExprHasNoEffect.ql @@ -16,7 +16,6 @@ import javascript import ExprHasNoEffect import semmle.javascript.RestrictedLocations - from Expr e where hasNoEffect(e) select e.(FirstLineOf), "This expression has no effect." diff --git a/javascript/ql/src/Expressions/HeterogeneousComparison.ql b/javascript/ql/src/Expressions/HeterogeneousComparison.ql index 3f1eb083a4a..a736fa5cdc9 100644 --- a/javascript/ql/src/Expressions/HeterogeneousComparison.ql +++ b/javascript/ql/src/Expressions/HeterogeneousComparison.ql @@ -200,10 +200,12 @@ where rightExprDescription = getDescription(right.asExpr(), "an expression") and leftTypeCount = strictcount(left.getAType()) and rightTypeCount = strictcount(right.getAType()) and - leftTypeDescription = getTypeDescription("is of type " + leftTypes, - "cannot be of type " + rightTypes, leftTypeCount, rightTypeCount) and - rightTypeDescription = getTypeDescription("of type " + rightTypes, - ", which cannot be of type " + leftTypes, rightTypeCount, leftTypeCount) + leftTypeDescription = + getTypeDescription("is of type " + leftTypes, "cannot be of type " + rightTypes, leftTypeCount, + rightTypeCount) and + rightTypeDescription = + getTypeDescription("of type " + rightTypes, ", which cannot be of type " + leftTypes, + rightTypeCount, leftTypeCount) select left, leftExprDescription + " " + leftTypeDescription + ", but it is compared to $@ " + rightTypeDescription + ".", right, rightExprDescription diff --git a/javascript/ql/src/Expressions/MissingSpaceInAppend.ql b/javascript/ql/src/Expressions/MissingSpaceInAppend.ql index 2df7940646c..7589c0d54f3 100644 --- a/javascript/ql/src/Expressions/MissingSpaceInAppend.ql +++ b/javascript/ql/src/Expressions/MissingSpaceInAppend.ql @@ -23,48 +23,40 @@ Expr leftChild(Expr e) { } predicate isInConcat(Expr e) { - exists(ParExpr par | isInConcat(par) and par.getExpression() = e) - or - exists(AddExpr a | a.getAnOperand() = e) + exists(ParExpr par | isInConcat(par) and par.getExpression() = e) + or + exists(AddExpr a | a.getAnOperand() = e) } class ConcatenationLiteral extends Expr { ConcatenationLiteral() { ( - this instanceof TemplateLiteral + this instanceof TemplateLiteral or this instanceof Literal - ) - and isInConcat(this) + ) and + isInConcat(this) } } Expr getConcatChild(Expr e) { - result = rightChild(e) or - result = leftChild(e) + result = rightChild(e) or + result = leftChild(e) } -Expr getConcatParent(Expr e) { - e = getConcatChild(result) -} +Expr getConcatParent(Expr e) { e = getConcatChild(result) } predicate isWordLike(ConcatenationLiteral lit) { - lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*") + lit.getStringValue().regexpMatch("(?i).*[a-z]{3,}.*") } class ConcatRoot extends AddExpr { - ConcatRoot() { - not isInConcat(this) - } + ConcatRoot() { not isInConcat(this) } } -ConcatRoot getAddRoot(AddExpr e) { - result = getConcatParent*(e) -} +ConcatRoot getAddRoot(AddExpr e) { result = getConcatParent*(e) } -predicate hasWordLikeFragment(AddExpr e) { - isWordLike(getConcatChild*(getAddRoot(e))) -} +predicate hasWordLikeFragment(AddExpr e) { isWordLike(getConcatChild*(getAddRoot(e))) } from AddExpr e, ConcatenationLiteral l, ConcatenationLiteral r, string word where @@ -79,7 +71,6 @@ where word = l.getStringValue().regexpCapture(".* (([-A-Za-z/'\\.:,]*[a-zA-Z]|[0-9]+)[\\.:,!?']*)", 1) and r.getStringValue().regexpMatch("[a-zA-Z].*") and not word.regexpMatch(".*[,\\.:].*[a-zA-Z].*[^a-zA-Z]") and - // There must be a constant-string in the concatenation that looks like a word. hasWordLikeFragment(e) select l, "This string appears to be missing a space after '" + word + "'." diff --git a/javascript/ql/src/Expressions/Misspelling.qll b/javascript/ql/src/Expressions/Misspelling.qll index d143c0394be..e7c35260edb 100644 --- a/javascript/ql/src/Expressions/Misspelling.qll +++ b/javascript/ql/src/Expressions/Misspelling.qll @@ -91,7 +91,8 @@ private string replaceATypoAndLowerCase(Identifier wrong) { idPart(wrong, wrongPart, offset) | normalized_typos(wrongPart, rightPart, _, _, _, _) and - rightName = wrong.getName().substring(0, offset) + rightPart + + rightName = + wrong.getName().substring(0, offset) + rightPart + wrong.getName().suffix(offset + wrongPart.length()) and result = rightName.toLowerCase() ) diff --git a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql index bbb5e80dc25..4ae3504d843 100644 --- a/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql +++ b/javascript/ql/src/Expressions/UnboundEventHandlerReceiver.ql @@ -36,7 +36,8 @@ private predicate isBoundInMethod(MethodDeclaration method) { mod = "react-autobind" | thiz.flowsTo(DataFlow::moduleImport(mod).getACall().getArgument(0)) - ) or + ) + or // heuristic reflective binders exists(DataFlow::CallNode binder, string calleeName | ( @@ -92,8 +93,8 @@ private DOM::AttributeDefinition getAnEventHandlerAttribute() { from MethodDeclaration callback, DOM::AttributeDefinition attribute, ThisExpr unbound where attribute = getAnEventHandlerAttribute() and - attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = callback - .getBody() and + attribute.getValueNode().analyze().getAValue().(AbstractFunction).getFunction() = + callback.getBody() and unbound.getBinder() = callback.getBody() and not isBoundInMethod(callback) select attribute, diff --git a/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql b/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql index 467b80f4dac..4c8939c4180 100644 --- a/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql +++ b/javascript/ql/src/LanguageFeatures/IllegalInvocation.ql @@ -59,7 +59,8 @@ where not isCallToFunction(cs) and // conservatively only flag call sites where _all_ callees are illegal forex(DataFlow::InvokeNode cs2, Function otherCallee | - cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() | + cs2.getInvokeExpr() = cs.getInvokeExpr() and otherCallee = cs2.getACallee() + | illegalInvocation(cs, otherCallee, _, _) ) and // require that all callees are known diff --git a/javascript/ql/src/LanguageFeatures/InconsistentNew.ql b/javascript/ql/src/LanguageFeatures/InconsistentNew.ql index 1e07cfd72f8..8a14dbe37c1 100644 --- a/javascript/ql/src/LanguageFeatures/InconsistentNew.ql +++ b/javascript/ql/src/LanguageFeatures/InconsistentNew.ql @@ -35,7 +35,8 @@ predicate guardsAgainstMissingNew(Function f) { * `true` if `cs` is a `new` expression, and to `false` otherwise. */ Function getALikelyCallee(DataFlow::InvokeNode cs, boolean isNew) { - result = min(Function callee, int imprecision | + result = + min(Function callee, int imprecision | callee = cs.getACallee(imprecision) | callee order by imprecision @@ -84,7 +85,8 @@ predicate whitelistedCall(DataFlow::CallNode call) { * and start column. */ DataFlow::InvokeNode getFirstInvocation(Function f, boolean isNew) { - result = min(DataFlow::InvokeNode invk, string path, int line, int col | + result = + min(DataFlow::InvokeNode invk, string path, int line, int col | f = getALikelyCallee(invk, isNew) and invk.hasLocationInfo(path, line, col, _, _) | invk order by path, line, col diff --git a/javascript/ql/src/Metrics/FCyclomaticComplexity.ql b/javascript/ql/src/Metrics/FCyclomaticComplexity.ql index a737832f395..5c34a1919b7 100644 --- a/javascript/ql/src/Metrics/FCyclomaticComplexity.ql +++ b/javascript/ql/src/Metrics/FCyclomaticComplexity.ql @@ -14,7 +14,8 @@ import javascript from File f, float n where - n = avg(Function fun, int toAvg | + n = + avg(Function fun, int toAvg | fun.getTopLevel().getFile() = f and toAvg = fun.getCyclomaticComplexity() | toAvg diff --git a/javascript/ql/src/Performance/PolynomialReDoS.ql b/javascript/ql/src/Performance/PolynomialReDoS.ql index 0d8fdb4990c..a4c6b971aa7 100644 --- a/javascript/ql/src/Performance/PolynomialReDoS.ql +++ b/javascript/ql/src/Performance/PolynomialReDoS.ql @@ -16,7 +16,6 @@ import javascript import semmle.javascript.security.performance.PolynomialReDoS::PolynomialReDoS import DataFlow::PathGraph - from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, "This expensive $@ use depends on $@.", diff --git a/javascript/ql/src/Performance/ReDoS.ql b/javascript/ql/src/Performance/ReDoS.ql index 7f74dcb47d5..fb5fb6aa856 100644 --- a/javascript/ql/src/Performance/ReDoS.ql +++ b/javascript/ql/src/Performance/ReDoS.ql @@ -292,9 +292,7 @@ class EdgeLabel extends TInputSymbol { * Gets the state before matching `t`. */ pragma[inline] -State before(RegExpTerm t) { - result = Match(t, 0) -} +State before(RegExpTerm t) { result = Match(t, 0) } /** * Gets a state the NFA may be in after matching `t`. @@ -337,9 +335,7 @@ predicate delta(State q1, EdgeLabel lbl, State q2) { ) ) or - exists(RegExpDot dot | - q1 = before(dot) and q2 = after(dot) - | + exists(RegExpDot dot | q1 = before(dot) and q2 = after(dot) | if dot.getLiteral().isDotAll() then lbl = Any() else lbl = Dot() ) or @@ -545,7 +541,8 @@ string intersect(InputSymbol c, InputSymbol d) { * Gets a character matched by character class `cc`. */ string choose(RegExpCharacterClass cc) { - result = min(string c | + result = + min(string c | exists(RegExpTerm child | child = cc.getAChild() | c = child.(RegExpConstant).getValue() or child.(RegExpCharacterRange).isRange(c, _) diff --git a/javascript/ql/src/RegExp/BackspaceEscape.ql b/javascript/ql/src/RegExp/BackspaceEscape.ql index 4de176ada2f..054f46f06b6 100644 --- a/javascript/ql/src/RegExp/BackspaceEscape.ql +++ b/javascript/ql/src/RegExp/BackspaceEscape.ql @@ -14,6 +14,7 @@ import javascript from RegExpCharEscape rece -where rece.toString() = "\\b" - and rece.isPartOfRegExpLiteral() +where + rece.toString() = "\\b" and + rece.isPartOfRegExpLiteral() select rece, "Backspace escape in regular expression." diff --git a/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql b/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql index 874f973fe1e..75141f556f4 100644 --- a/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql +++ b/javascript/ql/src/RegExp/DuplicateCharacterInCharacterClass.ql @@ -18,7 +18,8 @@ import javascript * Indexing is 1-based. */ predicate constantInCharacterClass(RegExpCharacterClass recc, int i, RegExpConstant cc, string val) { - cc = rank[i](RegExpConstant cc2, int j | + cc = + rank[i](RegExpConstant cc2, int j | cc2 = recc.getChild(j) and cc2.isCharacter() and cc2.getValue() = val | cc2 order by j diff --git a/javascript/ql/src/RegExp/IdentityReplacement.ql b/javascript/ql/src/RegExp/IdentityReplacement.ql index 27057708401..f8c62a1ab3a 100644 --- a/javascript/ql/src/RegExp/IdentityReplacement.ql +++ b/javascript/ql/src/RegExp/IdentityReplacement.ql @@ -58,7 +58,8 @@ predicate regExpMatchesString(RegExpTerm t, string s) { or // sequences match the concatenation of their elements exists(RegExpSequence seq | seq = t | - s = concat(int i, RegExpTerm child | + s = + concat(int i, RegExpTerm child | child = seq.getChild(i) | any(string subs | regExpMatchesString(child, subs)) order by i diff --git a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql index dd18ce3f866..e352617337f 100644 --- a/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql +++ b/javascript/ql/src/RegExp/RegExpAlwaysMatches.ql @@ -73,13 +73,9 @@ abstract class RegExpQuery extends DataFlow::CallNode { class RegExpTestCall extends DataFlow::MethodCallNode, RegExpQuery { DataFlow::RegExpCreationNode regexp; - RegExpTestCall() { - this = regexp.getAReference().getAMethodCall("test") - } + RegExpTestCall() { this = regexp.getAReference().getAMethodCall("test") } - override RegExpTerm getRegExp() { - result = regexp.getRoot() - } + override RegExpTerm getRegExp() { result = regexp.getRoot() } } /** @@ -93,9 +89,7 @@ class RegExpSearchCall extends DataFlow::MethodCallNode, RegExpQuery { regexp.getAReference().flowsTo(getArgument(0)) } - override RegExpTerm getRegExp() { - result = regexp.getRoot() - } + override RegExpTerm getRegExp() { result = regexp.getRoot() } } /** @@ -116,10 +110,12 @@ where ( call instanceof RegExpTestCall and not isPossiblyAnchoredOnBothEnds(term) and - message = "This regular expression always matches when used in a test $@, as it can match an empty substring." + message = + "This regular expression always matches when used in a test $@, as it can match an empty substring." or call instanceof RegExpSearchCall and not term.getAChild*() instanceof RegExpDollar and - message = "This regular expression always the matches at index 0 when used $@, as it matches the empty substring." + message = + "This regular expression always the matches at index 0 when used $@, as it matches the empty substring." ) select term, message, call, "here" diff --git a/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll b/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll index 337d46b7145..1afd522ac7c 100644 --- a/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll +++ b/javascript/ql/src/Security/CWE-020/HostnameRegexpShared.qll @@ -2,6 +2,7 @@ * Provides predicates for reasoning about regular expressions * that match URLs and hostname patterns. */ + import javascript /** @@ -39,9 +40,7 @@ predicate isDotLike(RegExpTerm term) { predicate matchesBeginningOfString(RegExpTerm term) { term.isRootTerm() or - exists(RegExpTerm parent | - matchesBeginningOfString(parent) - | + exists(RegExpTerm parent | matchesBeginningOfString(parent) | term = parent.(RegExpSequence).getChild(0) or parent.(RegExpSequence).getChild(0) instanceof RegExpCaret and @@ -60,7 +59,11 @@ predicate matchesBeginningOfString(RegExpTerm term) { * `i` is bound to the index of the last child in the top-level domain part. */ predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) { - seq.getChild(i).(RegExpConstant).getValue().regexpMatch("(?i)" + RegExpPatterns::commonTLD() + "(:\\d+)?([/?#].*)?") and + seq + .getChild(i) + .(RegExpConstant) + .getValue() + .regexpMatch("(?i)" + RegExpPatterns::commonTLD() + "(:\\d+)?([/?#].*)?") and isDotLike(seq.getChild(i - 1)) and not (i = 1 and matchesBeginningOfString(seq)) } @@ -69,9 +72,7 @@ predicate hasTopLevelDomainEnding(RegExpSequence seq, int i) { * Holds if the given regular expression term contains top-level domain preceded by a dot, * such as `.com`. */ -predicate hasTopLevelDomainEnding(RegExpSequence seq) { - hasTopLevelDomainEnding(seq, _) -} +predicate hasTopLevelDomainEnding(RegExpSequence seq) { hasTopLevelDomainEnding(seq, _) } /** * Holds if `term` will always match a hostname, that is, all disjunctions contain diff --git a/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql b/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql index 3b6ce8838e1..7cdef3c983b 100644 --- a/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql +++ b/javascript/ql/src/Security/CWE-020/IncompleteHostnameRegExp.ql @@ -61,9 +61,12 @@ predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence se unescapedDot = seq.getChild([0 .. i - 1]).getAChild*() and unescapedDot != seq.getChild(i - 1) and // Should not be the '.' immediately before the TLD not hasConsecutiveDots(unescapedDot.getParent()) and - hostname = seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() + seq.getChild(i).getRawValue() + hostname = + seq.getChild(i - 2).getRawValue() + seq.getChild(i - 1).getRawValue() + + seq.getChild(i).getRawValue() | - if unescapedDot.getParent() instanceof RegExpQuantifier then ( + if unescapedDot.getParent() instanceof RegExpQuantifier + then // `.*\.example.com` can match `evil.com/?x=.example.com` // // This problem only occurs when the pattern is applied against a full URL, not just a hostname/origin. @@ -73,16 +76,20 @@ predicate isIncompleteHostNameRegExpPattern(RegExpTerm regexp, RegExpSequence se seq.getChild(0) instanceof RegExpCaret and not seq.getAChild() instanceof RegExpDollar and seq.getChild([i .. i + 1]).(RegExpConstant).getValue().regexpMatch(".*[/?#].*") and - msg = "has an unrestricted wildcard '" + - unescapedDot.getParent().(RegExpQuantifier).getRawValue() + - "' which may cause '" + hostname + "' to be matched anywhere in the URL, outside the hostname." - ) else ( - msg = "has an unescaped '.' before '" + hostname + "', so it might match more hosts than expected." - ) + msg = + "has an unrestricted wildcard '" + unescapedDot.getParent().(RegExpQuantifier).getRawValue() + + "' which may cause '" + hostname + + "' to be matched anywhere in the URL, outside the hostname." + else + msg = + "has an unescaped '.' before '" + hostname + + "', so it might match more hosts than expected." ) } -from RegExpPatternSource re, RegExpTerm regexp, RegExpSequence hostSequence, string msg, string kind, DataFlow::Node aux +from + RegExpPatternSource re, RegExpTerm regexp, RegExpSequence hostSequence, string msg, string kind, + DataFlow::Node aux where regexp = re.getRegExpTerm() and isIncompleteHostNameRegExpPattern(regexp, hostSequence, msg) and @@ -96,5 +103,4 @@ where ) ) and not CharacterEscapes::hasALikelyRegExpPatternMistake(re) -select hostSequence, - "This " + kind + " " + msg, aux, "here" +select hostSequence, "This " + kind + " " + msg, aux, "here" diff --git a/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql b/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql index 8be00495b0f..86ab1744086 100644 --- a/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql +++ b/javascript/ql/src/Security/CWE-020/IncompleteUrlSchemeCheck.ql @@ -20,9 +20,7 @@ class DangerousScheme extends string { } /** Gets a data-flow node that checks `nd` against the given `scheme`. */ -DataFlow::Node schemeCheck( - DataFlow::Node nd, DangerousScheme scheme -) { +DataFlow::Node schemeCheck(DataFlow::Node nd, DangerousScheme scheme) { // check of the form `nd.startsWith(scheme)` exists(StringOps::StartsWith sw | sw = result | sw.getBaseString() = nd and diff --git a/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql b/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql index 7f8acd14030..972ca02ceb9 100644 --- a/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql +++ b/javascript/ql/src/Security/CWE-020/MissingRegExpAnchor.ql @@ -46,9 +46,7 @@ predicate isInteriorAnchor(RegExpAnchor term) { * Holds if `term` contains an anchor that is not the first or last node * in its tree, such as `(foo|bar$|baz)`. */ -predicate containsInteriorAnchor(RegExpTerm term) { - isInteriorAnchor(term.getAChild*()) -} +predicate containsInteriorAnchor(RegExpTerm term) { isInteriorAnchor(term.getAChild*()) } /** * Holds if `term` starts with a word boundary or lookbehind assertion, @@ -78,9 +76,7 @@ predicate containsTrailingPseudoAnchor(RegExpSequence term) { * Holds if `term` is an empty sequence, usually arising from * literals with a trailing alternative such as `foo|`. */ -predicate isEmpty(RegExpSequence term) { - term.getNumChild() = 0 -} +predicate isEmpty(RegExpSequence term) { term.getNumChild() = 0 } /** * Holds if `term` contains a letter constant. @@ -131,14 +127,14 @@ predicate hasMisleadingAnchorPrecedence(RegExpPatternSource src, string msg) { ( anchoredTerm = root.getChild(0) and anchoredTerm.getChild(0) instanceof RegExpCaret and - not containsLeadingPseudoAnchor(root.getChild([ 1 .. root.getNumChild() - 1 ])) and - containsLetters(root.getChild([ 1 .. root.getNumChild() - 1 ])) and + not containsLeadingPseudoAnchor(root.getChild([1 .. root.getNumChild() - 1])) and + containsLetters(root.getChild([1 .. root.getNumChild() - 1])) and direction = "beginning" or anchoredTerm = root.getLastChild() and anchoredTerm.getLastChild() instanceof RegExpDollar and - not containsTrailingPseudoAnchor(root.getChild([ 0 .. root.getNumChild() - 2 ])) and - containsLetters(root.getChild([ 0 .. root.getNumChild() - 2 ])) and + not containsTrailingPseudoAnchor(root.getChild([0 .. root.getNumChild() - 2])) and + containsLetters(root.getChild([0 .. root.getNumChild() - 2])) and direction = "end" ) and // is not used for replace @@ -146,8 +142,10 @@ predicate hasMisleadingAnchorPrecedence(RegExpPatternSource src, string msg) { replace.getMethodName() = "replace" and src.getARegExpObject().flowsTo(replace.getArgument(0)) ) and - msg = "Misleading operator precedence. The subexpression '" + anchoredTerm.getRawValue() + - "' is anchored at the " + direction + ", but the other parts of this regular expression are not" + msg = + "Misleading operator precedence. The subexpression '" + anchoredTerm.getRawValue() + + "' is anchored at the " + direction + + ", but the other parts of this regular expression are not" ) } @@ -181,7 +179,8 @@ predicate isSemiAnchoredHostnameRegExp(RegExpPatternSource src, string msg) { hasTopLevelDomainEnding(tld, i) and isFinalRegExpTerm(tld.getChild(i)) and // nothing is matched after the TLD tld.getChild(0) instanceof RegExpCaret and - msg = "This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end." + msg = + "This hostname pattern may match any domain name, as it is missing a '$' or '/' at the end." ) } @@ -214,7 +213,8 @@ predicate isUnanchoredHostnameRegExp(RegExpPatternSource src, string msg) { name = "match" and exists(mcn.getAPropertyRead()) ) ) and - msg = "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it." + msg = + "When this is used as a regular expression on a URL, it may match anywhere, and arbitrary hosts may come before or after it." ) } diff --git a/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql b/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql index 7cace237bbb..062c0c2999c 100644 --- a/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql +++ b/javascript/ql/src/Security/CWE-020/UselessRegExpCharacterEscape.ql @@ -105,13 +105,9 @@ class RegExpPatternMistake extends TRegExpPatternMistake { */ class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscapeInStringMistake { RegExpPatternSource src; - string char; - string mistake; - int index; - ASTNode rawStringNode; IdentityEscapeInStringMistake() { @@ -130,19 +126,19 @@ class IdentityEscapeInStringMistake extends RegExpPatternMistake, TIdentityEscap } /** - * A backspace as '\b' in a regular expression string, indicating - * programmer intent to use the word-boundary assertion '\b'. - */ + * A backspace as '\b' in a regular expression string, indicating + * programmer intent to use the word-boundary assertion '\b'. + */ class BackspaceInStringMistake extends RegExpPatternMistake, TBackspaceInStringMistake { RegExpPatternSource src; - int index; - ASTNode rawStringNode; BackspaceInStringMistake() { this = TBackspaceInStringMistake(src, rawStringNode, index) } - override string getMessage() { result = "'\\b' is a backspace, and not a word-boundary assertion" } + override string getMessage() { + result = "'\\b' is a backspace, and not a word-boundary assertion" + } override int getIndex() { result = index } diff --git a/javascript/ql/src/Security/CWE-078/CommandInjection.ql b/javascript/ql/src/Security/CWE-078/CommandInjection.ql index 62ca020753e..d1c22e33989 100644 --- a/javascript/ql/src/Security/CWE-078/CommandInjection.ql +++ b/javascript/ql/src/Security/CWE-078/CommandInjection.ql @@ -16,11 +16,16 @@ import javascript import semmle.javascript.security.dataflow.CommandInjection::CommandInjection import DataFlow::PathGraph -from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight, Source sourceNode +from + Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight, + Source sourceNode where cfg.hasFlowPath(source, sink) and - if cfg.isSinkWithHighlight(sink.getNode(), _) - then cfg.isSinkWithHighlight(sink.getNode(), highlight) - else highlight = sink.getNode() and + ( + if cfg.isSinkWithHighlight(sink.getNode(), _) + then cfg.isSinkWithHighlight(sink.getNode(), highlight) + else highlight = sink.getNode() + ) and sourceNode = source.getNode() -select highlight, source, sink, "This command depends on $@.", sourceNode, sourceNode.getSourceType() +select highlight, source, sink, "This command depends on $@.", sourceNode, + sourceNode.getSourceType() diff --git a/javascript/ql/src/Security/CWE-079/ExceptionXss.ql b/javascript/ql/src/Security/CWE-079/ExceptionXss.ql index cf22b54e465..f9e8ebfa774 100644 --- a/javascript/ql/src/Security/CWE-079/ExceptionXss.ql +++ b/javascript/ql/src/Security/CWE-079/ExceptionXss.ql @@ -15,10 +15,8 @@ import javascript import semmle.javascript.security.dataflow.ExceptionXss::ExceptionXss import DataFlow::PathGraph -from - Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink -where - cfg.hasFlowPath(source, sink) +from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink +where cfg.hasFlowPath(source, sink) select sink.getNode(), source, sink, - sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", source.getNode(), - "user-provided value" + sink.getNode().(Xss::Shared::Sink).getVulnerabilityKind() + " vulnerability due to $@.", + source.getNode(), "user-provided value" diff --git a/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql b/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql index f4c62fc84fa..cc1bb1290e5 100644 --- a/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql +++ b/javascript/ql/src/Security/CWE-116/DoubleEscaping.ql @@ -44,23 +44,17 @@ predicate escapingScheme(string metachar, string regex) { * A call to `String.prototype.replace` that replaces all instances of a pattern. */ class Replacement extends StringReplaceCall { - Replacement() { - isGlobal() - } + Replacement() { isGlobal() } /** * Gets the input of this replacement. */ - DataFlow::Node getInput() { - result = this.getReceiver() - } + DataFlow::Node getInput() { result = this.getReceiver() } /** * Gets the output of this replacement. */ - DataFlow::SourceNode getOutput() { - result = this - } + DataFlow::SourceNode getOutput() { result = this } /** * Holds if this replacement escapes `char` using `metachar`. @@ -93,9 +87,7 @@ class Replacement extends StringReplaceCall { /** * Gets the previous replacement in this chain of replacements. */ - Replacement getPreviousReplacement() { - result.getOutput() = getASimplePredecessor*(getInput()) - } + Replacement getPreviousReplacement() { result.getOutput() = getASimplePredecessor*(getInput()) } /** * Gets an earlier replacement in this chain of replacements that diff --git a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql index 987014036e6..e5f6e4c04f2 100644 --- a/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql +++ b/javascript/ql/src/Security/CWE-116/IncompleteSanitization.ql @@ -44,9 +44,7 @@ predicate isSimpleCharacterClass(RegExpCharacterClass t) { } /** Holds if `t` is an alternation of simple terms. */ -predicate isSimpleAlt(RegExpAlt t) { - forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) -} +predicate isSimpleAlt(RegExpAlt t) { forall(RegExpTerm ch | ch = t.getAChild() | isSimple(ch)) } /** * Holds if `mce` is of the form `x.replace(re, new)`, where `re` is a global diff --git a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql index f7db881fdf0..9f29ebc1ea2 100644 --- a/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql +++ b/javascript/ql/src/Security/CWE-352/MissingCsrfMiddleware.ql @@ -13,26 +13,20 @@ import javascript /** Gets a property name of `req` which refers to data usually derived from cookie data. */ -string cookieProperty() { - result = "session" or result = "cookies" or result = "user" -} +string cookieProperty() { result = "session" or result = "cookies" or result = "user" } /** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */ private DataFlow::SourceNode nodeLeadingToCookieAccess(DataFlow::TypeBackTracker t) { t.start() and exists(DataFlow::PropRead value | value = result.getAPropertyRead(cookieProperty()).getAPropertyRead() and - // Ignore accesses to values that are part of a CSRF or captcha check not value.getPropertyName().regexpMatch("(?i).*(csrf|xsrf|captcha).*") and - // Ignore calls like `req.session.save()` not value = any(DataFlow::InvokeNode call).getCalleeNode() ) or - exists(DataFlow::TypeBackTracker t2 | - result = nodeLeadingToCookieAccess(t2).backtrack(t2, t) - ) + exists(DataFlow::TypeBackTracker t2 | result = nodeLeadingToCookieAccess(t2).backtrack(t2, t)) } /** Gets a data flow node that flows to the base of an access to `cookies`, `session`, or `user`. */ @@ -52,9 +46,7 @@ private DataFlow::SourceNode getARouteUsingCookies(DataFlow::TypeTracker t) { t.start() and isRouteHandlerUsingCookies(result) or - exists(DataFlow::TypeTracker t2 | - result = getARouteUsingCookies(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getARouteUsingCookies(t2).track(t2, t)) } /** Gets a data flow node referring to a route handler that uses cookies. */ @@ -113,7 +105,6 @@ from where router = setup.getRouter() and handler = setup.getARouteHandlerExpr() and - // Require that the handler uses cookies and has cookie middleware. // // In practice, handlers that use cookies always have the cookie middleware or @@ -122,10 +113,8 @@ where // don't trust it to detect the presence of CSRF middleware either. getARouteUsingCookies().flowsToExpr(handler) and hasCookieMiddleware(handler, cookie) and - // Only flag the cookie parser registered first. not hasCookieMiddleware(cookie, _) and - not hasCsrfMiddleware(handler) and // Only warn for the last handler in a chain. handler.isLastHandler() and diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 59f637e95ef..04f35ff6ad8 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -1,7 +1,7 @@ /** * @name Prototype pollution in utility function * @description Recursively copying properties between objects may cause - accidental modification of a built-in prototype object. + * accidental modification of a built-in prototype object. * @kind path-problem * @problem.severity warning * @precision high @@ -30,9 +30,7 @@ predicate arePropertiesEnumerated(DataFlow::SourceNode node) { predicate isEnumeratedPropName(Node node) { node instanceof EnumeratedPropName or - exists(Node pred | - isEnumeratedPropName(pred) - | + exists(Node pred | isEnumeratedPropName(pred) | node = pred.getASuccessor() or argumentPassingStep(_, pred, _, node) @@ -54,7 +52,6 @@ predicate isPotentiallyObjectPrototype(SourceNode node) { exists(Node base, Node key | dynamicPropReadStep(base, key, node) and isEnumeratedPropName(key) and - // Ignore cases where the properties of `base` are enumerated, to avoid FPs // where the key came from that enumeration (and thus will not return Object.prototype). // For example, `src[key]` in `for (let key in src) { ... src[key] ... }` will generally @@ -62,9 +59,7 @@ predicate isPotentiallyObjectPrototype(SourceNode node) { not arePropertiesEnumerated(base.getALocalSource()) ) or - exists(Node use | - isPotentiallyObjectPrototype(use.getALocalSource()) - | + exists(Node use | isPotentiallyObjectPrototype(use.getALocalSource()) | argumentPassingStep(_, use, _, node) ) } @@ -87,7 +82,6 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N rhs = write.getRhs().flow() and not exists(prop.getStringValue()) and not arePropertiesEnumerated(base.getALocalSource()) and - // Prune writes that are unlikely to modify Object.prototype. // This is mainly for performance, but may block certain results due to // not tracking out of function returns and into callbacks. @@ -435,9 +429,7 @@ private DataFlow::SourceNode getANodeLeadingToBase(DataFlow::TypeBackTracker t, isPrototypePollutingAssignment(base, _, _, _) and result = base.getALocalSource() or - exists(DataFlow::TypeBackTracker t2 | - result = getANodeLeadingToBase(t2, base).backtrack(t2, t) - ) + exists(DataFlow::TypeBackTracker t2 | result = getANodeLeadingToBase(t2, base).backtrack(t2, t)) } /** diff --git a/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql b/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql index 1a3670013ed..14f51f07646 100644 --- a/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql +++ b/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql @@ -14,8 +14,9 @@ import PortalExitSource import PortalEntrySink from - TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p1, - Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, DataFlow::MidPathNode last + TaintTracking::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, + Portal p1, Portal p2, DataFlow::FlowLabel lbl1, DataFlow::FlowLabel lbl2, + DataFlow::MidPathNode last where cfg = source.getConfiguration() and last = source.getASuccessor*() and diff --git a/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql b/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql index 588fe796811..ddc1c950740 100644 --- a/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql +++ b/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql @@ -11,7 +11,8 @@ import Configurations import PortalExitSource import SinkFromAnnotation -from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, +from + DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p, DataFlow::MidPathNode last where cfg = source.getConfiguration() and diff --git a/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql b/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql index de7ddf04b3f..86741b692b8 100644 --- a/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql +++ b/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql @@ -11,7 +11,8 @@ import Configurations import PortalEntrySink import SourceFromAnnotation -from DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, +from + DataFlow::Configuration cfg, DataFlow::SourcePathNode source, DataFlow::SinkPathNode sink, Portal p, DataFlow::MidPathNode last where cfg = source.getConfiguration() and diff --git a/javascript/ql/src/Security/Summaries/ImportFromCsv.qll b/javascript/ql/src/Security/Summaries/ImportFromCsv.qll index 55018ef3ca5..461bdb27e8f 100644 --- a/javascript/ql/src/Security/Summaries/ImportFromCsv.qll +++ b/javascript/ql/src/Security/Summaries/ImportFromCsv.qll @@ -127,7 +127,8 @@ class AdditionalStepSpec extends ExternalData { exists(string config | if getField(4) = "" then config = "any configuration" else config = getConfiguration() | - result = "edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " + + result = + "edge from " + getStartPortal() + " to " + getEndPortal() + ", transforming " + getStartFlowLabel() + " into " + getEndFlowLabel() + " for " + config ) } diff --git a/javascript/ql/src/Statements/IgnoreArrayResult.ql b/javascript/ql/src/Statements/IgnoreArrayResult.ql index 6f6698797f1..9123b520f6f 100644 --- a/javascript/ql/src/Statements/IgnoreArrayResult.ql +++ b/javascript/ql/src/Statements/IgnoreArrayResult.ql @@ -13,7 +13,7 @@ import javascript import Expressions.ExprHasNoEffect DataFlow::SourceNode callsArray(DataFlow::TypeBackTracker t, DataFlow::MethodCallNode call) { - isIgnoredPureArrayCall(call) and + isIgnoredPureArrayCall(call) and t.start() and result = call.getReceiver().getALocalSource() or @@ -39,7 +39,7 @@ predicate isIgnoredPureArrayCall(DataFlow::MethodCallNode call) { } from DataFlow::MethodCallNode call -where +where callsArray(call) instanceof DataFlow::ArrayCreationNode and not call.getReceiver().asExpr().(ArrayExpr).getSize() = 0 select call, "Result from call to " + call.getMethodName() + " ignored." diff --git a/javascript/ql/src/Statements/ImplicitReturn.ql b/javascript/ql/src/Statements/ImplicitReturn.ql index b729c678240..e9de1026737 100644 --- a/javascript/ql/src/Statements/ImplicitReturn.ql +++ b/javascript/ql/src/Statements/ImplicitReturn.ql @@ -32,7 +32,8 @@ class ValueReturn extends ReturnStmt { /** Gets the lexically first explicit return statement in function `f`. */ ValueReturn getFirstExplicitReturn(Function f) { - result = min(ValueReturn ret | + result = + min(ValueReturn ret | ret.getContainer() = f | ret order by ret.getLocation().getStartLine(), ret.getLocation().getStartColumn() diff --git a/javascript/ql/src/Statements/UseOfReturnlessFunction.ql b/javascript/ql/src/Statements/UseOfReturnlessFunction.ql index 35825b01bb4..834183ce914 100644 --- a/javascript/ql/src/Statements/UseOfReturnlessFunction.ql +++ b/javascript/ql/src/Statements/UseOfReturnlessFunction.ql @@ -21,40 +21,41 @@ predicate returnsVoid(Function f) { } predicate isStub(Function f) { - f.getBody().(BlockStmt).getNumChild() = 0 - or - f instanceof ExternalDecl + f.getBody().(BlockStmt).getNumChild() = 0 + or + f instanceof ExternalDecl } /** * Holds if `e` is in a syntactic context where it likely is fine that the value of `e` comes from a call to a returnless function. */ predicate benignContext(Expr e) { - inVoidContext(e) or - + inVoidContext(e) + or // A return statement is often used to just end the function. e = any(Function f).getBody() or e = any(ReturnStmt r).getExpr() - or - exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond)) - or - exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin)) + or + exists(ConditionalExpr cond | cond.getABranch() = e and benignContext(cond)) + or + exists(LogicalBinaryExpr bin | bin.getAnOperand() = e and benignContext(bin)) or exists(Expr parent | parent.getUnderlyingValue() = e and benignContext(parent)) - or + or any(VoidExpr voidExpr).getOperand() = e or // weeds out calls inside HTML-attributes. - e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute or + e.getParent().(ExprStmt).getParent() instanceof CodeInAttribute + or // and JSX-attributes. - e = any(JSXAttribute attr).getValue() or - - exists(AwaitExpr await | await.getOperand() = e and benignContext(await)) + e = any(JSXAttribute attr).getValue() + or + exists(AwaitExpr await | await.getOperand() = e and benignContext(await)) or // Avoid double reporting with js/trivial-conditional isExplicitConditional(_, e) - or + or // Avoid double reporting with js/comparison-between-incompatible-types any(Comparison binOp).getAnOperand() = e or @@ -62,12 +63,14 @@ predicate benignContext(Expr e) { any(PropAccess ac).getBase() = e or // Avoid double-reporting with js/unused-local-variable - exists(VariableDeclarator v | v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal) + exists(VariableDeclarator v | + v.getInit() = e and v.getBindingPattern().getVariable() instanceof UnusedLocal + ) or // Avoid double reporting with js/call-to-non-callable any(InvokeExpr invoke).getCallee() = e or - // arguments to Promise.resolve (and promise library variants) are benign. + // arguments to Promise.resolve (and promise library variants) are benign. e = any(PromiseCreationCall promise).getValue().asExpr() } @@ -86,15 +89,13 @@ predicate alwaysThrows(Function f) { /** * Holds if the last statement in the function is flagged by the js/useless-expression query. */ -predicate lastStatementHasNoEffect(Function f) { - hasNoEffect(f.getExit().getAPredecessor()) -} +predicate lastStatementHasNoEffect(Function f) { hasNoEffect(f.getExit().getAPredecessor()) } /** * Holds if `func` is a callee of `call`, and all possible callees of `call` never return a value. */ predicate callToVoidFunction(DataFlow::CallNode call, Function func) { - not call.isIncomplete() and + not call.isIncomplete() and func = call.getACallee() and forall(Function f | f = call.getACallee() | returnsVoid(f) and not isStub(f) and not alwaysThrows(f) @@ -122,22 +123,20 @@ predicate hasNonVoidCallbackMethod(string name) { DataFlow::SourceNode array(DataFlow::TypeTracker t) { t.start() and result instanceof DataFlow::ArrayCreationNode or - exists (DataFlow::TypeTracker t2 | - result = array(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = array(t2).track(t2, t)) } DataFlow::SourceNode array() { result = array(DataFlow::TypeTracker::end()) } /** * Holds if `call` is an Array or Lodash method accepting a callback `func`, - * where the `call` expects a callback that returns an expression, - * but `func` does not return a value. + * where the `call` expects a callback that returns an expression, + * but `func` does not return a value. */ predicate voidArrayCallback(DataFlow::CallNode call, Function func) { hasNonVoidCallbackMethod(call.getCalleeName()) and - exists(int index | - index = min(int i | exists(call.getCallback(i))) and + exists(int index | + index = min(int i | exists(call.getCallback(i))) and func = call.getCallback(index).getFunction() ) and returnsVoid(func) and @@ -151,26 +150,23 @@ predicate voidArrayCallback(DataFlow::CallNode call, Function func) { } predicate hasNonVoidReturnType(Function f) { - exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | - not type.isVoid() - ) + exists(TypeAnnotation type | type = f.getReturnTypeAnnotation() | not type.isVoid()) } - /** - * Provides classes for working with various Deferred implementations. - * It is a heuristic. The heuristic assume that a class is a promise defintion + * Provides classes for working with various Deferred implementations. + * It is a heuristic. The heuristic assume that a class is a promise defintion * if the class is called "Deferred" and the method `resolve` is called on an instance. - * - * Removes some false positives in the js/use-of-returnless-function query. + * + * Removes some false positives in the js/use-of-returnless-function query. */ module Deferred { /** - * An instance of a `Deferred` class. + * An instance of a `Deferred` class. * For example the result from `new Deferred()` or `new $.Deferred()`. */ class DeferredInstance extends DataFlow::NewNode { - // Describes both `new Deferred()`, `new $.Deferred` and other variants. + // Describes both `new Deferred()`, `new $.Deferred` and other variants. DeferredInstance() { this.getCalleeName() = "Deferred" } private DataFlow::SourceNode ref(DataFlow::TypeTracker t) { @@ -179,7 +175,7 @@ module Deferred { or exists(DataFlow::TypeTracker t2 | result = ref(t2).track(t2, t)) } - + DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) } } @@ -188,7 +184,7 @@ module Deferred { */ private class DeferredPromiseDefinition extends PromiseDefinition, DeferredInstance { DeferredPromiseDefinition() { - // hardening of the "Deferred" heuristic: a method call to `resolve`. + // hardening of the "Deferred" heuristic: a method call to `resolve`. exists(ref().getAMethodCall("resolve")) } @@ -210,12 +206,14 @@ module Deferred { from DataFlow::CallNode call, Function func, string name, string msg where ( - callToVoidFunction(call, func) and + callToVoidFunction(call, func) and msg = "the $@ does not return anything, yet the return value is used." and name = func.describe() or - voidArrayCallback(call, func) and - msg = "the $@ does not return anything, yet the return value from the call to " + call.getCalleeName() + " is used." and + voidArrayCallback(call, func) and + msg = + "the $@ does not return anything, yet the return value from the call to " + + call.getCalleeName() + " is used." and name = "callback function" ) and not benignContext(call.getEnclosingExpr()) and @@ -224,5 +222,4 @@ where not oneshotClosure(call) and not hasNonVoidReturnType(func) and not call.getEnclosingExpr() instanceof SuperCall -select - call, msg, func, name +select call, msg, func, name diff --git a/javascript/ql/src/Statements/UselessConditional.qll b/javascript/ql/src/Statements/UselessConditional.qll index bcb4d88a734..9197a2b8a0f 100644 --- a/javascript/ql/src/Statements/UselessConditional.qll +++ b/javascript/ql/src/Statements/UselessConditional.qll @@ -1,6 +1,6 @@ -/** - * Provides predicates for working with useless conditionals. - */ +/** + * Provides predicates for working with useless conditionals. + */ import javascript @@ -18,4 +18,4 @@ predicate isExplicitConditional(ASTNode cond, Expr e) { or isExplicitConditional(_, cond) and e = cond.(Expr).getUnderlyingValue().(LogicalBinaryExpr).getAnOperand() -} \ No newline at end of file +} diff --git a/javascript/ql/src/external/CodeDuplication.qll b/javascript/ql/src/external/CodeDuplication.qll index 1573962265c..d0f9d97776a 100644 --- a/javascript/ql/src/external/CodeDuplication.qll +++ b/javascript/ql/src/external/CodeDuplication.qll @@ -266,7 +266,8 @@ predicate similarLines(File f, int line) { } private predicate similarLinesPerEquivalenceClass(int equivClass, int lines, File f) { - lines = strictsum(SimilarBlock b, int toSum | + lines = + strictsum(SimilarBlock b, int toSum | (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and toSum = b.sourceLines() | @@ -278,7 +279,8 @@ pragma[noopt] private predicate similarLinesCovered(File f, int coveredLines, File otherFile) { exists(int numLines | numLines = f.getNumberOfLines() | exists(int coveredApprox | - coveredApprox = strictsum(int num | + coveredApprox = + strictsum(int num | exists(int equivClass | similarLinesPerEquivalenceClass(equivClass, num, f) and similarLinesPerEquivalenceClass(equivClass, num, otherFile) and @@ -301,7 +303,8 @@ predicate duplicateLines(File f, int line) { } private predicate duplicateLinesPerEquivalenceClass(int equivClass, int lines, File f) { - lines = strictsum(DuplicateBlock b, int toSum | + lines = + strictsum(DuplicateBlock b, int toSum | (b.sourceFile() = f and b.getEquivalenceClass() = equivClass) and toSum = b.sourceLines() | @@ -313,7 +316,8 @@ pragma[noopt] private predicate duplicateLinesCovered(File f, int coveredLines, File otherFile) { exists(int numLines | numLines = f.getNumberOfLines() | exists(int coveredApprox | - coveredApprox = strictsum(int num | + coveredApprox = + strictsum(int num | exists(int equivClass | duplicateLinesPerEquivalenceClass(equivClass, num, f) and duplicateLinesPerEquivalenceClass(equivClass, num, otherFile) and diff --git a/javascript/ql/src/external/DefectFilter.qll b/javascript/ql/src/external/DefectFilter.qll index dcac0cf7b5e..d35e41d9afd 100644 --- a/javascript/ql/src/external/DefectFilter.qll +++ b/javascript/ql/src/external/DefectFilter.qll @@ -48,7 +48,8 @@ class DefectResult extends int { /** Gets the URL corresponding to the location of this query result. */ string getURL() { - result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() - + ":" + getEndLine() + ":" + getEndColumn() + result = + "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" + + getEndLine() + ":" + getEndColumn() } } diff --git a/javascript/ql/src/external/MetricFilter.qll b/javascript/ql/src/external/MetricFilter.qll index 43ac75ef889..5edecac75d8 100644 --- a/javascript/ql/src/external/MetricFilter.qll +++ b/javascript/ql/src/external/MetricFilter.qll @@ -66,7 +66,8 @@ class MetricResult extends int { /** Gets the URL corresponding to the location of this query result. */ string getURL() { - result = "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() - + ":" + getEndLine() + ":" + getEndColumn() + result = + "file://" + getFile().getAbsolutePath() + ":" + getStartLine() + ":" + getStartColumn() + ":" + + getEndLine() + ":" + getEndColumn() } } diff --git a/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll b/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll index 80c20a02143..3535ab5974c 100644 --- a/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll +++ b/javascript/ql/src/meta/analysis-quality/CallGraphQuality.qll @@ -86,9 +86,7 @@ SourceNode nodeLeadingToInvocation() { * Holds if there is a call edge `invoke -> f` between a relevant invocation * and a relevant function. */ -predicate relevantCall(RelevantInvoke invoke, RelevantFunction f) { - FlowSteps::calls(invoke, f) -} +predicate relevantCall(RelevantInvoke invoke, RelevantFunction f) { FlowSteps::calls(invoke, f) } /** * A call site that can be resolved to a function in the same project. @@ -105,9 +103,7 @@ class ResolvableCall extends RelevantInvoke { * A call site that could not be resolved. */ class UnresolvableCall extends RelevantInvoke { - UnresolvableCall() { - not this instanceof ResolvableCall - } + UnresolvableCall() { not this instanceof ResolvableCall } } /** diff --git a/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql index a2a35e8f11c..88f51d8c237 100644 --- a/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql +++ b/javascript/ql/src/meta/analysis-quality/UnresolvableImports.ql @@ -11,8 +11,6 @@ import javascript import CallGraphQuality -Import unresolvableImport() { - not exists(result.getImportedModule()) -} +Import unresolvableImport() { not exists(result.getImportedModule()) } select projectRoot(), count(unresolvableImport()) diff --git a/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql b/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql index 76216331f50..4f0b708b481 100644 --- a/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql +++ b/javascript/ql/src/meta/extraction-metrics/MissingMetrics.ql @@ -12,7 +12,8 @@ import javascript import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics from File f, string cause -where not extraction_data(f, _, _, _) and cause = "No extraction_data for this file" - or - not extraction_time(f, _,_, _) and cause = "No extraction_time for this file" -select f, cause \ No newline at end of file +where + not extraction_data(f, _, _, _) and cause = "No extraction_data for this file" + or + not extraction_time(f, _, _, _) and cause = "No extraction_time for this file" +select f, cause diff --git a/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql b/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql index a9fd872eea1..20e4bab4d35 100644 --- a/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql +++ b/javascript/ql/src/meta/extraction-metrics/PhaseTimings.ql @@ -9,4 +9,5 @@ import semmle.javascript.meta.ExtractionMetrics::ExtractionMetrics from PhaseName phaseName -select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO, Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO +select phaseName, Aggregated::getCpuTime(phaseName) as CPU_NANO, + Aggregated::getWallclockTime(phaseName) as WALLCLOCK_NANO diff --git a/javascript/ql/src/semmle/javascript/BasicBlocks.qll b/javascript/ql/src/semmle/javascript/BasicBlocks.qll index 0e0ebfabbc5..f6ee06df0f8 100644 --- a/javascript/ql/src/semmle/javascript/BasicBlocks.qll +++ b/javascript/ql/src/semmle/javascript/BasicBlocks.qll @@ -235,7 +235,8 @@ class BasicBlock extends @cfg_node, Locatable { */ private int nextDefOrUseAfter(PurelyLocalVariable v, int i, VarDef d) { defAt(i, v, d) and - result = min(int j | + result = + min(int j | (defAt(j, v, _) or useAt(j, v, _) or j = length()) and j > i ) diff --git a/javascript/ql/src/semmle/javascript/CharacterEscapes.qll b/javascript/ql/src/semmle/javascript/CharacterEscapes.qll index 33c2c01ec65..2d2a3c5f3a8 100644 --- a/javascript/ql/src/semmle/javascript/CharacterEscapes.qll +++ b/javascript/ql/src/semmle/javascript/CharacterEscapes.qll @@ -57,7 +57,8 @@ module CharacterEscapes { hasRawStringAndQuote(n, delim, rawStringNode, raw) and if rawStringNode instanceof RegExpLiteral then - additionalEscapeChars = Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() + + additionalEscapeChars = + Sets::regexpMetaChars() + Sets::regexpAssertionChars() + Sets::regexpCharClassChars() + Sets::regexpBackreferenceChars() else additionalEscapeChars = "b" | diff --git a/javascript/ql/src/semmle/javascript/Classes.qll b/javascript/ql/src/semmle/javascript/Classes.qll index 793ff5a92e4..e93e0565ce8 100644 --- a/javascript/ql/src/semmle/javascript/Classes.qll +++ b/javascript/ql/src/semmle/javascript/Classes.qll @@ -123,13 +123,14 @@ class ClassOrInterface extends @classorinterface, TypeParameterized { * Anonymous classes and interfaces do not have a canonical name. */ TypeName getTypeName() { result.getADefinition() = this } - + /** * Gets the ClassOrInterface corresponding to either a super type or an implemented interface. */ ClassOrInterface getASuperTypeDeclaration() { this.getSuperClass().(VarAccess).getVariable().getADeclaration() = result.getIdentifier() or - this.getASuperInterface().(LocalTypeAccess).getLocalTypeName().getADeclaration() = result.getIdentifier() + this.getASuperInterface().(LocalTypeAccess).getLocalTypeName().getADeclaration() = + result.getIdentifier() } } @@ -334,11 +335,8 @@ class ClassExpr extends @classexpr, ClassDefinition, Expr { else if exists(getClassInitializedMember()) then - result = min(ClassInitializedMember m | - m = getClassInitializedMember() - | - m order by m.getIndex() - ) + result = + min(ClassInitializedMember m | m = getClassInitializedMember() | m order by m.getIndex()) else result = this } @@ -688,7 +686,8 @@ class MethodDeclaration extends MemberDeclaration { */ int getOverloadIndex() { exists(ClassOrInterface type, string name | - this = rank[result + 1](MethodDeclaration method, int i | + this = + rank[result + 1](MethodDeclaration method, int i | methodDeclaredInType(type, name, i, method) | method order by i @@ -696,7 +695,8 @@ class MethodDeclaration extends MemberDeclaration { ) or exists(ClassDefinition type | - this = rank[result + 1](ConstructorDeclaration ctor, int i | + this = + rank[result + 1](ConstructorDeclaration ctor, int i | ctor = type.getMemberByIndex(i) | ctor order by i @@ -1156,7 +1156,8 @@ class FunctionCallSignature extends @function_call_signature, CallSignature { /** Gets the index of this function call signature among the function call signatures in the enclosing type. */ int getOverloadIndex() { exists(ClassOrInterface type | type = getDeclaringType() | - this = rank[result + 1](FunctionCallSignature sig, int i | + this = + rank[result + 1](FunctionCallSignature sig, int i | sig = type.getMemberByIndex(i) | sig order by i @@ -1186,7 +1187,8 @@ class ConstructorCallSignature extends @constructor_call_signature, CallSignatur /** Gets the index of this constructor call signature among the constructor call signatures in the enclosing type. */ int getOverloadIndex() { exists(ClassOrInterface type | type = getDeclaringType() | - this = rank[result + 1](ConstructorCallSignature sig, int i | + this = + rank[result + 1](ConstructorCallSignature sig, int i | sig = type.getMemberByIndex(i) | sig order by i diff --git a/javascript/ql/src/semmle/javascript/DefUse.qll b/javascript/ql/src/semmle/javascript/DefUse.qll index 204545c05fb..8c0c008256e 100644 --- a/javascript/ql/src/semmle/javascript/DefUse.qll +++ b/javascript/ql/src/semmle/javascript/DefUse.qll @@ -284,7 +284,8 @@ private SsaDefinition getAPseudoDefinitionInput(SsaDefinition nd) { */ private int nextDefAfter(BasicBlock bb, Variable v, int i, VarDef d) { bb.defAt(i, v, d) and - result = min(int jj | + result = + min(int jj | (bb.defAt(jj, v, _) or jj = bb.length()) and jj > i ) diff --git a/javascript/ql/src/semmle/javascript/ES2015Modules.qll b/javascript/ql/src/semmle/javascript/ES2015Modules.qll index ca50b3cdcc6..df094df2952 100644 --- a/javascript/ql/src/semmle/javascript/ES2015Modules.qll +++ b/javascript/ql/src/semmle/javascript/ES2015Modules.qll @@ -78,9 +78,7 @@ class ImportDeclaration extends Stmt, Import, @importdeclaration { } /** Holds if this is declared with the `type` keyword, so it only imports types. */ - predicate isTypeOnly() { - hasTypeKeyword(this) - } + predicate isTypeOnly() { hasTypeKeyword(this) } override predicate isAmbient() { Stmt.super.isAmbient() or @@ -268,9 +266,7 @@ abstract class ExportDeclaration extends Stmt, @exportdeclaration { abstract DataFlow::Node getSourceNode(string name); /** Holds if is declared with the `type` keyword, so only types are exported. */ - predicate isTypeOnly() { - hasTypeKeyword(this) - } + predicate isTypeOnly() { hasTypeKeyword(this) } override predicate isAmbient() { Stmt.super.isAmbient() or @@ -614,15 +610,10 @@ abstract class ReExportDeclaration extends ExportDeclaration { * * Gets the module from which this declaration re-exports. */ - deprecated - ES2015Module getImportedModule() { - result = getReExportedModule() - } + deprecated ES2015Module getImportedModule() { result = getReExportedModule() } /** Gets the module from which this declaration re-exports, if it is an ES2015 module. */ - ES2015Module getReExportedES2015Module() { - result = getReExportedModule() - } + ES2015Module getReExportedES2015Module() { result = getReExportedModule() } /** Gets the module from which this declaration re-exports. */ Module getReExportedModule() { @@ -635,7 +626,8 @@ abstract class ReExportDeclaration extends ExportDeclaration { * Gets a module in a `node_modules/@types/` folder that matches the imported module name. */ private Module resolveFromTypeRoot() { - result.getFile() = min(TypeRootFolder typeRoot | + result.getFile() = + min(TypeRootFolder typeRoot | | typeRoot.getModuleFile(getImportedPath().getStringValue()) order by diff --git a/javascript/ql/src/semmle/javascript/EmailClients.qll b/javascript/ql/src/semmle/javascript/EmailClients.qll index e84148c8152..11d12fa0658 100644 --- a/javascript/ql/src/semmle/javascript/EmailClients.qll +++ b/javascript/ql/src/semmle/javascript/EmailClients.qll @@ -43,9 +43,8 @@ abstract class EmailSender extends DataFlow::SourceNode { */ private class NodemailerEmailSender extends EmailSender, DataFlow::MethodCallNode { NodemailerEmailSender() { - this = DataFlow::moduleMember("nodemailer", "createTransport") - .getACall() - .getAMethodCall("sendMail") + this = + DataFlow::moduleMember("nodemailer", "createTransport").getACall().getAMethodCall("sendMail") } override DataFlow::Node getPlainTextBody() { result = getOptionArgument(0, "text") } diff --git a/javascript/ql/src/semmle/javascript/Expr.qll b/javascript/ql/src/semmle/javascript/Expr.qll index b43a38306ce..e835176c7ad 100644 --- a/javascript/ql/src/semmle/javascript/Expr.qll +++ b/javascript/ql/src/semmle/javascript/Expr.qll @@ -253,12 +253,15 @@ class Expr extends @expr, ExprOrStmt, ExprOrType, AST::ValueNode { DataFlow::Node getExceptionTarget() { if exists(this.getEnclosingStmt().getEnclosingTryCatchStmt()) then - result = DataFlow::parameterNode(this + result = + DataFlow::parameterNode(this .getEnclosingStmt() .getEnclosingTryCatchStmt() .getACatchClause() .getAParameter()) - else result = any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn() + else + result = + any(DataFlow::FunctionNode f | f.getFunction() = this.getContainer()).getExceptionalReturn() } } diff --git a/javascript/ql/src/semmle/javascript/Files.qll b/javascript/ql/src/semmle/javascript/Files.qll index 09b31bd7915..0ac6df39c65 100644 --- a/javascript/ql/src/semmle/javascript/Files.qll +++ b/javascript/ql/src/semmle/javascript/Files.qll @@ -183,11 +183,8 @@ class Folder extends Container, @folder { * HTML files will not be found by this method. */ File getJavaScriptFile(string stem) { - result = min(int p, string ext | - p = getFileExtensionPriority(ext) - | - getFile(stem, ext) order by p - ) + result = + min(int p, string ext | p = getFileExtensionPriority(ext) | getFile(stem, ext) order by p) } /** Gets a subfolder contained in this folder. */ diff --git a/javascript/ql/src/semmle/javascript/Functions.qll b/javascript/ql/src/semmle/javascript/Functions.qll index c8f41e76570..d6f0f965df7 100644 --- a/javascript/ql/src/semmle/javascript/Functions.qll +++ b/javascript/ql/src/semmle/javascript/Functions.qll @@ -206,7 +206,8 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine /** Gets the cyclomatic complexity of this function. */ int getCyclomaticComplexity() { - result = 2 + + result = + 2 + sum(Expr nd | nd.getContainer() = this and nd.isBranch() | @@ -420,9 +421,7 @@ class Function extends @function, Parameterized, TypeParameterized, StmtContaine /** * Gets the call signature of this function, as determined by the TypeScript compiler, if any. */ - CallSignatureType getCallSignature() { - declared_function_signature(this, result) - } + CallSignatureType getCallSignature() { declared_function_signature(this, result) } } /** diff --git a/javascript/ql/src/semmle/javascript/GeneratedCode.qll b/javascript/ql/src/semmle/javascript/GeneratedCode.qll index 0f8b161e7ae..e2f8e81d3c9 100644 --- a/javascript/ql/src/semmle/javascript/GeneratedCode.qll +++ b/javascript/ql/src/semmle/javascript/GeneratedCode.qll @@ -35,8 +35,10 @@ class CodeGeneratorMarkerComment extends GeneratedCodeMarkerComment { */ private predicate codeGeneratorMarkerComment(Comment c, string tool) { exists(string toolPattern | - toolPattern = "js_of_ocaml|CoffeeScript|LiveScript|dart2js|ANTLR|PEG\\.js|Opal|JSX|jison(?:-lex)?|(?:Microsoft \\(R\\) AutoRest Code Generator)|purs" and - tool = c + toolPattern = + "js_of_ocaml|CoffeeScript|LiveScript|dart2js|ANTLR|PEG\\.js|Opal|JSX|jison(?:-lex)?|(?:Microsoft \\(R\\) AutoRest Code Generator)|purs" and + tool = + c .getText() .regexpCapture("(?s)[\\s*]*(?:parser |Code )?[gG]eneratedy? (?:from .*)?by (" + toolPattern + ")\\b.*", 1) diff --git a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll index 2a3d8f963a2..5d260b98ac7 100644 --- a/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/GlobalAccessPaths.qll @@ -5,23 +5,18 @@ import javascript private import semmle.javascript.dataflow.InferredTypes -deprecated -module GlobalAccessPath { +deprecated module GlobalAccessPath { /** * DEPRECATED. Instead use `AccessPath::getAReferenceTo` with the result and parameter reversed. */ pragma[inline] - string fromReference(DataFlow::Node node) { - node = AccessPath::getAReferenceTo(result) - } + string fromReference(DataFlow::Node node) { node = AccessPath::getAReferenceTo(result) } /** * DEPRECATED. Instead use `AccessPath::getAnAssignmentTo` with the result and parameter reversed. */ pragma[inline] - string fromRhs(DataFlow::Node node) { - node = AccessPath::getAnAssignmentTo(result) - } + string fromRhs(DataFlow::Node node) { node = AccessPath::getAnAssignmentTo(result) } /** * DEPRECATED. Use `AccessPath::getAReferenceOrAssignmentTo`. @@ -67,9 +62,7 @@ module AccessPath { } /** Holds if this represents the root of the global access path. */ - predicate isGlobal() { - this = DataFlow::globalAccessPathRootPseudoNode() - } + predicate isGlobal() { this = DataFlow::globalAccessPathRootPseudoNode() } } /** @@ -212,7 +205,8 @@ module AccessPath { * ``` */ private predicate isSelfAssignment(DataFlow::Node rhs) { - fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode()) + fromRhs(rhs, DataFlow::globalAccessPathRootPseudoNode()) = + fromReference(rhs, DataFlow::globalAccessPathRootPseudoNode()) } /** @@ -418,8 +412,8 @@ module AccessPath { */ pragma[inline] DataFlow::SourceNode getAnAliasedSourceNode(DataFlow::Node node) { - exists(DataFlow::SourceNode root, string accessPath | - node = AccessPath::getAReferenceTo(root, accessPath) and + exists(DataFlow::SourceNode root, string accessPath | + node = AccessPath::getAReferenceTo(root, accessPath) and result = AccessPath::getAReferenceTo(root, accessPath) ) or diff --git a/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll b/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll index e624b1310bb..47f88f0390c 100644 --- a/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll +++ b/javascript/ql/src/semmle/javascript/HtmlSanitizers.qll @@ -49,9 +49,8 @@ private class DefaultHtmlSanitizerCall extends HtmlSanitizerCall { callee = LodashUnderscore::member("escape") or exists(string name | name = "encode" or name = "encodeNonUTF" | - callee = DataFlow::moduleMember("html-entities", _) - .getAnInstantiation() - .getAPropertyRead(name) or + callee = + DataFlow::moduleMember("html-entities", _).getAnInstantiation().getAPropertyRead(name) or callee = DataFlow::moduleMember("html-entities", _).getAPropertyRead(name) ) or diff --git a/javascript/ql/src/semmle/javascript/InclusionTests.qll b/javascript/ql/src/semmle/javascript/InclusionTests.qll index 7a90dbf1583..af0f21b050a 100644 --- a/javascript/ql/src/semmle/javascript/InclusionTests.qll +++ b/javascript/ql/src/semmle/javascript/InclusionTests.qll @@ -1,6 +1,7 @@ /** * Contains classes for recognizing array and string inclusion tests. */ + private import javascript /** diff --git a/javascript/ql/src/semmle/javascript/Locations.qll b/javascript/ql/src/semmle/javascript/Locations.qll index aa7dc6210b3..72939c8b419 100644 --- a/javascript/ql/src/semmle/javascript/Locations.qll +++ b/javascript/ql/src/semmle/javascript/Locations.qll @@ -142,5 +142,6 @@ class Locatable extends @locatable { */ private class FileLocatable extends File, Locatable { override Location getLocation() { result = File.super.getLocation() } + override string toString() { result = File.super.toString() } } diff --git a/javascript/ql/src/semmle/javascript/Modules.qll b/javascript/ql/src/semmle/javascript/Modules.qll index 26b2331afcf..2352e2ba496 100644 --- a/javascript/ql/src/semmle/javascript/Modules.qll +++ b/javascript/ql/src/semmle/javascript/Modules.qll @@ -140,7 +140,8 @@ abstract class Import extends ASTNode { * Gets a module in a `node_modules/@types/` folder that matches the imported module name. */ private Module resolveFromTypeRoot() { - result.getFile() = min(TypeRootFolder typeRoot | + result.getFile() = + min(TypeRootFolder typeRoot | | typeRoot.getModuleFile(getImportedPath().getValue()) order by diff --git a/javascript/ql/src/semmle/javascript/NodeJS.qll b/javascript/ql/src/semmle/javascript/NodeJS.qll index bf88039ff77..f184c0e1c38 100644 --- a/javascript/ql/src/semmle/javascript/NodeJS.qll +++ b/javascript/ql/src/semmle/javascript/NodeJS.qll @@ -243,7 +243,8 @@ class Require extends CallExpr, Import { private File load(int priority) { exists(int r | getEnclosingModule().searchRoot(getImportedPath(), _, r) | result = loadAsFile(this, r, priority - prioritiesPerCandidate() * r) or - result = loadAsDirectory(this, r, + result = + loadAsDirectory(this, r, priority - (prioritiesPerCandidate() * r + numberOfExtensions() + 1)) ) } diff --git a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll index 83023fabb6f..20354a1f086 100644 --- a/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll +++ b/javascript/ql/src/semmle/javascript/NodeModuleResolutionImpl.qll @@ -92,7 +92,7 @@ File resolveMainModule(PackageJSON pkg, int priority) { not exists(main.resolve()) and not exists(main.getExtension()) and exists(int n | n = main.getNumComponent() | - result = tryExtensions(main.resolveUpTo(n-1), main.getComponent(n-1), priority) + result = tryExtensions(main.resolveUpTo(n - 1), main.getComponent(n - 1), priority) ) ) else result = tryExtensions(pkg.getFile().getParentContainer(), "index", priority) diff --git a/javascript/ql/src/semmle/javascript/Promises.qll b/javascript/ql/src/semmle/javascript/Promises.qll index cac97e54e09..413938ec3f7 100644 --- a/javascript/ql/src/semmle/javascript/Promises.qll +++ b/javascript/ql/src/semmle/javascript/Promises.qll @@ -128,17 +128,13 @@ private module PromiseFlow { /** * Gets the pseudo-field used to describe resolved values in a promise. */ - string resolveField() { - result = "$PromiseResolveField$" - } + string resolveField() { result = "$PromiseResolveField$" } /** * Gets the pseudo-field used to describe rejected values in a promise. */ - string rejectField() { - result = "$PromiseRejectField$" - } - + string rejectField() { result = "$PromiseRejectField$" } + /** * A flow step describing a promise definition. * @@ -146,9 +142,8 @@ private module PromiseFlow { */ class PromiseDefitionStep extends DataFlow::AdditionalFlowStep { PromiseDefinition promise; - PromiseDefitionStep() { - this = promise - } + + PromiseDefitionStep() { this = promise } override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and @@ -170,15 +165,14 @@ private module PromiseFlow { succ = this } } - + /** * A flow step describing the a Promise.resolve (and similar) call. */ class CreationStep extends DataFlow::AdditionalFlowStep { PromiseCreationCall promise; - CreationStep() { - this = promise - } + + CreationStep() { this = promise } override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and @@ -194,7 +188,6 @@ private module PromiseFlow { } } - /** * A load step loading the pseudo-field describing that the promise is rejected. * The rejected value is thrown as a exception. @@ -202,6 +195,7 @@ private module PromiseFlow { class AwaitStep extends DataFlow::AdditionalFlowStep { DataFlow::Node operand; AwaitExpr await; + AwaitStep() { this.getEnclosingExpr() = await and operand.getEnclosingExpr() = await.getOperand() @@ -222,9 +216,7 @@ private module PromiseFlow { * A flow step describing the data-flow related to the `.then` method of a promise. */ class ThenStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { - ThenStep() { - this.getMethodName() = "then" - } + ThenStep() { this.getMethodName() = "then" } override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and @@ -235,7 +227,7 @@ private module PromiseFlow { pred = getReceiver() and succ = getCallback(1).getParameter(0) } - + override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { not exists(this.getArgument(1)) and prop = rejectField() and @@ -244,17 +236,17 @@ private module PromiseFlow { or // read the value of a resolved/rejected promise that is returned (prop = rejectField() or prop = resolveField()) and - pred = getCallback([0..1]).getAReturn() and + pred = getCallback([0 .. 1]).getAReturn() and succ = this } - + override predicate storeStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = resolveField() and - pred = getCallback([0..1]).getAReturn() and + pred = getCallback([0 .. 1]).getAReturn() and succ = this or prop = rejectField() and - pred = getCallback([0..1]).getExceptionalReturn() and + pred = getCallback([0 .. 1]).getExceptionalReturn() and succ = this } } @@ -263,9 +255,7 @@ private module PromiseFlow { * A flow step describing the data-flow related to the `.catch` method of a promise. */ class CatchStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { - CatchStep() { - this.getMethodName() = "catch" - } + CatchStep() { this.getMethodName() = "catch" } override predicate loadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { prop = rejectField() and @@ -299,9 +289,7 @@ private module PromiseFlow { * A flow step describing the data-flow related to the `.finally` method of a promise. */ class FinallyStep extends DataFlow::AdditionalFlowStep, DataFlow::MethodCallNode { - FinallyStep() { - this.getMethodName() = "finally" - } + FinallyStep() { this.getMethodName() = "finally" } override predicate loadStoreStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { (prop = resolveField() or prop = rejectField()) and @@ -332,15 +320,13 @@ predicate promiseTaintStep(DataFlow::Node pred, DataFlow::Node succ) { // from `x` to `Promise.resolve(x)` pred = succ.(PromiseCreationCall).getValue() or - exists(DataFlow::MethodCallNode thn | - thn.getMethodName() = "then" - | + exists(DataFlow::MethodCallNode thn | thn.getMethodName() = "then" | // from `p` to `x` in `p.then(x => ...)` pred = thn.getReceiver() and succ = thn.getCallback(0).getParameter(0) or // from `v` to `p.then(x => return v)` - pred = thn.getCallback([0..1]).getAReturn() and + pred = thn.getCallback([0 .. 1]).getAReturn() and succ = thn ) or @@ -406,22 +392,19 @@ module Bluebird { override DataFlow::Node getValue() { result = getArgument(0) } } - + /** - * An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`. + * An aggregated promise produced either by `Promise.all`, `Promise.race` or `Promise.map`. */ class AggregateBluebirdPromiseDefinition extends PromiseCreationCall { AggregateBluebirdPromiseDefinition() { - exists(string m | m = "all" or m = "race" or m = "map" | - this = bluebird().getAMemberCall(m) - ) + exists(string m | m = "all" or m = "race" or m = "map" | this = bluebird().getAMemberCall(m)) } override DataFlow::Node getValue() { result = getArgument(0).getALocalSource().(DataFlow::ArrayCreationNode).getAnElement() } } - } /** diff --git a/javascript/ql/src/semmle/javascript/Regexp.qll b/javascript/ql/src/semmle/javascript/Regexp.qll index e3c45261537..e4fdef07783 100644 --- a/javascript/ql/src/semmle/javascript/Regexp.qll +++ b/javascript/ql/src/semmle/javascript/Regexp.qll @@ -296,9 +296,7 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { forall(RegExpTerm child | child = getAChild() | child.isNullable()) } - override string getConstantValue() { - result = getConstantValue(0) - } + override string getConstantValue() { result = getConstantValue(0) } /** * Gets the single string matched by the `i`th child and all following children of @@ -308,13 +306,11 @@ class RegExpSequence extends RegExpTerm, @regexp_seq { i = getNumChild() and result = "" or - result = getChild(i).getConstantValue() + getConstantValue(i+1) + result = getChild(i).getConstantValue() + getConstantValue(i + 1) } - /** Gets the element preceding `element` in this sequence. */ - RegExpTerm previousElement(RegExpTerm element) { - element = nextElement(result) - } + /** Gets the element preceding `element` in this sequence. */ + RegExpTerm previousElement(RegExpTerm element) { element = nextElement(result) } /** Gets the element following `element` in this sequence. */ RegExpTerm nextElement(RegExpTerm element) { @@ -834,7 +830,7 @@ class RegExpParseError extends Error, @regexp_parse_error { } /** - * Holds if `func` is a method defined on `String.prototype` with name `name`. + * Holds if `func` is a method defined on `String.prototype` with name `name`. */ private predicate isNativeStringMethod(Function func, string name) { exists(ExternalInstanceMemberDecl decl | @@ -856,9 +852,7 @@ predicate isInterpretedAsRegExp(DataFlow::Node source) { exists(DataFlow::MethodCallNode mce, string methodName | mce.getReceiver().analyze().getAType() = TTString() and mce.getMethodName() = methodName and - not exists(Function func | - func = mce.getACallee() - | + not exists(Function func | func = mce.getACallee() | not isNativeStringMethod(func, methodName) ) | diff --git a/javascript/ql/src/semmle/javascript/RestrictedLocations.qll b/javascript/ql/src/semmle/javascript/RestrictedLocations.qll index 4146f177009..8c45ad533ba 100644 --- a/javascript/ql/src/semmle/javascript/RestrictedLocations.qll +++ b/javascript/ql/src/semmle/javascript/RestrictedLocations.qll @@ -25,9 +25,8 @@ class FirstLineOf extends Locatable { if xl = startline then endcolumn = xc else - endcolumn = max(int c | - any(Location l).hasLocationInfo(filepath, startline, _, startline, c) - ) + endcolumn = + max(int c | any(Location l).hasLocationInfo(filepath, startline, _, startline, c)) ) } } diff --git a/javascript/ql/src/semmle/javascript/SSA.qll b/javascript/ql/src/semmle/javascript/SSA.qll index b1b3a904955..a3d74e4f724 100644 --- a/javascript/ql/src/semmle/javascript/SSA.qll +++ b/javascript/ql/src/semmle/javascript/SSA.qll @@ -647,9 +647,7 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi { result = getDefReachingEndOf(bb, getSourceVariable()) } - override SsaVariable getAnInput() { - result = getInputFromBlock(_) - } + override SsaVariable getAnInput() { result = getInputFromBlock(_) } override predicate definesAt(ReachableBasicBlock bb, int i, SsaSourceVariable v) { bb = getBasicBlock() and v = getSourceVariable() and i = -1 @@ -676,9 +674,7 @@ class SsaPhiNode extends SsaPseudoDefinition, TPhi { * gets that variable. */ SsaVariable getRephinedVariable() { - forex(SsaVariable input | input = getAnInput() | - result = getRefinedVariable(input) - ) + forex(SsaVariable input | input = getAnInput() | result = getRefinedVariable(input)) } } diff --git a/javascript/ql/src/semmle/javascript/StandardLibrary.qll b/javascript/ql/src/semmle/javascript/StandardLibrary.qll index aae98553faf..b5ae842789e 100644 --- a/javascript/ql/src/semmle/javascript/StandardLibrary.qll +++ b/javascript/ql/src/semmle/javascript/StandardLibrary.qll @@ -49,7 +49,8 @@ class DirectEval extends CallExpr { * Models `Array.prototype.map` and friends as partial invocations that pass their second * argument as the receiver to the callback. */ -private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range, DataFlow::MethodCallNode { +private class ArrayIterationCallbackAsPartialInvoke extends DataFlow::PartialInvokeNode::Range, + DataFlow::MethodCallNode { ArrayIterationCallbackAsPartialInvoke() { getNumArgument() = 2 and // Filter out library methods named 'forEach' etc diff --git a/javascript/ql/src/semmle/javascript/Stmt.qll b/javascript/ql/src/semmle/javascript/Stmt.qll index c96d6509e8d..7ec69344aa7 100644 --- a/javascript/ql/src/semmle/javascript/Stmt.qll +++ b/javascript/ql/src/semmle/javascript/Stmt.qll @@ -55,15 +55,15 @@ class Stmt extends @stmt, ExprOrStmt, Documentable { } override predicate isAmbient() { hasDeclareKeyword(this) or getParent().isAmbient() } - + /** - * Gets the `try` statement with a catch block containing this statement without - * crossing function boundaries or other `try ` statements with catch blocks. - */ + * Gets the `try` statement with a catch block containing this statement without + * crossing function boundaries or other `try ` statements with catch blocks. + */ TryStmt getEnclosingTryCatchStmt() { getParentStmt+() = result.getBody() and exists(result.getACatchClause()) and - not exists(TryStmt mid | exists(mid.getACatchClause()) | + not exists(TryStmt mid | exists(mid.getACatchClause()) | getParentStmt+() = mid.getBody() and mid.getParentStmt+() = result.getBody() ) } diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index fac784cb49f..bfa2c84897a 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -165,10 +165,16 @@ module StringOps { StartsWith_Substring() { astNode.hasOperands(call.asExpr(), substring.asExpr()) and - (call.getMethodName() = "substring" or call.getMethodName() = "substr" or call.getMethodName() = "slice") and + ( + call.getMethodName() = "substring" or + call.getMethodName() = "substr" or + call.getMethodName() = "slice" + ) and call.getNumArgument() = 2 and ( - AccessPath::getAnAliasedSourceNode(substring).getAPropertyRead("length").flowsTo(call.getArgument(1)) + AccessPath::getAnAliasedSourceNode(substring) + .getAPropertyRead("length") + .flowsTo(call.getArgument(1)) or substring.getStringValue().length() = call.getArgument(1).asExpr().getIntValue() ) @@ -502,7 +508,8 @@ module StringOps { result = getStringValue() or not exists(getStringValue()) and - result = strictconcat(StringLiteralLike leaf | + result = + strictconcat(StringLiteralLike leaf | leaf = getALeaf().asExpr() | leaf.getStringValue() order by leaf.getFirstToken().getIndex() diff --git a/javascript/ql/src/semmle/javascript/TypeScript.qll b/javascript/ql/src/semmle/javascript/TypeScript.qll index 7821c463d8e..c6497355763 100644 --- a/javascript/ql/src/semmle/javascript/TypeScript.qll +++ b/javascript/ql/src/semmle/javascript/TypeScript.qll @@ -939,9 +939,7 @@ class PredicateTypeExpr extends @predicatetypeexpr, TypeExpr { /** * Holds if this is a type of form `asserts E is T` or `asserts E`. */ - predicate hasAssertsKeyword() { - hasAssertsKeyword(this) - } + predicate hasAssertsKeyword() { hasAssertsKeyword(this) } } /** @@ -954,9 +952,7 @@ class PredicateTypeExpr extends @predicatetypeexpr, TypeExpr { * ``` */ class IsTypeExpr extends PredicateTypeExpr { - IsTypeExpr() { - exists(getPredicateType()) - } + IsTypeExpr() { exists(getPredicateType()) } } /** @@ -2312,18 +2308,14 @@ class EnumLiteralType extends TypeReference { * A type that refers to a type alias. */ class TypeAliasReference extends TypeReference { - TypeAliasReference() { - type_alias(this, _) - } + TypeAliasReference() { type_alias(this, _) } /** * Gets the type behind the type alias. * * For example, for `type B = T[][]`, this maps the type `B` to `number[][]`. */ - Type getAliasedType() { - type_alias(this, result) - } + Type getAliasedType() { type_alias(this, result) } } /** @@ -2635,9 +2627,7 @@ class CallSignatureType extends @signature_type { * * For example, for the signature `(...y: string[])`, this gets the type `string[]`. */ - PlainArrayType getRestParameterArrayType() { - signature_rest_parameter(this, result) - } + PlainArrayType getRestParameterArrayType() { signature_rest_parameter(this, result) } } /** diff --git a/javascript/ql/src/semmle/javascript/Variables.qll b/javascript/ql/src/semmle/javascript/Variables.qll index df4dccaf987..5c5c3e3f115 100644 --- a/javascript/ql/src/semmle/javascript/Variables.qll +++ b/javascript/ql/src/semmle/javascript/Variables.qll @@ -189,10 +189,8 @@ class Variable extends @variable, LexicalName { */ predicate isCaptured() { this instanceof GlobalVariable or - getAnAccess().getContainer().getFunctionBoundary() != this - .(LocalVariable) - .getDeclaringContainer() - .getFunctionBoundary() + getAnAccess().getContainer().getFunctionBoundary() != + this.(LocalVariable).getDeclaringContainer().getFunctionBoundary() } /** Holds if there is a declaration of this variable in `tl`. */ @@ -764,9 +762,7 @@ class Parameter extends BindingPattern { * function f(x?: number) {} * ``` */ - predicate isDeclaredOptional() { - isOptionalParameterDeclaration(this) - } + predicate isDeclaredOptional() { isOptionalParameterDeclaration(this) } } /** diff --git a/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll b/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll index caf67e6db7f..29ed748dc68 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/BackwardExploration.qll @@ -18,9 +18,7 @@ import javascript private class BackwardExploringConfiguration extends DataFlow::Configuration { DataFlow::Configuration cfg; - BackwardExploringConfiguration() { - this = cfg - } + BackwardExploringConfiguration() { this = cfg } override predicate isSource(DataFlow::Node node) { any() } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index f71b6982f1e..d64239d458d 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -1035,7 +1035,9 @@ private predicate flowIntoHigherOrderCall( summary = oldSummary.append(PathSummary::call()) ) or - exists(DataFlow::SourceNode cb, DataFlow::FunctionNode f, int i, int boundArgs, PathSummary oldSummary | + exists( + DataFlow::SourceNode cb, DataFlow::FunctionNode f, int i, int boundArgs, PathSummary oldSummary + | higherOrderCall(pred, cb, i, cfg, oldSummary) and cb = CallGraph::getABoundFunctionReference(f, boundArgs, false) and succ = f.getParameter(boundArgs + i) and @@ -1494,9 +1496,9 @@ private class AdditionalBarrierGuardCall extends AdditionalBarrierGuardNode, Dat } /** - * A guard node for a variable in a negative condition, such as `x` in `if(!x)`. - * Can be added to a `isBarrier` in a data-flow configuration to block flow through such checks. - */ + * A guard node for a variable in a negative condition, such as `x` in `if(!x)`. + * Can be added to a `isBarrier` in a data-flow configuration to block flow through such checks. + */ class VarAccessBarrier extends DataFlow::Node { VarAccessBarrier() { exists(ConditionGuardNode guard, SsaRefinementNode refinement | diff --git a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll index e4c1097b877..5d7ce03e467 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/DataFlow.qll @@ -87,10 +87,10 @@ module DataFlow { Expr asExpr() { this = TValueNode(result) } /** - * Gets the expression enclosing this data flow node. - * In most cases the result is the same as `asExpr()`, however this method - * additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter` - * for a `DataFlow::ParameterNode`. + * Gets the expression enclosing this data flow node. + * In most cases the result is the same as `asExpr()`, however this method + * additionally the `InvokeExpr` corresponding to reflective calls, and the `Parameter` + * for a `DataFlow::ParameterNode`. */ Expr getEnclosingExpr() { result = asExpr() or @@ -536,10 +536,10 @@ module DataFlow { */ pragma[noinline] predicate accesses(Node base, string p) { getBase() = base and getPropertyName() = p } - + /** * Holds if this data flow node reads or writes a private field in a class. - */ + */ predicate isPrivateField() { getPropertyName().charAt(0) = "#" and getPropertyNameExpr() instanceof Label } @@ -860,7 +860,7 @@ module DataFlow { override Expr getPropertyNameExpr() { none() } override string getPropertyName() { - exists (int i | + exists(int i | elt = pattern.getElement(i) and result = i.toString() ) @@ -872,7 +872,6 @@ module DataFlow { */ private class ImportSpecifierAsPropRead extends PropRead, ValueNode { override ImportSpecifier astNode; - ImportDeclaration imprt; ImportSpecifierAsPropRead() { @@ -894,9 +893,7 @@ module DataFlow { private class ForOfLvalueAsPropRead extends PropRead { ForOfStmt stmt; - ForOfLvalueAsPropRead() { - this = lvalueNode(stmt.getLValue()) - } + ForOfLvalueAsPropRead() { this = lvalueNode(stmt.getLValue()) } override Node getBase() { result = stmt.getIterationDomain().flow() } @@ -1010,7 +1007,7 @@ module DataFlow { * Gets a pseudo-node representing the root of a global access path. */ DataFlow::Node globalAccessPathRootPseudoNode() { result instanceof TGlobalAccessPathRoot } - + /** * Gets a data flow node representing the underlying call performed by the given * call to `Function.prototype.call` or `Function.prototype.apply`. diff --git a/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll b/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll index 0e3a7ae91c2..080152d3094 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/ForwardExploration.qll @@ -16,9 +16,7 @@ import javascript private class ForwardExploringConfiguration extends DataFlow::Configuration { DataFlow::Configuration cfg; - ForwardExploringConfiguration() { - this = cfg - } + ForwardExploringConfiguration() { this = cfg } override predicate isSink(DataFlow::Node node) { any() } diff --git a/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll b/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll index 84d3c7dd5a5..90fed456d76 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/InferredTypes.qll @@ -134,6 +134,7 @@ class NonPrimitiveType extends InferredType { * Gets a pretty-printed list of all type tags in alphabetical order. */ string ppAllTypeTags() { - result = "boolean, class, date, function, null, number, object, regular expression," + + result = + "boolean, class, date, function, null, number, object, regular expression," + "string or undefined" } diff --git a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll index 937255e60a9..c988bc9362e 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Nodes.qll @@ -9,7 +9,7 @@ private import semmle.javascript.dependencies.Dependencies private import internal.CallGraphs /** - * A data flow node corresponding to an expression. + * A data flow node corresponding to an expression. * * Examples: * ```js @@ -147,7 +147,8 @@ class InvokeNode extends DataFlow::SourceNode { */ ParameterNode getABoundCallbackParameter(int callback, int param) { exists(int boundArgs | - result = getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs) + result = + getArgument(callback).getABoundFunctionValue(boundArgs).getParameter(param + boundArgs) ) } @@ -548,9 +549,7 @@ class RegExpLiteralNode extends DataFlow::ValueNode, DataFlow::SourceNode { RegExpTerm getRoot() { result = astNode.getRoot() } /** Gets the flags of this regular expression literal. */ - string getFlags() { - result = astNode.getFlags() - } + string getFlags() { result = astNode.getFlags() } } /** @@ -581,10 +580,9 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode { /** Gets the initial size of the created array, if it can be determined. */ int getSize() { - if getNumArgument() = 1 then - result = getArgument(0).getIntValue() - else - result = count(getAnElement()) + if getNumArgument() = 1 + then result = getArgument(0).getIntValue() + else result = count(getAnElement()) } } @@ -596,7 +594,7 @@ class ArrayConstructorInvokeNode extends DataFlow::InvokeNode { * Examples: * ```js * ['apple', 'orange']; - * Array('apple', 'orange') + * Array('apple', 'orange') * new Array('apple', 'orange') * Array(16) * new Array(16) @@ -1236,7 +1234,9 @@ class PartialInvokeNode extends DataFlow::Node { /** * Gets the node holding the receiver to be passed to the bound function, if specified. */ - DataFlow::Node getBoundReceiver(DataFlow::Node callback) { result = range.getBoundReceiver(callback) } + DataFlow::Node getBoundReceiver(DataFlow::Node callback) { + result = range.getBoundReceiver(callback) + } } module PartialInvokeNode { @@ -1247,7 +1247,9 @@ module PartialInvokeNode { /** * Holds if `argument` is passed as argument `index` to the function in `callback`. */ - predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { none() } + predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { + none() + } /** * Gets a node referring to a bound version of `callback` with `boundArgs` arguments bound. @@ -1259,8 +1261,7 @@ module PartialInvokeNode { * * Gets the node holding the receiver to be passed to the bound function, if specified. */ - deprecated - DataFlow::Node getBoundReceiver() { none() } + deprecated DataFlow::Node getBoundReceiver() { none() } /** * Gets the node holding the receiver to be passed to `callback`. @@ -1269,12 +1270,11 @@ module PartialInvokeNode { } /** - * A partial call through the built-in `Function.prototype.bind`. - */ + * A partial call through the built-in `Function.prototype.bind`. + */ private class BindPartialCall extends PartialInvokeNode::Range, DataFlow::MethodCallNode { BindPartialCall() { getMethodName() = "bind" and - // Avoid overlap with angular.bind and goog.bind not this = AngularJS::angular().getAMethodCall() and not getReceiver().accessesGlobal("goog") @@ -1299,8 +1299,8 @@ module PartialInvokeNode { } /** - * A partial call through `_.partial`. - */ + * A partial call through `_.partial`. + */ private class LodashPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode { LodashPartialCall() { this = LodashUnderscore::member("partial").getACall() } @@ -1323,9 +1323,7 @@ module PartialInvokeNode { private class RamdaPartialCall extends PartialInvokeNode::Range, DataFlow::CallNode { RamdaPartialCall() { this = DataFlow::moduleMember("ramda", "partial").getACall() } - private DataFlow::ArrayCreationNode getArgumentsArray() { - result.flowsTo(getArgument(1)) - } + private DataFlow::ArrayCreationNode getArgumentsArray() { result.flowsTo(getArgument(1)) } override predicate isPartialArgument(DataFlow::Node callback, DataFlow::Node argument, int index) { callback = getArgument(0) and @@ -1376,17 +1374,13 @@ deprecated class AdditionalPartialInvokeNode = PartialInvokeNode::Range; * ``` */ class RegExpConstructorInvokeNode extends DataFlow::InvokeNode { - RegExpConstructorInvokeNode() { - this = DataFlow::globalVarRef("RegExp").getAnInvocation() - } + RegExpConstructorInvokeNode() { this = DataFlow::globalVarRef("RegExp").getAnInvocation() } /** * Gets the AST of the regular expression created here, provided that the * first argument is a string literal. */ - RegExpTerm getRoot() { - result = getArgument(0).asExpr().(StringLiteral).asRegExp() - } + RegExpTerm getRoot() { result = getArgument(0).asExpr().(StringLiteral).asRegExp() } /** * Gets the flags provided in the second argument, or an empty string if no @@ -1462,13 +1456,9 @@ class RegExpCreationNode extends DataFlow::SourceNode { t.start() and result = this or - exists(DataFlow::TypeTracker t2 | - result = getAReference(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getAReference(t2).track(t2, t)) } /** Gets a data flow node referring to this regular expression. */ - DataFlow::SourceNode getAReference() { - result = getAReference(DataFlow::TypeTracker::end()) - } + DataFlow::SourceNode getAReference() { result = getAReference(DataFlow::TypeTracker::end()) } } diff --git a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll index e5acc6ee5b5..ea187f93f07 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TaintTracking.qll @@ -341,7 +341,7 @@ module TaintTracking { pred = call.getAnArgument() and succ = call or - // `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`. + // `e = arr1.concat(arr2, arr3)`: if any of the `arr` is tainted, then so is `e`. call.(DataFlow::MethodCallNode).calls(pred, "concat") and succ = call or @@ -574,7 +574,6 @@ module TaintTracking { succ = this } } - /** * A taint propagating data flow edge arising from calling `String.prototype.match()`. @@ -583,7 +582,7 @@ module TaintTracking { StringMatchTaintStep() { this.getMethodName() = "match" and this.getNumArgument() = 1 and - this.getArgument(0) .analyze().getAType() = TTRegExp() + this.getArgument(0).analyze().getAType() = TTRegExp() } override predicate step(DataFlow::Node pred, DataFlow::Node succ) { @@ -696,9 +695,7 @@ module TaintTracking { * A taint step through the Node.JS function `util.inspect(..)`. */ class UtilInspectTaintStep extends AdditionalTaintStep, DataFlow::InvokeNode { - UtilInspectTaintStep() { - this = DataFlow::moduleImport("util").getAMemberCall("inspect") - } + UtilInspectTaintStep() { this = DataFlow::moduleImport("util").getAMemberCall("inspect") } override predicate step(DataFlow::Node pred, DataFlow::Node succ) { succ = this and @@ -719,14 +716,16 @@ module TaintTracking { mce = astNode and mce.calls(base, m) and firstArg = mce.getArgument(0) | // /re/.test(u) or /re/.exec(u) - RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()), sanitizedOutcome) and + RegExp::isGenericRegExpSanitizer(RegExp::getRegExpObjectFromNode(base.flow()), + sanitizedOutcome) and (m = "test" or m = "exec") and firstArg = expr or // u.match(/re/) or u.match("re") base = expr and m = "match" and - RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()), sanitizedOutcome) + RegExp::isGenericRegExpSanitizer(RegExp::getRegExpFromNode(firstArg.flow()), + sanitizedOutcome) ) or // m = /re/.exec(u) and similar diff --git a/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll b/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll index ecbb3187728..e7db4e2d660 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/TypeTracking.qll @@ -137,10 +137,11 @@ module StepSummary { summary = LoadStep(prop) ) ) and - if param = fun.getAParameter() then ( + if param = fun.getAParameter() + then // Step from argument to call site. argumentPassing(succ, pred, fun.getFunction(), param) - ) else ( + else ( // Step from captured parameter to local call sites pred = param and succ = fun.getAnInvocation() diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll index bea4ddc8511..6aa26fe9cdb 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/AccessPaths.qll @@ -128,8 +128,9 @@ class AccessPath extends TAccessPath { exists(AccessPath base, PropertyName name, string rest | rest = "." + any(string s | name = StaticPropertyName(s)) or - rest = "[" + - any(SsaVariable var | name = DynamicPropertyName(var)).getSourceVariable().getName() + "]" + rest = + "[" + any(SsaVariable var | name = DynamicPropertyName(var)).getSourceVariable().getName() + + "]" | result = base.toString() + rest and this = MkAccessStep(base, name) diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll index 51bd466215e..944214a94af 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/CallGraphs.qll @@ -1,6 +1,7 @@ /** * Internal predicates for computing the call graph. */ + private import javascript cached @@ -24,21 +25,22 @@ module CallGraph { * from underlying class tracking if the function came from a class or instance. */ pragma[nomagic] - private - DataFlow::SourceNode getAFunctionReference(DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t) { + private DataFlow::SourceNode getAFunctionReference( + DataFlow::FunctionNode function, int imprecision, DataFlow::TypeTracker t + ) { t.start() and exists(Function fun | fun = function.getFunction() and fun = getAFunctionValue(result) | - if isIndefiniteGlobal(result) then + if isIndefiniteGlobal(result) + then fun.getFile() = result.getFile() and imprecision = 0 or fun.inExternsFile() and imprecision = 1 or imprecision = 2 - else - imprecision = 0 + else imprecision = 0 ) or imprecision = 0 and @@ -73,8 +75,9 @@ module CallGraph { * with `function` as the underlying function. */ pragma[nomagic] - private - DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t) { + private DataFlow::SourceNode getABoundFunctionReferenceAux( + DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t + ) { exists(DataFlow::PartialInvokeNode partial, DataFlow::Node callback | result = partial.getBoundFunction(callback, boundArgs) and getAFunctionReference(function, 0, t.continue()).flowsTo(callback) @@ -87,8 +90,10 @@ module CallGraph { } pragma[noinline] - private - DataFlow::SourceNode getABoundFunctionReferenceAux(DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, DataFlow::StepSummary summary) { + private DataFlow::SourceNode getABoundFunctionReferenceAux( + DataFlow::FunctionNode function, int boundArgs, DataFlow::TypeTracker t, + DataFlow::StepSummary summary + ) { exists(DataFlow::SourceNode prev | prev = getABoundFunctionReferenceAux(function, boundArgs, t) and DataFlow::StepSummary::step(prev, result, summary) @@ -100,7 +105,9 @@ module CallGraph { * with `function` as the underlying function. */ cached - DataFlow::SourceNode getABoundFunctionReference(DataFlow::FunctionNode function, int boundArgs, boolean contextDependent) { + DataFlow::SourceNode getABoundFunctionReference( + DataFlow::FunctionNode function, int boundArgs, boolean contextDependent + ) { exists(DataFlow::TypeTracker t | result = getABoundFunctionReferenceAux(function, boundArgs, t) and t.end() and @@ -116,8 +123,9 @@ module CallGraph { * This predicate may be overridden to customize the class hierarchy analysis. */ pragma[nomagic] - private - DataFlow::PropRead getAnInstanceMemberAccess(DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t) { + private DataFlow::PropRead getAnInstanceMemberAccess( + DataFlow::ClassNode cls, string name, DataFlow::TypeTracker t + ) { result = cls.getAnInstanceReference(t.continue()).getAPropertyRead(name) or exists(DataFlow::ClassNode subclass | diff --git a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll index d01c8ca9e8a..ad1897b1da8 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/internal/FlowSteps.qll @@ -440,8 +440,8 @@ class PathSummary extends TPathSummary { exists(Boolean hasReturn2, Boolean hasCall2, FlowLabel end2 | that = MkPathSummary(hasReturn2, hasCall2, end, end2) | - result = MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, - end2) and + result = + MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, end2) and // avoid constructing invalid paths not (hasCall = true and hasReturn2 = true) ) @@ -456,8 +456,8 @@ class PathSummary extends TPathSummary { exists(Boolean hasReturn2, Boolean hasCall2 | that = MkPathSummary(hasReturn2, hasCall2, FlowLabel::data(), FlowLabel::data()) | - result = MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, - end) and + result = + MkPathSummary(hasReturn.booleanOr(hasReturn2), hasCall.booleanOr(hasCall2), start, end) and // avoid constructing invalid paths not (hasCall = true and hasReturn2 = true) ) @@ -474,8 +474,9 @@ class PathSummary extends TPathSummary { (if hasReturn = true then withReturn = "with" else withReturn = "without") and (if hasCall = true then withCall = "with" else withCall = "without") | - result = "path " + withReturn + " return steps and " + withCall + " call steps " + - "transforming " + start + " into " + end + result = + "path " + withReturn + " return steps and " + withCall + " call steps " + "transforming " + + start + " into " + end ) } } diff --git a/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll b/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll index f7076aa710c..45eb4c8be32 100644 --- a/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll +++ b/javascript/ql/src/semmle/javascript/dependencies/FrameworkLibraries.qll @@ -145,8 +145,9 @@ abstract class FrameworkLibraryWithGenericURL extends FrameworkLibraryWithURLReg override string getAURLRegex() { exists(string id | id = getId() or id = getAnAlias() | result = ".*(?:^|/)" + id + "-(" + semverRegex() + ")" + variantRegex() + "\\.js" or - result = ".*/(?:\\w+@)?(" + semverRegex() + ")/(?:(?:dist|js|" + id + ")/)?" + id + - variantRegex() + "\\.js" + result = + ".*/(?:\\w+@)?(" + semverRegex() + ")/(?:(?:dist|js|" + id + ")/)?" + id + variantRegex() + + "\\.js" ) } } @@ -158,7 +159,8 @@ abstract class FrameworkLibraryWithGenericURL extends FrameworkLibraryWithURLReg * We ignore these when identifying frameworks. */ private string variantRegex() { - result = "([.-](slim|min|debug|dbg|umd|dev|all|testing|polyfills|" + + result = + "([.-](slim|min|debug|dbg|umd|dev|all|testing|polyfills|" + "core|compat|more|modern|sandbox|rtl|with-addons|legacy))*" } @@ -235,8 +237,8 @@ private predicate jQueryMarkerComment(Comment c, TopLevel tl, string version) { tl = c.getTopLevel() and exists(string txt | txt = c.getText() | // more recent versions use this format - version = txt - .regexpCapture("(?s).*jQuery (?:JavaScript Library )?v(" + versionRegex() + ").*", 1) + version = + txt.regexpCapture("(?s).*jQuery (?:JavaScript Library )?v(" + versionRegex() + ").*", 1) or // earlier versions used this format version = txt.regexpCapture("(?s).*jQuery (" + versionRegex() + ") - New Wave Javascript.*", 1) @@ -502,7 +504,8 @@ private class Lodash extends FrameworkLibraryWithGenericURL, FrameworkLibraryWit Lodash() { this = "lodash" } override string getAMarkerCommentRegex() { - result = "(?s).* (?:lod|Lo-D)ash ()" + "(?: \\(Custom Build\\))? " + + result = + "(?s).* (?:lod|Lo-D)ash ()" + "(?: \\(Custom Build\\))? " + ".*" } @@ -842,7 +845,8 @@ private class ApplicationInsightsInstance extends FrameworkLibraryInstance { string version; ApplicationInsightsInstance() { - version = this + version = + this .(TopLevel) .getFile() .getAbsolutePath() diff --git a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll index 6d1aba6bf8e..6eede4a1aa8 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSCore.qll @@ -832,7 +832,8 @@ private newtype TAngularScope = } or MkIsolateScope(CustomDirective dir) { dir.hasIsolateScope() } or MkElementScope(DOM::ElementDefinition elem) { - any(DirectiveInstance d | not d.(CustomDirective).hasIsolateScope()).getAMatchingElement() = elem + any(DirectiveInstance d | not d.(CustomDirective).hasIsolateScope()).getAMatchingElement() = + elem } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll index 191c8bf3268..bd0029abd0f 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/AngularJS/AngularJSExpressions.qll @@ -152,7 +152,8 @@ private class TemplateFieldNgSourceProvider extends NgSourceProvider { TemplateFieldNgSourceProvider() { this = directive.getMember("template").asExpr() and - source = this + source = + this .(ConstantString) .getStringValue() .regexpFind(getInterpolatedExpressionPattern(), _, offset) @@ -211,11 +212,8 @@ abstract class NgToken extends TNgToken { */ private int getIndex() { exists(NgSource src, int start | this.at(src, start) | - start = rank[result + 1](NgToken someToken, int someStart | - someToken.at(src, someStart) - | - someStart - ) + start = + rank[result + 1](NgToken someToken, int someStart | someToken.at(src, someStart) | someStart) ) } @@ -278,7 +276,8 @@ private module Lexer { NgOpTokenType() { this = "NgOpTokenType" } override string getPattern() { - result = concat(string op | + result = + concat(string op | op = "===" or op = "!==" or op = "==" or @@ -383,7 +382,8 @@ abstract class NgAstNode extends TNode { */ language[monotonicAggregates] string ppChildren() { - result = concat(NgAstNode child, int idx | + result = + concat(NgAstNode child, int idx | child = getChild(idx) and not child instanceof Empty | diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index d39b26f2563..4521c0a6534 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -553,10 +553,10 @@ module ClientRequest { /** * Gets a reference to an instance of `chrome-remote-interface`. * - * An instantiation of `chrome-remote-interface` either accepts a callback or returns a promise. - * - * The `isPromise` parameter reflects whether the reference is a promise containing - * an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`. + * An instantiation of `chrome-remote-interface` either accepts a callback or returns a promise. + * + * The `isPromise` parameter reflects whether the reference is a promise containing + * an instance of `chrome-remote-interface`, or an instance of `chrome-remote-interface`. */ private DataFlow::SourceNode chromeRemoteInterface(DataFlow::TypeTracker t, boolean isPromise) { t.start() and diff --git a/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll b/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll index 624040db386..71aa4b4a3fb 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/CryptoLibraries.qll @@ -609,7 +609,8 @@ private module Forge { NonKeyCipher() { exists(string algorithmName | algorithm.matchesName(algorithmName) | // require("forge").md.md5.create().update('The quick brown fox jumps over the lazy dog'); - this = getAnImportNode() + this = + getAnImportNode() .getAPropertyRead("md") .getAPropertyRead(algorithmName) .getAMemberCall("create") diff --git a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll index 5d146f7e036..7d099932738 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Electron.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Electron.qll @@ -138,9 +138,7 @@ module Electron { result = this.getABoundCallbackParameter(1, 0).getAPropertyWrite("returnValue").getRhs() } - override IPCDispatch getAReturnDispatch() { - result.getCalleeName() = "sendSync" - } + override IPCDispatch getAReturnDispatch() { result.getCalleeName() = "sendSync" } } /** @@ -168,7 +166,7 @@ module Electron { } /** - * Gets a registration that this dispatch can send an event to. + * Gets a registration that this dispatch can send an event to. */ override IPCSendRegistration getAReceiver() { this.getEmitter() instanceof RendererProcess and diff --git a/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll b/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll index ed3b04c8a7e..4db64ad0ab7 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/EventEmitter.qll @@ -129,9 +129,7 @@ module EventRegistration { * argument is the event handler callback. */ abstract class DefaultEventRegistration extends Range, DataFlow::InvokeNode { - override string getChannel() { - this.getArgument(0).mayHaveStringValue(result) - } + override string getChannel() { this.getArgument(0).mayHaveStringValue(result) } override DataFlow::Node getReceivedItem(int i) { result = this.getABoundCallbackParameter(1, i) @@ -187,13 +185,9 @@ module EventDispatch { * is the `i`th item sent to the event handler. */ abstract class DefaultEventDispatch extends Range, DataFlow::InvokeNode { - override string getChannel() { - this.getArgument(0).mayHaveStringValue(result) - } + override string getChannel() { this.getArgument(0).mayHaveStringValue(result) } - override DataFlow::Node getSentItem(int i) { - result = this.getArgument(i + 1) - } + override DataFlow::Node getSentItem(int i) { result = this.getArgument(i + 1) } override EventRegistration::Range getAReceiver() { this.getEmitter() = result.getEmitter() } } @@ -223,4 +217,3 @@ private class EventEmitterTaintStep extends DataFlow::AdditionalFlowStep { succ = dispatch } } - diff --git a/javascript/ql/src/semmle/javascript/frameworks/Express.qll b/javascript/ql/src/semmle/javascript/frameworks/Express.qll index b6a972c47d7..702e1efde43 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Express.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Express.qll @@ -147,36 +147,34 @@ module Express { this.getRequestMethod() = that.getRequestMethod() } } - + /** * A call that sets up a Passport router that includes the request object. */ private class PassportRouteSetup extends HTTP::Servers::StandardRouteSetup, CallExpr { DataFlow::ModuleImportNode importNode; DataFlow::FunctionNode callback; - + // looks for this pattern: passport.use(new Strategy({passReqToCallback: true}, callback)) PassportRouteSetup() { importNode = DataFlow::moduleImport("passport") and this = importNode.getAMemberCall("use").asExpr() and - exists(DataFlow::NewNode strategy | - strategy.flowsToExpr(this.getArgument(0)) and - strategy.getNumArgument() = 2 and - // new Strategy({passReqToCallback: true}, ...) - strategy.getOptionArgument(0, "passReqToCallback").mayHaveBooleanValue(true) and - callback.flowsTo(strategy.getArgument(1)) + exists(DataFlow::NewNode strategy | + strategy.flowsToExpr(this.getArgument(0)) and + strategy.getNumArgument() = 2 and + // new Strategy({passReqToCallback: true}, ...) + strategy.getOptionArgument(0, "passReqToCallback").mayHaveBooleanValue(true) and + callback.flowsTo(strategy.getArgument(1)) ) } - + override Expr getServer() { result = importNode.asExpr() } - - override DataFlow::SourceNode getARouteHandler() { - result = callback - } + + override DataFlow::SourceNode getARouteHandler() { result = callback } } - + /** - * The callback given to passport in PassportRouteSetup. + * The callback given to passport in PassportRouteSetup. */ private class PassportRouteHandler extends RouteHandler, HTTP::Servers::StandardRouteHandler, DataFlow::ValueNode { @@ -185,7 +183,7 @@ module Express { PassportRouteHandler() { this = any(PassportRouteSetup setup).getARouteHandler() } override SimpleParameter getRouteHandlerParameter(string kind) { - kind = "request" and + kind = "request" and result = astNode.getParameter(0) } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Files.qll b/javascript/ql/src/semmle/javascript/frameworks/Files.qll index 6c8f6351f6c..161a2c13b37 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Files.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Files.qll @@ -21,7 +21,8 @@ private class WalkFileNameSource extends FileNameSource { WalkFileNameSource() { // `stats.name` in `require('walk').walk(_).on(_, (_, stats) => stats.name)` exists(DataFlow::FunctionNode callback | - callback = DataFlow::moduleMember("walk", "walk") + callback = + DataFlow::moduleMember("walk", "walk") .getACall() .getAMethodCall(EventEmitter::on()) .getCallback(1) @@ -66,7 +67,8 @@ private class GlobbyFileNameSource extends FileNameSource { this = DataFlow::moduleMember(moduleName, "sync").getACall() or // `files` in `require('globby')(_).then(files => ...)` - this = DataFlow::moduleImport(moduleName) + this = + DataFlow::moduleImport(moduleName) .getACall() .getAMethodCall("then") .getCallback(0) @@ -95,7 +97,8 @@ private class FastGlobFileNameSource extends FileNameSource { ) or // `file` in `require('fast-glob').stream(_).on(_, file => ...)` - this = DataFlow::moduleMember(moduleName, "stream") + this = + DataFlow::moduleMember(moduleName, "stream") .getACall() .getAMethodCall(EventEmitter::on()) .getCallback(1) diff --git a/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll b/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll index 45fd12054b6..5eeda3df43b 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll @@ -123,8 +123,7 @@ module HTTP { * * Gets the string `http` or `https`. */ - deprecated - string httpOrHttps() { result = "http" or result = "https" } + deprecated string httpOrHttps() { result = "http" or result = "https" } /** * An expression whose value is sent as (part of) the body of an HTTP response. diff --git a/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll b/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll index 6879d4b7699..40c52849c25 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Handlebars.qll @@ -6,7 +6,7 @@ import javascript module Handlebars { /** - * A reference to the Handlebars library. + * A reference to the Handlebars library. */ class Handlebars extends DataFlow::SourceNode { Handlebars() { diff --git a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll index ab306964ebe..15378dc507d 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Koa.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Koa.qll @@ -11,7 +11,7 @@ module Koa { */ class AppDefinition extends HTTP::Servers::StandardServerDefinition, InvokeExpr { AppDefinition() { - // `app = new Koa()` / `app = Koa()` + // `app = new Koa()` / `app = Koa()` this = DataFlow::moduleImport("koa").getAnInvocation().asExpr() } } diff --git a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll index 8fda6f8e1c1..c40e579c850 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Logging.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Logging.qll @@ -58,9 +58,11 @@ private module Console { } override DataFlow::Node getAMessageComponent() { - if name = "assert" - then result = getArgument([1 .. getNumArgument()]) - else result = getAnArgument() + ( + if name = "assert" + then result = getArgument([1 .. getNumArgument()]) + else result = getAnArgument() + ) or result = getASpreadArgument() } @@ -68,9 +70,7 @@ private module Console { /** * Gets the name of the console logging method, e.g. "log", "error", "assert", etc. */ - string getName() { - result = name - } + string getName() { result = name } } } @@ -99,7 +99,8 @@ private module Winston { */ class WinstonLoggerCall extends LoggerCall, DataFlow::MethodCallNode { WinstonLoggerCall() { - this = DataFlow::moduleMember("winston", "createLogger") + this = + DataFlow::moduleMember("winston", "createLogger") .getACall() .getAMethodCall(getAStandardLoggerMethodName()) } @@ -121,7 +122,8 @@ private module log4js { */ class Log4jsLoggerCall extends LoggerCall { Log4jsLoggerCall() { - this = DataFlow::moduleMember("log4js", "getLogger") + this = + DataFlow::moduleMember("log4js", "getLogger") .getACall() .getAMethodCall(getAStandardLoggerMethodName()) } diff --git a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll index c7af3e40b01..563ba6b5db6 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NoSQL.qll @@ -84,9 +84,7 @@ private module MongoDB { * of `mongodb.Collection`. */ private class CollectionFromType extends Collection { - CollectionFromType() { - hasUnderlyingType("mongodb", "Collection") - } + CollectionFromType() { hasUnderlyingType("mongodb", "Collection") } } /** Gets a data flow node referring to a MongoDB collection. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 0ee7c10fa7f..cf2d34844b3 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -368,10 +368,8 @@ module NodeJSLib { class Credentials extends CredentialsExpr { Credentials() { exists(string http | http = "http" or http = "https" | - this = DataFlow::moduleMember(http, "request") - .getACall() - .getOptionArgument(0, "auth") - .asExpr() + this = + DataFlow::moduleMember(http, "request").getACall().getOptionArgument(0, "auth").asExpr() ) } @@ -937,16 +935,17 @@ module NodeJSLib { * By extending `NodeJSEventEmitter' we get data-flow on the events passing through this EventEmitter. */ class CustomEventEmitter extends NodeJSEventEmitter { - EventEmitterSubClass clazz; + EventEmitterSubClass clazz; + CustomEventEmitter() { - if exists(clazz.getAClassReference().getAnInstantiation()) then - this = clazz.getAClassReference().getAnInstantiation() + if exists(clazz.getAClassReference().getAnInstantiation()) + then this = clazz.getAClassReference().getAnInstantiation() else - // In case there are no explicit instantiations of the clazz, then we still want to track data flow between `this` nodes. - // This cannot produce false flow as the `.ref()` method below is always used when creating event-registrations/event-dispatches. + // In case there are no explicit instantiations of the clazz, then we still want to track data flow between `this` nodes. + // This cannot produce false flow as the `.ref()` method below is always used when creating event-registrations/event-dispatches. this = clazz } - + override DataFlow::SourceNode ref() { result = NodeJSEventEmitter.super.ref() and not this = clazz or @@ -988,25 +987,25 @@ module NodeJSLib { /** * Gets a reference to this server. - */ + */ DataFlow::SourceNode ref() { result = ref(DataFlow::TypeTracker::end()) } } - + /** * A connection opened on a NodeJS net server. - */ + */ private class NodeJSNetServerConnection extends EventEmitter::Range { - NodeJSNetServer server; - - NodeJSNetServerConnection() { - exists(DataFlow::MethodCallNode call | - call = server.ref().getAMethodCall("on") and - call.getArgument(0).mayHaveStringValue("connection") - | - this = call.getCallback(1).getParameter(0) - ) - } - + NodeJSNetServer server; + + NodeJSNetServerConnection() { + exists(DataFlow::MethodCallNode call | + call = server.ref().getAMethodCall("on") and + call.getArgument(0).mayHaveStringValue("connection") + | + this = call.getCallback(1).getParameter(0) + ) + } + DataFlow::SourceNode ref() { result = EventEmitter::trackEventEmitter(this) } } @@ -1030,32 +1029,25 @@ module NodeJSLib { override string getSourceType() { result = "NodeJS server" } } - + /** * An instantiation of the `respjs` library, which is an EventEmitter. */ private class RespJS extends NodeJSEventEmitter { - RespJS() { - this = DataFlow::moduleImport("respjs").getAnInstantiation() - } + RespJS() { this = DataFlow::moduleImport("respjs").getAnInstantiation() } } - + /** - * A event dispatch that serializes the input data and emits the result on the "data" channel. + * A event dispatch that serializes the input data and emits the result on the "data" channel. */ - private class RespWrite extends EventDispatch::DefaultEventDispatch, - DataFlow::MethodCallNode { + private class RespWrite extends EventDispatch::DefaultEventDispatch, DataFlow::MethodCallNode { override RespJS emitter; RespWrite() { this = emitter.ref().getAMethodCall("write") } - - override string getChannel() { - result = "data" - } - override DataFlow::Node getSentItem(int i) { - i = 0 and result = this.getArgument(i) - } + override string getChannel() { result = "data" } + + override DataFlow::Node getSentItem(int i) { i = 0 and result = this.getArgument(i) } } /** diff --git a/javascript/ql/src/semmle/javascript/frameworks/React.qll b/javascript/ql/src/semmle/javascript/frameworks/React.qll index 3b593e66478..3cf93056a8c 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/React.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/React.qll @@ -278,9 +278,7 @@ class FunctionalComponent extends ReactComponent, Function { t.start() and result = DataFlow::valueNode(this) or - exists(DataFlow::TypeTracker t2 | - result = getAComponentCreatorReference(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getAComponentCreatorReference(t2).track(t2, t)) } override DataFlow::SourceNode getAComponentCreatorReference() { @@ -446,9 +444,7 @@ class ES5Component extends ReactComponent, ObjectExpr { t.start() and result = create or - exists(DataFlow::TypeTracker t2 | - result = getAComponentCreatorReference(t2).track(t2, t) - ) + exists(DataFlow::TypeTracker t2 | result = getAComponentCreatorReference(t2).track(t2, t)) } override DataFlow::SourceNode getAComponentCreatorReference() { @@ -520,7 +516,8 @@ private class FactoryDefinition extends ReactElementDefinition { * Partial invocation for calls to `React.Children.map` or a similar library function * that binds `this` of a callback. */ -private class ReactCallbackPartialInvoke extends DataFlow::PartialInvokeNode::Range, DataFlow::CallNode { +private class ReactCallbackPartialInvoke extends DataFlow::PartialInvokeNode::Range, + DataFlow::CallNode { ReactCallbackPartialInvoke() { exists(string name | // React.Children.map or React.Children.forEach diff --git a/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll b/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll index 82b716f46dd..45a58f9ec26 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SocketIO.qll @@ -24,28 +24,26 @@ module SocketIO { } /** - * A common superclass for all socket-like objects on the serverside of SocketIO. - * All of the subclasses can be used to send data to SocketIO clients (see the `SendNode` class). + * A common superclass for all socket-like objects on the serverside of SocketIO. + * All of the subclasses can be used to send data to SocketIO clients (see the `SendNode` class). */ abstract private class SocketIOObject extends DataFlow::SourceNode, EventEmitter::Range { /** * Gets a node that refers to this SocketIOObject object. */ abstract DataFlow::SourceNode ref(); - + /** Gets the namespace belonging to this object. */ abstract NamespaceObject getNamespace(); } /** A socket.io server. */ class ServerObject extends SocketIOObject { - ServerObject() { - this = newServer() - } + ServerObject() { this = newServer() } /** Gets the default namespace of this server. */ NamespaceObject getDefaultNamespace() { result = MkNamespace(this, "/") } - + /** Gets the default namespace of this server. */ override NamespaceObject getNamespace() { result = getDefaultNamespace() } @@ -331,9 +329,7 @@ module SocketIO { /** * Gets the namespace to which data is sent. */ - NamespaceObject getNamespace() { - result = emitter.getNamespace() - } + NamespaceObject getNamespace() { result = emitter.getNamespace() } /** Gets the event name associated with the data, if it can be determined. */ override string getChannel() { @@ -366,7 +362,7 @@ module SocketIO { /** * An acknowledgment callback registered when sending a message to a client. - * Responses from clients are received using this callback. + * Responses from clients are received using this callback. */ class SendCallback extends EventRegistration::Range, DataFlow::FunctionNode { SendNode send; @@ -617,7 +613,7 @@ module SocketIOClient { } } - /** + /** * An acknowledgment callback registered when sending a message to a server. * Responses from servers are received using this callback. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 7cea87bb3f3..32bfd8f70f7 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -7,7 +7,6 @@ import javascript private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode { int cmdArg; - boolean shell; SystemCommandExecutors() { diff --git a/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll b/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll index 161c738b311..83001dd2e09 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/Typeahead.qll @@ -91,7 +91,8 @@ module Typeahead { TypeaheadSuggestionFunction() { // Matches `$(...).typeahead({..}, { templates: { suggestion: } })`. - this = typeaheadCall + this = + typeaheadCall .getOptionArgument(1, "templates") .getALocalSource() .getAPropertyWrite("suggestion") diff --git a/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll b/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll index ffe05017057..65f458153f4 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/WebSocket.qll @@ -83,7 +83,9 @@ module ClientWebSocket { /** * Gets a handler, that is registered using method `methodName` and receives messages sent to `emitter`. */ - private DataFlow::FunctionNode getAMessageHandler(ClientWebSocket::ClientSocket emitter, string methodName) { + private DataFlow::FunctionNode getAMessageHandler( + ClientWebSocket::ClientSocket emitter, string methodName + ) { exists(DataFlow::CallNode call | call = emitter.getAMemberCall(methodName) and call.getArgument(0).mayHaveStringValue("message") and @@ -93,8 +95,8 @@ module ClientWebSocket { /** * A handler that receives a message using the WebSocket API. - * The WebSocket API is used both by the WebSocket library in browsers, and the same API is also implemented as part of the "ws" library. - * This class therefore models both the WebSocket library, and a subset of the "ws" library. + * The WebSocket API is used both by the WebSocket library in browsers, and the same API is also implemented as part of the "ws" library. + * This class therefore models both the WebSocket library, and a subset of the "ws" library. */ private class WebSocketReceiveNode extends ClientWebSocket::ReceiveNode { WebSocketReceiveNode() { @@ -109,18 +111,16 @@ module ClientWebSocket { } /** - * A handler that receives a message using the API from the "ws" library. - * The "ws" library additionally implements the WebSocket API, which is modeled in the `WebSocketReceiveNode` class. + * A handler that receives a message using the API from the "ws" library. + * The "ws" library additionally implements the WebSocket API, which is modeled in the `WebSocketReceiveNode` class. */ private class WSReceiveNode extends ClientWebSocket::ReceiveNode { - WSReceiveNode () { + WSReceiveNode() { emitter.isNode() and this = getAMessageHandler(emitter, EventEmitter::on()) } - override DataFlow::Node getReceivedItem(int i) { - i = 0 and result = this.getParameter(0) - } + override DataFlow::Node getReceivedItem(int i) { i = 0 and result = this.getParameter(0) } } } @@ -134,7 +134,8 @@ module ServerWebSocket { class ServerSocket extends EventEmitter::Range, DataFlow::SourceNode { ServerSocket() { exists(DataFlow::CallNode onCall | - onCall = DataFlow::moduleImport("ws") + onCall = + DataFlow::moduleImport("ws") .getAConstructorInvocation("Server") .getAMemberCall(EventEmitter::on()) and onCall.getArgument(0).mayHaveStringValue("connection") diff --git a/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll b/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll index 458f0710931..61ad6d52878 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/jQuery.qll @@ -125,7 +125,7 @@ private class JQueryDomElementDefinition extends DOM::ElementDefinition, @callex /** * An attribute defined using jQuery APIs. */ -abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition {} +abstract private class JQueryAttributeDefinition extends DOM::AttributeDefinition { } /** * An attribute definition supplied when constructing a DOM element using `$(...)`. @@ -150,9 +150,7 @@ private class JQueryAttributeDefinitionInElement extends JQueryAttributeDefiniti } /** Gets the `attr` or `prop` string. */ -private string attrOrProp() { - result = "attr" or result = "prop" -} +private string attrOrProp() { result = "attr" or result = "prop" } /** * An attribute definition using `elt.attr(name, value)` or `elt.prop(name, value)` @@ -382,9 +380,7 @@ module JQuery { } /** A source of jQuery objects from the AST-based `JQueryObject` class. */ - private DataFlow::Node legacyObjectSource() { - result = any(JQueryObjectInternal e).flow() - } + private DataFlow::Node legacyObjectSource() { result = any(JQueryObjectInternal e).flow() } /** Gets a source of jQuery objects. */ private DataFlow::SourceNode objectSource(DataFlow::TypeTracker t) { diff --git a/javascript/ql/src/semmle/javascript/linters/JSLint.qll b/javascript/ql/src/semmle/javascript/linters/JSLint.qll index 4c94db457fb..bfe8de1768d 100644 --- a/javascript/ql/src/semmle/javascript/linters/JSLint.qll +++ b/javascript/ql/src/semmle/javascript/linters/JSLint.qll @@ -23,9 +23,8 @@ abstract class JSLintDirective extends SlashStarComment { * and with end-of-line characters replaced by spaces. */ string getContent() { - result = getText() - .regexpReplaceAll("[\\n\\r\\u2028\\u2029]", " ") - .regexpCapture("\\s*\\w+ (.*)", 1) + result = + getText().regexpReplaceAll("[\\n\\r\\u2028\\u2029]", " ").regexpCapture("\\s*\\w+ (.*)", 1) } /** diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll index 240e8e43309..615f8ea2ead 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLogging.qll @@ -37,7 +37,7 @@ module CleartextLogging { override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { succ.(DataFlow::PropRead).getBase() = pred } - + override predicate isAdditionalTaintStep(DataFlow::Node src, DataFlow::Node trg) { // A taint propagating data flow edge through objects: a tainted write taints the entire object. exists(DataFlow::PropWrite write | diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll index 3c7a8d818a7..3ceadf90e5f 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/CleartextLoggingCustomizations.qll @@ -15,7 +15,7 @@ module CleartextLogging { abstract class Source extends DataFlow::Node { /** Gets a string that describes the type of this data flow source. */ abstract string describe(); - + abstract DataFlow::FlowLabel getLabel(); } @@ -23,16 +23,14 @@ module CleartextLogging { * A data flow sink for clear-text logging of sensitive information. */ abstract class Sink extends DataFlow::Node { - DataFlow::FlowLabel getLabel() { - result.isDataOrTaint() - } + DataFlow::FlowLabel getLabel() { result.isDataOrTaint() } } /** * A barrier for clear-text logging of sensitive information. */ abstract class Barrier extends DataFlow::Node { } - + /** * A call to `.replace()` that seems to mask sensitive information. */ @@ -43,8 +41,7 @@ module CleartextLogging { reg = this.getArgument(0).getALocalSource().asExpr() and reg.isGlobal() and any(RegExpDot term).getLiteral() = reg - ) - and + ) and exists(this.getArgument(1).getStringValue()) } } @@ -129,10 +126,8 @@ module CleartextLogging { } override string describe() { result = "an access to " + name } - - override DataFlow::FlowLabel getLabel() { - result.isData() - } + + override DataFlow::FlowLabel getLabel() { result.isData() } } /** An access to a variable or property that might contain a password. */ @@ -157,10 +152,8 @@ module CleartextLogging { } override string describe() { result = "an access to " + name } - - override DataFlow::FlowLabel getLabel() { - result.isData() - } + + override DataFlow::FlowLabel getLabel() { result.isData() } } /** A call that might return a password. */ @@ -173,33 +166,27 @@ module CleartextLogging { } override string describe() { result = "a call to " + name } - - override DataFlow::FlowLabel getLabel() { - result.isData() - } + + override DataFlow::FlowLabel getLabel() { result.isData() } } /** An access to the sensitive object `process.env`. */ class ProcessEnvSource extends Source { - ProcessEnvSource() { - this = NodeJSLib::process().getAPropertyRead("env") - } + ProcessEnvSource() { this = NodeJSLib::process().getAPropertyRead("env") } override string describe() { result = "process environment" } - + override DataFlow::FlowLabel getLabel() { - result.isData() or + result.isData() or result instanceof PartiallySensitiveMap } } - + /** * A flow label describing a map that might contain sensitive information in some properties. - * Property reads on such maps where the property name is fixed is unlikely to leak sensitive information. + * Property reads on such maps where the property name is fixed is unlikely to leak sensitive information. */ class PartiallySensitiveMap extends DataFlow::FlowLabel { - PartiallySensitiveMap() { - this = "PartiallySensitiveMap" - } + PartiallySensitiveMap() { this = "PartiallySensitiveMap" } } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll b/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll index c2c1425ab82..ff40919e25e 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/CommandInjection.qll @@ -35,5 +35,4 @@ module CommandInjection { override predicate isSanitizer(DataFlow::Node node) { node instanceof Sanitizer } } - } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll index 5b639abfa11..d02e85609aa 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DOM.qll @@ -85,8 +85,13 @@ class DomMethodCallExpr extends MethodCallExpr { name = "setAttributeNS" and argPos = 2 ) and // restrict to potentially dangerous attributes - exists(string attr | - attr = "action" or attr = "formaction" or attr = "href" or attr = "src" or attr = "xlink:href" | + exists(string attr | + attr = "action" or + attr = "formaction" or + attr = "href" or + attr = "src" or + attr = "xlink:href" + | getArgument(argPos - 1).getStringValue().toLowerCase() = attr ) ) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll index b74977c8cb5..34c8eeeac50 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/DomBasedXssCustomizations.qll @@ -1,9 +1,8 @@ /** * Provides default sources for reasoning about DOM-based - * cross-site scripting vulnerabilities. + * cross-site scripting vulnerabilities. */ - import javascript module DomBasedXss { diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll index c64ecfd361d..a23e9fecf4c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/ExceptionXss.qll @@ -37,7 +37,8 @@ module ExceptionXss { * Holds if `node` is unlikely to cause an exception containing sensitive information to be thrown. */ private predicate isUnlikelyToThrowSensitiveInformation(DataFlow::Node node) { - node = any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName()) + node = + any(DataFlow::CallNode call | call.getCalleeName() = getAnUnlikelyToThrowMethodName()) .getAnArgument() or node = DataFlow::globalVarRef("console").getAMemberCall(_).getAnArgument() diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll index e0c8e0a05d9..3873f90c810 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandArgument.qll @@ -34,7 +34,7 @@ private DataFlow::Node commandArgument(SystemCommandExecution sys, DataFlow::Typ } /** - * Gets a data-flow node whose value ends up being interpreted as the command argument in `sys`. + * Gets a data-flow node whose value ends up being interpreted as the command argument in `sys`. */ private DataFlow::Node commandArgument(SystemCommandExecution sys) { result = commandArgument(sys, DataFlow::TypeBackTracker::end()) @@ -48,9 +48,7 @@ private DataFlow::SourceNode argumentList(SystemCommandExecution sys, DataFlow:: t.start() and result = sys.getArgumentList().getALocalSource() or - exists(DataFlow::TypeBackTracker t2, DataFlow::SourceNode pred | - pred = argumentList(sys, t2) - | + exists(DataFlow::TypeBackTracker t2, DataFlow::SourceNode pred | pred = argumentList(sys, t2) | result = pred.backtrack(t2, t) or t = t2.continue() and @@ -59,7 +57,7 @@ private DataFlow::SourceNode argumentList(SystemCommandExecution sys, DataFlow:: } /** - * Gets a data-flow node whose value ends up being interpreted as the argument list in `sys`. + * Gets a data-flow node whose value ends up being interpreted as the argument list in `sys`. */ private DataFlow::SourceNode argumentList(SystemCommandExecution sys) { result = argumentList(sys, DataFlow::TypeBackTracker::end()) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll index df7376188a5..dba4fb62763 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/IndirectCommandInjectionCustomizations.qll @@ -45,11 +45,14 @@ module IndirectCommandInjection { class ParsedCommandLineArgumentsAsSource extends Source { ParsedCommandLineArgumentsAsSource() { // `require('get-them-args')(...)` => `{ unknown: [], a: ... b: ... }` - this = DataFlow::moduleImport("get-them-args").getACall() or + this = DataFlow::moduleImport("get-them-args").getACall() + or // `require('minimist')(...)` => `{ _: [], a: ... b: ... }` - this = DataFlow::moduleImport("minimist").getACall() or + this = DataFlow::moduleImport("minimist").getACall() + or // `require('yargs').argv` => `{ _: [], a: ... b: ... }` - this = DataFlow::moduleMember("yargs", "argv") or + this = DataFlow::moduleMember("yargs", "argv") + or // `require('optimist').argv` => `{ _: [], a: ... b: ... }` this = DataFlow::moduleMember("optimist", "argv") } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll index 6bf1ef5634a..8e3857d049b 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/LoopBoundInjectionCustomizations.qll @@ -33,9 +33,7 @@ module LoopBoundInjection { */ class ArrayIterationLoop extends Stmt { LocalVariable indexVariable; - LoopStmt loop; - DataFlow::PropRead lengthRead; ArrayIterationLoop() { @@ -203,9 +201,7 @@ module LoopBoundInjection { ) or // Similar to the loop sink - the existence of an early-exit usually means that no DoS can happen. - exists(ThrowStmt throw | - throw.getTarget() = func.asExpr() - ) + exists(ThrowStmt throw | throw.getTarget() = func.asExpr()) or // A crash happens looking up the index variable. hasCrashingArrayAccess(func.getParameter(1).getParameter().getVariable()) @@ -269,7 +265,6 @@ module LoopBoundInjection { class LengthCheckSanitizerGuard extends TaintTracking::LabeledSanitizerGuardNode, DataFlow::ValueNode { override RelationalComparison astNode; - DataFlow::PropRead propRead; LengthCheckSanitizerGuard() { diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll index 0a62b8cda3f..82db9e0fad3 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/MissingRateLimiting.qll @@ -150,9 +150,8 @@ class RouteHandlerLimitedByExpressLimiter extends RateLimitedRouteHandlerExpr { RouteHandlerLimitedByExpressLimiter() { exists(DataFlow::ModuleImportNode expressLimiter | expressLimiter.getPath() = "express-limiter" and - expressLimiter.getACall().getArgument(0).getALocalSource().asExpr() = this - .getSetup() - .getRouter() + expressLimiter.getACall().getArgument(0).getALocalSource().asExpr() = + this.getSetup().getRouter() ) } } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index a3060a74eb8..d1be274314c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -106,12 +106,10 @@ module TaintedPath { } /** - * A flow label representing an array of path elements that may include "..". - */ + * A flow label representing an array of path elements that may include "..". + */ class SplitPath extends DataFlow::FlowLabel { - SplitPath() { - this = "splitPath" - } + SplitPath() { this = "splitPath" } } } @@ -395,7 +393,7 @@ module TaintedPath { e = relativeCall.getArgument(1).asExpr() and outcome = startsWith.getPolarity().booleanNot() } } - + /** * A guard node for a variable in a negative condition, such as `x` in `if(!x)`. */ diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll index b08cbb2f5f8..e9981d97db0 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/UnsafeJQueryPlugin.qll @@ -34,7 +34,6 @@ module UnsafeJQueryPlugin { override predicate isSanitizerEdge(DataFlow::Node pred, DataFlow::Node succ) { // prefixing prevents forced html/css confusion: - // prefixing through concatenation: StringConcatenation::taintStep(pred, succ, _, any(int i | i >= 1)) or diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index d9567862fc6..fb0f94e84da 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -243,10 +243,8 @@ module DomBasedXss { VHtmlSourceWrite() { exists(Vue::Instance instance, string expr | - attr.getAttr().getRoot() = instance - .getTemplateElement() - .(Vue::Template::HtmlElement) - .getElement() and + attr.getAttr().getRoot() = + instance.getTemplateElement().(Vue::Template::HtmlElement).getElement() and expr = attr.getAttr().getValue() and // only support for simple identifier expressions expr.regexpMatch("(?i)[a-z0-9_]+") and diff --git a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll index 2ae0fcc749d..68c85931aa3 100644 --- a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll +++ b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoS.qll @@ -6,6 +6,7 @@ * `PolynomialReDoS::Configuration` is needed, otherwise * `PolynomialReDoSCustomizations` should be imported instead. */ + import javascript module PolynomialReDoS { diff --git a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll index d4b8a5a8317..5507f9e6981 100644 --- a/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/performance/PolynomialReDoSCustomizations.qll @@ -63,7 +63,6 @@ module PolynomialReDoS { override RegExpTerm getRegExp() { result = term } } - /** * An operation that limits the length of a string, seen as a sanitizer. */ diff --git a/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll b/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll index d1bdd0bc92c..55015ff17ef 100644 --- a/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll +++ b/javascript/ql/src/semmle/javascript/security/performance/SuperlinearBackTracking.qll @@ -128,8 +128,9 @@ class PolynomialBackTrackingTerm extends InfiniteRepetitionQuantifier { pred = getAMatchPredecessor(this.getPredecessor()) and compatible(pred.getAChild(), this.getAChild()) | - reason = "it can start matching anywhere after the start of the preceeding '" + - pred.toString() + "'" + reason = + "it can start matching anywhere after the start of the preceeding '" + pred.toString() + + "'" ) ) and not this.getParent*() instanceof RegExpSubPattern // too many corner cases diff --git a/javascript/ql/test/library-tests/Classes/ClassFlow.qll b/javascript/ql/test/library-tests/Classes/ClassFlow.qll index 6a6d44b667f..dec2475702c 100644 --- a/javascript/ql/test/library-tests/Classes/ClassFlow.qll +++ b/javascript/ql/test/library-tests/Classes/ClassFlow.qll @@ -14,4 +14,4 @@ class Configuration extends DataFlow::Configuration { query predicate dataflow(DataFlow::Node pred, DataFlow::Node succ) { any(Configuration c).hasFlow(pred, succ) -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/Classes/PrivateField.qll b/javascript/ql/test/library-tests/Classes/PrivateField.qll index a5b4a5ef04a..d4f5d3d4678 100644 --- a/javascript/ql/test/library-tests/Classes/PrivateField.qll +++ b/javascript/ql/test/library-tests/Classes/PrivateField.qll @@ -2,8 +2,5 @@ import javascript query string getAccessModifier(DataFlow::PropRef ref, Expr prop) { prop = ref.getPropertyNameExpr() and - if ref.isPrivateField() then - result = "Private" - else - result = "Public" -} \ No newline at end of file + if ref.isPrivateField() then result = "Private" else result = "Public" +} diff --git a/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll b/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll index 5efd8896141..9a1debdc536 100644 --- a/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll +++ b/javascript/ql/test/library-tests/Promises/ResolvedPromiseDefinition.qll @@ -1,7 +1,5 @@ import javascript -query predicate test_ResolvedPromiseDefinition( - PromiseCreationCall resolved, DataFlow::Node res -) { +query predicate test_ResolvedPromiseDefinition(PromiseCreationCall resolved, DataFlow::Node res) { res = resolved.getValue() } diff --git a/javascript/ql/test/library-tests/Promises/flow.qll b/javascript/ql/test/library-tests/Promises/flow.qll index 395d5cb88a8..0b99ee9bcbd 100644 --- a/javascript/ql/test/library-tests/Promises/flow.qll +++ b/javascript/ql/test/library-tests/Promises/flow.qll @@ -31,4 +31,4 @@ query predicate flow(DataFlow::Node source, DataFlow::Node sink) { query predicate exclusiveTaintFlow(DataFlow::Node source, DataFlow::Node sink) { not any(Configuration c).hasFlow(source, sink) and any(TaintConfig c).hasFlow(source, sink) -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll index c38bf54ad1f..a3a11cb89b6 100644 --- a/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll +++ b/javascript/ql/test/library-tests/TypeScript/TypeAnnotations/PredicateTypeExpr.qll @@ -8,6 +8,4 @@ query predicate test_PredicateTypeExpr(PredicateTypeExpr type, VarTypeAccess res res0 = type.getParameterName() } -query predicate test_hasAssertsKeyword(PredicateTypeExpr type) { - type.hasAssertsKeyword() -} +query predicate test_hasAssertsKeyword(PredicateTypeExpr type) { type.hasAssertsKeyword() } diff --git a/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll b/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll index 5c2d8d9c22f..46b2ee0436d 100644 --- a/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll +++ b/javascript/ql/test/library-tests/frameworks/SocketIO/ClientSendNode_getAck.qll @@ -1,5 +1,7 @@ import javascript -query predicate test_ClientSendNode_getAck(SocketIOClient::SendNode sn, SocketIOClient::SendCallback res) { +query predicate test_ClientSendNode_getAck( + SocketIOClient::SendNode sn, SocketIOClient::SendCallback res +) { res.getSendNode() = sn } From 33f6392be5c73f91d0e480fbedab1ffda44cd833 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Thu, 27 Feb 2020 10:47:25 +0100 Subject: [PATCH 115/236] Java: Add some more taint-getter-setter tests. --- .../dataflow/taintgettersetter/A.java | 34 +++++++++++++++++++ .../taintgettersetter.expected | 4 +++ 2 files changed, 38 insertions(+) diff --git a/java/ql/test/library-tests/dataflow/taintgettersetter/A.java b/java/ql/test/library-tests/dataflow/taintgettersetter/A.java index 7128072977a..b92128981c8 100644 --- a/java/ql/test/library-tests/dataflow/taintgettersetter/A.java +++ b/java/ql/test/library-tests/dataflow/taintgettersetter/A.java @@ -34,4 +34,38 @@ public class A { Box b4 = Box.mk(taint()); sink(b4.getS1()); } + + static class Box2 { + String s; + String getS() { return s; } + void setS(String s) { this.s = s; } + + Box2(String s) { + setS(s + "1"); + } + String getS1() { return getS() + "2"; } + String getS2() { return step(getS() + "_") + "2"; } + void setS1(String s) { setS("3" + s); } + void setS2(String s) { setS("3" + step("_" + s)); } + static Box2 mk(String s) { + Box2 b = new Box2(""); + b.setS(step(s)); + return b; + } + } + + void foo2(Box2 b1, Box2 b2) { + b1.setS1(taint()); + sink(b1.getS1()); + + b2.setS2(taint()); + sink(b2.getS2()); + + String t3 = taint(); + Box2 b3 = new Box2(step(t3)); + sink(b3.s); + + Box2 b4 = Box2.mk(taint()); + sink(b4.getS1()); + } } diff --git a/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected b/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected index 6eb9d3aa505..0617ef3ede7 100644 --- a/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected +++ b/java/ql/test/library-tests/dataflow/taintgettersetter/taintgettersetter.expected @@ -2,3 +2,7 @@ | A.java:27:14:27:20 | taint(...) | A.java:28:10:28:19 | getS2(...) | | A.java:30:17:30:23 | taint(...) | A.java:32:10:32:13 | b3.s | | A.java:34:21:34:27 | taint(...) | A.java:35:10:35:19 | getS1(...) | +| A.java:58:14:58:20 | taint(...) | A.java:59:10:59:19 | getS1(...) | +| A.java:61:14:61:20 | taint(...) | A.java:62:10:62:19 | getS2(...) | +| A.java:64:17:64:23 | taint(...) | A.java:66:10:66:13 | b3.s | +| A.java:68:23:68:29 | taint(...) | A.java:69:10:69:19 | getS1(...) | From bb911bbbf132e3c58f46d70c1ee34eedf6d6ee23 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 27 Feb 2020 12:38:06 +0100 Subject: [PATCH 116/236] Apply suggestions from code review Co-Authored-By: Esben Sparre Andreasen --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index a0dfaa26fb6..c154885925f 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -78,8 +78,8 @@ private class CommandCall extends DataFlow::InvokeNode { * Holds if the input `str` contains some character that might be interpreted in a non-trivial way by a shell. */ bindingset[str] -predicate containsNonTrivialBashChar(string str) { - exists(str.regexpFind("\\*|\\||>|<| |\\$|&|,|\\`| ", _, _)) +private predicate containsNonTrivialShellChar(string str) { + exists(str.regexpFind("\\*|\\||>|<| |\\$|&|,|\\`| |;", _, _)) } /** @@ -142,7 +142,7 @@ class UselessCat extends CommandCall { /** * Gets a string used to call `cat`. */ -string getACatExecuteable() { +private string getACatExecuteable() { result = "cat" or result = "/bin/cat" } @@ -173,6 +173,7 @@ module PrettyPrintCatCall { callback = "" and not exists(cat.getCallback()) ) and fileArg = createFileArgument(cat).trim() and + // sanity check in case of surprising `toString` results, other uses of `containsNonTrivialBashChar` should ensure that this conjunct will hold most of the time not(containsNonTrivialBashChar(fileArg.regexpReplaceAll("\\$|\\`| ", ""))) // string concat might contain " ", template strings might contain "$" or `, and that is OK. | result = From a872d7c5c52ef4b5d2d1cc1e23bc046f1feb7b65 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Thu, 27 Feb 2020 12:42:22 +0100 Subject: [PATCH 117/236] add comment about negative optionsArg --- .../src/semmle/javascript/frameworks/SystemCommandExecutors.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll index 89a0929a182..dbb398be953 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/SystemCommandExecutors.qll @@ -7,7 +7,7 @@ import javascript private class SystemCommandExecutors extends SystemCommandExecution, DataFlow::InvokeNode { int cmdArg; - int optionsArg; + int optionsArg; // either a positive number representing the n'th argument, or a negative number representing the n'th last argument (e.g. -2 is the second last argument). boolean shell; boolean sync; From 52ebe49a0bd775508bfb7334d575cb96d4166c57 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Fri, 21 Feb 2020 17:04:44 +0000 Subject: [PATCH 118/236] JS: Flag deep assignments in prototype pollution query --- change-notes/1.24/analysis-javascript.md | 2 +- .../CWE-400/PrototypePollutionUtility.qhelp | 9 +- .../CWE-400/PrototypePollutionUtility.ql | 157 +++++++++++++--- .../javascript/DynamicPropertyAccess.qll | 2 +- .../PrototypePollutionUtility.expected | 174 +++++++++++++++++- .../path-assignment.js | 62 +++++++ 6 files changed, 375 insertions(+), 31 deletions(-) create mode 100644 javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 1cfc11deb48..1b25249ae18 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -44,7 +44,7 @@ | Regular expression always matches (`js/regex/always-matches`) | correctness, regular-expressions | Highlights regular expression checks that trivially succeed by matching an empty substring. Results are shown on LGTM by default. | | Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. | | Polynomial regular expression used on uncontrolled data (`js/polynomial-redos`) | security, external/cwe/cwe-730, external/cwe/cwe-400 | Highlights expensive regular expressions that may be used on malicious input. Results are shown on LGTM by default. | -| Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | +| Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive assignment operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | | Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp index 49a77a7445d..a6a86043db5 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.qhelp @@ -13,7 +13,8 @@

    One way to cause prototype pollution is through use of an unsafe merge or extend function - to recursively copy properties from one object to another. + to recursively copy properties from one object to another, or through the use of a deep assignment + function to assign to an unverified chain of property names. Such a function has the potential to modify any object reachable from the destination object, and the built-in Object.prototype is usually reachable through the special properties __proto__ and constructor.prototype. @@ -23,13 +24,13 @@

    The most effective place to guard against this is in the function that performs - the recursive copy. + the recursive copy or deep assignment.

    - Only merge a property recursively when it is an own property of the destination object. + Only merge or assign a property recursively when it is an own property of the destination object. Alternatively, blacklist the property names __proto__ and constructor - from being merged. + from being merged or assigned to.

    diff --git a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql index 04f35ff6ad8..4198b0f4686 100644 --- a/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql +++ b/javascript/ql/src/Security/CWE-400/PrototypePollutionUtility.ql @@ -1,7 +1,7 @@ /** * @name Prototype pollution in utility function - * @description Recursively copying properties between objects may cause - * accidental modification of a built-in prototype object. + * @description Recursively assigning properties on objects may cause + * accidental modification of a built-in prototype object. * @kind path-problem * @problem.severity warning * @precision high @@ -16,6 +16,81 @@ import DataFlow import PathGraph import semmle.javascript.DynamicPropertyAccess +/** + * A call of form `x.split(".")` where `x` is a parameter. + * + * We restrict this to parameter nodes to focus on "deep assignment" functions. + */ +class SplitCall extends MethodCallNode { + SplitCall() { + getMethodName() = "split" and + getArgument(0).mayHaveStringValue(".") and + getReceiver().getALocalSource() instanceof ParameterNode + } +} + +/** + * Holds if `pred -> succ` should preserve polluted property names. + */ +predicate copyArrayStep(SourceNode pred, SourceNode succ) { + // x -> [...x] + exists(SpreadElement spread | + pred.flowsTo(spread.getOperand().flow()) and + succ.asExpr().(ArrayExpr).getAnElement() = spread + ) + or + // `x -> y` in `y.push( x[i] )` + exists(MethodCallNode push | + push = succ.getAMethodCall("push") and + ( + getAnEnumeratedArrayElement(pred).flowsTo(push.getAnArgument()) + or + pred.flowsTo(push.getASpreadArgument()) + ) + ) + or + // x -> x.concat(...) + exists(MethodCallNode concat_ | + concat_.getMethodName() = "concat" and + (pred = concat_.getReceiver() or pred = concat_.getAnArgument()) and + succ = concat_ + ) +} + +/** + * Holds if `node` may refer to a `SplitCall` or a copy thereof, possibly + * returned through a function call. + */ +predicate isSplitArray(SourceNode node) { + node instanceof SplitCall + or + exists(SourceNode pred | isSplitArray(pred) | + copyArrayStep(pred, node) + or + pred.flowsToExpr(node.(CallNode).getACallee().getAReturnedExpr()) + ) +} + +/** + * A property name originating from a `x.split(".")` call. + */ +class SplitPropName extends SourceNode { + SourceNode array; + + SplitPropName() { + isSplitArray(array) and + this = getAnEnumeratedArrayElement(array) + } + + /** + * Gets the array from which this property name was obtained (the result from `split`). + */ + SourceNode getArray() { result = array } + + /** Gets an element accessed on the same underlying array. */ + SplitPropName getAnAlias() { result.getArray() = getArray() } +} + /** * Holds if the properties of `node` are enumerated locally. */ @@ -24,13 +99,23 @@ predicate arePropertiesEnumerated(DataFlow::SourceNode node) { } /** - * Holds if `node` may flow from an enumerated prop name, possibly - * into function calls (but not returns). + * Holds if `node` is a source of property names that we consider possible + * prototype pollution payloads. */ -predicate isEnumeratedPropName(Node node) { +predicate isPollutedPropNameSource(DataFlow::Node node) { node instanceof EnumeratedPropName or - exists(Node pred | isEnumeratedPropName(pred) | + node instanceof SplitPropName +} + +/** + * Holds if `node` may flow from a source of polluted propery names, possibly + * into function calls (but not returns). + */ +predicate isPollutedPropName(Node node) { + isPollutedPropNameSource(node) + or + exists(Node pred | isPollutedPropName(pred) | node = pred.getASuccessor() or argumentPassingStep(_, pred, _, node) @@ -51,7 +136,7 @@ predicate isEnumeratedPropName(Node node) { predicate isPotentiallyObjectPrototype(SourceNode node) { exists(Node base, Node key | dynamicPropReadStep(base, key, node) and - isEnumeratedPropName(key) and + isPollutedPropName(key) and // Ignore cases where the properties of `base` are enumerated, to avoid FPs // where the key came from that enumeration (and thus will not return Object.prototype). // For example, `src[key]` in `for (let key in src) { ... src[key] ... }` will generally @@ -85,7 +170,13 @@ predicate dynamicPropWrite(DataFlow::Node base, DataFlow::Node prop, DataFlow::N // Prune writes that are unlikely to modify Object.prototype. // This is mainly for performance, but may block certain results due to // not tracking out of function returns and into callbacks. - isPotentiallyObjectPrototype(base.getALocalSource()) + isPotentiallyObjectPrototype(base.getALocalSource()) and + // Ignore writes with an obviously safe RHS. + not exists(Expr e | e = rhs.asExpr() | + e instanceof Literal or + e instanceof ObjectExpr or + e instanceof ArrayExpr + ) ) } @@ -141,10 +232,10 @@ class PropNameTracking extends DataFlow::Configuration { override predicate isSource(DataFlow::Node node, FlowLabel label) { label instanceof UnsafePropLabel and - exists(EnumeratedPropName prop | - node = prop + ( + isPollutedPropNameSource(node) or - node = prop.getASourceProp() + node = any(EnumeratedPropName prop).getASourceProp() ) } @@ -404,22 +495,29 @@ string deriveExprName(DataFlow::Node node) { result = getExprName(node) or not exists(getExprName(node)) and - result = "this object" + result = "here" } /** * Holds if the dynamic property write `base[prop] = rhs` can pollute the prototype - * of `base` due to flow from `enum`. + * of `base` due to flow from `propNameSource`. * * In most cases this will result in an alert, the exception being the case where * `base` does not have a prototype at all. */ -predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, EnumeratedPropName enum) { +predicate isPrototypePollutingAssignment(Node base, Node prop, Node rhs, Node propNameSource) { dynamicPropWrite(base, prop, rhs) and + isPollutedPropNameSource(propNameSource) and exists(PropNameTracking cfg | - cfg.hasFlow(enum, base) and - cfg.hasFlow(enum, prop) and - cfg.hasFlow(enum.getASourceProp(), rhs) + cfg.hasFlow(propNameSource, base) and + if propNameSource instanceof EnumeratedPropName + then + cfg.hasFlow(propNameSource, prop) and + cfg.hasFlow(propNameSource.(EnumeratedPropName).getASourceProp(), rhs) + else ( + cfg.hasFlow(propNameSource.(SplitPropName).getAnAlias(), prop) and + rhs.getALocalSource() instanceof ParameterNode + ) ) } @@ -464,18 +562,29 @@ class ObjectCreateNullCall extends CallNode { } from - PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, EnumeratedPropName enum, - Node base + PropNameTracking cfg, DataFlow::PathNode source, DataFlow::PathNode sink, Node prop, Node base, + string msg, Node col1, Node col2 where + isPollutedPropName(prop) and cfg.hasFlowPath(source, sink) and - isPrototypePollutingAssignment(base, _, _, enum) and + isPrototypePollutingAssignment(base, _, _, prop) and sink.getNode() = base and - source.getNode() = enum and + source.getNode() = prop and ( getANodeLeadingToBaseBase(base) instanceof ObjectLiteralNode or not getANodeLeadingToBaseBase(base) instanceof ObjectCreateNullCall + ) and + // Generate different messages for deep merge and deep assign cases. + if prop instanceof EnumeratedPropName + then ( + col1 = prop.(EnumeratedPropName).getSourceObject() and + col2 = base and + msg = "Properties are copied from $@ to $@ without guarding against prototype pollution." + ) else ( + col1 = prop and + col2 = base and + msg = + "The property chain $@ is recursively assigned to $@ without guarding against prototype pollution." ) -select base, source, sink, - "Properties are copied from $@ to $@ without guarding against prototype pollution.", - enum.getSourceObject(), deriveExprName(enum.getSourceObject()), base, deriveExprName(base) +select base, source, sink, msg, col1, deriveExprName(col1), col2, deriveExprName(col2) diff --git a/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll b/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll index 807e4265b26..456fd773f64 100644 --- a/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll +++ b/javascript/ql/src/semmle/javascript/DynamicPropertyAccess.qll @@ -11,7 +11,7 @@ private import semmle.javascript.dataflow.internal.FlowSteps * Gets a node that refers to an element of `array`, likely obtained * as a result of enumerating the elements of the array. */ -private SourceNode getAnEnumeratedArrayElement(SourceNode array) { +SourceNode getAnEnumeratedArrayElement(SourceNode array) { exists(MethodCallNode call, string name | call = array.getAMethodCall(name) and (name = "forEach" or name = "map") and diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected index 2a40a00d1ef..68da6ad6cae 100644 --- a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility.expected @@ -1,4 +1,85 @@ nodes +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | | PrototypePollutionUtility/tests.js:3:30:3:32 | src | @@ -1173,6 +1254,94 @@ nodes | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | edges +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | PrototypePollutionUtility/path-assignment.js:15:20:15:22 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:8:13:8:25 | key | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:22:13:27 | target | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | PrototypePollutionUtility/path-assignment.js:13:13:13:32 | target | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:13:29:13:31 | key | PrototypePollutionUtility/path-assignment.js:13:22:13:32 | target[key] | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:25:42:27 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:41:13:41:25 | key | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:18:42:23 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:9:42:48 | target | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:37 | target | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:32:42:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:42:18:42:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:42:39:42:41 | key | PrototypePollutionUtility/path-assignment.js:42:32:42:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | PrototypePollutionUtility/path-assignment.js:44:12:44:18 | keys[i] | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:25:59:27 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:58:13:58:25 | key | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:18:59:23 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:9:59:48 | target | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:37 | target | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:32:59:48 | target[key] \|\| {} | PrototypePollutionUtility/path-assignment.js:59:18:59:48 | target[ ... ] \|\| {} | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:59:39:59:41 | key | PrototypePollutionUtility/path-assignment.js:59:32:59:42 | target[key] | +| PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | PrototypePollutionUtility/path-assignment.js:61:12:61:18 | keys[i] | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | PrototypePollutionUtility/tests.js:6:28:6:30 | dst | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | PrototypePollutionUtility/tests.js:6:28:6:30 | dst | | PrototypePollutionUtility/tests.js:3:25:3:27 | dst | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | @@ -2669,6 +2838,9 @@ edges | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | examples/PrototypePollutionUtility_fixed.js:7:24:7:31 | src[key] | | examples/PrototypePollutionUtility_fixed.js:7:28:7:30 | key | examples/PrototypePollutionUtility_fixed.js:7:24:7:31 | src[key] | #select +| PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | PrototypePollutionUtility/path-assignment.js:8:19:8:25 | keys[i] | here | PrototypePollutionUtility/path-assignment.js:15:13:15:18 | target | target | +| PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | PrototypePollutionUtility/path-assignment.js:41:19:41:25 | keys[i] | here | PrototypePollutionUtility/path-assignment.js:44:5:44:10 | target | target | +| PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | The property chain $@ is recursively assigned to $@ without guarding against prototype pollution. | PrototypePollutionUtility/path-assignment.js:58:19:58:25 | keys[i] | here | PrototypePollutionUtility/path-assignment.js:61:5:61:10 | target | target | | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | PrototypePollutionUtility/tests.js:4:14:4:16 | key | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:4:21:4:23 | src | src | PrototypePollutionUtility/tests.js:8:13:8:15 | dst | dst | | PrototypePollutionUtility/tests.js:18:13:18:15 | dst | PrototypePollutionUtility/tests.js:14:30:14:32 | key | PrototypePollutionUtility/tests.js:18:13:18:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:14:17:14:19 | src | src | PrototypePollutionUtility/tests.js:18:13:18:15 | dst | dst | | PrototypePollutionUtility/tests.js:36:9:36:11 | dst | PrototypePollutionUtility/tests.js:25:18:25:20 | key | PrototypePollutionUtility/tests.js:36:9:36:11 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:25:25:25:30 | source | source | PrototypePollutionUtility/tests.js:36:9:36:11 | dst | dst | @@ -2677,7 +2849,7 @@ edges | PrototypePollutionUtility/tests.js:109:13:109:15 | dst | PrototypePollutionUtility/tests.js:102:14:102:16 | key | PrototypePollutionUtility/tests.js:109:13:109:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:102:21:102:23 | src | src | PrototypePollutionUtility/tests.js:109:13:109:15 | dst | dst | | PrototypePollutionUtility/tests.js:154:13:154:15 | dst | PrototypePollutionUtility/tests.js:150:14:150:16 | key | PrototypePollutionUtility/tests.js:154:13:154:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:150:21:150:23 | src | src | PrototypePollutionUtility/tests.js:154:13:154:15 | dst | dst | | PrototypePollutionUtility/tests.js:196:13:196:15 | dst | PrototypePollutionUtility/tests.js:192:19:192:25 | keys[i] | PrototypePollutionUtility/tests.js:196:13:196:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:190:28:190:30 | src | src | PrototypePollutionUtility/tests.js:196:13:196:15 | dst | dst | -| PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | PrototypePollutionUtility/tests.js:238:14:238:16 | key | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:238:21:238:24 | data | data | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | this object | +| PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | PrototypePollutionUtility/tests.js:238:14:238:16 | key | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:238:21:238:24 | data | data | PrototypePollutionUtility/tests.js:233:5:233:13 | map[key1] | here | | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | PrototypePollutionUtility/tests.js:265:19:265:26 | entry[0] | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:264:20:264:22 | src | src | PrototypePollutionUtility/tests.js:270:13:270:15 | dst | dst | | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | PrototypePollutionUtility/tests.js:276:34:276:36 | key | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:276:21:276:23 | src | src | PrototypePollutionUtility/tests.js:280:13:280:15 | dst | dst | | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | PrototypePollutionUtility/tests.js:302:14:302:16 | key | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | Properties are copied from $@ to $@ without guarding against prototype pollution. | PrototypePollutionUtility/tests.js:302:21:302:23 | src | src | PrototypePollutionUtility/tests.js:308:17:308:19 | dst | dst | diff --git a/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js new file mode 100644 index 00000000000..0cc279f0ed5 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-400/PrototypePollutionUtility/path-assignment.js @@ -0,0 +1,62 @@ +function isSafe(key) { + return key !== "__proto__" && key !== "constructor" && key !== "prototype"; +} + +function assignToPath(target, path, value) { + let keys = path.split('.'); + for (let i = 0; i < keys.length; ++i) { + let key = keys[i]; + if (i < keys.length - 1) { + if (!target[key]) { + target[key] = {}; + } + target = target[key]; + } else { + target[key] = value; // NOT OK + } + } +} + +function assignToPathSafe(target, path, value) { + let keys = path.split('.'); + for (let i = 0; i < keys.length; ++i) { + let key = keys[i]; + if (!isSafe(key)) return; + if (i < keys.length - 1) { + if (!target[key]) { + target[key] = {}; + } + target = target[key]; + } else { + target[key] = value; // OK + } + } +} + + +function assignToPathAfterLoop(target, path, value) { + let keys = path.split('.'); + let i; + for (i = 0; i < keys.length - 1; ++i) { + let key = keys[i]; + target = target[key] = target[key] || {}; + } + target[keys[i]] = value; // NOT OK +} + +function splitHelper(path, sep) { + let parts = typeof path === 'string' ? path.split(sep || '.') : path; + let result = []; + result.push(...parts); + return result; +} + +function assignToPathWithHelper(target, path, value, sep) { + let keys = splitHelper(path, sep) + let i; + for (i = 0; i < keys.length - 1; ++i) { + let key = keys[i]; + target = target[key] = target[key] || {}; + } + target[keys[i]] = value; // NOT OK +} From c9e56d13f72ee6ea5326391ec4fa29a4504ebe72 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Thu, 27 Feb 2020 14:52:42 +0100 Subject: [PATCH 119/236] C++: Add Expr.getUnconverted predicate This gets rid of the expensive predicate `#Cast::Conversion::getExpr_dispred#ffPlus`, I've observed to cause memory pressure on large databases. --- cpp/ql/src/semmle/code/cpp/exprs/Expr.qll | 14 ++++++++++++++ .../code/cpp/ir/dataflow/internal/DataFlowUtil.qll | 5 +---- .../implementation/raw/internal/IRConstruction.qll | 9 +-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll index c71403e84a2..079b031c530 100644 --- a/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll +++ b/cpp/ql/src/semmle/code/cpp/exprs/Expr.qll @@ -442,6 +442,20 @@ class Expr extends StmtParent, @expr { else result = this } + /** + * Gets the unique non-`Conversion` expression `e` for which + * `this = e.getConversion*()`. + * + * For example, if called on the expression `(int)(char)x`, this predicate + * gets the expression `x`. + */ + Expr getUnconverted() { + not this instanceof Conversion and + result = this + or + result = this.(Conversion).getExpr().getUnconverted() + } + /** * Gets the type of this expression, after any implicit conversions and explicit casts, and after resolving typedefs. * 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 b10997f3f18..e19912a63ee 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 @@ -131,10 +131,7 @@ class ExprNode extends InstructionNode { * `Conversion`, then the result is that `Conversion`'s non-`Conversion` base * expression. */ - Expr getExpr() { - result.getConversion*() = instr.getConvertedResultExpression() and - not result instanceof Conversion - } + Expr getExpr() { result = instr.getUnconvertedResultExpression() } /** * Gets the expression corresponding to this node, if any. The returned diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll index 96ae480405a..dbb27cd1bca 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/internal/IRConstruction.qll @@ -68,14 +68,7 @@ private module Cached { cached Expr getInstructionUnconvertedResultExpression(Instruction instruction) { - exists(Expr converted | - result = converted.(Conversion).getExpr+() - or - result = converted - | - not result instanceof Conversion and - converted = getInstructionConvertedResultExpression(instruction) - ) + result = getInstructionConvertedResultExpression(instruction).getUnconverted() } cached From e6d35d314dcb80cd0ec11efd6507021b637339a6 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 21 Feb 2020 17:23:08 +0000 Subject: [PATCH 120/236] C++: Fix typo. --- cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll index 638e0e0269c..4312afa4611 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/DefaultTaintTracking.qll @@ -267,7 +267,7 @@ private predicate modelTaintToParameter(Function f, int parameterIn, int paramet /** * Holds if `chi` is on the chain of chi-instructions for all aliased memory. - * Taint shoud not pass through these instructions since they tend to mix up + * Taint should not pass through these instructions since they tend to mix up * unrelated objects. */ private predicate isChiForAllAliasedMemory(Instruction instr) { From 0a7d9db3352acf80e80c1d3fdc4a391629e66c1d Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 21 Feb 2020 17:26:47 +0000 Subject: [PATCH 121/236] C++: Add example described in ODASA-640. --- .../declarationEntry/declarationEntry.expected | 2 ++ .../declarationEntry/declarationEntry/fde.expected | 2 ++ .../declarationEntry/declarationEntry/macro.c | 7 +++++++ 3 files changed, 11 insertions(+) create mode 100644 cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected index 912fa5bf1b3..76f564d1f7b 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/declarationEntry.expected @@ -27,3 +27,5 @@ | declarationEntry.cpp:31:4:31:19 | myMemberVariable | declarationEntry.cpp:31:4:31:19 | definition of myMemberVariable | 1 | 1 | | declarationEntry.cpp:34:22:34:28 | mtc_int | declarationEntry.cpp:34:22:34:28 | definition of mtc_int | 1 | 1 | | declarationEntry.cpp:35:24:35:32 | mtc_short | declarationEntry.cpp:35:24:35:32 | definition of mtc_short | 1 | 1 | +| macro.c:2:1:2:3 | foo | macro.c:2:1:2:3 | declaration of foo | 1 | 1 | +| macro.c:4:5:4:8 | main | macro.c:4:5:4:8 | definition of main | 1 | 1 | diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected index 6a452473c72..08da7618763 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected @@ -10,3 +10,5 @@ | declarationEntry.cpp:28:7:28:7 | declaration of operator= | | | declarationEntry.cpp:28:7:28:7 | declaration of operator= | | | declarationEntry.cpp:28:7:28:7 | declaration of operator= | | +| macro.c:2:1:2:3 | declaration of foo | | +| macro.c:4:5:4:8 | definition of main | | diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c new file mode 100644 index 00000000000..590b6a92b95 --- /dev/null +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/macro.c @@ -0,0 +1,7 @@ +#define Foo static void foo() +Foo; + +int main() +{ + return 0; +} From f8a61ffc4cbd2d9c29f78e814fe7fb446e3006b3 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 21 Feb 2020 17:29:37 +0000 Subject: [PATCH 122/236] C++: Expand the test as described in ODASA-640. --- .../declarationEntry/fde.expected | 28 +++++++++---------- .../declarationEntry/declarationEntry/fde.ql | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected index 08da7618763..9a544200cae 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.expected @@ -1,14 +1,14 @@ -| declarationEntry.c:2:6:2:20 | declaration of myFirstFunction | | -| declarationEntry.c:4:6:4:21 | definition of mySecondFunction | | -| declarationEntry.c:8:6:8:20 | definition of myThirdFunction | | -| declarationEntry.c:13:2:13:2 | declaration of myFourthFunction | isImplicit | -| declarationEntry.c:14:2:14:2 | declaration of myFifthFunction | isImplicit | -| declarationEntry.c:17:6:17:21 | declaration of myFourthFunction | | -| declarationEntry.cpp:9:6:9:15 | declaration of myFunction | | -| declarationEntry.cpp:11:6:11:15 | definition of myFunction | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | -| macro.c:2:1:2:3 | declaration of foo | | -| macro.c:4:5:4:8 | definition of main | | +| declarationEntry.c:2:6:2:20 | declaration of myFirstFunction | | 1 | c_linkage | +| declarationEntry.c:4:6:4:21 | definition of mySecondFunction | | 1 | c_linkage | +| declarationEntry.c:8:6:8:20 | definition of myThirdFunction | | 1 | c_linkage | +| declarationEntry.c:13:2:13:2 | declaration of myFourthFunction | isImplicit | 1 | c_linkage | +| declarationEntry.c:14:2:14:2 | declaration of myFifthFunction | isImplicit | 1 | c_linkage | +| declarationEntry.c:17:6:17:21 | declaration of myFourthFunction | | 1 | c_linkage | +| declarationEntry.cpp:9:6:9:15 | declaration of myFunction | | 0 | | +| declarationEntry.cpp:11:6:11:15 | definition of myFunction | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| declarationEntry.cpp:28:7:28:7 | declaration of operator= | | 0 | | +| macro.c:2:1:2:3 | declaration of foo | | 2 | c_linkage, static | +| macro.c:4:5:4:8 | definition of main | | 1 | c_linkage | diff --git a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql index 7d120121e88..fbc97861f4b 100644 --- a/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql +++ b/cpp/ql/test/library-tests/declarationEntry/declarationEntry/fde.ql @@ -2,4 +2,4 @@ import cpp from FunctionDeclarationEntry fde, string imp where if fde.isImplicit() then imp = "isImplicit" else imp = "" -select fde, imp +select fde, imp, count(fde.getASpecifier()), concat(fde.getASpecifier().toString(), ", ") From d686347315306c3fa6be9ad638fcda18f0a88804 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Thu, 27 Feb 2020 16:12:03 +0100 Subject: [PATCH 123/236] C++: Optimize EnumSwitch.getAMissingCase The `cpp/missing-case-in-switch` performed badly on some snapshots, to the extent where it was as slow as the most expensive IR stages (example: ChakraCore). This commit makes it faster, removing a `pragma[noopt]` along the way. The intermediate tuple counts on a customer codebase drop from 84M to 3M, while the content hash of `getAMissingCase` is the same. Before: (124s) Tuple counts for Stmt::EnumSwitch::getAMissingCase#ff#antijoin_rhs: 20867789 ~0% {3} r1 = JOIN Stmt::SwitchStmt::getASwitchCase_dispred#ff AS L WITH Stmt::EnumSwitch::getAMissingCase#ff#shared AS R ON FIRST 1 OUTPUT L.<1>, R.<0>, R.<1> 20122830 ~0% {3} r2 = JOIN r1 WITH Stmt::SwitchCase::getExpr_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, r1.<1>, r1.<2> 20122830 ~0% {3} r3 = JOIN r2 WITH Expr::Expr::getValue_dispred#ff AS R ON FIRST 1 OUTPUT r2.<2>, r2.<1>, R.<1> 83961918 ~0% {4} r4 = JOIN r3 WITH Enum::EnumConstant::getInitializer_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, r3.<1>, r3.<0>, r3.<2> 83961918 ~0% {4} r5 = JOIN r4 WITH initialisers AS R ON FIRST 1 OUTPUT R.<2>, r4.<3>, r4.<1>, r4.<2> 234348 ~185% {2} r6 = JOIN r5 WITH Expr::Expr::getValue_dispred#ff AS R ON FIRST 2 OUTPUT r5.<2>, r5.<3> return r6 ... (124s) Tuple counts for Stmt::EnumSwitch::getAMissingCase#ff: 663127 ~4% {2} r1 = Stmt::EnumSwitch::getAMissingCase#ff#shared AS L AND NOT Stmt::EnumSwitch::getAMissingCase#ff#antijoin_rhs AS R(L.<0>, L.<1>) return r1 (124s) Registering Stmt::EnumSwitch::getAMissingCase#ff + [] with content 2060ff326cvhihcsvoph6k9divuv4 (124s) >>> Wrote relation Stmt::EnumSwitch::getAMissingCase#ff with 663127 rows and 2 columns. After: (5s) Tuple counts for Stmt::EnumSwitch::getAMissingCase_dispred#ff#antijoin_rhs: 746029 ~0% {2} r1 = JOIN Stmt::EnumSwitch::getAMissingCase_dispred#ff#shared AS L WITH Enum::Enum::getAnEnumConstant_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, L.<1> 3116197 ~2% {3} r2 = JOIN r1 WITH Enum::EnumConstant::getInitializer_dispred#ff AS R ON FIRST 1 OUTPUT R.<1>, r1.<1>, r1.<0> 3116197 ~0% {3} r3 = JOIN r2 WITH initialisers AS R ON FIRST 1 OUTPUT R.<2>, r2.<1>, r2.<2> 3116197 ~311% {3} r4 = JOIN r3 WITH Expr::Expr::getValue_dispred#ff AS R ON FIRST 1 OUTPUT r3.<1>, R.<1>, r3.<2> 234348 ~185% {2} r5 = JOIN r4 WITH Stmt::EnumSwitch::matchesValue#ff AS R ON FIRST 2 OUTPUT r4.<0>, r4.<2> return r5 (5s) Registering Stmt::EnumSwitch::getAMissingCase_dispred#ff#antijoin_rhs + [] with content 173483d71508vl534mvlr1g0ehi12 (5s) >>> Wrote relation Stmt::EnumSwitch::getAMissingCase_dispred#ff#antijoin_rhs with 82902 rows and 2 columns. (5s) Starting to evaluate predicate Stmt::EnumSwitch::getAMissingCase_dispred#ff/2@ae4c0b (5s) Tuple counts for Stmt::EnumSwitch::getAMissingCase_dispred#ff: 746029 ~2% {2} r1 = JOIN Stmt::EnumSwitch::getAMissingCase_dispred#ff#shared AS L WITH Enum::Enum::getAnEnumConstant_dispred#ff AS R ON FIRST 1 OUTPUT L.<1>, R.<1> 663127 ~4% {2} r2 = r1 AND NOT Stmt::EnumSwitch::getAMissingCase_dispred#ff#antijoin_rhs AS R(r1.<0>, r1.<1>) return r2 (5s) Registering Stmt::EnumSwitch::getAMissingCase_dispred#ff + [] with content 2060ff326cvhihcsvoph6k9divuv4 (5s) >>> Wrote relation Stmt::EnumSwitch::getAMissingCase_dispred#ff with 663127 rows and 2 columns. --- cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll | 24 +++++------------------ 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll index aa675261b14..8cbfb92152f 100644 --- a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll +++ b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll @@ -1604,30 +1604,16 @@ class EnumSwitch extends SwitchStmt { * ``` * there are results `GREEN` and `BLUE`. */ - pragma[noopt] EnumConstant getAMissingCase() { exists(Enum et | - exists(Expr e, Type t | - e = this.getExpr() and - this instanceof EnumSwitch and - t = e.getType() and - et = t.getUnderlyingType() - ) and + et = this.getExpr().getUnderlyingType() and result = et.getAnEnumConstant() and - not exists(string value | - exists(SwitchCase sc, Expr e | - sc = this.getASwitchCase() and - e = sc.getExpr() and - value = e.getValue() - ) and - exists(Initializer init, Expr e | - init = result.getInitializer() and - e = init.getExpr() and - e.getValue() = value - ) - ) + not this.matchesValue(result.getInitializer().getExpr().getValue()) ) } + + pragma[noinline] + private predicate matchesValue(string value) { value = this.getASwitchCase().getExpr().getValue() } } /** From 729c310eb9b609a11dc638cf4a012551ff0dd434 Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Thu, 27 Feb 2020 15:27:58 +0000 Subject: [PATCH 124/236] C++: More typos. --- .../code/cpp/ir/implementation/aliased_ssa/IRVariable.qll | 2 +- cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll | 2 +- .../code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { From b0fb16c06873289a373fb97a2c1b2d8d2681f402 Mon Sep 17 00:00:00 2001 From: Dave Bartolomeo Date: Thu, 27 Feb 2020 13:44:02 -0500 Subject: [PATCH 125/236] C++/C#: Fix formatting --- .../src/semmle/code/cpp/ir/implementation/Opcode.qll | 10 +++++----- .../semmle/code/csharp/ir/implementation/Opcode.qll | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll index 3fb716d78e0..5cfe8dd1c5e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/Opcode.qll @@ -222,8 +222,7 @@ abstract class BufferAccessOpcode extends Opcode { /** * An opcode that accesses a memory buffer of unknown size. */ -abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { -} +abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { } /** * An opcode that writes to a memory buffer of unknown size. @@ -670,7 +669,8 @@ module Opcode { final override string toString() { result = "IndirectMayWriteSideEffect" } } - class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, TBufferReadSideEffect { + class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, + TBufferReadSideEffect { final override string toString() { result = "BufferReadSideEffect" } } @@ -679,8 +679,8 @@ module Opcode { final override string toString() { result = "BufferMustWriteSideEffect" } } - class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, MayWriteOpcode, - TBufferMayWriteSideEffect { + class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, + MayWriteOpcode, TBufferMayWriteSideEffect { final override string toString() { result = "BufferMayWriteSideEffect" } } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll index 3fb716d78e0..5cfe8dd1c5e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/Opcode.qll @@ -222,8 +222,7 @@ abstract class BufferAccessOpcode extends Opcode { /** * An opcode that accesses a memory buffer of unknown size. */ -abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { -} +abstract class UnsizedBufferAccessOpcode extends BufferAccessOpcode { } /** * An opcode that writes to a memory buffer of unknown size. @@ -670,7 +669,8 @@ module Opcode { final override string toString() { result = "IndirectMayWriteSideEffect" } } - class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, TBufferReadSideEffect { + class BufferReadSideEffect extends ReadSideEffectOpcode, UnsizedBufferReadOpcode, + TBufferReadSideEffect { final override string toString() { result = "BufferReadSideEffect" } } @@ -679,8 +679,8 @@ module Opcode { final override string toString() { result = "BufferMustWriteSideEffect" } } - class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, MayWriteOpcode, - TBufferMayWriteSideEffect { + class BufferMayWriteSideEffect extends WriteSideEffectOpcode, UnsizedBufferWriteOpcode, + MayWriteOpcode, TBufferMayWriteSideEffect { final override string toString() { result = "BufferMayWriteSideEffect" } } From d19957f09ddb1b9dd9f01a8e66c4d865390ae729 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 27 Feb 2020 10:44:46 -0800 Subject: [PATCH 126/236] Puts use_of_apply example back into expressions_test to avoid messing up other tests --- python/ql/test/2/query-tests/Expressions/UseofApply.expected | 1 + python/ql/test/2/query-tests/Expressions/expressions_test.py | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/python/ql/test/2/query-tests/Expressions/UseofApply.expected b/python/ql/test/2/query-tests/Expressions/UseofApply.expected index 4c4d84b6ce5..aac8fbcda88 100644 --- a/python/ql/test/2/query-tests/Expressions/UseofApply.expected +++ b/python/ql/test/2/query-tests/Expressions/UseofApply.expected @@ -1 +1,2 @@ | UseofApply.py:19:3:19:17 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | +| expressions_test.py:3:5:3:21 | ControlFlowNode for apply() | Call to the obsolete builtin function 'apply'. | diff --git a/python/ql/test/2/query-tests/Expressions/expressions_test.py b/python/ql/test/2/query-tests/Expressions/expressions_test.py index 61a0d702041..943bcea98a8 100644 --- a/python/ql/test/2/query-tests/Expressions/expressions_test.py +++ b/python/ql/test/2/query-tests/Expressions/expressions_test.py @@ -1,3 +1,7 @@ + +def use_of_apply(func, args): + apply(func, args) + def use_of_input(): return input() From 9601c41fe587c5a9474a613c93934cee3b820776 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine <171941+BekaValentine@users.noreply.github.com> Date: Thu, 27 Feb 2020 10:46:07 -0800 Subject: [PATCH 127/236] Update python/ql/src/Expressions/IsComparisons.qll Co-Authored-By: Taus --- python/ql/src/Expressions/IsComparisons.qll | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/ql/src/Expressions/IsComparisons.qll b/python/ql/src/Expressions/IsComparisons.qll index 0eed3bd9552..bf9d8bb71db 100644 --- a/python/ql/src/Expressions/IsComparisons.qll +++ b/python/ql/src/Expressions/IsComparisons.qll @@ -120,7 +120,7 @@ predicate invalid_portable_is_comparison(Compare comp, Cmpop op, ClassValue cls) ) ) and - // OK to use 'is' when comparing with a member of an enumÏ + // OK to use 'is' when comparing with a member of an enum not exists(Expr left, Expr right, AstNode origin | comp.compares(left, op, right) and enum_member(origin) | @@ -135,4 +135,3 @@ private predicate enum_member(AstNode obj) { asgn.getValue() = obj ) } - From 19c1ee5427f0cc5ea230514a6595ae2b33638248 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Thu, 27 Feb 2020 15:46:51 -0800 Subject: [PATCH 128/236] Adds preliminary modernization --- .../WrongNumberArgumentsForFormat.ql | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql index aa163d91544..3b2116734cd 100644 --- a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql +++ b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql @@ -15,29 +15,29 @@ import python import semmle.python.strings -predicate string_format(BinaryExpr operation, StrConst str, Object args, AstNode origin) { - exists(Object fmt, Context ctx | operation.getOp() instanceof Mod | - operation.getLeft().refersTo(ctx, fmt, _, str) and - operation.getRight().refersTo(ctx, args, _, origin) +predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) { + exists(Value fmt, Context ctx | operation.getOp() instanceof Mod | + operation.getLeft().pointsTo(ctx, fmt, str) and + operation.getRight().pointsTo(ctx, args, origin) ) } -int sequence_length(Object args) { +int sequence_length(Value args) { /* Guess length of sequence */ - exists(Tuple seq | - seq = args.getOrigin() | + exists(Tuple seq, AstNode origin | + seq.pointsTo(args,origin) | result = strictcount(seq.getAnElt()) and not seq.getAnElt() instanceof Starred ) or exists(ImmutableLiteral i | - i.getLiteralObject() = args | + i.getLiteralValue() = args | result = 1 ) } -from BinaryExpr operation, StrConst fmt, Object args, int slen, int alen, AstNode origin, string provided +from BinaryExpr operation, StrConst fmt, Value args, int slen, int alen, AstNode origin, string provided where string_format(operation, fmt, args, origin) and slen = sequence_length(args) and alen = format_items(fmt) and slen != alen and (if slen = 1 then provided = " is provided." else provided = " are provided.") select operation, "Wrong number of $@ for string format. Format $@ takes " + alen.toString() + ", but " + slen.toString() + provided, From 17f1974e05a6719dd0587aebd6152ec18402451a Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 28 Feb 2020 09:43:32 +0100 Subject: [PATCH 129/236] Apply suggestions from code review Co-Authored-By: mc <42146119+mchammer01@users.noreply.github.com> --- change-notes/1.24/analysis-javascript.md | 2 +- .../ql/src/Security/CWE-078/UselessUseOfCat.qhelp | 15 +++++++-------- .../ql/src/Security/CWE-078/UselessUseOfCat.ql | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index eaebf0c1251..9c1afc25c07 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -39,7 +39,7 @@ | Missing await (`js/missing-await`) | correctness | Highlights expressions that operate directly on a promise object in a nonsensical way, instead of awaiting its result. Results are shown on LGTM by default. | | Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | | Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | -| Useless use of cat (`js/useless-use-of-cat`) | correctness, security, maintainability | Highlights command executions of cat where the fs API should be used instead. Results are shown on LGTM by default. | +| Useless use of cat (`js/useless-use-of-cat`) | correctness, security, maintainability | Highlights command executions of `cat` where the fs API should be used instead. Results are shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp index 85a7a2e4949..51ac109ae6c 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -3,9 +3,9 @@ "qhelp.dtd"> -

    Using the unix command cat to simply read a file is a -unnecessarily complex way to achieve something that can be done simpler and -safer using the Node.js fs.readFile API. +

    Using the unix command cat only to read a file is a +an unnecessarily complex way to achieve something that can be done in a simpler and +safer manner using the Node.js fs.readFile API.

    The use of cat for simple file reads leads to code that is @@ -25,12 +25,11 @@ from the file system. -

    The code in the example will break if the input name contain -special characters (including space), the code does not work on windows, -and if the input is user controlled a command injection attack can happen.

    +

    The code in the example will break if the input name contains +special characters (including space). Additionally, it does not work on Windows +and if the input is user-controlled, a command injection attack can happen.

    -

    To avoid these potential issues the fs.readFile API can be -used instead:

    +

    The fs.readFile API should be used to avoid these potential issues: diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index 9f28c5fb0cf..0c33fc8c06f 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -1,6 +1,6 @@ /** * @name Useless use of cat - * @description Using `cat`-process to simply read a file is unnecessarily complex, inefficient, unportable, can lead to subtle bugs, or even security vulnerabilities. + * @description Using the `cat` process to read a file is unnecessarily complex, inefficient, unportable, and can lead to subtle bugs, or even security vulnerabilities. * @kind problem * @problem.severity error * @precision high @@ -22,4 +22,4 @@ where if cat.isSync() then message = " Can be replaced with a call to fs.readFileSync(..)." else message = " Can be replaced with a call to fs.readFile(..)." -select cat.asExpr().(FirstLineOf), "Useless use of `cat`." + message \ No newline at end of file +select cat.asExpr().(FirstLineOf), "Useless use of `cat`." + message From 922779e0491a484d456ba46be45409e798bd50de Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 28 Feb 2020 09:48:07 +0100 Subject: [PATCH 130/236] remove double a/an and adjust line lenghts --- javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp index 51ac109ae6c..8afc06a861e 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -3,9 +3,9 @@ "qhelp.dtd"> -

    Using the unix command cat only to read a file is a -an unnecessarily complex way to achieve something that can be done in a simpler and -safer manner using the Node.js fs.readFile API. +

    Using the unix command cat only to read a file is an +unnecessarily complex way to achieve something that can be done in a simpler +and safer manner using the Node.js fs.readFile API.

    The use of cat for simple file reads leads to code that is From d8a96dd7713045de9bc3d7f536d4b5e2fa11a38f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 28 Feb 2020 09:55:15 +0100 Subject: [PATCH 131/236] change name to suggestion from previous code review --- .../ql/src/semmle/javascript/security/UselessUseOfCat.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index c154885925f..4c8b3815586 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -105,7 +105,7 @@ class UselessCat extends CommandCall { getArgument(0).mayHaveStringValue(getACatExecuteable()) ) and // wildcards, pipes, redirections, other bash features, and multiple files (spaces) are OK. - not containsNonTrivialBashChar(getNonCommandConstantString()) and + not containsNonTrivialShellChar(getNonCommandConstantString()) and // Only acceptable option is "encoding", everything else is non-trivial to emulate with fs.readFile. ( not exists(getOptionsArg()) @@ -174,7 +174,7 @@ module PrettyPrintCatCall { ) and fileArg = createFileArgument(cat).trim() and // sanity check in case of surprising `toString` results, other uses of `containsNonTrivialBashChar` should ensure that this conjunct will hold most of the time - not(containsNonTrivialBashChar(fileArg.regexpReplaceAll("\\$|\\`| ", ""))) // string concat might contain " ", template strings might contain "$" or `, and that is OK. + not(containsNonTrivialShellChar(fileArg.regexpReplaceAll("\\$|\\`| ", ""))) // string concat might contain " ", template strings might contain "$" or `, and that is OK. | result = "fs.readFile" + sync + "(" + fileArg + extraArg + callback + ")" From 5e0ae7b4d08f8bce3f5ea9539b80764d93c4da4e Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 28 Feb 2020 10:23:03 +0100 Subject: [PATCH 132/236] add end

    tag --- javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp index 8afc06a861e..aa8acaee700 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -29,7 +29,7 @@ from the file system. special characters (including space). Additionally, it does not work on Windows and if the input is user-controlled, a command injection attack can happen.

    -

    The fs.readFile API should be used to avoid these potential issues: +

    The fs.readFile API should be used to avoid these potential issues:

    From dfe1a7e2f01519adf61f6b9e7eb80da9fac1cfda Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 28 Feb 2020 10:48:23 +0100 Subject: [PATCH 133/236] C++: Avoid `iDominates*` in Overflow.qll The `iDominates` relation is directly on control-flow nodes, and its transitive closure is far too large. It got compiled into a recursion rather than `fastTC`, and I've observed that recursion to take about an hour on a medium-size customer snapshot. The fix is to check for dominance at the basic-block level. --- .../src/semmle/code/cpp/security/Overflow.qll | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index ba98f42d096..be9dcf650cb 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -13,19 +13,32 @@ predicate guardedAbs(Operation e, Expr use) { ) } +pragma[inline] +private predicate stmtDominates(Stmt dominator, Stmt dominated) { + // In same block + exists(BasicBlock block, int dominatorIndex, int dominatedIndex | + block.getNode(dominatorIndex) = dominator and + block.getNode(dominatedIndex) = dominated and + dominatedIndex >= dominatorIndex + ) + or + // In (possibly) different blocks + bbStrictlyDominates(dominator.getBasicBlock(), dominated.getBasicBlock()) +} + /** is the size of this use guarded to be less than something? */ pragma[nomagic] predicate guardedLesser(Operation e, Expr use) { exists(IfStmt c, RelationalOperation guard | use = guard.getLesserOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getThen(), e.getEnclosingStmt()) + stmtDominates(c.getThen(), e.getEnclosingStmt()) ) or exists(Loop c, RelationalOperation guard | use = guard.getLesserOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getStmt(), e.getEnclosingStmt()) + stmtDominates(c.getStmt(), e.getEnclosingStmt()) ) or exists(ConditionalExpr c, RelationalOperation guard | @@ -43,13 +56,13 @@ predicate guardedGreater(Operation e, Expr use) { exists(IfStmt c, RelationalOperation guard | use = guard.getGreaterOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getThen(), e.getEnclosingStmt()) + stmtDominates(c.getThen(), e.getEnclosingStmt()) ) or exists(Loop c, RelationalOperation guard | use = guard.getGreaterOperand().getAChild*() and guard = c.getControllingExpr().getAChild*() and - iDominates*(c.getStmt(), e.getEnclosingStmt()) + stmtDominates(c.getStmt(), e.getEnclosingStmt()) ) or exists(ConditionalExpr c, RelationalOperation guard | From 2e7807718f07979d7fbcf4310842f2b586fc4195 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 28 Feb 2020 11:45:39 +0100 Subject: [PATCH 134/236] Python: Add a bit of regex docs and use the canonical way of specifying a ModuleValue --- python/ql/src/semmle/python/regex.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/ql/src/semmle/python/regex.qll b/python/ql/src/semmle/python/regex.qll index 9e3da135f32..83f3251ff80 100644 --- a/python/ql/src/semmle/python/regex.qll +++ b/python/ql/src/semmle/python/regex.qll @@ -12,10 +12,14 @@ private predicate re_module_function(string name, int flags) { name = "subn" and flags = 4 } +/** + * Holds if `s` is used as a regex with the `re` module, with the regex-mode `mode` (if known). + * If regex mode is not known, `mode` will be `"None"`. + */ predicate used_as_regex(Expr s, string mode) { (s instanceof Bytes or s instanceof Unicode) and - exists(ModuleValue re | re.getName() = "re" | + exists(ModuleValue re | re = Module::named("re") | /* Call to re.xxx(regex, ... [mode]) */ exists(CallNode call, string name | call.getArg(0).refersTo(_, _, s.getAFlowNode()) and From 5a3a1c480d6de7b0c3405831d4859b0551752286 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Fri, 28 Feb 2020 10:48:24 +0100 Subject: [PATCH 135/236] JS: add tests for the fs-module and friends --- .../CWE-022/TaintedPath/Consistency.expected | 3 + .../CWE-022/TaintedPath/TaintedPath.expected | 299 ++++++++++++++++++ .../CWE-022/TaintedPath/my-fs-module.js | 7 + .../CWE-022/TaintedPath/other-fs-libraries.js | 26 ++ 4 files changed, 335 insertions(+) create mode 100644 javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js create mode 100644 javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected index 68ed692f741..8b86e60245c 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected @@ -1,4 +1,7 @@ | normalizedPaths.js:208:38:208:63 | // OK - ... anyway | Spurious alert | +| other-fs-libraries.js:14:34:14:42 | // NOT OK | Missing alert | +| other-fs-libraries.js:16:41:16:49 | // NOT OK | Missing alert | +| other-fs-libraries.js:17:42:17:50 | // NOT OK | Missing alert | | tainted-string-steps.js:25:43:25:74 | // NOT ... flagged | Missing alert | | tainted-string-steps.js:26:49:26:74 | // OK - ... flagged | Spurious alert | | tainted-string-steps.js:28:39:28:70 | // NOT ... flagged | Missing alert | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 061f3522e34..424e94fffd2 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1631,6 +1631,126 @@ nodes | normalizedPaths.js:332:19:332:32 | normalizedPath | | normalizedPaths.js:332:19:332:32 | normalizedPath | | normalizedPaths.js:332:19:332:32 | normalizedPath | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:9:24:9:30 | req.url | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:13:24:13:27 | path | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -4531,6 +4651,182 @@ edges | normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | | normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | | normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:11:19:11:22 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:12:27:12:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:43 | url.par ... ).query | other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:14:9:48 | url.par ... ry.path | other-fs-libraries.js:9:7:9:48 | path | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | +| other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | @@ -5411,6 +5707,9 @@ edges | normalizedPaths.js:316:19:316:22 | path | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:316:19:316:22 | path | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | | normalizedPaths.js:325:19:325:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:325:19:325:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | | normalizedPaths.js:332:19:332:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:332:19:332:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| other-fs-libraries.js:11:19:11:22 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:11:19:11:22 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:12:27:12:30 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:12:27:12:30 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:13:24:13:27 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:13:24:13:27 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js new file mode 100644 index 00000000000..c57c6341428 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/my-fs-module.js @@ -0,0 +1,7 @@ +exports.require = function(special) { + if (special) { + return require("fs"); + } else { + return require("original-fs"); + } +}; diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js new file mode 100644 index 00000000000..c7859982c0f --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js @@ -0,0 +1,26 @@ +var http = require("http"), + url = require("url"), + fs = require("fs"), + gracefulFs = require("graceful-fs"), + fsExtra = require("fs-extra"), + originalFs = require("original-fs"); + +var server = http.createServer(function(req, res) { + var path = url.parse(req.url, true).query.path; + + fs.readFileSync(path); // NOT OK + gracefulFs.readFileSync(path); // NOT OK + fsExtra.readFileSync(path); // NOT OK + originalFs.readFileSync(path); // NOT OK + + getFsModule(true).readFileSync(path); // NOT OK + getFsModule(false).readFileSync(path); // NOT OK +}); + +function getFsModule(special) { + if (special) { + return require("fs"); + } else { + return require("original-fs"); + } +} From a589061bee1cc8f6fb5505e47f984f305daed4e0 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Fri, 28 Feb 2020 12:20:17 +0100 Subject: [PATCH 136/236] JS: add type-tracking to the fs-module and model the `original-fs` --- change-notes/1.24/analysis-javascript.md | 2 +- .../javascript/frameworks/NodeJSLib.qll | 19 +- .../CWE-022/TaintedPath/Consistency.expected | 3 - .../CWE-022/TaintedPath/TaintedPath.expected | 250 ++++++++++++++++++ .../CWE-022/TaintedPath/other-fs-libraries.js | 7 + 5 files changed, 273 insertions(+), 8 deletions(-) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 1cfc11deb48..bea62aa9e85 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -58,7 +58,7 @@ | Expression has no effect (`js/useless-expression`) | Fewer false positive results | The query now recognizes block-level flow type annotations and ignores the first statement of a try block. | | Use of call stack introspection in strict mode (`js/strict-mode-call-stack-introspection`) | Fewer false positive results | The query no longer flags expression statements. | | Missing CSRF middleware (`js/missing-token-validation`) | Fewer false positive results | The query reports fewer duplicates and only flags handlers that explicitly access cookie data. | -| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed. | +| Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed and used. | | Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional ways of constructing arguments to `cmd.exe` and `/bin/sh`. | ## Changes to libraries diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index cf2d34844b3..1fca6e624ed 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -429,16 +429,27 @@ module NodeJSLib { } /** - * A member `member` from module `fs` or its drop-in replacements `graceful-fs` or `fs-extra`. + * A member `member` from module `fs` or its drop-in replacements `graceful-fs`, `fs-extra`, `original-fs`. */ private DataFlow::SourceNode fsModuleMember(string member) { + result = fsModule(DataFlow::TypeTracker::end()).getAPropertyRead(member) + } + + private DataFlow::SourceNode fsModule(DataFlow::TypeTracker t) { exists(string moduleName | moduleName = "fs" or moduleName = "graceful-fs" or - moduleName = "fs-extra" + moduleName = "fs-extra" or + moduleName = "original-fs" | - result = DataFlow::moduleMember(moduleName, member) - ) + result = DataFlow::moduleImport(moduleName) + or + // extra support for flexible names + result.asExpr().(Require).getArgument(0).mayHaveStringValue(moduleName) + ) and + t.start() + or + exists(DataFlow::TypeTracker t2 | result = fsModule(t2).track(t2, t)) } /** diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected index 8b86e60245c..68ed692f741 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/Consistency.expected @@ -1,7 +1,4 @@ | normalizedPaths.js:208:38:208:63 | // OK - ... anyway | Spurious alert | -| other-fs-libraries.js:14:34:14:42 | // NOT OK | Missing alert | -| other-fs-libraries.js:16:41:16:49 | // NOT OK | Missing alert | -| other-fs-libraries.js:17:42:17:50 | // NOT OK | Missing alert | | tainted-string-steps.js:25:43:25:74 | // NOT ... flagged | Missing alert | | tainted-string-steps.js:26:49:26:74 | // OK - ... flagged | Spurious alert | | tainted-string-steps.js:28:39:28:70 | // NOT ... flagged | Missing alert | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 424e94fffd2..1256634d8e5 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1751,6 +1751,91 @@ nodes | other-fs-libraries.js:13:24:13:27 | path | | other-fs-libraries.js:13:24:13:27 | path | | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:24:35:24:38 | path | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -4747,6 +4832,166 @@ edges | other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | | other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | | other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:13:24:13:27 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:14:27:14:30 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:16:34:16:37 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:17:35:17:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:19:56:19:59 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | +| other-fs-libraries.js:9:7:9:48 | path | other-fs-libraries.js:24:35:24:38 | path | | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | | other-fs-libraries.js:9:14:9:37 | url.par ... , true) | other-fs-libraries.js:9:14:9:43 | url.par ... ).query | @@ -5710,6 +5955,11 @@ edges | other-fs-libraries.js:11:19:11:22 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:11:19:11:22 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | | other-fs-libraries.js:12:27:12:30 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:12:27:12:30 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | | other-fs-libraries.js:13:24:13:27 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:13:24:13:27 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:14:27:14:30 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:14:27:14:30 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:16:34:16:37 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:16:34:16:37 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:17:35:17:38 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:17:35:17:38 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:19:56:19:59 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:19:56:19:59 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | +| other-fs-libraries.js:24:35:24:38 | path | other-fs-libraries.js:9:24:9:30 | req.url | other-fs-libraries.js:24:35:24:38 | path | This path depends on $@. | other-fs-libraries.js:9:24:9:30 | req.url | a user-provided value | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js index c7859982c0f..611aac86f30 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/other-fs-libraries.js @@ -15,6 +15,13 @@ var server = http.createServer(function(req, res) { getFsModule(true).readFileSync(path); // NOT OK getFsModule(false).readFileSync(path); // NOT OK + + require("./my-fs-module").require(true).readFileSync(path); // NOT OK + + let flexibleModuleName = require(process.versions["electron"] + ? "original-fs" + : "fs"); + flexibleModuleName.readFileSync(path); // NOT OK }); function getFsModule(special) { From 8dd19ca6cd34f9ce5ff7eaf4275684e5f74d5201 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Thu, 27 Feb 2020 16:08:00 +0100 Subject: [PATCH 137/236] Python: Add example of re.compile missing points-to --- .../regressions/missing/re-compile/Test.expected | 1 + .../PointsTo/regressions/missing/re-compile/Test.ql | 10 ++++++++++ .../PointsTo/regressions/missing/re-compile/test.py | 5 +++++ 3 files changed, 16 insertions(+) create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected new file mode 100644 index 00000000000..c625cceb62f --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.expected @@ -0,0 +1 @@ +| test.py:5:7:5:13 | ControlFlowNode for PATTERN | | diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql new file mode 100644 index 00000000000..dd894ad5cea --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/Test.ql @@ -0,0 +1,10 @@ +import python + +from NameNode name, CallNode call, string debug +where + call.getAnArg() = name and + call.getFunction().(NameNode).getId() = "check" and + if exists(name.pointsTo()) + then debug = name.pointsTo().toString() + else debug = "" +select name, debug diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py new file mode 100644 index 00000000000..b1e46a33287 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/re-compile/test.py @@ -0,0 +1,5 @@ +import re + +PATTERN = re.compile("a|b") + +check(PATTERN) From ee4190e0d933fa6edd2f744e317a7cbedc920478 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 28 Feb 2020 13:43:10 +0100 Subject: [PATCH 138/236] Python: Add example `if urlsplit(url).path:` => missing points-to I wanted to highlight that it's not the call to `urlsplit` that is a problem, so that's why I inserted a `check` in-between. --- .../missing/if-urlsplit-access/Test.expected | 3 +++ .../missing/if-urlsplit-access/Test.ql | 10 ++++++++++ .../missing/if-urlsplit-access/options | 1 + .../missing/if-urlsplit-access/test.py | 17 +++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options create mode 100644 python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected new file mode 100644 index 00000000000..fc25b86ca0a --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.expected @@ -0,0 +1,3 @@ +| test.py:5:7:5:9 | ControlFlowNode for foo | int 42 | +| test.py:11:11:11:13 | ControlFlowNode for foo | int 1 | +| test.py:17:11:17:13 | ControlFlowNode for foo | | diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql new file mode 100644 index 00000000000..dd894ad5cea --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/Test.ql @@ -0,0 +1,10 @@ +import python + +from NameNode name, CallNode call, string debug +where + call.getAnArg() = name and + call.getFunction().(NameNode).getId() = "check" and + if exists(name.pointsTo()) + then debug = name.pointsTo().toString() + else debug = "" +select name, debug diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options new file mode 100644 index 00000000000..cfef58cf2b2 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/options @@ -0,0 +1 @@ +semmle-extractor-options: --max-import-depth=1 --lang=3 diff --git a/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py new file mode 100644 index 00000000000..6301475e4a0 --- /dev/null +++ b/python/ql/test/library-tests/PointsTo/regressions/missing/if-urlsplit-access/test.py @@ -0,0 +1,17 @@ +# Only a problem in Python 3 +from urllib.parse import urlsplit + +foo = 42 +check(foo) + +def func(url): + parts = urlsplit(url) + + foo = 1 + check(foo) + + if parts.path: # using `urlsplit(url).path` here is equivalent + return # using `pass` here instead makes points-to work + + foo = 2 + check(foo) # no points-to information From 71ff32e930efb902c36c4e35ef784e524ac94bb7 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 28 Feb 2020 14:55:41 +0100 Subject: [PATCH 139/236] recognize another prefix check for js/path-injection --- .../security/dataflow/TaintedPath.qll | 2 +- .../dataflow/TaintedPathCustomizations.qll | 30 ++++++++--- .../CWE-022/TaintedPath/TaintedPath.expected | 53 +++++++++++++++++++ .../CWE-022/TaintedPath/normalizedPaths.js | 14 +++++ 4 files changed, 90 insertions(+), 9 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index 8dca676af46..242504a9c59 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -36,7 +36,7 @@ module TaintedPath { guard instanceof StartsWithDirSanitizer or guard instanceof IsAbsoluteSanitizer or guard instanceof ContainsDotDotSanitizer or - guard instanceof RelativePathStartsWithDotDotSanitizer or + guard instanceof RelativePathStartsWithSanitizer or guard instanceof IsInsideCheckSanitizer } diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index d1be274314c..dc749d6994c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -368,29 +368,43 @@ module TaintedPath { * // pathname is safe * } * ``` + * + * or + * ``` + * var relative = path.resolve(pathname); // or path.normalize + * if(relative.startsWith(webroot) { + * // pathname is safe + * } else { + * // pathname is unsafe + * } + * ``` */ - class RelativePathStartsWithDotDotSanitizer extends DataFlow::BarrierGuardNode { + class RelativePathStartsWithSanitizer extends DataFlow::BarrierGuardNode { StringOps::StartsWith startsWith; - DataFlow::CallNode relativeCall; + DataFlow::CallNode pathCall; + string member; - RelativePathStartsWithDotDotSanitizer() { + RelativePathStartsWithSanitizer() { + (member = "relative" or member = "resolve" or member = "normalize") and this = startsWith and - relativeCall = NodeJSLib::Path::moduleMember("relative").getACall() and + pathCall = NodeJSLib::Path::moduleMember(member).getACall() and ( - startsWith.getBaseString().getALocalSource() = relativeCall + startsWith.getBaseString().getALocalSource() = pathCall or startsWith .getBaseString() .getALocalSource() .(NormalizingPathCall) .getInput() - .getALocalSource() = relativeCall + .getALocalSource() = pathCall ) and - isDotDotSlashPrefix(startsWith.getSubstring()) + (not member = "relative" or isDotDotSlashPrefix(startsWith.getSubstring())) } override predicate blocks(boolean outcome, Expr e) { - e = relativeCall.getArgument(1).asExpr() and outcome = startsWith.getPolarity().booleanNot() + member = "relative" and e = pathCall.getArgument(1).asExpr() and outcome = startsWith.getPolarity().booleanNot() + or + not member = "relative" and e = pathCall.getArgument(0).asExpr() and outcome = startsWith.getPolarity() } } diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 061f3522e34..8b22a2fc186 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1631,6 +1631,29 @@ nodes | normalizedPaths.js:332:19:332:32 | normalizedPath | | normalizedPaths.js:332:19:332:32 | normalizedPath | | normalizedPaths.js:332:19:332:32 | normalizedPath | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:339:32:339:45 | req.query.path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:346:19:346:22 | path | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -4531,6 +4554,34 @@ edges | normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | | normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | | normalizedPaths.js:320:45:320:48 | path | normalizedPaths.js:320:23:320:49 | pathMod ... , path) | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:341:18:341:21 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:6:339:46 | path | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | normalizedPaths.js:339:6:339:46 | path | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | @@ -5411,6 +5462,8 @@ edges | normalizedPaths.js:316:19:316:22 | path | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:316:19:316:22 | path | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | | normalizedPaths.js:325:19:325:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:325:19:325:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | | normalizedPaths.js:332:19:332:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:332:19:332:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | +| normalizedPaths.js:341:18:341:21 | path | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:341:18:341:21 | path | This path depends on $@. | normalizedPaths.js:339:32:339:45 | req.query.path | a user-provided value | +| normalizedPaths.js:346:19:346:22 | path | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:346:19:346:22 | path | This path depends on $@. | normalizedPaths.js:339:32:339:45 | req.query.path | a user-provided value | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js index 6a7a143f7bd..cdbdf84b966 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js @@ -334,3 +334,17 @@ app.get('/pseudo-normalizations', (req, res) => { } }); + +app.get('/yet-another-prefix', (req, res) => { + let path = pathModule.resolve(req.query.path); + + fs.readFileSync(path); // NOT OK + + var abs = pathModule.resolve(path); + + if (abs.indexOf(root) !== 0) { + fs.readFileSync(path); // NOT OK + return; + } + fs.readFileSync(path); // OK +}); \ No newline at end of file From c6b0d4bbda1d4fc25dc25c2273d886d37a6f873c Mon Sep 17 00:00:00 2001 From: Geoffrey White <40627776+geoffw0@users.noreply.github.com> Date: Fri, 28 Feb 2020 17:55:59 +0000 Subject: [PATCH 140/236] C#: Sync identical files. --- .../src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll | 2 +- .../code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll index ee7aebc4749..ae585ec2c7e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRVariable.qll @@ -176,7 +176,7 @@ IRTempVariable getIRTempVariable(Language::AST ast, TempVariableTag tag) { /** * A temporary variable introduced by IR construction. The most common examples are the variable - * generated to hold the return value of afunction, or the variable generated to hold the result of + * generated to hold the return value of a function, or the variable generated to hold the result of * a condition operator (`a ? b : c`). */ class IRTempVariable extends IRGeneratedVariable, IRAutomaticVariable, TIRTempVariable { From d5f689e04137d482c11a7e138a003cf0e52e258c Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Fri, 28 Feb 2020 10:42:27 -0800 Subject: [PATCH 141/236] Adds preliminary modernization --- python/ql/src/Expressions/CallToSuperWrongClass.ql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/python/ql/src/Expressions/CallToSuperWrongClass.ql b/python/ql/src/Expressions/CallToSuperWrongClass.ql index a42cbcefe4b..57ae36fa30e 100644 --- a/python/ql/src/Expressions/CallToSuperWrongClass.ql +++ b/python/ql/src/Expressions/CallToSuperWrongClass.ql @@ -17,13 +17,13 @@ import python from CallNode call_to_super, string name where exists(GlobalVariable gv, ControlFlowNode cn | - call_to_super = theSuperType().getACall() and + call_to_super = ClassValue::super_().getACall() and gv.getId() = "super" and cn = call_to_super.getArg(0) and name = call_to_super.getScope().getScope().(Class).getName() and - exists(ClassObject other | - cn.refersTo(other) and - not other.getPyClass().getName() = name + exists(ClassValue other | + cn.pointsTo(other) and + not other.getScope().getName() = name ) ) select call_to_super.getNode(), "First argument to super() should be " + name + "." From bbc57878dd8a9f037bacc7797ac224ed6258db10 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Mon, 2 Mar 2020 08:46:58 +0100 Subject: [PATCH 142/236] C++: Performance fix for large basic blocks The code is now quadratic in the number of statements in a basic block, whereas before it was quadratic in the number of _control-flow nodes_ in a basic block. --- cpp/ql/src/semmle/code/cpp/security/Overflow.qll | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll index be9dcf650cb..59993954cc8 100644 --- a/cpp/ql/src/semmle/code/cpp/security/Overflow.qll +++ b/cpp/ql/src/semmle/code/cpp/security/Overflow.qll @@ -13,12 +13,16 @@ predicate guardedAbs(Operation e, Expr use) { ) } +/** This is `BasicBlock.getNode`, restricted to `Stmt` for performance. */ +pragma[noinline] +private int getStmtIndexInBlock(BasicBlock block, Stmt stmt) { block.getNode(result) = stmt } + pragma[inline] private predicate stmtDominates(Stmt dominator, Stmt dominated) { // In same block exists(BasicBlock block, int dominatorIndex, int dominatedIndex | - block.getNode(dominatorIndex) = dominator and - block.getNode(dominatedIndex) = dominated and + dominatorIndex = getStmtIndexInBlock(block, dominator) and + dominatedIndex = getStmtIndexInBlock(block, dominated) and dominatedIndex >= dominatorIndex ) or From 30b5db3b7f05b5442ca258391c5a3d38e1a87a0e Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Mon, 2 Mar 2020 08:48:54 +0100 Subject: [PATCH 143/236] C++: autoformat fixup --- cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll index acef227b193..1640bee0f35 100644 --- a/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll +++ b/cpp/ql/src/semmle/code/cpp/stmts/Stmt.qll @@ -1613,7 +1613,9 @@ class EnumSwitch extends SwitchStmt { } pragma[noinline] - private predicate matchesValue(string value) { value = this.getASwitchCase().getExpr().getValue() } + private predicate matchesValue(string value) { + value = this.getASwitchCase().getExpr().getValue() + } } /** From c14a485ca798acbfd9d442ed1844a215e7fb49dc Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Fri, 28 Feb 2020 10:42:08 +0100 Subject: [PATCH 144/236] recognize more HttpResponseSink by restricting the `hasNonHtmlHeader` check --- .../src/semmle/javascript/frameworks/HTTP.qll | 2 +- .../javascript/frameworks/NodeJSLib.qll | 3 +- .../javascript/security/dataflow/Xss.qll | 56 +++++++++++++-- .../Security/CWE-079/ReflectedXss.expected | 36 ++++++++++ .../CWE-079/ReflectedXssContentTypes.js | 71 +++++++++++++++++++ 5 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js diff --git a/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll b/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll index 5eeda3df43b..fd28cd43666 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/HTTP.qll @@ -271,7 +271,7 @@ module HTTP { */ abstract class StandardRouteHandler extends RouteHandler { override HeaderDefinition getAResponseHeader(string name) { - result.(StandardHeaderDefinition).getRouteHandler() = this and + result.getRouteHandler() = this and result.getAHeaderName() = name } diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index cf2d34844b3..f1c10b1a9df 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -252,10 +252,11 @@ module NodeJSLib { private class WriteHead extends HeaderDefinition { WriteHead() { astNode.getMethodName() = "writeHead" and - astNode.getNumArgument() > 1 + astNode.getNumArgument() >= 1 } override predicate definesExplicitly(string headerName, Expr headerValue) { + astNode.getNumArgument() > 1 and exists(DataFlow::SourceNode headers, string header | headers.flowsToExpr(astNode.getLastArgument()) and headers.hasPropertyWrite(header, DataFlow::valueNode(headerValue)) and diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index fb0f94e84da..af1206432bf 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -305,18 +305,64 @@ module ReflectedXss { * a content type that does not (case-insensitively) contain the string "html". This * is to prevent us from flagging plain-text or JSON responses as vulnerable. */ - private class HttpResponseSink extends Sink, DataFlow::ValueNode { + class HttpResponseSink extends Sink, DataFlow::ValueNode { override HTTP::ResponseSendArgument astNode; - HttpResponseSink() { not nonHtmlContentType(astNode.getRouteHandler()) } + HttpResponseSink() { not exists(getAnonHtmlHeaderDefinition(astNode)) } + } + + /** + * Gets a HeaderDefinition that defines a non-html content-type for `send`. + */ + HTTP::HeaderDefinition getAnonHtmlHeaderDefinition(HTTP::ResponseSendArgument send) { + exists(HTTP::RouteHandler h | + send.getRouteHandler() = h and + result = nonHtmlContentTypeHeader(h) + | + // not the case that the control just exists without potentially going to the worksFor. + not isIrrelevantFor(result, send) + ) } /** * Holds if `h` may send a response with a content type other than HTML. */ - private predicate nonHtmlContentType(HTTP::RouteHandler h) { - exists(HTTP::HeaderDefinition hd | hd = h.getAResponseHeader("content-type") | - not exists(string tp | hd.defines("content-type", tp) | tp.regexpMatch("(?i).*html.*")) + HTTP::HeaderDefinition nonHtmlContentTypeHeader(HTTP::RouteHandler h) { + result = h.getAResponseHeader("content-type") and + not exists(string tp | result.defines("content-type", tp) | tp.regexpMatch("(?i).*html.*")) + } + + /** + * Holds if a header set in `header` is unlikely to affect a resonse send in `sender`. + */ + predicate isIrrelevantFor(HTTP::HeaderDefinition header, HTTP::ResponseSendArgument sender) { + not header.getBasicBlock().getASuccessor*() = sender.getBasicBlock() and + not sender.getBasicBlock().getASuccessor*() = header.getBasicBlock() and + ( + // If there is another header `otherHeader` next to `sender`, then `header` is probably irrelevant. + exists(HTTP::HeaderDefinition otherHeader | not header = otherHeader | + otherHeader.getBasicBlock().getASuccessor*() = sender.getBasicBlock() and + not otherHeader = nonHtmlContentTypeHeader(sender.getRouteHandler()) + ) + or + // Tries to recognize variants of: + // ``` + // response.writeHead(500, {'Content-Type': 'text/plain'}); + // response.end('Some error'); + // return; + // ``` + exists(ReachableBasicBlock headerBlock | headerBlock = header.getBasicBlock() | + headerBlock.getASuccessor() instanceof ControlFlowExitNode and + strictcount(headerBlock.getASuccessor()) = 1 and + not ( + exists(DataFlow::CallNode call | + exists(call.getACallee()) and + call.getBasicBlock() = headerBlock + ) + or + exists(Expr e | e.getBasicBlock() = headerBlock and e instanceof Function) + ) + ) ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected index e283a5676fa..56a3c4dc6ca 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXss.expected @@ -3,6 +3,22 @@ nodes | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | | ReflectedXss.js:8:33:8:45 | req.params.id | | ReflectedXss.js:8:33:8:45 | req.params.id | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | | etherpad.js:9:5:9:53 | response | | etherpad.js:9:16:9:30 | req.query.jsonp | | etherpad.js:9:16:9:30 | req.query.jsonp | @@ -75,6 +91,22 @@ 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 | | ReflectedXss.js:8:33:8:45 | req.params.id | ReflectedXss.js:8:14:8:45 | "Unknow ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | +| ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | | etherpad.js:9:5:9:53 | response | etherpad.js:11:12:11:19 | response | | etherpad.js:9:5:9:53 | response | etherpad.js:11:12:11:19 | response | | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:9:16:9:53 | req.que ... e + ")" | @@ -134,6 +166,10 @@ edges | tst2.js:14:9:14:9 | p | tst2.js:14:7:14: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 | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | user-provided value | | etherpad.js:11:12:11:19 | response | etherpad.js:9:16:9:30 | req.query.jsonp | etherpad.js:11:12:11:19 | response | Cross-site scripting vulnerability due to $@. | etherpad.js:9:16:9:30 | req.query.jsonp | user-provided value | | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | formatting.js:4:16:4:29 | req.query.evil | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js new file mode 100644 index 00000000000..bb930409a61 --- /dev/null +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js @@ -0,0 +1,71 @@ +var express = require('express'); +var app = express(); + +app.get('/user/:id', function (req, res) { + if (whatever) { + res.set('Content-Type', 'text/plain'); + res.send("FOO: " + req.params.id); // OK - content type is plain text + } else { + res.set('Content-Type', 'text/html'); + res.send("FOO: " + req.params.id); // NOT OK - content type is HTML. + } +}); + +app.get('/user/:id', function (req, res) { + if (whatever) { + res.writeHead(200, {'Content-Type': 'application/json'}); + res.send("FOO: " + req.params.id); // OK - content type is JSON + } else { + res.writeHead(404); + res.send("FOO: " + req.params.id); // NOT OK - content type is not set. + } +}); + + +app.get('/user/:id', function (req, res) { + res.writeHead(200, {'Content-Type': 'application/json'}); + if (whatever) { + res.send("FOO: " + req.params.id); // OK - content type is JSON + } else { + res.send("FOO: " + req.params.id); // OK - content type is still JSON + } + res.send("FOO: " + req.params.id); // OK - content type is still JSON +}); + + +app.get('/user/:id', function (req, res) { + if (err) { + res.statusCode = 404; + res.end("FOO: " + req.params.id); // NOT OK + } else { + res.setHeader('Content-Type', 'text/plain;charset=utf8'); + res.end("FOO: " + req.params.id); // OK + } +}); + +function textContentType() { + result = "text/plain"; +} + +app.get('/user/:id', function (req, res) { + if (err) { + res.header({'Content-Type': textContentType()}); + res.end("FOO: " + req.params.id); // OK + } else { + res.setHeader('Content-Type', 'text/plain;charset=utf8'); + res.end("FOO: " + req.params.id); // OK + } +}); + +app.get('/user/:id', function (req, res) { + if (err) { + res.writeHead(200, {'Content-Type': 'application/json'}); + res.send("FOO: " + req.params.id); // OK - content type is JSON + return; + } + doSomething(); + somethingMOre(); + while(Math.random()) {}; + res.writeHead(404); + res.send("FOO: " + req.params.id); // NOT OK - content type is not set. +}); \ No newline at end of file From e405a9769ccd95460ba6d6e8dcc856394bad7119 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Sun, 1 Mar 2020 10:33:53 +0000 Subject: [PATCH 145/236] JS: Really autoformat everything --- .../queries/dataflow/StoredXss/StoredXss.ql | 3 ++- .../dataflow/TaintedPathCustomizations.qll | 10 +++++++--- .../CallGraphs/AnnotatedTest/Test.ql | 4 +--- .../ql/test/library-tests/Classes/tests.ql | 2 +- .../CustomLoadStoreSteps/test.ql | 2 +- .../DependencyModuleImports/Test.ql | 5 ++--- .../library-tests/PartialInvokeNode/test.ql | 16 +++++++-------- .../ql/test/library-tests/Promises/tests.ql | 2 +- .../library-tests/RangeAnalysis/DeadBranch.ql | 4 ++-- .../RegExp/EscapeInString/EscapeInString.ql | 5 +++-- .../MissingUnicodeFlag/MissingUnicodeFlag.ql | 3 ++- .../getPredecessor.ql | 2 +- .../getSuccessor.ql | 2 +- .../TorrentLibraries/ParsedTorrent.ql | 2 +- .../UserControlledTorrentInfo.ql | 2 +- .../TorrentLibraries/parsedTorrentRef.ql | 2 +- .../TypeScript/CallSignatureTypes/test.ql | 8 ++++---- .../TypeScript/CallSignatures/test.ql | 18 ++++++++++++----- .../TypeScript/ImportMeta/test.ql | 2 +- .../TypeScript/Namespaces/CheckBindings.ql | 4 ++-- .../TypeScript/PathMapping/Imports.ql | 4 +--- .../TypeScript/PrivateFields/test.ql | 8 ++------ .../RegressionTests/ImportSelf/test.ql | 4 ++-- .../RegressionTests/TraceResolution/test.ql | 3 ++- .../TypeScript/Tokens/PrettyPrintTokens.ql | 3 ++- .../TypeScript/TypeAliases/TypeAliases.ql | 16 ++++----------- .../TypeScript/TypeOnlyImportExport/test.ql | 20 ++++++------------- .../frameworks/EventEmitter/test.ql | 5 +---- .../frameworks/LazyCache/LazyCache.ql | 4 +--- .../frameworks/WebSocket/test.ql | 2 +- .../frameworks/typeahead/test.ql | 10 +++------- .../CWE-020/UselessCharacterEscape.ql | 4 +--- .../CWE-079/XssWithAdditionalSources.ql | 1 - 33 files changed, 81 insertions(+), 101 deletions(-) diff --git a/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql b/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql index 5d7234ef504..2a00a49c020 100644 --- a/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql +++ b/javascript/ql/examples/queries/dataflow/StoredXss/StoredXss.ql @@ -22,7 +22,8 @@ import DataFlow::PathGraph */ class MysqlSource extends StoredXss::Source { MysqlSource() { - this = moduleImport("mysql") + this = + moduleImport("mysql") .getAMemberCall("createConnection") .getAMethodCall("query") .getCallback(1) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index dc749d6994c..2ed29813835 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -368,7 +368,7 @@ module TaintedPath { * // pathname is safe * } * ``` - * + * * or * ``` * var relative = path.resolve(pathname); // or path.normalize @@ -402,9 +402,13 @@ module TaintedPath { } override predicate blocks(boolean outcome, Expr e) { - member = "relative" and e = pathCall.getArgument(1).asExpr() and outcome = startsWith.getPolarity().booleanNot() + member = "relative" and + e = pathCall.getArgument(1).asExpr() and + outcome = startsWith.getPolarity().booleanNot() or - not member = "relative" and e = pathCall.getArgument(0).asExpr() and outcome = startsWith.getPolarity() + not member = "relative" and + e = pathCall.getArgument(0).asExpr() and + outcome = startsWith.getPolarity() } } diff --git a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql index dd41c794213..06b9671c2f5 100644 --- a/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql +++ b/javascript/ql/test/library-tests/CallGraphs/AnnotatedTest/Test.ql @@ -34,9 +34,7 @@ class AnnotatedCall extends InvokeExpr { AnnotatedFunction getAnExpectedCallee() { result.getCalleeName() = getCallTargetName() } - int getBoundArgs() { - result = getAnnotation(this, "boundArgs").toInt() - } + int getBoundArgs() { result = getAnnotation(this, "boundArgs").toInt() } int getBoundArgsOrMinusOne() { result = getBoundArgs() diff --git a/javascript/ql/test/library-tests/Classes/tests.ql b/javascript/ql/test/library-tests/Classes/tests.ql index 41fb13f3af0..5cb3c90db85 100644 --- a/javascript/ql/test/library-tests/Classes/tests.ql +++ b/javascript/ql/test/library-tests/Classes/tests.ql @@ -17,4 +17,4 @@ import ConstructorDefinitions import ClassNodeConstructor import ClassNodeInstanceMethod import PrivateField -import ClassFlow \ No newline at end of file +import ClassFlow diff --git a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql index e4ffc89294d..0bde5a737a6 100644 --- a/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql +++ b/javascript/ql/test/library-tests/CustomLoadStoreSteps/test.ql @@ -13,7 +13,7 @@ class Configuration extends TaintTracking::Configuration { // When the source code states that "foo" is being read, "bar" is additionally being read. override predicate isAdditionalLoadStep(DataFlow::Node pred, DataFlow::Node succ, string prop) { - exists(DataFlow::PropRead read | read = succ | + exists(DataFlow::PropRead read | read = succ | read.getBase() = pred and read.getPropertyName() = "foo" ) and diff --git a/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql b/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql index 9b8e63bf6d2..0552ff7b48a 100644 --- a/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql +++ b/javascript/ql/test/library-tests/DependencyModuleImports/Test.ql @@ -32,9 +32,8 @@ class SampleVersionSink extends DataFlow::Node { functionName = "m1" and argNumber = 0 | - this = DataFlow::dependencyModuleImport(dep) - .getAMemberCall(functionName) - .getArgument(argNumber) and + this = + DataFlow::dependencyModuleImport(dep).getAMemberCall(functionName).getArgument(argNumber) and dep.info(dependencyName, vDep) and vDep.maybeBetween(vFrom, vTo) ) diff --git a/javascript/ql/test/library-tests/PartialInvokeNode/test.ql b/javascript/ql/test/library-tests/PartialInvokeNode/test.ql index a7e5a010a05..1c2093d0dad 100644 --- a/javascript/ql/test/library-tests/PartialInvokeNode/test.ql +++ b/javascript/ql/test/library-tests/PartialInvokeNode/test.ql @@ -1,21 +1,21 @@ import javascript -query -DataFlow::Node getBoundFunction(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, int boundArgs) { +query DataFlow::Node getBoundFunction( + DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, int boundArgs +) { result = invoke.getBoundFunction(callback, boundArgs) } -query -predicate isPartialArgument(DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, DataFlow::Node argument, int index) { +query predicate isPartialArgument( + DataFlow::PartialInvokeNode invoke, DataFlow::Node callback, DataFlow::Node argument, int index +) { invoke.isPartialArgument(callback, argument, index) } -query -DataFlow::Node getBoundReceiver(DataFlow::PartialInvokeNode invoke) { +query DataFlow::Node getBoundReceiver(DataFlow::PartialInvokeNode invoke) { result = invoke.getBoundReceiver() } -query -DataFlow::Node clickEvent() { +query DataFlow::Node clickEvent() { result = DataFlow::globalVarRef("addEventListener").getACall().getABoundCallbackParameter(1, 0) } diff --git a/javascript/ql/test/library-tests/Promises/tests.ql b/javascript/ql/test/library-tests/Promises/tests.ql index 2490f378970..bcb41981fe0 100644 --- a/javascript/ql/test/library-tests/Promises/tests.ql +++ b/javascript/ql/test/library-tests/Promises/tests.ql @@ -7,4 +7,4 @@ import PromiseDefinition_getAResolveHandler import PromiseDefinition_getRejectParameter import PromiseDefinition_getResolveParameter import PromiseDefinition_getACatchHandler -import flow \ No newline at end of file +import flow diff --git a/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql b/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql index 8e6a798d737..d1554c34933 100644 --- a/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql +++ b/javascript/ql/test/library-tests/RangeAnalysis/DeadBranch.ql @@ -22,8 +22,8 @@ class AssertionComment extends LineComment { isOK = true and exists(ConditionGuardNode guard | guard = getAGuardNode() | RangeAnalysis::isContradictoryGuardNode(guard) and - result = "Error: analysis claims " + getTestExpr() + " is always " + - guard.getOutcome().booleanNot() + result = + "Error: analysis claims " + getTestExpr() + " is always " + guard.getOutcome().booleanNot() ) or isOK = false and diff --git a/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql b/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql index f824fe01920..22fcecfe3c6 100644 --- a/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql +++ b/javascript/ql/test/library-tests/RegExp/EscapeInString/EscapeInString.ql @@ -1,6 +1,7 @@ import javascript from StringLiteral literal, RegExpDot dot, int pos -where dot.getParent*() = literal - and pos = dot.getLocation().getStartColumn() - literal.getLocation().getStartColumn() +where + dot.getParent*() = literal and + pos = dot.getLocation().getStartColumn() - literal.getLocation().getStartColumn() select dot, literal.getRawValue().charAt(pos) diff --git a/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql b/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql index 80c214b41d6..e8d47d789f3 100644 --- a/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql +++ b/javascript/ql/test/library-tests/RegExp/MissingUnicodeFlag/MissingUnicodeFlag.ql @@ -1,7 +1,8 @@ import javascript from RegExpLiteral literal, RegExpConstant wideConstant -where wideConstant.getLiteral() = literal and +where + wideConstant.getLiteral() = literal and not literal.getFlags().matches("%u%") and wideConstant.getValue().length() > 1 and ( diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql index c356564dc7f..c38742da802 100644 --- a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getPredecessor.ql @@ -2,4 +2,4 @@ import javascript from RegExpTerm pred, RegExpTerm succ where pred = succ.getPredecessor() -select pred, succ \ No newline at end of file +select pred, succ diff --git a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql index 49916f8c426..486a874c975 100644 --- a/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql +++ b/javascript/ql/test/library-tests/RegExp/predecessors_and_successors/getSuccessor.ql @@ -2,4 +2,4 @@ import javascript from RegExpTerm pred, RegExpTerm succ where succ = pred.getSuccessor() -select pred, succ \ No newline at end of file +select pred, succ diff --git a/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql b/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql index 1ad59af1d8b..eb50d8d4414 100644 --- a/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql +++ b/javascript/ql/test/library-tests/TorrentLibraries/ParsedTorrent.ql @@ -1,3 +1,3 @@ import javascript -select any(ParseTorrent::ParsedTorrent t) \ No newline at end of file +select any(ParseTorrent::ParsedTorrent t) diff --git a/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql b/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql index 85d08e75896..c18323953bf 100644 --- a/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql +++ b/javascript/ql/test/library-tests/TorrentLibraries/UserControlledTorrentInfo.ql @@ -1,3 +1,3 @@ import javascript -select any(ParseTorrent::UserControlledTorrentInfo i) \ No newline at end of file +select any(ParseTorrent::UserControlledTorrentInfo i) diff --git a/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql b/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql index e72bde720f3..69701796383 100644 --- a/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql +++ b/javascript/ql/test/library-tests/TorrentLibraries/parsedTorrentRef.ql @@ -1,3 +1,3 @@ import javascript -select ParseTorrent::parsedTorrentRef() \ No newline at end of file +select ParseTorrent::parsedTorrentRef() diff --git a/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql b/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql index 57105b7de6b..03cc288f054 100644 --- a/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/CallSignatureTypes/test.ql @@ -13,7 +13,9 @@ query predicate test_ExprSignature(Expr expr, string type) { type = getASignatureOrElseType(expr.getType()) } -query predicate test_TypeReferenceSig(TypeReference type, SignatureKind kind, int n, CallSignatureType sig) { +query predicate test_TypeReferenceSig( + TypeReference type, SignatureKind kind, int n, CallSignatureType sig +) { sig = type.getSignature(kind, n) } @@ -21,9 +23,7 @@ query predicate test_FunctionCallSig(Function f, CallSignatureType sig) { sig = f.getCallSignature() } -query Type test_getRestParameterType(CallSignatureType sig) { - result = sig.getRestParameterType() -} +query Type test_getRestParameterType(CallSignatureType sig) { result = sig.getRestParameterType() } query Type test_getRestParameterArray(CallSignatureType sig) { result = sig.getRestParameterArrayType() diff --git a/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql b/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql index f3d79f91feb..e1254629fe1 100644 --- a/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/CallSignatures/test.ql @@ -1,12 +1,16 @@ import javascript -query predicate test_CallSignature(CallSignature call, string declType, ASTNode body, string abstractness) { +query predicate test_CallSignature( + CallSignature call, string declType, ASTNode body, string abstractness +) { (if call.isAbstract() then abstractness = "abstract" else abstractness = "not abstract") and declType = call.getDeclaringType().describe() and body = call.getBody() } -query predicate test_IndexSignature(IndexSignature sig, string declType, ASTNode body, string abstractness) { +query predicate test_IndexSignature( + IndexSignature sig, string declType, ASTNode body, string abstractness +) { (if sig.isAbstract() then abstractness = "abstract" else abstractness = "not abstract") and declType = sig.getDeclaringType().describe() and body = sig.getBody() @@ -21,12 +25,16 @@ query predicate test_MethodOverload(MethodDeclaration method, int index, boolean if method.isOverloaded() then overloaded = true else overloaded = false } -query predicate test_FunctionCallSigOverload(FunctionCallSignature sig, int index, boolean overloaded) { +query predicate test_FunctionCallSigOverload( + FunctionCallSignature sig, int index, boolean overloaded +) { index = sig.getOverloadIndex() and if sig.isOverloaded() then overloaded = true else overloaded = false } -query predicate test_ConstructorCallSigOverload(ConstructorCallSignature sig, int index, boolean overloaded) { +query predicate test_ConstructorCallSigOverload( + ConstructorCallSignature sig, int index, boolean overloaded +) { index = sig.getOverloadIndex() and if sig.isOverloaded() then overloaded = true else overloaded = false -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql b/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql index 311aa4b4050..3753c8bab98 100644 --- a/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/ImportMeta/test.ql @@ -2,4 +2,4 @@ import javascript from Expr prop where prop instanceof ImportMetaExpr -select prop \ No newline at end of file +select prop diff --git a/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql b/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql index a3c29fc4023..1e313974e97 100644 --- a/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql +++ b/javascript/ql/test/library-tests/TypeScript/Namespaces/CheckBindings.ql @@ -20,8 +20,8 @@ string getNamespaceName(NamespaceDeclaration decl) { result = decl.getStmt(0).(ExprStmt).getExpr().getStringValue() or not decl.getStmt(0).(ExprStmt).getExpr() instanceof ConstantString and - result = "Namespace " + decl.getId() + " on line " + - decl.getFirstToken().getLocation().getStartLine() + result = + "Namespace " + decl.getId() + " on line " + decl.getFirstToken().getLocation().getStartLine() } from ResolveCall resolve diff --git a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql index bd272eee884..a271f821ac4 100644 --- a/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql +++ b/javascript/ql/test/library-tests/TypeScript/PathMapping/Imports.ql @@ -1,8 +1,6 @@ import javascript -query predicate symbols(ASTNode astNode, CanonicalName symbol) { - ast_node_symbol(astNode, symbol) -} +query predicate symbols(ASTNode astNode, CanonicalName symbol) { ast_node_symbol(astNode, symbol) } from Import imprt select imprt, imprt.getImportedModule() diff --git a/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql index 27147fcb461..bf1f61039f5 100644 --- a/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/PrivateFields/test.ql @@ -1,9 +1,5 @@ import javascript -query PropAccess propAccess(string name) { - result.getPropertyName() = name -} +query PropAccess propAccess(string name) { result.getPropertyName() = name } -query FieldDeclaration fieldDecl(string name) { - result.getName() = name -} +query FieldDeclaration fieldDecl(string name) { result.getName() = name } diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql index 267a0e32c7a..d194e75f71a 100644 --- a/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/ImportSelf/test.ql @@ -1,5 +1,5 @@ import javascript // We're mainly testing extraction succeeds, so just test that some types are extracted. - -from Expr e select e, e.getType() +from Expr e +select e, e.getType() diff --git a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql index 9b7f3a42e02..574b7c54d4e 100644 --- a/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/RegressionTests/TraceResolution/test.ql @@ -1,3 +1,4 @@ import javascript -from Expr e select e, e.getType() +from Expr e +select e, e.getType() diff --git a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql index 1f5491fc28c..c040343b2ad 100644 --- a/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql +++ b/javascript/ql/test/library-tests/TypeScript/Tokens/PrettyPrintTokens.ql @@ -7,7 +7,8 @@ Token getATokenAtLine(File file, int line) { bindingset[line] string getTokenStringAtLine(File file, int line) { - result = concat(Token tok | + result = + concat(Token tok | tok = getATokenAtLine(file, line) | tok.toString().replaceAll(" ", "~") + " " order by tok.getLocation().getStartColumn() diff --git a/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql b/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql index 60b404be4b3..e8133898ff2 100644 --- a/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql +++ b/javascript/ql/test/library-tests/TypeScript/TypeAliases/TypeAliases.ql @@ -3,18 +3,10 @@ import javascript from TypeAliasDeclaration decl select decl, decl.getIdentifier(), decl.getNumTypeParameter(), decl.getDefinition() -query Type rightHandSide(TypeAliasDeclaration decl) { - result = decl.getDefinition().getType() -} +query Type rightHandSide(TypeAliasDeclaration decl) { result = decl.getDefinition().getType() } -query Type getAliasedType(TypeAliasReference ref) { - result = ref.getAliasedType() -} +query Type getAliasedType(TypeAliasReference ref) { result = ref.getAliasedType() } -query Type getTypeArgument(TypeAliasReference ref, int n) { - result = ref.getTypeArgument(n) -} +query Type getTypeArgument(TypeAliasReference ref, int n) { result = ref.getTypeArgument(n) } -query Type unfold(TypeAliasReference t) { - result = t.unfold() -} +query Type unfold(TypeAliasReference t) { result = t.unfold() } diff --git a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql index 17939b3b380..df364791e6c 100644 --- a/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql +++ b/javascript/ql/test/library-tests/TypeScript/TypeOnlyImportExport/test.ql @@ -1,22 +1,14 @@ import javascript -query VarRef getAVarReference(Variable v) { - result = v.getAReference() -} +query VarRef getAVarReference(Variable v) { result = v.getAReference() } -query VarRef getAnExportAccess(LocalTypeName t) { - result = t.getAnExportAccess() -} +query VarRef getAnExportAccess(LocalTypeName t) { result = t.getAnExportAccess() } -query TypeDecl getATypeDecl(LocalTypeName t) { - result = t.getADeclaration() -} +query TypeDecl getATypeDecl(LocalTypeName t) { result = t.getADeclaration() } -query Function calls(DataFlow::InvokeNode invoke) { - result = invoke.getACallee() -} +query Function calls(DataFlow::InvokeNode invoke) { result = invoke.getACallee() } query predicate exportsAs(ExportDeclaration exprt, LexicalName v, string name, string kind) { - exprt.exportsAs(v, name) and - kind = v.getDeclarationSpace() + exprt.exportsAs(v, name) and + kind = v.getDeclarationSpace() } diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql index 846536672b1..e8de4ff4c53 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.ql @@ -1,8 +1,5 @@ import javascript query predicate taintSteps(DataFlow::Node pred, DataFlow::Node succ) { - exists(DataFlow::AdditionalFlowStep step | - step.step(pred, succ) - ) + exists(DataFlow::AdditionalFlowStep step | step.step(pred, succ)) } - diff --git a/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql b/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql index bca53d0f163..95c42bf99eb 100644 --- a/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql +++ b/javascript/ql/test/library-tests/frameworks/LazyCache/LazyCache.ql @@ -1,5 +1,3 @@ import javascript -query DataFlow::Node moduleImport(string name) { - result = DataFlow::moduleImport(name) -} +query DataFlow::Node moduleImport(string name) { result = DataFlow::moduleImport(name) } diff --git a/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql b/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql index 5cb76ca7ec2..e7b9d451a55 100644 --- a/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql +++ b/javascript/ql/test/library-tests/frameworks/WebSocket/test.ql @@ -14,4 +14,4 @@ query ServerWebSocket::ReceiveNode serverReceive() { any() } query predicate taintStep(DataFlow::Node pred, DataFlow::Node succ) { any(DataFlow::AdditionalFlowStep s).step(pred, succ) -} \ No newline at end of file +} diff --git a/javascript/ql/test/library-tests/frameworks/typeahead/test.ql b/javascript/ql/test/library-tests/frameworks/typeahead/test.ql index 4b444d66bd0..29b76a1851e 100644 --- a/javascript/ql/test/library-tests/frameworks/typeahead/test.ql +++ b/javascript/ql/test/library-tests/frameworks/typeahead/test.ql @@ -1,13 +1,9 @@ import javascript -query DataFlow::Node url() { - result = any(ClientRequest r).getUrl() -} +query DataFlow::Node url() { result = any(ClientRequest r).getUrl() } query DataFlow::Node response(string responseType, boolean promise) { - result = any(ClientRequest r).getAResponseDataNode(responseType, promise) + result = any(ClientRequest r).getAResponseDataNode(responseType, promise) } -query DataFlow::Node suggestionFunction() { - result = any(Typeahead::TypeaheadSuggestionFunction t) -} \ No newline at end of file +query DataFlow::Node suggestionFunction() { result = any(Typeahead::TypeaheadSuggestionFunction t) } diff --git a/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql b/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql index ea18eafe350..9e4f8d9f67f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql +++ b/javascript/ql/test/query-tests/Security/CWE-020/UselessCharacterEscape.ql @@ -6,6 +6,4 @@ where char = getAnIdentityEscapedCharacter(n, _, _) and not hasALikelyRegExpPatternMistake(n) and not char = "\n" // ignore escaped newlines in multiline strings -select n, - "The escape sequence '\\" + char + "' is equivalent to just '" + - char + "'." +select n, "The escape sequence '\\" + char + "' is equivalent to just '" + char + "'." diff --git a/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql b/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql index 543c2537253..96dee32082b 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql +++ b/javascript/ql/test/query-tests/Security/CWE-079/XssWithAdditionalSources.ql @@ -16,7 +16,6 @@ import semmle.javascript.security.dataflow.DomBasedXss::DomBasedXss import DataFlow::PathGraph import semmle.javascript.heuristics.AdditionalSources - from Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink where cfg.hasFlowPath(source, sink) and source.getNode() instanceof HeuristicSource select sink.getNode(), source, sink, From 14d836ba59909c64a7c00f9508572314c5bed5e7 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 2 Mar 2020 12:15:28 +0100 Subject: [PATCH 146/236] C++: should only match those functions that has the same number of parameters as the call has arguments. --- .../code/cpp/ir/dataflow/internal/DataFlowDispatch.qll | 8 +++++++- .../cpp/ir/implementation/aliased_ssa/Instruction.qll | 5 +++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll index f1d8c21595e..bbc6d165600 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll @@ -22,7 +22,13 @@ Function viableCallable(CallInstruction call) { ) or // Virtual dispatch - result = call.(VirtualDispatch::DataSensitiveCall).resolve() + result = call.(VirtualDispatch::DataSensitiveCall).resolve() and + ( + call.getNumberOfArguments() <= result.getEffectiveNumberOfParameters() and + call.getNumberOfArguments() >= result.getEffectiveNumberOfParameters() + or + result.isVarargs() + ) } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index d14cdf08910..2d414e0c46e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -1202,6 +1202,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** From 20529b4436e53299a17cc537431cb61b56db9943 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 2 Mar 2020 12:15:54 +0100 Subject: [PATCH 147/236] C++/C#: Sync identical files --- .../semmle/code/cpp/ir/implementation/raw/Instruction.qll | 5 +++++ .../code/cpp/ir/implementation/unaliased_ssa/Instruction.qll | 5 +++++ .../semmle/code/csharp/ir/implementation/raw/Instruction.qll | 5 +++++ .../csharp/ir/implementation/unaliased_ssa/Instruction.qll | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index d14cdf08910..2d414e0c46e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -1202,6 +1202,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index d14cdf08910..2d414e0c46e 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1202,6 +1202,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll index d14cdf08910..2d414e0c46e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll @@ -1202,6 +1202,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll index d14cdf08910..2d414e0c46e 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll @@ -1202,6 +1202,11 @@ class CallInstruction extends Instruction { final Instruction getPositionalArgument(int index) { result = getPositionalArgumentOperand(index).getDef() } + + /** + * Gets the number of arguments of the call, including the `this` pointer, if any. + */ + final int getNumberOfArguments() { result = count(this.getAnArgumentOperand()) } } /** From 391b6a833c2927f460526f6d7ce759caade35b14 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 12:28:51 +0100 Subject: [PATCH 148/236] add link to The Useless Use of Cat Award --- javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp index aa8acaee700..5ef218bdf59 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.qhelp @@ -36,10 +36,9 @@ and if the input is user-controlled, a command injection attack can happen.

    -
  • -OWASP: Command Injection. -Node.js: File System API. -
  • +
  • OWASP: Command Injection.
  • +
  • Node.js: File System API.
  • +
  • The Useless Use of Cat Award.
  • From 26fd17bf39445409a2c7fbdab4e962015ff98206 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 12:47:43 +0100 Subject: [PATCH 149/236] recognize utility functions implementing a StartsWith check --- .../ql/src/semmle/javascript/StringOps.qll | 32 ++++++++++ .../CWE-022/TaintedPath/TaintedPath.expected | 60 +++++++++++++++++++ .../CWE-022/TaintedPath/normalizedPaths.js | 23 +++++++ 3 files changed, 115 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index bfa2c84897a..f558cd40f56 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -56,6 +56,38 @@ module StringOps { boolean getPolarity() { result = true } } + /** + * A call to a utility function (`callee`) that performs a StartsWith check (`inner`). + */ + private class IndirectStartsWith extends Range, DataFlow::CallNode { + StartsWith inner; + Function callee; + + IndirectStartsWith() { + inner.getEnclosingExpr() = callee.getAReturnedExpr() and + this.getACallee() = callee and + count(this.getACallee()) = 1 and + count(callee.getAReturnedExpr()) = 1 and + not this.isImprecise() and + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter() + } + + override DataFlow::Node getBaseString() { + exists(int arg | + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override DataFlow::Node getSubstring() { + exists(int arg | + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + } + /** * An expression of form `A.startsWith(B)`. */ diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 8b22a2fc186..3e87e7f75b6 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1654,6 +1654,33 @@ nodes | normalizedPaths.js:346:19:346:22 | path | | normalizedPaths.js:346:19:346:22 | path | | normalizedPaths.js:346:19:346:22 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:354:14:354:27 | req.query.path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:363:21:363:31 | requestPath | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-require.js:7:19:7:37 | req.param("module") | @@ -4582,6 +4609,37 @@ edges | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:339:13:339:46 | pathMod ... y.path) | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:356:19:356:22 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:354:7:354:27 | path | normalizedPaths.js:358:47:358:50 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:354:7:354:27 | path | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:7:358:51 | requestPath | normalizedPaths.js:363:21:363:31 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:21:358:51 | pathMod ... , path) | normalizedPaths.js:358:7:358:51 | requestPath | +| normalizedPaths.js:358:47:358:50 | path | normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:47:358:50 | path | normalizedPaths.js:358:21:358:51 | pathMod ... , path) | +| normalizedPaths.js:358:47:358:50 | path | normalizedPaths.js:358:21:358:51 | pathMod ... , path) | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | @@ -5464,6 +5522,8 @@ edges | normalizedPaths.js:332:19:332:32 | normalizedPath | normalizedPaths.js:303:13:303:26 | req.query.path | normalizedPaths.js:332:19:332:32 | normalizedPath | This path depends on $@. | normalizedPaths.js:303:13:303:26 | req.query.path | a user-provided value | | normalizedPaths.js:341:18:341:21 | path | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:341:18:341:21 | path | This path depends on $@. | normalizedPaths.js:339:32:339:45 | req.query.path | a user-provided value | | normalizedPaths.js:346:19:346:22 | path | normalizedPaths.js:339:32:339:45 | req.query.path | normalizedPaths.js:346:19:346:22 | path | This path depends on $@. | normalizedPaths.js:339:32:339:45 | req.query.path | a user-provided value | +| normalizedPaths.js:356:19:356:22 | path | normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:356:19:356:22 | path | This path depends on $@. | normalizedPaths.js:354:14:354:27 | req.query.path | a user-provided value | +| normalizedPaths.js:363:21:363:31 | requestPath | normalizedPaths.js:354:14:354:27 | req.query.path | normalizedPaths.js:363:21:363:31 | requestPath | This path depends on $@. | normalizedPaths.js:354:14:354:27 | req.query.path | a user-provided value | | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | tainted-require.js:7:19:7:37 | req.param("module") | This path depends on $@. | tainted-require.js:7:19:7:37 | req.param("module") | a user-provided value | | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:8:16:8:33 | req.param("gimme") | a user-provided value | | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | This path depends on $@. | tainted-sendFile.js:10:16:10:33 | req.param("gimme") | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js index cdbdf84b966..92a8841470a 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/normalizedPaths.js @@ -347,4 +347,27 @@ app.get('/yet-another-prefix', (req, res) => { return; } fs.readFileSync(path); // OK +}); + +var rootPath = process.cwd(); +app.get('/yet-another-prefix2', (req, res) => { + let path = req.query.path; + + fs.readFileSync(path); // NOT OK + + var requestPath = pathModule.join(rootPath, path); + + var targetPath; + if (!allowPath(requestPath, rootPath)) { + targetPath = rootPath; + fs.readFileSync(requestPath); // NOT OK + } else { + targetPath = requestPath; + fs.readFileSync(requestPath); // OK + } + fs.readFileSync(targetPath); // OK + + function allowPath(requestPath, rootPath) { + return requestPath.indexOf(rootPath) === 0; + } }); \ No newline at end of file From 019266e5370a5d5f978900f964543d2a552335f1 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 13:06:08 +0100 Subject: [PATCH 150/236] change name of Useless cat --- change-notes/1.24/analysis-javascript.md | 2 +- javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index abd07271140..dad8caf09bb 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -46,7 +46,7 @@ | Polynomial regular expression used on uncontrolled data (`js/polynomial-redos`) | security, external/cwe/cwe-730, external/cwe/cwe-400 | Highlights expensive regular expressions that may be used on malicious input. Results are shown on LGTM by default. | | Prototype pollution in utility function (`js/prototype-pollution-utility`) | security, external/cwe/cwe-400, external/cwe/cwe-471 | Highlights recursive copying operations that are susceptible to prototype pollution. Results are shown on LGTM by default. | | Unsafe jQuery plugin (`js/unsafe-jquery-plugin`) | Highlights potential XSS vulnerabilities in unsafely designed jQuery plugins. Results are shown on LGTM by default. | -| Useless use of cat (`js/useless-use-of-cat`) | correctness, security, maintainability | Highlights command executions of `cat` where the fs API should be used instead. Results are shown on LGTM by default. | +| Unnecessary use of `cat` process (`js/unnecessary-use-of-cat`) | correctness, security, maintainability | Highlights command executions of `cat` where the fs API should be used instead. Results are shown on LGTM by default. | ## Changes to existing queries diff --git a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql index 0c33fc8c06f..6b0ed59e632 100644 --- a/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/src/Security/CWE-078/UselessUseOfCat.ql @@ -1,10 +1,10 @@ /** - * @name Useless use of cat + * @name Unnecessary use of `cat` process * @description Using the `cat` process to read a file is unnecessarily complex, inefficient, unportable, and can lead to subtle bugs, or even security vulnerabilities. * @kind problem * @problem.severity error * @precision high - * @id js/useless-use-of-cat + * @id js/unnecessary-use-of-cat * @tags correctness * security * maintainability @@ -22,4 +22,4 @@ where if cat.isSync() then message = " Can be replaced with a call to fs.readFileSync(..)." else message = " Can be replaced with a call to fs.readFile(..)." -select cat.asExpr().(FirstLineOf), "Useless use of `cat`." + message +select cat.asExpr().(FirstLineOf), "Unnecessary use of `cat` process." + message From 76066afe6a5a2b37ae8c024cb6cf149072313560 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Mon, 2 Mar 2020 13:49:12 +0100 Subject: [PATCH 151/236] C++: Add getCanonicalQLClass overrides in Variable --- cpp/ql/src/semmle/code/cpp/Field.qll | 4 ++++ cpp/ql/src/semmle/code/cpp/Variable.qll | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/cpp/ql/src/semmle/code/cpp/Field.qll b/cpp/ql/src/semmle/code/cpp/Field.qll index de52fd79788..eb864a16063 100644 --- a/cpp/ql/src/semmle/code/cpp/Field.qll +++ b/cpp/ql/src/semmle/code/cpp/Field.qll @@ -19,6 +19,8 @@ import semmle.code.cpp.exprs.Access class Field extends MemberVariable { Field() { fieldoffsets(underlyingElement(this), _, _) } + override string getCanonicalQLClass() { result = "Field" } + /** * Gets the offset of this field in bytes from the start of its declaring * type (on the machine where facts were extracted). @@ -84,6 +86,8 @@ class Field extends MemberVariable { class BitField extends Field { BitField() { bitfield(underlyingElement(this), _, _) } + override string getCanonicalQLClass() { result = "BitField" } + /** * Gets the size of this bitfield in bits (on the machine where facts * were extracted). diff --git a/cpp/ql/src/semmle/code/cpp/Variable.qll b/cpp/ql/src/semmle/code/cpp/Variable.qll index cf85f1f9fd2..8e4a5d73664 100644 --- a/cpp/ql/src/semmle/code/cpp/Variable.qll +++ b/cpp/ql/src/semmle/code/cpp/Variable.qll @@ -28,6 +28,8 @@ private import semmle.code.cpp.internal.ResolveClass * can have multiple declarations. */ class Variable extends Declaration, @variable { + override string getCanonicalQLClass() { result = "Variable" } + /** Gets the initializer of this variable, if any. */ Initializer getInitializer() { result.getDeclaration() = this } @@ -351,6 +353,8 @@ class StackVariable extends LocalScopeVariable { * A local variable can be declared by a `DeclStmt` or a `ConditionDeclExpr`. */ class LocalVariable extends LocalScopeVariable, @localvariable { + override string getCanonicalQLClass() { result = "LocalVariable" } + override string getName() { localvariables(underlyingElement(this), _, result) } override Type getType() { localvariables(underlyingElement(this), unresolveElement(result), _) } @@ -396,6 +400,8 @@ class NamespaceVariable extends GlobalOrNamespaceVariable { NamespaceVariable() { exists(Namespace n | namespacembrs(unresolveElement(n), underlyingElement(this))) } + + override string getCanonicalQLClass() { result = "NamespaceVariable" } } /** @@ -415,6 +421,8 @@ class NamespaceVariable extends GlobalOrNamespaceVariable { */ class GlobalVariable extends GlobalOrNamespaceVariable { GlobalVariable() { not this instanceof NamespaceVariable } + + override string getCanonicalQLClass() { result = "GlobalVariable" } } /** @@ -434,6 +442,8 @@ class GlobalVariable extends GlobalOrNamespaceVariable { class MemberVariable extends Variable, @membervariable { MemberVariable() { this.isMember() } + override string getCanonicalQLClass() { result = "MemberVariable" } + /** Holds if this member is private. */ predicate isPrivate() { this.hasSpecifier("private") } From 0b082a408961fced579bc617f64b3a72e11902ac Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 2 Mar 2020 16:22:05 +0100 Subject: [PATCH 152/236] C++: Only do argument check for --- .../cpp/ir/dataflow/internal/DataFlowDispatch.qll | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll index bbc6d165600..abb62b9a021 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll @@ -22,13 +22,7 @@ Function viableCallable(CallInstruction call) { ) or // Virtual dispatch - result = call.(VirtualDispatch::DataSensitiveCall).resolve() and - ( - call.getNumberOfArguments() <= result.getEffectiveNumberOfParameters() and - call.getNumberOfArguments() >= result.getEffectiveNumberOfParameters() - or - result.isVarargs() - ) + result = call.(VirtualDispatch::DataSensitiveCall).resolve() } /** @@ -141,6 +135,12 @@ private module VirtualDispatch { exists(FunctionInstruction fi | this.flowsFrom(DataFlow::instructionNode(fi), _) and result = fi.getFunctionSymbol() + ) and + ( + this.getNumberOfArguments() <= result.getEffectiveNumberOfParameters() and + this.getNumberOfArguments() >= result.getEffectiveNumberOfParameters() + or + result.isVarargs() ) } } From e0fcc4af6a6e569238068b724893b10fbd979e71 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 16:23:58 +0100 Subject: [PATCH 153/236] handle this parameters when finding unreachable overloads --- .../ql/src/Declarations/UnreachableMethodOverloads.ql | 7 +++++++ .../UnreachableMethodOverloads.expected | 2 +- .../query-tests/Declarations/UnreachableOverloads/tst.ts | 3 +++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql index befe4914032..8d50cb48b99 100644 --- a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql +++ b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql @@ -100,6 +100,13 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) { method.getName() = other.getName() and // same number of parameters. method.getBody().getNumParameter() = other.getBody().getNumParameter() and + // same this parameter (if exists) + ( + not exists(method.getBody().getThisTypeAnnotation()) and + not exists(other.getBody().getThisTypeAnnotation()) + or + method.getBody().getThisTypeAnnotation() = other.getBody().getThisTypeAnnotation() + ) and // The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar. forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] | getParameterTypeAnnotation(method, i) = getParameterTypeAnnotation(other, i) diff --git a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected index a8104985696..afa9530ebbf 100644 --- a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected +++ b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected @@ -1,4 +1,4 @@ | tst.ts:3:3:3:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:2:3:2:30 | method( ... string; | previous | | tst.ts:6:3:6:17 | types1(): any[] | This overload of types1() is unreachable, the $@ overload will always be selected. | tst.ts:5:3:5:18 | types1(): T[] | previous | | tst.ts:15:3:15:74 | on(even ... nction; | This overload of on() is unreachable, the $@ overload will always be selected. | tst.ts:14:3:14:74 | on(even ... nction; | previous | -| tst.ts:21:3:21:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:20:3:20:30 | method( ... string; | previous | +| tst.ts:24:3:24:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:23:3:23:30 | method( ... string; | previous | diff --git a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts index 21abdf16966..ec14abbf78f 100644 --- a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts +++ b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts @@ -14,6 +14,9 @@ declare class Foobar { on(event: string, fn?: (event?: any, ...args: any[]) => void): Function; on(event: string, fn?: (event?: any, ...args: any[]) => void): Function; // NOT OK. + foo(this: string): string; + foo(this: number): number; // OK + } declare class Base { From 68fb8c52e9413d033277e825246e6c789cb63d58 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 16:35:16 +0100 Subject: [PATCH 154/236] check the type of the this-type, instead of the AST-node --- javascript/ql/src/Declarations/UnreachableMethodOverloads.ql | 2 +- .../UnreachableOverloads/UnreachableMethodOverloads.expected | 3 ++- .../test/query-tests/Declarations/UnreachableOverloads/tst.ts | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql index 8d50cb48b99..d3c5a54bf2d 100644 --- a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql +++ b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql @@ -105,7 +105,7 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) { not exists(method.getBody().getThisTypeAnnotation()) and not exists(other.getBody().getThisTypeAnnotation()) or - method.getBody().getThisTypeAnnotation() = other.getBody().getThisTypeAnnotation() + method.getBody().getThisTypeAnnotation().getType() = other.getBody().getThisTypeAnnotation().getType() ) and // The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar. forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] | diff --git a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected index afa9530ebbf..44bd568e767 100644 --- a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected +++ b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/UnreachableMethodOverloads.expected @@ -1,4 +1,5 @@ | tst.ts:3:3:3:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:2:3:2:30 | method( ... string; | previous | | tst.ts:6:3:6:17 | types1(): any[] | This overload of types1() is unreachable, the $@ overload will always be selected. | tst.ts:5:3:5:18 | types1(): T[] | previous | | tst.ts:15:3:15:74 | on(even ... nction; | This overload of on() is unreachable, the $@ overload will always be selected. | tst.ts:14:3:14:74 | on(even ... nction; | previous | -| tst.ts:24:3:24:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:23:3:23:30 | method( ... string; | previous | +| tst.ts:21:3:21:28 | bar(thi ... number; | This overload of bar() is unreachable, the $@ overload will always be selected. | tst.ts:20:3:20:28 | bar(thi ... string; | previous | +| tst.ts:27:3:27:30 | method( ... number; | This overload of method() is unreachable, the $@ overload will always be selected. | tst.ts:26:3:26:30 | method( ... string; | previous | diff --git a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts index ec14abbf78f..9455c9a161b 100644 --- a/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts +++ b/javascript/ql/test/query-tests/Declarations/UnreachableOverloads/tst.ts @@ -17,6 +17,9 @@ declare class Foobar { foo(this: string): string; foo(this: number): number; // OK + bar(this: number): string; + bar(this: number): number; // NOT OK + } declare class Base { From 53d1cd33f6d2b4a242c15bf9323e3322a5653661 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 21:05:25 +0100 Subject: [PATCH 155/236] support sanitizers that remove all forward slashes --- .../dataflow/TaintedPathCustomizations.qll | 21 +- .../CWE-022/TaintedPath/TaintedPath.expected | 231 ++++++++++++++++++ .../CWE-022/TaintedPath/TaintedPath.js | 12 + 3 files changed, 257 insertions(+), 7 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index 2ed29813835..1f5b2f96727 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -213,17 +213,24 @@ module TaintedPath { output = this ) or - // non-global replace or replace of something other than /\.\./g + // non-global replace or replace of something other than /\.\./g, /[/]/g, or /[\.]/g. this.getCalleeName() = "replace" and input = getReceiver() and output = this and - not exists(RegExpLiteral literal, RegExpSequence seq | + not exists(RegExpLiteral literal | getArgument(0).getALocalSource().asExpr() = literal and - literal.isGlobal() and - literal.getRoot() = seq and - seq.getChild(0).(RegExpConstant).getValue() = "." and - seq.getChild(1).(RegExpConstant).getValue() = "." and - seq.getNumChild() = 2 + literal.isGlobal() + | + exists(RegExpSequence seq | literal.getRoot() = seq | + seq.getChild(0).(RegExpConstant).getValue() = "." and + seq.getChild(1).(RegExpConstant).getValue() = "." and + seq.getNumChild() = 2 + ) + or + exists(RegExpCharacterClass choice | literal.getRoot() = choice | + choice.getAMatchedString() = "/" or + choice.getAMatchedString() = "." + ) ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index 8b22a2fc186..de3de113811 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1123,6 +1123,108 @@ nodes | TaintedPath.js:168:19:168:29 | split.pop() | | TaintedPath.js:168:19:168:29 | split.pop() | | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:173:24:173:30 | req.url | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | @@ -3951,6 +4053,134 @@ edges | TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | | TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | | TaintedPath.js:168:19:168:23 | split | TaintedPath.js:168:19:168:29 | split.pop() | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:43 | url.par ... ).query | TaintedPath.js:173:14:173:48 | url.par ... ry.path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:14:173:48 | url.par ... ry.path | TaintedPath.js:173:7:173:48 | path | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:173:14:173:37 | url.par ... , true) | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | @@ -5409,6 +5639,7 @@ edges | TaintedPath.js:163:19:163:37 | concatted.join("/") | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:163:19:163:37 | concatted.join("/") | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | | TaintedPath.js:166:19:166:38 | concatted2.join("/") | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:166:19:166:38 | concatted2.join("/") | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | | TaintedPath.js:168:19:168:29 | split.pop() | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:168:19:168:29 | split.pop() | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | +| TaintedPath.js:177:29:177:55 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | | normalizedPaths.js:13:19:13:22 | path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:13:19:13:22 | path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | | normalizedPaths.js:14:19:14:29 | './' + path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:14:19:14:29 | './' + path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | | normalizedPaths.js:15:19:15:38 | path + '/index.html' | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:15:19:15:38 | path + '/index.html' | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js index df38860cd8a..a5a5e278a93 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js @@ -167,4 +167,16 @@ var server = http.createServer(function(req, res) { fs.readFileSync(split.pop()); // NOT OK +}); + +var server = http.createServer(function(req, res) { + let path = url.parse(req.url, true).query.path; + + // Removal of forward-slash. + res.write(fs.readFileSync(path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''))); // OK. + res.write(fs.readFileSync(path.replace(/[abcd]/g, ''))); // NOT OK + res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // OK (can still be absolute) + res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/[./]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/[foobar/foobar]/g, ''))); // OK }); \ No newline at end of file From 97c16929cac16f4afbabb07e1c22c45394b0315e Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 21:38:22 +0100 Subject: [PATCH 156/236] implement getPolarity and forward to inner StartsWith --- javascript/ql/src/semmle/javascript/StringOps.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index f558cd40f56..5bdcdd9c41f 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -86,6 +86,8 @@ module StringOps { result = this.getArgument(arg) ) } + + override boolean getPolarity() { result = inner.getPolarity() } } /** From d2d5af42bfadd0b194d591b408ea2b4990f93e95 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Mon, 2 Mar 2020 21:42:08 +0100 Subject: [PATCH 157/236] add IndirectInclusionTest and IndirectEndsWith --- .../src/semmle/javascript/InclusionTests.qll | 34 ++++++++++++++++++ .../ql/src/semmle/javascript/StringOps.qll | 35 +++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/InclusionTests.qll b/javascript/ql/src/semmle/javascript/InclusionTests.qll index af0f21b050a..e7152f96a94 100644 --- a/javascript/ql/src/semmle/javascript/InclusionTests.qll +++ b/javascript/ql/src/semmle/javascript/InclusionTests.qll @@ -58,6 +58,40 @@ module InclusionTest { boolean getPolarity() { result = true } } + /** + * A call to a utility function (`callee`) that performs an InclusionTest (`inner`). + */ + private class IndirectInclusionTest extends Range, DataFlow::CallNode { + InclusionTest inner; + Function callee; + + IndirectInclusionTest() { + inner.getEnclosingExpr() = callee.getAReturnedExpr() and + this.getACallee() = callee and + count(this.getACallee()) = 1 and + count(callee.getAReturnedExpr()) = 1 and + not this.isImprecise() and + inner.getContainerNode().getALocalSource().getEnclosingExpr() = callee.getAParameter() and + inner.getContainedNode().getALocalSource().getEnclosingExpr() = callee.getAParameter() + } + + override DataFlow::Node getContainerNode() { + exists(int arg | + inner.getContainerNode().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override DataFlow::Node getContainedNode() { + exists(int arg | + inner.getContainedNode().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override boolean getPolarity() { result = inner.getPolarity() } + } + /** * A call to a method named `includes`, assumed to refer to `String.prototype.includes` * or `Array.prototype.includes`. diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index 5bdcdd9c41f..744f5796f05 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -287,6 +287,41 @@ module StringOps { boolean getPolarity() { result = true } } + /** + * A call to a utility function (`callee`) that performs an EndsWith check (`inner`). + */ + private class IndirectEndsWith extends Range, DataFlow::CallNode { + EndsWith inner; + Function callee; + + IndirectEndsWith() { + inner.getEnclosingExpr() = callee.getAReturnedExpr() and + this.getACallee() = callee and + count(this.getACallee()) = 1 and + count(callee.getAReturnedExpr()) = 1 and + not this.isImprecise() and + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter() + } + + override DataFlow::Node getBaseString() { + exists(int arg | + inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override DataFlow::Node getSubstring() { + exists(int arg | + inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getParameter(arg) and + result = this.getArgument(arg) + ) + } + + override boolean getPolarity() { result = inner.getPolarity() } + } + + /** * A call of form `A.endsWith(B)`. */ From de6ea63fae928f0a7885351e2ce4c348c3606702 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 15:43:57 -0800 Subject: [PATCH 158/236] Python: Adds preliminary modernization. --- .../Formatting/AdvancedFormatting.qll | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll index 3fe71313460..7d8821f09ea 100644 --- a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -104,7 +104,7 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en ) } -private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { +private predicate advanced_format_call_objectapi(Call format_expr, PossibleAdvancedFormatString fmt, int args) { exists(CallNode call | call = format_expr.getAFlowNode() | call.getFunction().refersTo(Object::builtin("format")) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and @@ -115,27 +115,27 @@ private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatS ) } -class AdvancedFormatString extends PossibleAdvancedFormatString { +class AdvancedFormatString_objectapi extends PossibleAdvancedFormatString { - AdvancedFormatString() { - advanced_format_call(_, this, _) + AdvancedFormatString_objectapi() { + advanced_format_call_objectapi(_, this, _) } } -class AdvancedFormattingCall extends Call { +class AdvancedFormattingCall_objectapi extends Call { - AdvancedFormattingCall() { - advanced_format_call(this, _, _) + AdvancedFormattingCall_objectapi() { + advanced_format_call_objectapi(this, _, _) } /** Count of the arguments actually provided */ int providedArgCount() { - advanced_format_call(this, _, result) + advanced_format_call_objectapi(this, _, result) } - AdvancedFormatString getAFormat() { - advanced_format_call(this, result, _) + AdvancedFormatString_objectapi getAFormat() { + advanced_format_call_objectapi(this, result, _) } } From 48bcde8e0f1b572b1d56170a1f192170aa2bf9f4 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 16:31:06 -0800 Subject: [PATCH 159/236] Python: Move objectapi code to suffixed name --- python/ql/src/Exceptions/NotImplemented.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Exceptions/NotImplemented.qll b/python/ql/src/Exceptions/NotImplemented.qll index 1a799c37db0..34a23cdeee0 100644 --- a/python/ql/src/Exceptions/NotImplemented.qll +++ b/python/ql/src/Exceptions/NotImplemented.qll @@ -2,7 +2,7 @@ import python /** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ -predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) { +predicate use_of_not_implemented_in_raise_objectapi(Raise raise, Expr notimpl) { notimpl.refersTo(Object::notImplemented()) and ( notimpl = raise.getException() or From 7930037bb6e6cc0e1bca280590d63d7859f9421a Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 16:39:02 -0800 Subject: [PATCH 160/236] Python: Move AdvancedFormatting dependents over to suffixed API --- .../Formatting/MixedExplicitImplicitIn3101Format.ql | 2 +- .../Expressions/Formatting/UnusedArgumentIn3101Format.ql | 6 +++--- .../Formatting/UnusedNamedArgumentIn3101Format.ql | 4 ++-- .../Formatting/WrongNameInArgumentsFor3101Format.ql | 2 +- .../Formatting/WrongNumberArgumentsFor3101Format.ql | 2 +- python/ql/test/library-tests/formatting/FormatArguments.ql | 2 +- python/ql/test/library-tests/formatting/FormatFields.ql | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql index 3f488aa9507..766c9e525ac 100644 --- a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql +++ b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql @@ -13,6 +13,6 @@ import python import AdvancedFormatting -from AdvancedFormattingCall call, AdvancedFormatString fmt +from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt where call.getAFormat() = fmt and fmt.isImplicitlyNumbered() and fmt.isExplicitlyNumbered() select fmt, "Formatting string mixes implicitly and explicitly numbered fields." \ No newline at end of file diff --git a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql index 67c95277375..64bd4f935fd 100644 --- a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql +++ b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql @@ -16,11 +16,11 @@ import python import python import AdvancedFormatting -int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 } +int field_count(AdvancedFormatString_objectapi fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 } -from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field +from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, int arg_count, int max_field where arg_count = call.providedArgCount() and max_field = field_count(fmt) and call.getAFormat() = fmt and not exists(call.getStarargs()) and -forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count) +forall(AdvancedFormatString_objectapi other | other = call.getAFormat() | field_count(other) < arg_count) select call, "Too many arguments for string format. Format $@ requires only " + max_field + ", but " + arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\"" diff --git a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql index c902b992b1c..cc6218e3737 100644 --- a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql +++ b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql @@ -13,10 +13,10 @@ import python import AdvancedFormatting -from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr +from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, string name, string fmt_repr where call.getAFormat() = fmt and name = call.getAKeyword().getArg() and -forall(AdvancedFormatString format | format = call.getAFormat() | not format.getFieldName(_, _) = name) +forall(AdvancedFormatString_objectapi format | format = call.getAFormat() | not format.getFieldName(_, _) = name) and not exists(call.getKwargs()) and (strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\"" or diff --git a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql index 412d8d55830..0e9cf66ae65 100644 --- a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql +++ b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql @@ -14,7 +14,7 @@ import python import AdvancedFormatting -from AdvancedFormattingCall call, AdvancedFormatString fmt, string name +from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, string name where call.getAFormat() = fmt and not name = call.getAKeyword().getArg() and fmt.getFieldName(_, _) = name diff --git a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql index fe766ae2d8b..1edeefac084 100644 --- a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql +++ b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql @@ -14,7 +14,7 @@ import python import AdvancedFormatting -from AdvancedFormattingCall call, AdvancedFormatString fmt, +from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, int arg_count, int max_field, string provided where arg_count = call.providedArgCount() and max_field = max(fmt.getFieldNumber(_, _)) and call.getAFormat() = fmt and not exists(call.getStarargs()) and arg_count <= max_field and diff --git a/python/ql/test/library-tests/formatting/FormatArguments.ql b/python/ql/test/library-tests/formatting/FormatArguments.ql index 19e47b7fc44..5a0126f6e8c 100644 --- a/python/ql/test/library-tests/formatting/FormatArguments.ql +++ b/python/ql/test/library-tests/formatting/FormatArguments.ql @@ -2,7 +2,7 @@ import python import Expressions.Formatting.AdvancedFormatting -from AdvancedFormatString a, string name, int start, int end +from AdvancedFormatString_objectapi a, string name, int start, int end where name = "'" + a.getFieldName(start, end) + "'" or diff --git a/python/ql/test/library-tests/formatting/FormatFields.ql b/python/ql/test/library-tests/formatting/FormatFields.ql index b8a3b913355..3eff457f3fa 100644 --- a/python/ql/test/library-tests/formatting/FormatFields.ql +++ b/python/ql/test/library-tests/formatting/FormatFields.ql @@ -2,5 +2,5 @@ import python import Expressions.Formatting.AdvancedFormatting -from AdvancedFormatString a, int start, int end +from AdvancedFormatString_objectapi a, int start, int end select a.getLocation().getStartLine(), a.getText(), start, end, a.getField(start, end) From 8ee2587997c8bf8a9c076c7ab32a8a90626eca17 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 17:04:32 -0800 Subject: [PATCH 161/236] Python: Moves library predicates to suffixed names --- python/ql/src/Exceptions/Raising.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Exceptions/Raising.qll b/python/ql/src/Exceptions/Raising.qll index b820bfa24e9..312998cef25 100644 --- a/python/ql/src/Exceptions/Raising.qll +++ b/python/ql/src/Exceptions/Raising.qll @@ -1,7 +1,7 @@ import python /** Whether the raise statement 'r' raises 'type' from origin 'orig' */ -predicate type_or_typeof(Raise r, ClassObject type, AstNode orig) { +predicate type_or_typeof_objectapi(Raise r, ClassObject type, AstNode orig) { exists(Expr exception | exception = r.getRaised() | exception.refersTo(type, _, orig) From 5b0cb10f9bcd8e7d9df4d8f14896bc05ace6e3bc Mon Sep 17 00:00:00 2001 From: Ian Lynagh Date: Tue, 25 Feb 2020 14:51:12 +0000 Subject: [PATCH 162/236] C++: Update tests following extractor no longer extracting some unused types --- cpp/ql/test/library-tests/lambdas/captures/elements.expected | 3 --- .../templates/instantiations_functions/elements.expected | 4 ---- 2 files changed, 7 deletions(-) diff --git a/cpp/ql/test/library-tests/lambdas/captures/elements.expected b/cpp/ql/test/library-tests/lambdas/captures/elements.expected index 598dec39fe5..1720fa2ca8e 100644 --- a/cpp/ql/test/library-tests/lambdas/captures/elements.expected +++ b/cpp/ql/test/library-tests/lambdas/captures/elements.expected @@ -213,11 +213,9 @@ | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 | | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 & | | file://:0:0:0:0 | const lambda [] type at line 3, col. 5 * | -| file://:0:0:0:0 | const lambda [] type at line 3, col. 5 *const | | file://:0:0:0:0 | const lambda [] type at line 9, col. 5 | | file://:0:0:0:0 | const lambda [] type at line 9, col. 5 & | | file://:0:0:0:0 | const lambda [] type at line 9, col. 5 * | -| file://:0:0:0:0 | const lambda [] type at line 9, col. 5 *const | | file://:0:0:0:0 | const lambda [] type at line 9, col. 15 | | file://:0:0:0:0 | const lambda [] type at line 9, col. 15 & | | file://:0:0:0:0 | const lambda [] type at line 15, col. 5 | @@ -225,7 +223,6 @@ | file://:0:0:0:0 | const lambda [] type at line 22, col. 19 | | file://:0:0:0:0 | const lambda [] type at line 22, col. 19 & | | file://:0:0:0:0 | const lambda [] type at line 22, col. 19 * | -| file://:0:0:0:0 | const lambda [] type at line 22, col. 19 *const | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | | file://:0:0:0:0 | declaration of 1st parameter | diff --git a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected index df99979ff44..52af59bd1f7 100644 --- a/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected +++ b/cpp/ql/test/library-tests/templates/instantiations_functions/elements.expected @@ -39,17 +39,13 @@ | file://:0:0:0:0 | __va_list_tag && | | file://:0:0:0:0 | abstract | | file://:0:0:0:0 | action * | -| file://:0:0:0:0 | action *const | | file://:0:0:0:0 | action>> & | | file://:0:0:0:0 | action>> && | | file://:0:0:0:0 | action>> * | -| file://:0:0:0:0 | action>> *const | | file://:0:0:0:0 | actor1 * | -| file://:0:0:0:0 | actor1 *const | | file://:0:0:0:0 | actor1> & | | file://:0:0:0:0 | actor1> && | | file://:0:0:0:0 | actor1> * | -| file://:0:0:0:0 | actor1> *const | | file://:0:0:0:0 | atomic | | file://:0:0:0:0 | auto | | file://:0:0:0:0 | auto | From 0dcd52bd874a5ec1824994fab9e2e4ef16b8408b Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 17:35:13 -0800 Subject: [PATCH 163/236] Python: Moves dependent query over to suffixed predicate names --- python/ql/src/Exceptions/IllegalRaise.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql index 673289b9e97..89f8ab91c35 100644 --- a/python/ql/src/Exceptions/IllegalRaise.ql +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -16,6 +16,6 @@ import Raising import Exceptions.NotImplemented from Raise r, ClassObject t -where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) +where type_or_typeof_objectapi(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." From c058e170893ed58dc5537f583fbacc1079908903 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 17:37:11 -0800 Subject: [PATCH 164/236] Python: Moves dependent queries to suffixed predicate names --- python/ql/src/Exceptions/IllegalRaise.ql | 2 +- python/ql/src/Exceptions/NotImplementedIsNotAnException.ql | 2 +- python/ql/src/Expressions/NonCallableCalled.ql | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql index 673289b9e97..240d9abea8c 100644 --- a/python/ql/src/Exceptions/IllegalRaise.ql +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -16,6 +16,6 @@ import Raising import Exceptions.NotImplemented from Raise r, ClassObject t -where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) +where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise_objectapi(r, _) select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." diff --git a/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql b/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql index 89f1bb04568..8299de2f917 100644 --- a/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql +++ b/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql @@ -14,6 +14,6 @@ import python import Exceptions.NotImplemented from Expr notimpl -where use_of_not_implemented_in_raise(_, notimpl) +where use_of_not_implemented_in_raise_objectapi(_, notimpl) select notimpl, "NotImplemented is not an Exception. Did you mean NotImplementedError?" diff --git a/python/ql/src/Expressions/NonCallableCalled.ql b/python/ql/src/Expressions/NonCallableCalled.ql index 2f9a0f8f5cc..7924b55d22f 100644 --- a/python/ql/src/Expressions/NonCallableCalled.ql +++ b/python/ql/src/Expressions/NonCallableCalled.ql @@ -19,6 +19,6 @@ where f = c.getFunc() and f.pointsTo(v, origin) and t = v.getClass() and not t.isCallable() and not t.failedInference(_) and not t.hasAttribute("__get__") and not v = Value::named("None") - and not use_of_not_implemented_in_raise(_, f) + and not use_of_not_implemented_in_raise_objectapi(_, f) select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString() From 31fc0f224078847d16932b86fe9e4a63d5f35243 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 18:08:10 -0800 Subject: [PATCH 165/236] Python: Moves library and queries over to the new predicates, removes old ones --- .../Formatting/AdvancedFormatting.qll | 26 +++++++++---------- .../MixedExplicitImplicitIn3101Format.ql | 2 +- .../Formatting/UnusedArgumentIn3101Format.ql | 6 ++--- .../UnusedNamedArgumentIn3101Format.ql | 4 +-- .../WrongNameInArgumentsFor3101Format.ql | 2 +- .../WrongNumberArgumentsFor3101Format.ql | 2 +- .../formatting/FormatArguments.ql | 2 +- .../library-tests/formatting/FormatFields.ql | 2 +- 8 files changed, 23 insertions(+), 23 deletions(-) diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll index 7d8821f09ea..13292465650 100644 --- a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -1,4 +1,5 @@ import python +private import semmle.python.types.Builtins library class PossibleAdvancedFormatString extends StrConst { @@ -104,39 +105,38 @@ private predicate brace_pair(PossibleAdvancedFormatString fmt, int start, int en ) } -private predicate advanced_format_call_objectapi(Call format_expr, PossibleAdvancedFormatString fmt, int args) { +private predicate advanced_format_call(Call format_expr, PossibleAdvancedFormatString fmt, int args) { exists(CallNode call | call = format_expr.getAFlowNode() | - call.getFunction().refersTo(Object::builtin("format")) and call.getArg(0).refersTo(_, fmt.getAFlowNode()) and + call.getFunction().pointsTo(Value::named("format")) and call.getArg(0).pointsTo(_, fmt.getAFlowNode()) and args = count(format_expr.getAnArg()) - 1 or - call.getFunction().(AttrNode).getObject("format").refersTo(_, fmt.getAFlowNode()) and + call.getFunction().(AttrNode).getObject("format").pointsTo(_, fmt.getAFlowNode()) and args = count(format_expr.getAnArg()) ) } -class AdvancedFormatString_objectapi extends PossibleAdvancedFormatString { +class AdvancedFormatString extends PossibleAdvancedFormatString { - AdvancedFormatString_objectapi() { - advanced_format_call_objectapi(_, this, _) + AdvancedFormatString() { + advanced_format_call(_, this, _) } } -class AdvancedFormattingCall_objectapi extends Call { +class AdvancedFormattingCall extends Call { - AdvancedFormattingCall_objectapi() { - advanced_format_call_objectapi(this, _, _) + AdvancedFormattingCall() { + advanced_format_call(this, _, _) } /** Count of the arguments actually provided */ int providedArgCount() { - advanced_format_call_objectapi(this, _, result) + advanced_format_call(this, _, result) } - AdvancedFormatString_objectapi getAFormat() { - advanced_format_call_objectapi(this, result, _) + AdvancedFormatString getAFormat() { + advanced_format_call(this, result, _) } } - diff --git a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql index 766c9e525ac..3f488aa9507 100644 --- a/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql +++ b/python/ql/src/Expressions/Formatting/MixedExplicitImplicitIn3101Format.ql @@ -13,6 +13,6 @@ import python import AdvancedFormatting -from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt +from AdvancedFormattingCall call, AdvancedFormatString fmt where call.getAFormat() = fmt and fmt.isImplicitlyNumbered() and fmt.isExplicitlyNumbered() select fmt, "Formatting string mixes implicitly and explicitly numbered fields." \ No newline at end of file diff --git a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql index 64bd4f935fd..67c95277375 100644 --- a/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql +++ b/python/ql/src/Expressions/Formatting/UnusedArgumentIn3101Format.ql @@ -16,11 +16,11 @@ import python import python import AdvancedFormatting -int field_count(AdvancedFormatString_objectapi fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 } +int field_count(AdvancedFormatString fmt) { result = max(fmt.getFieldNumber(_, _)) + 1 } -from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, int arg_count, int max_field +from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field where arg_count = call.providedArgCount() and max_field = field_count(fmt) and call.getAFormat() = fmt and not exists(call.getStarargs()) and -forall(AdvancedFormatString_objectapi other | other = call.getAFormat() | field_count(other) < arg_count) +forall(AdvancedFormatString other | other = call.getAFormat() | field_count(other) < arg_count) select call, "Too many arguments for string format. Format $@ requires only " + max_field + ", but " + arg_count.toString() + " are provided.", fmt, "\"" + fmt.getText() + "\"" diff --git a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql index cc6218e3737..c902b992b1c 100644 --- a/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql +++ b/python/ql/src/Expressions/Formatting/UnusedNamedArgumentIn3101Format.ql @@ -13,10 +13,10 @@ import python import AdvancedFormatting -from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, string name, string fmt_repr +from AdvancedFormattingCall call, AdvancedFormatString fmt, string name, string fmt_repr where call.getAFormat() = fmt and name = call.getAKeyword().getArg() and -forall(AdvancedFormatString_objectapi format | format = call.getAFormat() | not format.getFieldName(_, _) = name) +forall(AdvancedFormatString format | format = call.getAFormat() | not format.getFieldName(_, _) = name) and not exists(call.getKwargs()) and (strictcount(call.getAFormat()) = 1 and fmt_repr = "format \"" + fmt.getText() + "\"" or diff --git a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql index 0e9cf66ae65..412d8d55830 100644 --- a/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql +++ b/python/ql/src/Expressions/Formatting/WrongNameInArgumentsFor3101Format.ql @@ -14,7 +14,7 @@ import python import AdvancedFormatting -from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, string name +from AdvancedFormattingCall call, AdvancedFormatString fmt, string name where call.getAFormat() = fmt and not name = call.getAKeyword().getArg() and fmt.getFieldName(_, _) = name diff --git a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql index 1edeefac084..fe766ae2d8b 100644 --- a/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql +++ b/python/ql/src/Expressions/Formatting/WrongNumberArgumentsFor3101Format.ql @@ -14,7 +14,7 @@ import python import AdvancedFormatting -from AdvancedFormattingCall_objectapi call, AdvancedFormatString_objectapi fmt, +from AdvancedFormattingCall call, AdvancedFormatString fmt, int arg_count, int max_field, string provided where arg_count = call.providedArgCount() and max_field = max(fmt.getFieldNumber(_, _)) and call.getAFormat() = fmt and not exists(call.getStarargs()) and arg_count <= max_field and diff --git a/python/ql/test/library-tests/formatting/FormatArguments.ql b/python/ql/test/library-tests/formatting/FormatArguments.ql index 5a0126f6e8c..19e47b7fc44 100644 --- a/python/ql/test/library-tests/formatting/FormatArguments.ql +++ b/python/ql/test/library-tests/formatting/FormatArguments.ql @@ -2,7 +2,7 @@ import python import Expressions.Formatting.AdvancedFormatting -from AdvancedFormatString_objectapi a, string name, int start, int end +from AdvancedFormatString a, string name, int start, int end where name = "'" + a.getFieldName(start, end) + "'" or diff --git a/python/ql/test/library-tests/formatting/FormatFields.ql b/python/ql/test/library-tests/formatting/FormatFields.ql index 3eff457f3fa..b8a3b913355 100644 --- a/python/ql/test/library-tests/formatting/FormatFields.ql +++ b/python/ql/test/library-tests/formatting/FormatFields.ql @@ -2,5 +2,5 @@ import python import Expressions.Formatting.AdvancedFormatting -from AdvancedFormatString_objectapi a, int start, int end +from AdvancedFormatString a, int start, int end select a.getLocation().getStartLine(), a.getText(), start, end, a.getField(start, end) From 7161ca57c8ac7e4304b64c84510870e68c95869c Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 18:45:41 -0800 Subject: [PATCH 166/236] Python: Adds modernizations and moves query over to them --- python/ql/src/Exceptions/IllegalRaise.ql | 4 ++-- python/ql/src/Exceptions/Raising.qll | 15 +++++++++++++++ python/ql/src/semmle/python/objects/ObjectAPI.qll | 9 +++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql index 89f8ab91c35..da1bc56ae33 100644 --- a/python/ql/src/Exceptions/IllegalRaise.ql +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -15,7 +15,7 @@ import python import Raising import Exceptions.NotImplemented -from Raise r, ClassObject t -where type_or_typeof_objectapi(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) +from Raise r, ClassValue t +where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference(_) and not use_of_not_implemented_in_raise(r, _) select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." diff --git a/python/ql/src/Exceptions/Raising.qll b/python/ql/src/Exceptions/Raising.qll index 312998cef25..2aa6ef4af8d 100644 --- a/python/ql/src/Exceptions/Raising.qll +++ b/python/ql/src/Exceptions/Raising.qll @@ -12,3 +12,18 @@ predicate type_or_typeof_objectapi(Raise r, ClassObject type, AstNode orig) { ) } + +/** Whether the raise statement 'r' raises 'type' from origin 'orig' */ +predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) { + exists(Expr exception | + exception = r.getRaised() | + exception.pointsTo(type, orig) + or + not exists(ClassValue exc_type | exception.pointsTo(exc_type)) and + not type = ClassValue::type() and // First value is an unknown exception type + exists(Value val | exception.pointsTo(val, orig) | + val.getClass() = type + ) + ) + +} diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 1f1f7001da1..49087648c52 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -481,6 +481,15 @@ class ClassValue extends Value { predicate declaresAttribute(string name) { this.(ClassObjectInternal).getClassDeclaration().declaresAttribute(name) } + + /** Whether this class is a legal exception class. + * What constitutes a legal exception class differs between major versions */ + predicate isLegalExceptionType() { + not this.isNewStyle() or + this.getASuperType() = ClassValue::baseException() + or + major_version() = 2 and this = ClassValue::tuple() + } } From a8ae843059b566aaf10feab103181012d5c476b1 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 18:46:19 -0800 Subject: [PATCH 167/236] Python: Removes now obsolete original predicate --- python/ql/src/Exceptions/Raising.qll | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/python/ql/src/Exceptions/Raising.qll b/python/ql/src/Exceptions/Raising.qll index 2aa6ef4af8d..7e53c0bbd27 100644 --- a/python/ql/src/Exceptions/Raising.qll +++ b/python/ql/src/Exceptions/Raising.qll @@ -1,18 +1,5 @@ import python -/** Whether the raise statement 'r' raises 'type' from origin 'orig' */ -predicate type_or_typeof_objectapi(Raise r, ClassObject type, AstNode orig) { - exists(Expr exception | - exception = r.getRaised() | - exception.refersTo(type, _, orig) - or - not exists(ClassObject exc_type | exception.refersTo(exc_type)) and - not type = theTypeType() and // First value is an unknown exception type - exception.refersTo(_, type, orig) - ) - -} - /** Whether the raise statement 'r' raises 'type' from origin 'orig' */ predicate type_or_typeof(Raise r, ClassValue type, AstNode orig) { exists(Expr exception | From e481ddf99e29af834d1fbd038a79b381bf912722 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 18:52:45 -0800 Subject: [PATCH 168/236] Python: Adds modernized predicate and moves queries over to it --- python/ql/src/Exceptions/IllegalRaise.ql | 2 +- python/ql/src/Exceptions/NotImplemented.qll | 9 +++++++++ .../ql/src/Exceptions/NotImplementedIsNotAnException.ql | 2 +- python/ql/src/Expressions/NonCallableCalled.ql | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Exceptions/IllegalRaise.ql b/python/ql/src/Exceptions/IllegalRaise.ql index 240d9abea8c..673289b9e97 100644 --- a/python/ql/src/Exceptions/IllegalRaise.ql +++ b/python/ql/src/Exceptions/IllegalRaise.ql @@ -16,6 +16,6 @@ import Raising import Exceptions.NotImplemented from Raise r, ClassObject t -where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise_objectapi(r, _) +where type_or_typeof(r, t, _) and not t.isLegalExceptionType() and not t.failedInference() and not use_of_not_implemented_in_raise(r, _) select r, "Illegal class '" + t.getName() + "' raised; will result in a TypeError being raised instead." diff --git a/python/ql/src/Exceptions/NotImplemented.qll b/python/ql/src/Exceptions/NotImplemented.qll index 34a23cdeee0..0c523bcde05 100644 --- a/python/ql/src/Exceptions/NotImplemented.qll +++ b/python/ql/src/Exceptions/NotImplemented.qll @@ -9,3 +9,12 @@ predicate use_of_not_implemented_in_raise_objectapi(Raise raise, Expr notimpl) { notimpl = raise.getException().(Call).getFunc() ) } + +/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ +predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) { + notimpl.pointsTo(Value::named("NotImplemented")) and + ( + notimpl = raise.getException() or + notimpl = raise.getException().(Call).getFunc() + ) +} diff --git a/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql b/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql index 8299de2f917..89f1bb04568 100644 --- a/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql +++ b/python/ql/src/Exceptions/NotImplementedIsNotAnException.ql @@ -14,6 +14,6 @@ import python import Exceptions.NotImplemented from Expr notimpl -where use_of_not_implemented_in_raise_objectapi(_, notimpl) +where use_of_not_implemented_in_raise(_, notimpl) select notimpl, "NotImplemented is not an Exception. Did you mean NotImplementedError?" diff --git a/python/ql/src/Expressions/NonCallableCalled.ql b/python/ql/src/Expressions/NonCallableCalled.ql index 7924b55d22f..2f9a0f8f5cc 100644 --- a/python/ql/src/Expressions/NonCallableCalled.ql +++ b/python/ql/src/Expressions/NonCallableCalled.ql @@ -19,6 +19,6 @@ where f = c.getFunc() and f.pointsTo(v, origin) and t = v.getClass() and not t.isCallable() and not t.failedInference(_) and not t.hasAttribute("__get__") and not v = Value::named("None") - and not use_of_not_implemented_in_raise_objectapi(_, f) + and not use_of_not_implemented_in_raise(_, f) select c, "Call to a $@ of $@.", origin, "non-callable", t, t.toString() From 2bbe4759baab22b0a04006b83fbbccf9bb220192 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 2 Mar 2020 18:53:06 -0800 Subject: [PATCH 169/236] Python: Removes obsolete predicate --- python/ql/src/Exceptions/NotImplemented.qll | 9 --------- 1 file changed, 9 deletions(-) diff --git a/python/ql/src/Exceptions/NotImplemented.qll b/python/ql/src/Exceptions/NotImplemented.qll index 0c523bcde05..09b3c37998b 100644 --- a/python/ql/src/Exceptions/NotImplemented.qll +++ b/python/ql/src/Exceptions/NotImplemented.qll @@ -1,15 +1,6 @@ import python -/** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ -predicate use_of_not_implemented_in_raise_objectapi(Raise raise, Expr notimpl) { - notimpl.refersTo(Object::notImplemented()) and - ( - notimpl = raise.getException() or - notimpl = raise.getException().(Call).getFunc() - ) -} - /** Holds if `notimpl` refers to `NotImplemented` or `NotImplemented()` in the `raise` statement */ predicate use_of_not_implemented_in_raise(Raise raise, Expr notimpl) { notimpl.pointsTo(Value::named("NotImplemented")) and From c4ebd66b345dad9451036828633c348c99b3a034 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 09:29:04 +0100 Subject: [PATCH 170/236] fix capitalization of predicate --- javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index af1206432bf..f67dc5f7470 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -308,13 +308,13 @@ module ReflectedXss { class HttpResponseSink extends Sink, DataFlow::ValueNode { override HTTP::ResponseSendArgument astNode; - HttpResponseSink() { not exists(getAnonHtmlHeaderDefinition(astNode)) } + HttpResponseSink() { not exists(getANonHtmlHeaderDefinition(astNode)) } } /** * Gets a HeaderDefinition that defines a non-html content-type for `send`. */ - HTTP::HeaderDefinition getAnonHtmlHeaderDefinition(HTTP::ResponseSendArgument send) { + HTTP::HeaderDefinition getANonHtmlHeaderDefinition(HTTP::ResponseSendArgument send) { exists(HTTP::RouteHandler h | send.getRouteHandler() = h and result = nonHtmlContentTypeHeader(h) From 1781179e25f8344443ef41ffaa32b7de7e50c8e9 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 09:50:02 +0100 Subject: [PATCH 171/236] doc fixes --- .../ql/src/semmle/javascript/security/dataflow/Xss.qll | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index f67dc5f7470..04501cdbf21 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -319,7 +319,7 @@ module ReflectedXss { send.getRouteHandler() = h and result = nonHtmlContentTypeHeader(h) | - // not the case that the control just exists without potentially going to the worksFor. + // The HeaderDefinition affects a response sent at `send`. not isIrrelevantFor(result, send) ) } @@ -333,9 +333,10 @@ module ReflectedXss { } /** - * Holds if a header set in `header` is unlikely to affect a resonse send in `sender`. + * Holds if a header set in `header` is unlikely to affect a response sent at `sender`. */ predicate isIrrelevantFor(HTTP::HeaderDefinition header, HTTP::ResponseSendArgument sender) { + sender.getRouteHandler() = header.getRouteHandler() and not header.getBasicBlock().getASuccessor*() = sender.getBasicBlock() and not sender.getBasicBlock().getASuccessor*() = header.getBasicBlock() and ( From 9016f43d8097fe860f7db2419af8ff7dbd49c45f Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 10:04:57 +0100 Subject: [PATCH 172/236] update expected output --- .../Security/CWE-079/ReflectedXssWithCustomSanitizer.expected | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected index 560dca58553..e8d727f51f8 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssWithCustomSanitizer.expected @@ -1,4 +1,8 @@ | 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 | +| ReflectedXssContentTypes.js:10:14:10:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:10:24:10:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:20:14:20:36 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:20:24:20:36 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:39:13:39:35 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:39:23:39:35 | req.params.id | user-provided value | +| ReflectedXssContentTypes.js:70:12:70:34 | "FOO: " ... rams.id | Cross-site scripting vulnerability due to $@. | ReflectedXssContentTypes.js:70:22:70:34 | req.params.id | user-provided value | | exception-xss.js:190:12:190:24 | req.params.id | Cross-site scripting vulnerability due to $@. | exception-xss.js:190:12:190:24 | req.params.id | user-provided value | | formatting.js:6:14:6:47 | util.fo ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | | formatting.js:7:14:7:53 | require ... , evil) | Cross-site scripting vulnerability due to $@. | formatting.js:4:16:4:29 | req.query.evil | user-provided value | From bc1320419340d84ec3f0e9164318e999f79f6caa Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 12:04:31 +0100 Subject: [PATCH 173/236] refactor header checks to be based on dominance --- .../javascript/security/dataflow/Xss.qll | 63 ++++++++++--------- .../CWE-079/ReflectedXssContentTypes.js | 10 ++- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll index 04501cdbf21..47aff96c9c3 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/Xss.qll @@ -319,8 +319,8 @@ module ReflectedXss { send.getRouteHandler() = h and result = nonHtmlContentTypeHeader(h) | - // The HeaderDefinition affects a response sent at `send`. - not isIrrelevantFor(result, send) + // The HeaderDefinition affects a response sent at `send`. + headerAffects(result, send) ) } @@ -333,40 +333,47 @@ module ReflectedXss { } /** - * Holds if a header set in `header` is unlikely to affect a response sent at `sender`. + * Holds if a header set in `header` is likely to affect a response sent at `sender`. */ - predicate isIrrelevantFor(HTTP::HeaderDefinition header, HTTP::ResponseSendArgument sender) { + predicate headerAffects(HTTP::HeaderDefinition header, HTTP::ResponseSendArgument sender) { sender.getRouteHandler() = header.getRouteHandler() and - not header.getBasicBlock().getASuccessor*() = sender.getBasicBlock() and - not sender.getBasicBlock().getASuccessor*() = header.getBasicBlock() and ( - // If there is another header `otherHeader` next to `sender`, then `header` is probably irrelevant. - exists(HTTP::HeaderDefinition otherHeader | not header = otherHeader | - otherHeader.getBasicBlock().getASuccessor*() = sender.getBasicBlock() and - not otherHeader = nonHtmlContentTypeHeader(sender.getRouteHandler()) - ) + // `sender` is affected by a dominating `header`. + header.getBasicBlock().(ReachableBasicBlock).dominates(sender.getBasicBlock()) or - // Tries to recognize variants of: - // ``` - // response.writeHead(500, {'Content-Type': 'text/plain'}); - // response.end('Some error'); - // return; - // ``` - exists(ReachableBasicBlock headerBlock | headerBlock = header.getBasicBlock() | - headerBlock.getASuccessor() instanceof ControlFlowExitNode and - strictcount(headerBlock.getASuccessor()) = 1 and - not ( - exists(DataFlow::CallNode call | - exists(call.getACallee()) and - call.getBasicBlock() = headerBlock - ) - or - exists(Expr e | e.getBasicBlock() = headerBlock and e instanceof Function) - ) + // There is no dominating header, and `header` is non-local. + not isLocalHeaderDefinition(header) and + not exists(HTTP::HeaderDefinition dominatingHeader | + dominatingHeader.getBasicBlock().(ReachableBasicBlock).dominates(sender.getBasicBlock()) ) ) } + /** + * Holds if the HeaderDefinition `header` seems to be local. + * A HeaderDefinition is local if it dominates exactly one `ResponseSendArgument`. + * + * Recognizes variants of: + * ``` + * response.writeHead(500, ...); + * response.end('Some error'); + * return; + * ``` + */ + predicate isLocalHeaderDefinition(HTTP::HeaderDefinition header) { + exists(ReachableBasicBlock headerBlock | + headerBlock = header.getBasicBlock().(ReachableBasicBlock) + | + 1 = + strictcount(HTTP::ResponseSendArgument sender | + sender.getRouteHandler() = header.getRouteHandler() and + header.getBasicBlock().(ReachableBasicBlock).dominates(sender.getBasicBlock()) + ) and + // doesn't dominate something that looks like a callback. + not exists(Expr e | e instanceof Function | headerBlock.dominates(e.getBasicBlock())) + ) + } + /** * A regexp replacement involving an HTML meta-character, viewed as a sanitizer for * XSS vulnerabilities. diff --git a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js index bb930409a61..64acfded0a6 100644 --- a/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js +++ b/javascript/ql/test/query-tests/Security/CWE-079/ReflectedXssContentTypes.js @@ -64,8 +64,16 @@ app.get('/user/:id', function (req, res) { return; } doSomething(); - somethingMOre(); + somethingMore(); while(Math.random()) {}; res.writeHead(404); res.send("FOO: " + req.params.id); // NOT OK - content type is not set. +}); + +app.get('/user/:id', function (req, res) { + res.header({'Content-Type': textContentType()}); + myFancyFunction(() => { + res.send("FOO: " + req.params.id); // OK + }); + res.end("FOO: " + req.params.id); // OK }); \ No newline at end of file From 622a2fcfdc2c4c1566405666d3e6f5605a704203 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 12:24:13 +0100 Subject: [PATCH 174/236] use regexp term instead of char class --- .../security/dataflow/TaintedPathCustomizations.qll | 6 +++--- .../query-tests/Security/CWE-022/TaintedPath/TaintedPath.js | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index 1f5b2f96727..c214ce4aa6c 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -227,9 +227,9 @@ module TaintedPath { seq.getNumChild() = 2 ) or - exists(RegExpCharacterClass choice | literal.getRoot() = choice | - choice.getAMatchedString() = "/" or - choice.getAMatchedString() = "." + exists(RegExpTerm term | literal.getRoot() = term | + term.getAMatchedString() = "/" or + term.getAMatchedString() = "." ) ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js index a5a5e278a93..43ed20497ac 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js @@ -179,4 +179,6 @@ var server = http.createServer(function(req, res) { res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // OK res.write(fs.readFileSync(path.replace(/[./]/g, ''))); // OK res.write(fs.readFileSync(path.replace(/[foobar/foobar]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\//g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\./g, ''))); // OK }); \ No newline at end of file From 98524556c38772d6ec16409f988d8f28517150d2 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 3 Mar 2020 10:32:28 +0000 Subject: [PATCH 175/236] JS: Add some tests --- .../TaintTracking/BasicTaintTracking.expected | 6 ++ .../TaintTracking/DataFlowTracking.expected | 5 ++ .../TaintTracking/nested-props.js | 81 +++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 javascript/ql/test/library-tests/TaintTracking/nested-props.js diff --git a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected index 53dbe8d1b53..5616da680b9 100644 --- a/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/BasicTaintTracking.expected @@ -67,6 +67,12 @@ typeInferenceMismatch | exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e | | importedReactComponent.jsx:4:40:4:47 | source() | exportedReactComponent.jsx:2:10:2:19 | props.text | | indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x | +| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x | +| nested-props.js:9:18:9:25 | source() | nested-props.js:10:10:10:16 | obj.x.y | +| nested-props.js:35:13:35:20 | source() | nested-props.js:36:10:36:20 | doLoad(obj) | +| nested-props.js:43:13:43:20 | source() | nested-props.js:44:10:44:18 | id(obj).x | +| nested-props.js:67:31:67:38 | source() | nested-props.js:68:10:68:10 | x | +| nested-props.js:77:36:77:43 | source() | nested-props.js:78:10:78:10 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:30:14:30:20 | x.value | diff --git a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected index 127d58056ae..bcd301b5d53 100644 --- a/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected +++ b/javascript/ql/test/library-tests/TaintTracking/DataFlowTracking.expected @@ -42,6 +42,11 @@ | exceptions.js:158:13:158:20 | source() | exceptions.js:161:10:161:10 | e | | indexOf.js:4:11:4:18 | source() | indexOf.js:9:10:9:10 | x | | indexOf.js:4:11:4:18 | source() | indexOf.js:13:10:13:10 | x | +| nested-props.js:4:13:4:20 | source() | nested-props.js:5:10:5:14 | obj.x | +| nested-props.js:9:18:9:25 | source() | nested-props.js:10:10:10:16 | obj.x.y | +| nested-props.js:35:13:35:20 | source() | nested-props.js:36:10:36:20 | doLoad(obj) | +| nested-props.js:43:13:43:20 | source() | nested-props.js:44:10:44:18 | id(obj).x | +| nested-props.js:67:31:67:38 | source() | nested-props.js:68:10:68:10 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:17:14:17:14 | x | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:20:14:20:14 | y | | partialCalls.js:4:17:4:24 | source() | partialCalls.js:30:14:30:20 | x.value | diff --git a/javascript/ql/test/library-tests/TaintTracking/nested-props.js b/javascript/ql/test/library-tests/TaintTracking/nested-props.js new file mode 100644 index 00000000000..a5ea3cc248b --- /dev/null +++ b/javascript/ql/test/library-tests/TaintTracking/nested-props.js @@ -0,0 +1,81 @@ +import * as dummy from 'dummy'; + +function storeLoadFlow(obj) { + obj.x = source(); + sink(obj.x); // NOT OK +} + +function storeStoreLoadLoadFlow(obj) { + obj.x = { y: source() }; + sink(obj.x.y); // NOT OK +} + +function loadStoreFlow(obj) { + obj.x.y = source(); + sink(obj.x.y); // NOT OK - but not found +} + +function loadLoadStoreFlow(obj) { + obj.x.y.z = source(); + sink(obj.x.y.z); // NOT OK - but not found +} + +function doStore(obj, x) { + obj.x = x; +} +function callStoreBackcallFlow(obj) { + doStore(obj, source()); + sink(obj.x); // NOT OK - but not found +} + +function doLoad(obj) { + return obj.x +} +function storeCallLoadReturnFlow(obj) { + obj.x = source(); + sink(doLoad(obj)); // NOT OK +} + +function id(obj) { + return obj +} +function storeCallReturnLoadFlow(obj) { + obj.x = source(); + sink(id(obj).x); // NOT OK +} + +function doLoadStore(obj, val) { + obj.x.y = val; +} +function callStoreBackloadBackcallLoadLoad(obj) { + doLoadStore(obj, source()); + sink(obj.x.y); // NOT OK - but not found +} + +function doLoadLoad(obj) { + return obj.x.y +} +function storeBackloadCallLoadLoadReturn(obj) { + obj.x.y = source(); + sink(doLoadStore(obj)); // NOT OK - but not found +} + +function doStoreReturn(val) { + return { x: val } +} +function callStoreReturnLoad() { + let { x } = doStoreReturn(source()); + sink(x); // NOT OK + + doStoreReturn(null); // ensure multiple call sites exist +} + +function doTaintStoreReturn(val) { + return { x: val + "!!" } +} +function callTaintStoreReturnLoadFlow() { + let { x } = doTaintStoreReturn(source()); + sink(x); // NOT OK + + doTaintStoreReturn(null); // ensure multiple call sites exist +} From a2042094cf67baa9a99def20c3b49eb53252b8ac Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 3 Mar 2020 00:54:50 +0000 Subject: [PATCH 176/236] JS: Restrict reachableFromStoreBase --- .../src/semmle/javascript/dataflow/Configuration.qll | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index d64239d458d..6809bb97718 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -901,8 +901,14 @@ private predicate reachableFromStoreBase( string prop, DataFlow::Node rhs, DataFlow::Node nd, DataFlow::Configuration cfg, PathSummary summary ) { - isRelevant(rhs, cfg) and - storeStep(rhs, nd, prop, cfg, summary) + exists(PathSummary s1, PathSummary s2 | + reachableFromSource(rhs, cfg, s1) + or + reachableFromStoreBase(_, _, rhs, cfg, s1) + | + storeStep(rhs, nd, prop, cfg, s2) and + summary = MkPathSummary(false, s1.hasCall().booleanOr(s2.hasCall()), s2.getStartLabel(), s2.getEndLabel()) + ) or exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | reachableFromStoreBase(prop, rhs, mid, cfg, oldSummary) and From 95819c8731b3233dd40520f666fb1e8c67012fe0 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 12:34:18 +0100 Subject: [PATCH 177/236] use RegExpTerm to generalize predicate --- .../dataflow/TaintedPathCustomizations.qll | 18 ++++++------------ .../CWE-022/TaintedPath/TaintedPath.js | 4 +++- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index c214ce4aa6c..6a8d1ddd825 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -217,20 +217,14 @@ module TaintedPath { this.getCalleeName() = "replace" and input = getReceiver() and output = this and - not exists(RegExpLiteral literal | + not exists(RegExpLiteral literal, RegExpTerm term | getArgument(0).getALocalSource().asExpr() = literal and - literal.isGlobal() + literal.isGlobal() and + literal.getRoot() = term | - exists(RegExpSequence seq | literal.getRoot() = seq | - seq.getChild(0).(RegExpConstant).getValue() = "." and - seq.getChild(1).(RegExpConstant).getValue() = "." and - seq.getNumChild() = 2 - ) - or - exists(RegExpTerm term | literal.getRoot() = term | - term.getAMatchedString() = "/" or - term.getAMatchedString() = "." - ) + term.getAMatchedString() = "/" or + term.getAMatchedString() = "." or + term.getAMatchedString() = ".." ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js index 43ed20497ac..d12709d6344 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js @@ -172,7 +172,7 @@ var server = http.createServer(function(req, res) { var server = http.createServer(function(req, res) { let path = url.parse(req.url, true).query.path; - // Removal of forward-slash. + // Removal of forward-slash or dots. res.write(fs.readFileSync(path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''))); // OK. res.write(fs.readFileSync(path.replace(/[abcd]/g, ''))); // NOT OK res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // OK (can still be absolute) @@ -181,4 +181,6 @@ var server = http.createServer(function(req, res) { res.write(fs.readFileSync(path.replace(/[foobar/foobar]/g, ''))); // OK res.write(fs.readFileSync(path.replace(/\//g, ''))); // OK res.write(fs.readFileSync(path.replace(/\./g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\.|\//g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // OK }); \ No newline at end of file From f03c67266abaf379cb83edceb9531cd668e3fd68 Mon Sep 17 00:00:00 2001 From: Erik Krogh Kristensen Date: Tue, 3 Mar 2020 12:58:06 +0100 Subject: [PATCH 178/236] add taint step for replace call that only removes dots --- .../security/dataflow/TaintedPath.qll | 9 ++ .../dataflow/TaintedPathCustomizations.qll | 33 ++++ .../CWE-022/TaintedPath/TaintedPath.expected | 152 ++++++++++++++++++ .../CWE-022/TaintedPath/TaintedPath.js | 16 +- 4 files changed, 206 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll index 242504a9c59..06b0a6eb9e6 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPath.qll @@ -197,6 +197,15 @@ module TaintedPath { srclabel = dstlabel ) or + // foo.replace(/\./, "") and similar + exists(DotRemovingReplaceCall call | + src = call.getInput() and + dst = call.getOutput() and + srclabel.isAbsolute() and + dstlabel.isAbsolute() and + dstlabel.isNormalized() + ) + or // path.join() exists(DataFlow::CallNode join, int n | join = NodeJSLib::Path::moduleMember("join").getACall() diff --git a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll index 6a8d1ddd825..c55fa8bad33 100644 --- a/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll +++ b/javascript/ql/src/semmle/javascript/security/dataflow/TaintedPathCustomizations.qll @@ -239,6 +239,39 @@ module TaintedPath { DataFlow::Node getOutput() { result = output } } + /** + * A call that removes all "." or ".." from a path, without also removing all forward slashes. + */ + class DotRemovingReplaceCall extends DataFlow::CallNode { + DataFlow::Node input; + DataFlow::Node output; + + DotRemovingReplaceCall() { + this.getCalleeName() = "replace" and + input = getReceiver() and + output = this and + exists(RegExpLiteral literal, RegExpTerm term | + getArgument(0).getALocalSource().asExpr() = literal and + literal.isGlobal() and + literal.getRoot() = term and + not term.getAMatchedString() = "/" + | + term.getAMatchedString() = "." or + term.getAMatchedString() = ".." + ) + } + + /** + * Gets the input path to be normalized. + */ + DataFlow::Node getInput() { result = input } + + /** + * Gets the normalized path. + */ + DataFlow::Node getOutput() { result = output } + } + /** * Holds if `node` is a prefix of the string `../`. */ diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected index de3de113811..8dd69861f7e 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.expected @@ -1225,6 +1225,58 @@ nodes | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | | normalizedPaths.js:11:7:11:27 | path | @@ -4069,6 +4121,38 @@ edges | TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | | TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | | TaintedPath.js:173:7:173:48 | path | TaintedPath.js:177:29:177:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:183:29:183:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:184:29:184:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:185:29:185:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | +| TaintedPath.js:173:7:173:48 | path | TaintedPath.js:186:29:186:32 | path | | TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | | TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | | TaintedPath.js:173:14:173:37 | url.par ... , true) | TaintedPath.js:173:14:173:43 | url.par ... ).query | @@ -4181,6 +4265,70 @@ edges | TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | | TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | | TaintedPath.js:177:29:177:32 | path | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:183:29:183:32 | path | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:184:29:184:32 | path | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:185:29:185:32 | path | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | +| TaintedPath.js:186:29:186:32 | path | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | | normalizedPaths.js:11:7:11:27 | path | normalizedPaths.js:13:19:13:22 | path | @@ -5640,6 +5788,10 @@ edges | TaintedPath.js:166:19:166:38 | concatted2.join("/") | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:166:19:166:38 | concatted2.join("/") | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | | TaintedPath.js:168:19:168:29 | split.pop() | TaintedPath.js:149:24:149:30 | req.url | TaintedPath.js:168:19:168:29 | split.pop() | This path depends on $@. | TaintedPath.js:149:24:149:30 | req.url | a user-provided value | | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:177:29:177:55 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:183:29:183:52 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:183:29:183:52 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:184:29:184:53 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:184:29:184:53 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:185:29:185:51 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:185:29:185:51 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | +| TaintedPath.js:186:29:186:57 | path.re ... /g, '') | TaintedPath.js:173:24:173:30 | req.url | TaintedPath.js:186:29:186:57 | path.re ... /g, '') | This path depends on $@. | TaintedPath.js:173:24:173:30 | req.url | a user-provided value | | normalizedPaths.js:13:19:13:22 | path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:13:19:13:22 | path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | | normalizedPaths.js:14:19:14:29 | './' + path | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:14:19:14:29 | './' + path | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | | normalizedPaths.js:15:19:15:38 | path + '/index.html' | normalizedPaths.js:11:14:11:27 | req.query.path | normalizedPaths.js:15:19:15:38 | path + '/index.html' | This path depends on $@. | normalizedPaths.js:11:14:11:27 | req.query.path | a user-provided value | diff --git a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js index d12709d6344..0ecf680367f 100644 --- a/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js +++ b/javascript/ql/test/query-tests/Security/CWE-022/TaintedPath/TaintedPath.js @@ -175,12 +175,20 @@ var server = http.createServer(function(req, res) { // Removal of forward-slash or dots. res.write(fs.readFileSync(path.replace(/[\]\[*,;'"`<>\\?\/]/g, ''))); // OK. res.write(fs.readFileSync(path.replace(/[abcd]/g, ''))); // NOT OK - res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // OK (can still be absolute) - res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // OK res.write(fs.readFileSync(path.replace(/[./]/g, ''))); // OK res.write(fs.readFileSync(path.replace(/[foobar/foobar]/g, ''))); // OK res.write(fs.readFileSync(path.replace(/\//g, ''))); // OK - res.write(fs.readFileSync(path.replace(/\./g, ''))); // OK res.write(fs.readFileSync(path.replace(/\.|\//g, ''))); // OK - res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // OK + + res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // NOT OK (can be absolute) + res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // NOT OK (can be absolute) + res.write(fs.readFileSync(path.replace(/\./g, ''))); // NOT OK (can be absolute) + res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // NOT OK (can be absolute) + + if (!pathModule.isAbsolute(path)) { + res.write(fs.readFileSync(path.replace(/[.]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/[..]/g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\./g, ''))); // OK + res.write(fs.readFileSync(path.replace(/\.\.|BLA/g, ''))); // OK + } }); \ No newline at end of file From 4601639bad5b2f2032f259b8cf23f7db6b67916b Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 3 Mar 2020 13:39:26 +0100 Subject: [PATCH 179/236] Java: Document a FP in a test. --- java/ql/test/query-tests/Nullness/C.java | 12 ++++++++++++ java/ql/test/query-tests/Nullness/NullMaybe.expected | 1 + 2 files changed, 13 insertions(+) diff --git a/java/ql/test/query-tests/Nullness/C.java b/java/ql/test/query-tests/Nullness/C.java index 78df15756c4..48d7799c9a1 100644 --- a/java/ql/test/query-tests/Nullness/C.java +++ b/java/ql/test/query-tests/Nullness/C.java @@ -208,4 +208,16 @@ public class C { } } } + + public void ex15(Object o1, Object o2) { + if (o1 == null && o2 != null) { + return; + } + if (o1 == o2) { + return; + } + if (o1.equals(o2)) { // NPE - false positive + return; + } + } } diff --git a/java/ql/test/query-tests/Nullness/NullMaybe.expected b/java/ql/test/query-tests/Nullness/NullMaybe.expected index be68a274c25..2ddb51dfe4c 100644 --- a/java/ql/test/query-tests/Nullness/NullMaybe.expected +++ b/java/ql/test/query-tests/Nullness/NullMaybe.expected @@ -30,5 +30,6 @@ | C.java:144:15:144:15 | a | Variable $@ may be null here as suggested by $@ null guard. | C.java:141:20:141:26 | a | a | C.java:142:13:142:21 | ... == ... | this | | C.java:188:9:188:11 | obj | Variable $@ may be null here because of $@ assignment. | C.java:181:5:181:22 | Object obj | obj | C.java:181:12:181:21 | obj | this | | C.java:207:9:207:11 | obj | Variable $@ may be null here because of $@ assignment. | C.java:201:5:201:22 | Object obj | obj | C.java:201:12:201:21 | obj | this | +| C.java:219:9:219:10 | o1 | Variable $@ may be null here as suggested by $@ null guard. | C.java:212:20:212:28 | o1 | o1 | C.java:213:9:213:18 | ... == ... | this | | F.java:11:5:11:7 | obj | Variable $@ may be null here as suggested by $@ null guard. | F.java:8:18:8:27 | obj | obj | F.java:9:9:9:19 | ... == ... | this | | F.java:17:5:17:7 | obj | Variable $@ may be null here as suggested by $@ null guard. | F.java:14:18:14:27 | obj | obj | F.java:15:9:15:19 | ... == ... | this | From dfa07130b56495067c8b4d7a0175e1ac9d2848c0 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 3 Mar 2020 13:47:13 +0100 Subject: [PATCH 180/236] JS: add `process` EventEmitter test --- .../ql/test/library-tests/frameworks/EventEmitter/tst.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js b/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js index d45069b2c8e..229e75b5b57 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/tst.js @@ -37,4 +37,11 @@ class ExtendsMyCustomEmitter extends MyEventEmitter{} var em5 = new ExtendsMyCustomEmitter(); em5.on("yibity", (x) => {}); -em5.emit("yibity", "yabity"); \ No newline at end of file +em5.emit("yibity", "yabity"); + +var process = require('process'); +process.addListener('FirstEvent', function (first) {}); +process.on('SecondEvent', function (second) {}); + +process.emit('FirstEvent', 'FirstData'); +process.emit('SecondEvent', 'SecondData'); From eecace788f29dd0fe1a2ef27ea1c8054db4efffd Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 3 Mar 2020 18:18:03 +0100 Subject: [PATCH 181/236] Python: Fix or disable CPs introduced by #2700 and #2875. --- .../semmle/python/dataflow/Implementation.qll | 2 + .../python/security/strings/External.qll | 10 ++-- .../taint/unpacking/TestTaint.expected | 14 ++--- .../taint/unpacking/TestStep.expected | 27 ---------- .../taint/unpacking/TestTaint.expected | 54 +++++++++---------- 5 files changed, 41 insertions(+), 66 deletions(-) diff --git a/python/ql/src/semmle/python/dataflow/Implementation.qll b/python/ql/src/semmle/python/dataflow/Implementation.qll index b2326f2d100..9f7d44bdfb6 100644 --- a/python/ql/src/semmle/python/dataflow/Implementation.qll +++ b/python/ql/src/semmle/python/dataflow/Implementation.qll @@ -726,6 +726,8 @@ private class EssaTaintTracking extends string { private TaintKind iterable_unpacking_descent( SequenceNode left_parent, ControlFlowNode left_defn, CollectionKind parent_kind ) { + //TODO: Fix the cartesian product in this predicate + none() and left_parent.getAnElement() = left_defn and // Handle `a, *b = some_iterable` if left_defn instanceof StarredNode diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index 40dafff0f62..10687423c29 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -223,19 +223,19 @@ class UrlsplitUrlparseTempSanitizer extends Sanitizer { or full_use.(AttrNode).getObject() = test.getInput().getAUse() | - clears_taint(_, full_use, test.getTest(), test.getSense()) + clears_taint(full_use, test.getTest(), test.getSense()) ) } - private predicate clears_taint(ControlFlowNode final_test, ControlFlowNode tainted, ControlFlowNode test, boolean sense) { - test_equality_with_const(final_test, tainted, sense) + private predicate clears_taint(ControlFlowNode tainted, ControlFlowNode test, boolean sense) { + test_equality_with_const(test, tainted, sense) or - test_in_const_seq(final_test, tainted, sense) + test_in_const_seq(test, tainted, sense) or test.(UnaryExprNode).getNode().getOp() instanceof Not and exists(ControlFlowNode nested_test | nested_test = test.(UnaryExprNode).getOperand() and - clears_taint(final_test, tainted, nested_test, sense.booleanNot()) + clears_taint(tainted, nested_test, sense.booleanNot()) ) } diff --git a/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected b/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected index b6aacc7d670..3b24db6bee3 100644 --- a/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected +++ b/python/ql/test/3/library-tests/taint/unpacking/TestTaint.expected @@ -1,9 +1,9 @@ -| test.py:11 | extended_unpacking | first | externally controlled string | -| test.py:11 | extended_unpacking | last | externally controlled string | -| test.py:11 | extended_unpacking | rest | [externally controlled string] | -| test.py:16 | also_allowed | a | [externally controlled string] | +| test.py:11 | extended_unpacking | first | NO TAINT | +| test.py:11 | extended_unpacking | last | NO TAINT | +| test.py:11 | extended_unpacking | rest | NO TAINT | +| test.py:16 | also_allowed | a | NO TAINT | | test.py:24 | also_allowed | b | NO TAINT | | test.py:24 | also_allowed | c | NO TAINT | -| test.py:31 | nested | x | externally controlled string | -| test.py:31 | nested | xs | [externally controlled string] | -| test.py:31 | nested | ys | [externally controlled string] | +| test.py:31 | nested | x | NO TAINT | +| test.py:31 | nested | xs | NO TAINT | +| test.py:31 | nested | ys | NO TAINT | diff --git a/python/ql/test/library-tests/taint/unpacking/TestStep.expected b/python/ql/test/library-tests/taint/unpacking/TestStep.expected index 5d800e6b5b8..51c4e7fc410 100644 --- a/python/ql/test/library-tests/taint/unpacking/TestStep.expected +++ b/python/ql/test/library-tests/taint/unpacking/TestStep.expected @@ -1,35 +1,8 @@ | Taint [[externally controlled string]] | test.py:19 | test.py:19:10:19:18 | List | | --> | Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | | Taint [[externally controlled string]] | test.py:19 | test.py:19:10:19:18 | List | | --> | Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | | Taint [[externally controlled string]] | test.py:19 | test.py:19:10:19:18 | List | | --> | Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint [externally controlled string] | test.py:23 | test.py:23:22:23:22 | b | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint [externally controlled string] | test.py:23 | test.py:23:25:23:25 | c | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint externally controlled string | test.py:23 | test.py:23:10:23:11 | a1 | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint externally controlled string | test.py:23 | test.py:23:14:23:15 | a2 | | -| Taint [[externally controlled string]] | test.py:22 | test.py:22:28:22:29 | ll | | --> | Taint externally controlled string | test.py:23 | test.py:23:18:23:19 | a3 | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint [externally controlled string] | test.py:27 | test.py:27:22:27:22 | b | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint [externally controlled string] | test.py:27 | test.py:27:25:27:25 | c | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint externally controlled string | test.py:27 | test.py:27:10:27:11 | a1 | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint externally controlled string | test.py:27 | test.py:27:14:27:15 | a2 | | -| Taint [[externally controlled string]] | test.py:26 | test.py:26:28:26:29 | ll | | --> | Taint externally controlled string | test.py:27 | test.py:27:18:27:19 | a3 | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint [externally controlled string] | test.py:31 | test.py:31:22:31:22 | b | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint [externally controlled string] | test.py:31 | test.py:31:25:31:25 | c | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint externally controlled string | test.py:31 | test.py:31:10:31:11 | a1 | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint externally controlled string | test.py:31 | test.py:31:14:31:15 | a2 | | -| Taint [[externally controlled string]] | test.py:30 | test.py:30:28:30:29 | ll | | --> | Taint externally controlled string | test.py:31 | test.py:31:18:31:19 | a3 | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:10:48:10 | a | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:13:48:13 | b | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:16:48:16 | c | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:19:48:19 | d | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:22:48:22 | e | | -| Taint [[externally controlled string]] | test.py:47 | test.py:47:28:47:54 | Tuple | | --> | Taint externally controlled string | test.py:48 | test.py:48:25:48:25 | f | | | Taint [externally controlled string] | test.py:6 | test.py:6:9:6:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | -| Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | --> | Taint externally controlled string | test.py:8 | test.py:8:10:8:10 | a | | -| Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | --> | Taint externally controlled string | test.py:8 | test.py:8:13:8:13 | b | | -| Taint [externally controlled string] | test.py:7 | test.py:7:15:7:15 | l | | --> | Taint externally controlled string | test.py:8 | test.py:8:16:8:16 | c | | | Taint [externally controlled string] | test.py:12 | test.py:12:9:12:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | -| Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | --> | Taint externally controlled string | test.py:14 | test.py:14:10:14:10 | a | | -| Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | --> | Taint externally controlled string | test.py:14 | test.py:14:13:14:13 | b | | -| Taint [externally controlled string] | test.py:13 | test.py:13:17:13:17 | l | | --> | Taint externally controlled string | test.py:14 | test.py:14:16:14:16 | c | | | Taint [externally controlled string] | test.py:18 | test.py:18:9:18:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:19 | test.py:19:11:19:11 | l | | | Taint [externally controlled string] | test.py:18 | test.py:18:9:18:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:19 | test.py:19:14:19:14 | l | | | Taint [externally controlled string] | test.py:18 | test.py:18:9:18:20 | TAINTED_LIST | | --> | Taint [externally controlled string] | test.py:19 | test.py:19:17:19:17 | l | | diff --git a/python/ql/test/library-tests/taint/unpacking/TestTaint.expected b/python/ql/test/library-tests/taint/unpacking/TestTaint.expected index d1bad70f811..dc704043c7d 100644 --- a/python/ql/test/library-tests/taint/unpacking/TestTaint.expected +++ b/python/ql/test/library-tests/taint/unpacking/TestTaint.expected @@ -1,33 +1,33 @@ -| test.py:8 | unpacking | a | externally controlled string | -| test.py:8 | unpacking | b | externally controlled string | -| test.py:8 | unpacking | c | externally controlled string | -| test.py:14 | unpacking_to_list | a | externally controlled string | -| test.py:14 | unpacking_to_list | b | externally controlled string | -| test.py:14 | unpacking_to_list | c | externally controlled string | -| test.py:23 | nested | a1 | externally controlled string | -| test.py:23 | nested | a2 | externally controlled string | -| test.py:23 | nested | a3 | externally controlled string | -| test.py:23 | nested | b | [externally controlled string] | -| test.py:23 | nested | c | [externally controlled string] | -| test.py:27 | nested | a1 | externally controlled string | -| test.py:27 | nested | a2 | externally controlled string | -| test.py:27 | nested | a3 | externally controlled string | -| test.py:27 | nested | b | [externally controlled string] | -| test.py:27 | nested | c | [externally controlled string] | -| test.py:31 | nested | a1 | externally controlled string | -| test.py:31 | nested | a2 | externally controlled string | -| test.py:31 | nested | a3 | externally controlled string | -| test.py:31 | nested | b | [externally controlled string] | -| test.py:31 | nested | c | [externally controlled string] | +| test.py:8 | unpacking | a | NO TAINT | +| test.py:8 | unpacking | b | NO TAINT | +| test.py:8 | unpacking | c | NO TAINT | +| test.py:14 | unpacking_to_list | a | NO TAINT | +| test.py:14 | unpacking_to_list | b | NO TAINT | +| test.py:14 | unpacking_to_list | c | NO TAINT | +| test.py:23 | nested | a1 | NO TAINT | +| test.py:23 | nested | a2 | NO TAINT | +| test.py:23 | nested | a3 | NO TAINT | +| test.py:23 | nested | b | NO TAINT | +| test.py:23 | nested | c | NO TAINT | +| test.py:27 | nested | a1 | NO TAINT | +| test.py:27 | nested | a2 | NO TAINT | +| test.py:27 | nested | a3 | NO TAINT | +| test.py:27 | nested | b | NO TAINT | +| test.py:27 | nested | c | NO TAINT | +| test.py:31 | nested | a1 | NO TAINT | +| test.py:31 | nested | a2 | NO TAINT | +| test.py:31 | nested | a3 | NO TAINT | +| test.py:31 | nested | b | NO TAINT | +| test.py:31 | nested | c | NO TAINT | | test.py:38 | unpack_from_set | a | NO TAINT | | test.py:38 | unpack_from_set | b | NO TAINT | | test.py:38 | unpack_from_set | c | NO TAINT | -| test.py:48 | contrived_1 | a | externally controlled string | -| test.py:48 | contrived_1 | b | externally controlled string | -| test.py:48 | contrived_1 | c | externally controlled string | -| test.py:48 | contrived_1 | d | externally controlled string | -| test.py:48 | contrived_1 | e | externally controlled string | -| test.py:48 | contrived_1 | f | externally controlled string | +| test.py:48 | contrived_1 | a | NO TAINT | +| test.py:48 | contrived_1 | b | NO TAINT | +| test.py:48 | contrived_1 | c | NO TAINT | +| test.py:48 | contrived_1 | d | NO TAINT | +| test.py:48 | contrived_1 | e | NO TAINT | +| test.py:48 | contrived_1 | f | NO TAINT | | test.py:56 | contrived_2 | a | NO TAINT | | test.py:56 | contrived_2 | b | NO TAINT | | test.py:56 | contrived_2 | c | NO TAINT | From 48a47e1b54123959ede3ec4c2f7fe212fd19c4aa Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Tue, 3 Mar 2020 19:45:13 +0100 Subject: [PATCH 182/236] Python: Fix broken test output. --- .../test/library-tests/taint/collections/TestStep.expected | 3 --- .../test/library-tests/taint/collections/TestTaint.expected | 6 +++--- .../test/library-tests/taint/namedtuple/TestTaint.expected | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/python/ql/test/library-tests/taint/collections/TestStep.expected b/python/ql/test/library-tests/taint/collections/TestStep.expected index a9591db0b9f..609c7c3bcd1 100644 --- a/python/ql/test/library-tests/taint/collections/TestStep.expected +++ b/python/ql/test/library-tests/taint/collections/TestStep.expected @@ -26,9 +26,6 @@ | Taint [externally controlled string] | test.py:29 | test.py:29:9:29:25 | Subscript | | --> | Taint [externally controlled string] | test.py:32 | test.py:32:16:32:16 | c | | | Taint [externally controlled string] | test.py:30 | test.py:30:9:30:20 | tainted_list | | --> | Taint [externally controlled string] | test.py:30 | test.py:30:9:30:27 | Attribute() | | | Taint [externally controlled string] | test.py:30 | test.py:30:9:30:27 | Attribute() | | --> | Taint [externally controlled string] | test.py:32 | test.py:32:19:32:19 | d | | -| Taint [externally controlled string] | test.py:31 | test.py:31:15:31:26 | tainted_list | | --> | Taint externally controlled string | test.py:32 | test.py:32:22:32:22 | e | | -| Taint [externally controlled string] | test.py:31 | test.py:31:15:31:26 | tainted_list | | --> | Taint externally controlled string | test.py:32 | test.py:32:25:32:25 | f | | -| Taint [externally controlled string] | test.py:31 | test.py:31:15:31:26 | tainted_list | | --> | Taint externally controlled string | test.py:32 | test.py:32:28:32:28 | g | | | Taint [externally controlled string] | test.py:33 | test.py:33:14:33:25 | tainted_list | | --> | Taint externally controlled string | test.py:33 | test.py:33:5:33:26 | For | | | Taint [externally controlled string] | test.py:35 | test.py:35:14:35:35 | reversed() | | --> | Taint externally controlled string | test.py:35 | test.py:35:5:35:36 | For | | | Taint [externally controlled string] | test.py:35 | test.py:35:23:35:34 | tainted_list | | --> | Taint [externally controlled string] | test.py:35 | test.py:35:14:35:35 | reversed() | | diff --git a/python/ql/test/library-tests/taint/collections/TestTaint.expected b/python/ql/test/library-tests/taint/collections/TestTaint.expected index aae51236e6d..6f3ca9515d1 100644 --- a/python/ql/test/library-tests/taint/collections/TestTaint.expected +++ b/python/ql/test/library-tests/taint/collections/TestTaint.expected @@ -10,9 +10,9 @@ | test.py:32 | test_access | b | externally controlled string | | test.py:32 | test_access | c | [externally controlled string] | | test.py:32 | test_access | d | [externally controlled string] | -| test.py:32 | test_access | e | externally controlled string | -| test.py:32 | test_access | f | externally controlled string | -| test.py:32 | test_access | g | externally controlled string | +| test.py:32 | test_access | e | NO TAINT | +| test.py:32 | test_access | f | NO TAINT | +| test.py:32 | test_access | g | NO TAINT | | test.py:34 | test_access | h | externally controlled string | | test.py:36 | test_access | i | externally controlled string | | test.py:43 | test_dict_access | a | externally controlled string | diff --git a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected index 62b589299dd..79a262c61d5 100644 --- a/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected +++ b/python/ql/test/library-tests/taint/namedtuple/TestTaint.expected @@ -1,7 +1,7 @@ | test.py:13 | test_basic | a | externally controlled string | | test.py:13 | test_basic | b | externally controlled string | | test.py:13 | test_basic | c | externally controlled string | -| test.py:13 | test_basic | d | externally controlled string | +| test.py:13 | test_basic | d | NO TAINT | | test.py:13 | test_basic | urlsplit_res | [externally controlled string] | | test.py:19 | test_sanitizer | Attribute | externally controlled string | | test.py:22 | test_sanitizer | Attribute | NO TAINT | From 8e1c089ff35658f9e5ce66b9514c285a4b6865a5 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 14:38:26 -0800 Subject: [PATCH 183/236] Python: Removes unused import --- python/ql/src/Expressions/Formatting/AdvancedFormatting.qll | 1 - 1 file changed, 1 deletion(-) diff --git a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll index 13292465650..f32b338c8ac 100644 --- a/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll +++ b/python/ql/src/Expressions/Formatting/AdvancedFormatting.qll @@ -1,5 +1,4 @@ import python -private import semmle.python.types.Builtins library class PossibleAdvancedFormatString extends StrConst { From ce204ac949dee9e569a8de89bfc7b10fdb7e4655 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 14:57:30 -0800 Subject: [PATCH 184/236] Python: Moves statement out of quantifier that it didnt need to be in --- python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql index 3b2116734cd..71d5bec2fca 100644 --- a/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql +++ b/python/ql/src/Expressions/WrongNumberArgumentsForFormat.ql @@ -16,7 +16,8 @@ import python import semmle.python.strings predicate string_format(BinaryExpr operation, StrConst str, Value args, AstNode origin) { - exists(Value fmt, Context ctx | operation.getOp() instanceof Mod | + operation.getOp() instanceof Mod and + exists(Value fmt, Context ctx | operation.getLeft().pointsTo(ctx, fmt, str) and operation.getRight().pointsTo(ctx, args, origin) ) From e39a0c91218d92b3dc76c110d0dce2f8bcaedf1e Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 15:38:35 -0800 Subject: [PATCH 185/236] Python: Moves `non_local_control_flow` to new API, and fixes bug in ClassValue implementation --- python/ql/src/Exceptions/EmptyExcept.ql | 2 +- python/ql/src/semmle/python/objects/ObjectAPI.qll | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql index 9e2b16a49e7..83c078fd4e2 100755 --- a/python/ql/src/Exceptions/EmptyExcept.ql +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -31,7 +31,7 @@ predicate no_comment(ExceptStmt ex) { } predicate non_local_control_flow(ExceptStmt ex) { - ex.getType().refersTo(theStopIterationType()) + ex.getType().pointsTo(ClassValue::stopIteration()) } predicate try_has_normal_exit(Try try) { diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 1f1f7001da1..aa4b375254f 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -839,7 +839,7 @@ module ClassValue { /** Get the `ClassValue` for the `StopIteration` class. */ ClassValue stopIteration() { - result = TBuiltinClassObject(Builtin::special("StopIteration")) + result = TBuiltinClassObject(Builtin::builtin("StopIteration")) } /** Get the `ClassValue` for the class of modules. */ From 5fea31eed1911516b4e84350b954f242d1783ed7 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 15:55:40 -0800 Subject: [PATCH 186/236] Python: Moves predicates over to suffixed form --- python/ql/src/Exceptions/EmptyExcept.ql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql index 83c078fd4e2..2a748fd54b9 100755 --- a/python/ql/src/Exceptions/EmptyExcept.ql +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -64,7 +64,7 @@ predicate subscript(Stmt s) { s.(Delete).getATarget() instanceof Subscript } -predicate encode_decode(Expr ex, ClassObject type) { +predicate encode_decode_objectapi(Expr ex, ClassObject type) { exists(string name | ex.(Call).getFunc().(Attribute).getName() = name | name = "encode" and type = Object::builtin("UnicodeEncodeError") @@ -73,7 +73,7 @@ predicate encode_decode(Expr ex, ClassObject type) { ) } -predicate small_handler(ExceptStmt ex, Stmt s, ClassObject type) { +predicate small_handler_objectapi(ExceptStmt ex, Stmt s, ClassObject type) { not exists(ex.getTry().getStmt(1)) and s = ex.getTry().getStmt(0) and ex.getType().refersTo(type) @@ -82,16 +82,16 @@ predicate small_handler(ExceptStmt ex, Stmt s, ClassObject type) { /** Holds if this exception handler is sufficiently small in scope to not need a comment * as to what it is doing. */ -predicate focussed_handler(ExceptStmt ex) { +predicate focussed_handler_objectapi(ExceptStmt ex) { exists(Stmt s, ClassObject type | - small_handler(ex, s, type) | + small_handler_objectapi(ex, s, type) | subscript(s) and type.getAnImproperSuperType() = theLookupErrorType() or attribute_access(s) and type = theAttributeErrorType() or s.(ExprStmt).getValue() instanceof Name and type = theNameErrorType() or - encode_decode(s.(ExprStmt).getValue(), type) + encode_decode_objectapi(s.(ExprStmt).getValue(), type) ) } @@ -102,5 +102,5 @@ Try try_return() { from ExceptStmt ex where empty_except(ex) and no_else(ex) and no_comment(ex) and not non_local_control_flow(ex) and not ex.getTry() = try_return() and try_has_normal_exit(ex.getTry()) and - not focussed_handler(ex) + not focussed_handler_objectapi(ex) select ex, "'except' clause does nothing but pass and there is no explanatory comment." From 3bedd5cb58ccd5eb55096f4ec9de133bd7a4cf93 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 16:02:25 -0800 Subject: [PATCH 187/236] Python: Adds modernized predicates and moves query over to them --- python/ql/src/Exceptions/EmptyExcept.ql | 30 ++++++++++++++++++- .../src/semmle/python/objects/ObjectAPI.qll | 15 ++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql index 2a748fd54b9..e96fd8c28e8 100755 --- a/python/ql/src/Exceptions/EmptyExcept.ql +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -73,12 +73,27 @@ predicate encode_decode_objectapi(Expr ex, ClassObject type) { ) } +predicate encode_decode(Expr ex, ClassValue type) { + exists(string name | + ex.(Call).getFunc().(Attribute).getName() = name | + name = "encode" and type = ClassValue::unicodeEncodeError() + or + name = "decode" and type = ClassValue::unicodeDecodeError() + ) +} + predicate small_handler_objectapi(ExceptStmt ex, Stmt s, ClassObject type) { not exists(ex.getTry().getStmt(1)) and s = ex.getTry().getStmt(0) and ex.getType().refersTo(type) } +predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) { + not exists(ex.getTry().getStmt(1)) and + s = ex.getTry().getStmt(0) and + ex.getType().pointsTo(type) +} + /** Holds if this exception handler is sufficiently small in scope to not need a comment * as to what it is doing. */ @@ -95,6 +110,19 @@ predicate focussed_handler_objectapi(ExceptStmt ex) { ) } +predicate focussed_handler(ExceptStmt ex) { + exists(Stmt s, ClassValue type | + small_handler(ex, s, type) | + subscript(s) and type.getASuperType() = ClassValue::lookupError() + or + attribute_access(s) and type = ClassValue::attributeError() + or + s.(ExprStmt).getValue() instanceof Name and type = ClassValue::nameError() + or + encode_decode(s.(ExprStmt).getValue(), type) + ) +} + Try try_return() { not exists(result.getStmt(1)) and result.getStmt(0) instanceof Return } @@ -102,5 +130,5 @@ Try try_return() { from ExceptStmt ex where empty_except(ex) and no_else(ex) and no_comment(ex) and not non_local_control_flow(ex) and not ex.getTry() = try_return() and try_has_normal_exit(ex.getTry()) and - not focussed_handler_objectapi(ex) + not focussed_handler(ex) select ex, "'except' clause does nothing but pass and there is no explanatory comment." diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index aa4b375254f..1e759480847 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -882,6 +882,11 @@ module ClassValue { result = TBuiltinClassObject(Builtin::builtin("KeyError")) } + /** Get the `ClassValue` for the `LookupError` class. */ + ClassValue lookupError() { + result = TBuiltinClassObject(Builtin::builtin("LookupError")) + } + /** Get the `ClassValue` for the `IOError` class. */ ClassValue ioError() { result = TBuiltinClassObject(Builtin::builtin("IOError")) @@ -896,5 +901,15 @@ module ClassValue { ClassValue importError() { result = TBuiltinClassObject(Builtin::builtin("ImportError")) } + + /** Get the `ClassValue` for the `UnicodeEncodeError` class. */ + ClassValue unicodeEncodeError() { + result = TBuiltinClassObject(Builtin::builtin("UnicodeEncodeError")) + } + + /** Get the `ClassValue` for the `UnicodeDecodeError` class. */ + ClassValue unicodeDecodeError() { + result = TBuiltinClassObject(Builtin::builtin("UnicodeDecodeError")) + } } From 88daf658543763edb330066b629e42ae09df3f26 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 16:03:27 -0800 Subject: [PATCH 188/236] Python: Removes obsolete predicates --- python/ql/src/Exceptions/EmptyExcept.ql | 31 ------------------------- 1 file changed, 31 deletions(-) diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql index e96fd8c28e8..7cb09203e9d 100755 --- a/python/ql/src/Exceptions/EmptyExcept.ql +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -64,15 +64,6 @@ predicate subscript(Stmt s) { s.(Delete).getATarget() instanceof Subscript } -predicate encode_decode_objectapi(Expr ex, ClassObject type) { - exists(string name | - ex.(Call).getFunc().(Attribute).getName() = name | - name = "encode" and type = Object::builtin("UnicodeEncodeError") - or - name = "decode" and type = Object::builtin("UnicodeDecodeError") - ) -} - predicate encode_decode(Expr ex, ClassValue type) { exists(string name | ex.(Call).getFunc().(Attribute).getName() = name | @@ -82,34 +73,12 @@ predicate encode_decode(Expr ex, ClassValue type) { ) } -predicate small_handler_objectapi(ExceptStmt ex, Stmt s, ClassObject type) { - not exists(ex.getTry().getStmt(1)) and - s = ex.getTry().getStmt(0) and - ex.getType().refersTo(type) -} - predicate small_handler(ExceptStmt ex, Stmt s, ClassValue type) { not exists(ex.getTry().getStmt(1)) and s = ex.getTry().getStmt(0) and ex.getType().pointsTo(type) } -/** Holds if this exception handler is sufficiently small in scope to not need a comment - * as to what it is doing. - */ -predicate focussed_handler_objectapi(ExceptStmt ex) { - exists(Stmt s, ClassObject type | - small_handler_objectapi(ex, s, type) | - subscript(s) and type.getAnImproperSuperType() = theLookupErrorType() - or - attribute_access(s) and type = theAttributeErrorType() - or - s.(ExprStmt).getValue() instanceof Name and type = theNameErrorType() - or - encode_decode_objectapi(s.(ExprStmt).getValue(), type) - ) -} - predicate focussed_handler(ExceptStmt ex) { exists(Stmt s, ClassValue type | small_handler(ex, s, type) | From b33b222db84caec7a6153c60ab50fbf314ddc227 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Tue, 3 Mar 2020 16:41:52 -0800 Subject: [PATCH 189/236] Python: Modernizes query --- python/ql/src/Exceptions/CatchingBaseException.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Exceptions/CatchingBaseException.ql b/python/ql/src/Exceptions/CatchingBaseException.ql index 4d5be501ecf..d175297f1c9 100644 --- a/python/ql/src/Exceptions/CatchingBaseException.ql +++ b/python/ql/src/Exceptions/CatchingBaseException.ql @@ -19,7 +19,7 @@ predicate doesnt_reraise(ExceptStmt ex) { } predicate catches_base_exception(ExceptStmt ex) { - ex.getType().refersTo(theBaseExceptionType()) + ex.getType().pointsTo(ClassValue::baseException()) or not exists(ex.getType()) } From ae43e90a675fa28a002cd6ec1276dd8dcb41e98e Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Tue, 3 Mar 2020 14:00:48 +0100 Subject: [PATCH 190/236] JS: model `process` as an EventEmitter --- .../ql/src/semmle/javascript/frameworks/NodeJSLib.qll | 9 +++++++++ .../library-tests/frameworks/EventEmitter/test.expected | 2 ++ 2 files changed, 11 insertions(+) diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index 9a5fe815d55..d10e219a001 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -935,6 +935,15 @@ module NodeJSLib { ImportedNodeJSEventEmitter() { this = getAnEventEmitterImport().getAnInstantiation() } } + /** + * The NodeJS `process` object as an EventEmitter subclass. + */ + private class ProcessAsNodeJSEventEmitter extends NodeJSEventEmitter { + ProcessAsNodeJSEventEmitter() { + this = process() + } + } + /** * A class that extends EventEmitter. */ diff --git a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected index c8526d5eecf..34a042bf626 100644 --- a/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected +++ b/javascript/ql/test/library-tests/frameworks/EventEmitter/test.expected @@ -12,3 +12,5 @@ | tst.js:28:17:28:22 | "blab" | tst.js:25:16:25:20 | event | | tst.js:34:18:34:22 | "BOH" | tst.js:33:17:33:17 | x | | tst.js:40:20:40:27 | "yabity" | tst.js:39:19:39:19 | x | +| tst.js:46:28:46:38 | 'FirstData' | tst.js:43:45:43:49 | first | +| tst.js:47:29:47:40 | 'SecondData' | tst.js:44:37:44:42 | second | From 60bcbf477a10a718ba6d21911c5d5893566c7481 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Wed, 4 Mar 2020 10:26:48 +0100 Subject: [PATCH 191/236] C++: pragma[noinline] on GVN charpred The charpred of class `GVN` in `ASTValueNumbering.qll` got inlined into the member predicate `getAnInstruction` and caused a tuple explosion on Wireshark in the query `StrncpyFlippedArgs.ql`. I interrupted the predicate after 10 minutes and got these intermediate tuple counts: (5208s) Tuple counts for ASTValueNumbering::GVN::getAnInstruction_dispred#ff: 8754900909 ~5% {3} r1 = JOIN ValueNumberingInternal::tvalueNumber#ff_10#join_rhs AS L WITH ValueNumberingInternal::tvalueNumber#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, L.<1>, L.<0> 4390274632 ~150085% {2} r2 = JOIN r1 WITH project#SSAConstruction::Cached::getInstructionUnconvertedResultExpression AS R ON FIRST 1 OUTPUT r1.<2>, r1.<1> return r2 After this change, the `getAnInstruction` predicate is itself inlined, like it should be. The new non-inlined charpred takes 2.1s and has these tuple counts: (2s) Tuple counts for ASTValueNumbering::GVN#f: 9158442 ~117% {1} r1 = JOIN project#SSAConstruction::Cached::getInstructionUnconvertedResultExpression AS L WITH ValueNumberingInternal::tvalueNumber#ff@staged_ext AS R ON FIRST 1 OUTPUT R.<1> return r1 --- cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll b/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll index 2bb8315990a..90e9b4ef920 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/internal/ASTValueNumbering.qll @@ -51,6 +51,7 @@ private import semmle.code.cpp.ir.IR * methods. */ class GVN extends TValueNumber { + pragma[noinline] GVN() { exists(Instruction instr | this = tvalueNumber(instr) and exists(instr.getUnconvertedResultExpression()) From af0df6c369c6b274a3621e33fe3de7caaa77a3ce Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 4 Mar 2020 11:16:59 +0000 Subject: [PATCH 192/236] JS: Lower severity of js/syntax-error --- javascript/ql/src/LanguageFeatures/SyntaxError.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/LanguageFeatures/SyntaxError.ql b/javascript/ql/src/LanguageFeatures/SyntaxError.ql index 4d0a21a9661..6053567a8d6 100644 --- a/javascript/ql/src/LanguageFeatures/SyntaxError.ql +++ b/javascript/ql/src/LanguageFeatures/SyntaxError.ql @@ -2,7 +2,7 @@ * @name Syntax error * @description A piece of code could not be parsed due to syntax errors. * @kind problem - * @problem.severity error + * @problem.severity recommendation * @id js/syntax-error * @tags reliability * correctness From 6f2b05932ea80ec3e1939698cb74b61733f1a097 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 4 Mar 2020 11:18:12 +0000 Subject: [PATCH 193/236] JS: Change note --- change-notes/1.24/analysis-javascript.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index c5959147a10..e454d2b8f87 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -62,6 +62,7 @@ | Missing CSRF middleware (`js/missing-token-validation`) | Fewer false positive results | The query reports fewer duplicates and only flags handlers that explicitly access cookie data. | | Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed and used. | | Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional ways of constructing arguments to `cmd.exe` and `/bin/sh`. | +| Syntax error (`js/syntax-error`) | Lower severity | This results of this query are now displayed with lower severity. | ## Changes to libraries From 92b3e8c0601b740b92dd910b63b3e2effa4bfb61 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 4 Mar 2020 12:25:23 +0100 Subject: [PATCH 194/236] JS: add default/chaining tests for `request` --- .../frameworks/ClientRequests/ClientRequests.expected | 2 ++ .../test/library-tests/frameworks/ClientRequests/tst.js | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected index f3017c72a77..8137aecbe85 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected @@ -52,6 +52,7 @@ test_ClientRequest | tst.js:147:5:147:19 | got.stream(url) | | tst.js:151:5:151:23 | superagent.get(url) | | tst.js:160:5:160:17 | xhr.send(url) | +| tst.js:171:2:171:10 | base(url) | 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 | @@ -144,6 +145,7 @@ test_getUrl | tst.js:147:5:147:19 | got.stream(url) | tst.js:147:16:147:18 | url | | tst.js:151:5:151:23 | superagent.get(url) | tst.js:151:20:151:22 | url | | tst.js:160:5:160:17 | xhr.send(url) | tst.js:160:14:160:16 | url | +| tst.js:171:2:171:10 | base(url) | tst.js:171:7:171:9 | url | 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 | diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js index 8bd15b3a9eb..70244682892 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/tst.js @@ -163,3 +163,12 @@ import {ClientRequest, net} from 'electron'; xhr.getResponseHeaders(); }); }) + +(function() { + let base = request; + let variant1 = base.defaults({}); + let variant2 = variant1.defaults({}); + base(url); + variant1(url); + variant2(url); +}); From db335ae89b7008741fe4cbc2ae788450f11e5c29 Mon Sep 17 00:00:00 2001 From: Esben Sparre Andreasen Date: Wed, 4 Mar 2020 12:36:12 +0100 Subject: [PATCH 195/236] JS: add default/chaining for `request` --- change-notes/1.24/analysis-javascript.md | 1 + .../javascript/frameworks/ClientRequests.qll | 40 +++++++++++-------- .../ClientRequests/ClientRequests.expected | 4 ++ 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 1cfc11deb48..7e64cd0a562 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -32,6 +32,7 @@ - [http2](https://nodejs.org/api/http2.html) - [lazy-cache](https://www.npmjs.com/package/lazy-cache) - [react](https://www.npmjs.com/package/react) + - [request](https://www.npmjs.com/package/request) - [send](https://www.npmjs.com/package/send) - [typeahead.js](https://www.npmjs.com/package/typeahead.js) - [ws](https://github.com/websockets/ws) diff --git a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll index 4521c0a6534..e97169264e0 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ClientRequests.qll @@ -110,6 +110,27 @@ module ClientRequest { */ private string httpMethodName() { result = any(HTTP::RequestMethodName m).toLowerCase() } + /** + * Gets a model of an instance of the `request` library, or one of + * its wrappers, `promise` is true if the instance uses promises + * rather than callbacks. + */ + private DataFlow::SourceNode getRequestLibrary(boolean promise) { + exists(string moduleName | result = DataFlow::moduleImport(moduleName) | + promise = false and + moduleName = "request" + or + promise = true and + ( + moduleName = "request-promise" or + moduleName = "request-promise-any" or + moduleName = "request-promise-native" + ) + ) + or + result = getRequestLibrary(promise).getAMethodCall("defaults") + } + /** * A model of a URL request made using the `request` library. */ @@ -117,22 +138,9 @@ module ClientRequest { boolean promise; RequestUrlRequest() { - exists(string moduleName, DataFlow::SourceNode callee | this = callee.getACall() | - ( - promise = false and - moduleName = "request" - or - promise = true and - ( - moduleName = "request-promise" or - moduleName = "request-promise-any" or - moduleName = "request-promise-native" - ) - ) and - ( - callee = DataFlow::moduleImport(moduleName) or - callee = DataFlow::moduleMember(moduleName, httpMethodName()) - ) + exists(DataFlow::SourceNode callee | this = callee.getACall() | + callee = getRequestLibrary(promise) or + callee = getRequestLibrary(promise).getAPropertyRead(httpMethodName()) ) } diff --git a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected index 8137aecbe85..2548ec3dd34 100644 --- a/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected +++ b/javascript/ql/test/library-tests/frameworks/ClientRequests/ClientRequests.expected @@ -53,6 +53,8 @@ test_ClientRequest | tst.js:151:5:151:23 | superagent.get(url) | | tst.js:160:5:160:17 | xhr.send(url) | | tst.js:171:2:171:10 | base(url) | +| tst.js:172:2:172:14 | variant1(url) | +| tst.js:173:2:173:14 | variant2(url) | 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 | @@ -146,6 +148,8 @@ test_getUrl | tst.js:151:5:151:23 | superagent.get(url) | tst.js:151:20:151:22 | url | | tst.js:160:5:160:17 | xhr.send(url) | tst.js:160:14:160:16 | url | | tst.js:171:2:171:10 | base(url) | tst.js:171:7:171:9 | url | +| tst.js:172:2:172:14 | variant1(url) | tst.js:172:11:172:13 | url | +| tst.js:173:2:173:14 | variant2(url) | tst.js:173:11:173:13 | url | 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 | From 6b2fd17f03a9cf9786ddef06e2ccb6186b1ed837 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Wed, 4 Mar 2020 14:31:10 +0100 Subject: [PATCH 196/236] C++: IR: faster definitionReachesRank On Wireshark with 6GB RAM, I've observed `definitionReachesRank` to be the slowest predicate in the IR. It seems that the implementation was slow because the optimizer failed to eliminate the common `reachesRank - 1` subexpression. This led to context being pushed into the `not`, which got implemented as `MATERIALIZE`. That wouldn't normally be a disaster, but this is one of the largest predicates in the IR SSA construction, and iteration 2 was very slow. Before: (1505s) Starting to evaluate predicate SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta/4[1]@93f592 (iteration 1) (1535s) Tuple counts for SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta: 130670697 ~0% {4} r1 = SCAN project#SSAConstruction::DefUse::hasDefinitionAtRank#fffff AS I OUTPUT I.<0>, I.<1>, I.<2>, (I.<2> + 1) 130670697 ~6% {5} r2 = JOIN r1 WITH SSAConstruction::DefUse::exitRank#fff AS R ON FIRST 2 OUTPUT r1.<0>, r1.<1>, r1.<2>, r1.<3>, R.<2> 130670697 ~6% {5} r3 = SELECT r2 ON r2.<3> <= r2.<4> 130670697 ~0% {4} r4 = SCAN r3 OUTPUT r3.<0>, r3.<1>, r3.<2>, r3.<3> return r4 (1535s) - SSAConstruction::DefUse::definitionReachesRank#ffff_delta has 130670697 rows (order for disjuncts: delta=). (1535s) Starting to evaluate predicate SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta/4[2]@866c14 (iteration 2) (1626s) Tuple counts for SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta: 261341394 ~107% {4} r1 = JOIN SSAConstruction::DefUse::definitionReachesRank#ffff#prev_delta AS L WITH SSAConstruction::DefUse::definitionReachesRank#ffff#join_rhs AS R ON FIRST 3 OUTPUT R.<0>, R.<1>, R.<2>, (1 + L.<3>) 261341394 ~107% {4} r2 = r1 AND NOT SSAConstruction::DefUse::definitionReachesRank#ffff#prev AS R(r1.<0>, r1.<1>, r1.<2>, r1.<3>) 130670697 ~0% {5} r3 = SCAN r2 OUTPUT r2.<0>, r2.<1>, (r2.<3> - 1), r2.<2>, r2.<3> 106034590 ~1% {4} r4 = JOIN r3 WITH project#SSAConstruction::DefUse::hasDefinitionAtRank#fffff AS R ON FIRST 3 OUTPUT r3.<0>, r3.<1>, r3.<3>, r3.<4> 106034590 {4} r5 = MATERIALIZE r4 AS antijoin_rhs 24636107 ~3% {4} r6 = r2 AND NOT r5(r2.<0>, r2.<1>, r2.<2>, r2.<3>) 24636107 ~0% {5} r7 = JOIN r6 WITH SSAConstruction::DefUse::exitRank#fff AS R ON FIRST 2 OUTPUT r6.<0>, r6.<1>, r6.<2>, r6.<3>, R.<2> 2749441 ~0% {5} r8 = SELECT r7 ON r7.<3> <= r7.<4> 2749441 ~4% {4} r9 = SCAN r8 OUTPUT r8.<0>, r8.<1>, r8.<2>, r8.<3> return r9 (1626s) - SSAConstruction::DefUse::definitionReachesRank#ffff_delta has 2749441 rows (order for disjuncts: delta=). After: (12s) Tuple counts for SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta: 130670697 ~0% {4} r1 = SCAN project#SSAConstruction::DefUse::hasDefinitionAtRank#fffff AS I OUTPUT I.<0>, I.<1>, I.<2>, (I.<2> + 1) return r1 (12s) - SSAConstruction::DefUse::definitionReachesRank#ffff_delta has 130670697 rows (order for disjuncts: delta=). (12s) Starting to evaluate predicate SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta/4[2]@fff64c (iteration 2) (34s) Tuple counts for SSAConstruction::DefUse::definitionReachesRank#ffff#cur_delta: 108784031 ~0% {4} r1 = SSAConstruction::DefUse::definitionReachesRank#ffff#prev_delta AS L AND NOT SSAConstruction::DefUse::exitRank#fff AS R(L.<0>, L.<1>, L.<3>) 2749441 ~5% {4} r2 = r1 AND NOT project#SSAConstruction::DefUse::hasDefinitionAtRank#fffff AS R(r1.<0>, r1.<1>, r1.<3>) 2749441 ~4% {4} r3 = SCAN r2 OUTPUT r2.<0>, r2.<1>, r2.<2>, (r2.<3> + 1) 2749441 ~4% {4} r4 = r3 AND NOT SSAConstruction::DefUse::definitionReachesRank#ffff#prev AS R(r3.<0>, r3.<1>, r3.<2>, r3.<3>) return r4 (34s) - SSAConstruction::DefUse::definitionReachesRank#ffff_delta has 2749441 rows (order for disjuncts: delta=). Note that the row counts are exactly the same before and after. --- .../aliased_ssa/internal/SSAConstruction.qll | 21 ++++++++++--------- .../internal/SSAConstruction.qll | 21 ++++++++++--------- .../internal/SSAConstruction.qll | 21 ++++++++++--------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll index 62753e298cd..6578968b1ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll @@ -665,17 +665,18 @@ module DefUse { private predicate definitionReachesRank( Alias::MemoryLocation useLocation, OldBlock block, int defRank, int reachesRank ) { + // The def always reaches the next use, even if there is also a def on the + // use instruction. hasDefinitionAtRank(useLocation, _, block, defRank, _) and - reachesRank <= exitRank(useLocation, block) and // Without this, the predicate would be infinite. - ( - // The def always reaches the next use, even if there is also a def on the - // use instruction. - reachesRank = defRank + 1 - or - // If the def reached the previous rank, it also reaches the current rank, - // unless there was another def at the previous rank. - definitionReachesRank(useLocation, block, defRank, reachesRank - 1) and - not hasDefinitionAtRank(useLocation, _, block, reachesRank - 1, _) + reachesRank = defRank + 1 + or + // If the def reached the previous rank, it also reaches the current rank, + // unless there was another def at the previous rank. + exists(int prevRank | + reachesRank = prevRank + 1 and + definitionReachesRank(useLocation, block, defRank, prevRank) and + not prevRank = exitRank(useLocation, block) and + not hasDefinitionAtRank(useLocation, _, block, prevRank, _) ) } diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 62753e298cd..6578968b1ff 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -665,17 +665,18 @@ module DefUse { private predicate definitionReachesRank( Alias::MemoryLocation useLocation, OldBlock block, int defRank, int reachesRank ) { + // The def always reaches the next use, even if there is also a def on the + // use instruction. hasDefinitionAtRank(useLocation, _, block, defRank, _) and - reachesRank <= exitRank(useLocation, block) and // Without this, the predicate would be infinite. - ( - // The def always reaches the next use, even if there is also a def on the - // use instruction. - reachesRank = defRank + 1 - or - // If the def reached the previous rank, it also reaches the current rank, - // unless there was another def at the previous rank. - definitionReachesRank(useLocation, block, defRank, reachesRank - 1) and - not hasDefinitionAtRank(useLocation, _, block, reachesRank - 1, _) + reachesRank = defRank + 1 + or + // If the def reached the previous rank, it also reaches the current rank, + // unless there was another def at the previous rank. + exists(int prevRank | + reachesRank = prevRank + 1 and + definitionReachesRank(useLocation, block, defRank, prevRank) and + not prevRank = exitRank(useLocation, block) and + not hasDefinitionAtRank(useLocation, _, block, prevRank, _) ) } diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll index 62753e298cd..6578968b1ff 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll @@ -665,17 +665,18 @@ module DefUse { private predicate definitionReachesRank( Alias::MemoryLocation useLocation, OldBlock block, int defRank, int reachesRank ) { + // The def always reaches the next use, even if there is also a def on the + // use instruction. hasDefinitionAtRank(useLocation, _, block, defRank, _) and - reachesRank <= exitRank(useLocation, block) and // Without this, the predicate would be infinite. - ( - // The def always reaches the next use, even if there is also a def on the - // use instruction. - reachesRank = defRank + 1 - or - // If the def reached the previous rank, it also reaches the current rank, - // unless there was another def at the previous rank. - definitionReachesRank(useLocation, block, defRank, reachesRank - 1) and - not hasDefinitionAtRank(useLocation, _, block, reachesRank - 1, _) + reachesRank = defRank + 1 + or + // If the def reached the previous rank, it also reaches the current rank, + // unless there was another def at the previous rank. + exists(int prevRank | + reachesRank = prevRank + 1 and + definitionReachesRank(useLocation, block, defRank, prevRank) and + not prevRank = exitRank(useLocation, block) and + not hasDefinitionAtRank(useLocation, _, block, prevRank, _) ) } From 4a415d8ad6100fa2f6799b73dc46816085cbb043 Mon Sep 17 00:00:00 2001 From: Felicity Chapman Date: Wed, 4 Mar 2020 14:18:24 +0000 Subject: [PATCH 197/236] Update or remove unwanted links --- docs/language/learn-ql/index.rst | 2 +- docs/language/learn-ql/ql-training.rst | 4 ++-- docs/language/ql-training/cpp/bad-overflow-guard.rst | 2 +- docs/language/ql-training/java/query-injection-java.rst | 2 +- docs/language/ql-training/slide-snippets/intro-ql-general.rst | 4 +--- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/language/learn-ql/index.rst b/docs/language/learn-ql/index.rst index f25bf8bb644..12ed9cef8a9 100644 --- a/docs/language/learn-ql/index.rst +++ b/docs/language/learn-ql/index.rst @@ -1,7 +1,7 @@ Learning CodeQL ############### -CodeQL is the code analysis platform used by security researchers to automate `variant analysis `__. +CodeQL is the code analysis platform used by security researchers to automate variant analysis. You can use CodeQL queries to explore code and quickly find variants of security vulnerabilities and bugs. These queries are easy to write and share–visit the topics below and `our open source repository on GitHub `__ to learn more. You can also try out CodeQL in the `query console `__ on `LGTM.com `__. diff --git a/docs/language/learn-ql/ql-training.rst b/docs/language/learn-ql/ql-training.rst index fcb14476d88..55c3d33f705 100644 --- a/docs/language/learn-ql/ql-training.rst +++ b/docs/language/learn-ql/ql-training.rst @@ -4,9 +4,9 @@ CodeQL training and variant analysis examples CodeQL and variant analysis --------------------------- -`Variant analysis `__ is the process of using a known vulnerability as a seed to find similar problems in your code. Security engineers typically perform variant analysis to identify possible vulnerabilities and to ensure that these threats are properly fixed across multiple code bases. +Variant analysis is the process of using a known vulnerability as a seed to find similar problems in your code. Security engineers typically perform variant analysis to identify possible vulnerabilities and to ensure that these threats are properly fixed across multiple code bases. -`CodeQL `__ is the code analysis engine that underpins LGTM, Semmle's community driven security analysis platform. Together, CodeQL and LGTM provide continuous monitoring and scalable variant analysis for your projects, even if you don’t have your own team of dedicated security engineers. You can read more about using CodeQL and LGTM in variant analysis on the `Security Lab research page `__. +CodeQL is the code analysis engine that underpins LGTM, the community driven security analysis platform. Together, CodeQL and LGTM provide continuous monitoring and scalable variant analysis for your projects, even if you don’t have your own team of dedicated security engineers. You can read more about using CodeQL and LGTM in variant analysis on the `Security Lab research page `__. CodeQL is easy to learn, and exploring code using CodeQL is the most efficient way to perform variant analysis. diff --git a/docs/language/ql-training/cpp/bad-overflow-guard.rst b/docs/language/ql-training/cpp/bad-overflow-guard.rst index 1ae193b10f0..0630bdcb039 100644 --- a/docs/language/ql-training/cpp/bad-overflow-guard.rst +++ b/docs/language/ql-training/cpp/bad-overflow-guard.rst @@ -141,7 +141,7 @@ Let’s look for overflow guards of the form ``v + b < v``, using the classes .. note:: - - When performing `variant analysis `__, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. + - When performing variant analysis, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. - In this case, we start by looking for all the *overflow* checks, before trying to refine the query to find all *bad overflow* checks. - The ``select`` clause defines what this query is looking for: diff --git a/docs/language/ql-training/java/query-injection-java.rst b/docs/language/ql-training/java/query-injection-java.rst index f9911de137b..ce215069b8d 100644 --- a/docs/language/ql-training/java/query-injection-java.rst +++ b/docs/language/ql-training/java/query-injection-java.rst @@ -77,7 +77,7 @@ Let’s start by looking for calls to methods with names of the form ``sparql*Qu .. note:: - - When performing `variant analysis `__, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. + - When performing variant analysis, it is usually helpful to write a simple query that finds the simple syntactic pattern, before trying to go on to describe the cases where it goes wrong. - In this case, we start by looking for all the method calls that appear to run, before trying to refine the query to find cases which are vulnerable to query injection. - The ``select`` clause defines what this query is looking for: diff --git a/docs/language/ql-training/slide-snippets/intro-ql-general.rst b/docs/language/ql-training/slide-snippets/intro-ql-general.rst index b53c6bbb015..8d6a25afad7 100644 --- a/docs/language/ql-training/slide-snippets/intro-ql-general.rst +++ b/docs/language/ql-training/slide-snippets/intro-ql-general.rst @@ -81,8 +81,6 @@ Find all instances! - All were fixed with a mid-flight patch. -- For more detail on the collaboration between Semmle and NASA, see our case study: `Semmle at NASA: Landing Curiosity safely on Mars `__. - .. note:: The JPL team ran the query across the full Curiosity control software–it identified the original problem, and more than 30 other variants, of which three were in the critical Entry, Descent, and Landing module. @@ -107,7 +105,7 @@ Analysis overview Once the extraction finishes, all this information is collected into a single `CodeQL database `__, which is then ready to query, possibly on a different machine. A copy of the source files, made at the time the database was created, is also included in the CodeQL database so analysis results can be displayed at the correct location in the code. The database schema is (source) language specific. - Queries are written in `QL `__ and usually depend on one or more of the `standard CodeQL libraries `__ (and of course you can write your own custom libraries). They are compiled into an efficiently executable format by the QL compiler and then run on a CodeQL database by the QL evaluator, either on a remote worker machine or locally on a developer’s machine. + Queries are written in QL and usually depend on one or more of the `standard CodeQL libraries `__ (and of course you can write your own custom libraries). They are compiled into an efficiently executable format by the QL compiler and then run on a CodeQL database by the QL evaluator, either on a remote worker machine or locally on a developer’s machine. Query results can be interpreted and presented in a variety of ways, including displaying them in an `IDE extension `__ such as CodeQL for Visual Studio Code, or in a web dashboard as on `LGTM `__. From 3973a50c9becd9302b823e08cf76d86bc598c0d2 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Wed, 4 Mar 2020 15:51:14 +0100 Subject: [PATCH 198/236] C++: Add testcase for cpp/uncontrolled-allocation-size --- .../TaintedAllocationSize.expected | 1 + .../semmle/TaintedAllocationSize/test.cpp | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+) 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 2463fad2e2b..f77bf73d52d 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 @@ -6,3 +6,4 @@ | test.cpp:49:17:49:30 | new[] | 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 | This allocation size is derived from $@ and might overflow | test.cpp:39:21:39:24 | argv | user input (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:127:17:127:22 | call to malloc | This allocation size is derived from $@ and might overflow | test.cpp:123:25:123:30 | 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 0722d95cf46..5cd5f0c0246 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 @@ -105,3 +105,24 @@ void processFile() fclose(f); } } + +char *getenv(const char *name); + +#define MAX_SIZE 500 + +int bounded(int x, int limit) { + int result = x; + if (x <= 0) + result = 1; + else if (x > limit) + result = limit; + return result; +} + +void open_file_bounded () { + int size = size = atoi(getenv("USER")); + int bounded_size = bounded(size, MAX_SIZE); + + int* a = (int*)malloc(bounded_size); // GOOD + int* b = (int*)malloc(size); // BAD +} \ No newline at end of file From c2f7cdce2563d4578f9b53621235d173d7731b3d Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 4 Mar 2020 15:06:10 +0000 Subject: [PATCH 199/236] JS: Change precision to very-high --- javascript/ql/src/LanguageFeatures/SyntaxError.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/ql/src/LanguageFeatures/SyntaxError.ql b/javascript/ql/src/LanguageFeatures/SyntaxError.ql index 6053567a8d6..6f6ac10a8cc 100644 --- a/javascript/ql/src/LanguageFeatures/SyntaxError.ql +++ b/javascript/ql/src/LanguageFeatures/SyntaxError.ql @@ -7,7 +7,7 @@ * @tags reliability * correctness * language-features - * @precision high + * @precision very-high */ import javascript From 53569453ba79d70cbc46ea5b4f99f2c5bef37f74 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 4 Mar 2020 19:28:24 +0000 Subject: [PATCH 200/236] JS: Autoformat again --- .../UnreachableMethodOverloads.ql | 3 ++- .../ql/src/semmle/javascript/StringOps.qll | 5 ++--- .../javascript/frameworks/NodeJSLib.qll | 13 +++++++------ .../semmle/javascript/frameworks/ShellJS.qll | 2 +- .../javascript/security/UselessUseOfCat.qll | 19 +++++++++---------- .../Security/CWE-078/UselessUseOfCat.ql | 8 ++------ 6 files changed, 23 insertions(+), 27 deletions(-) diff --git a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql index d3c5a54bf2d..1f131270363 100644 --- a/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql +++ b/javascript/ql/src/Declarations/UnreachableMethodOverloads.ql @@ -105,7 +105,8 @@ predicate signaturesMatch(MethodSignature method, MethodSignature other) { not exists(method.getBody().getThisTypeAnnotation()) and not exists(other.getBody().getThisTypeAnnotation()) or - method.getBody().getThisTypeAnnotation().getType() = other.getBody().getThisTypeAnnotation().getType() + method.getBody().getThisTypeAnnotation().getType() = + other.getBody().getThisTypeAnnotation().getType() ) and // The types are compared in matchingCallSignature. This is sanity-check that the textual representation of the type-annotations are somewhat similar. forall(int i | i in [0 .. -1 + method.getBody().getNumParameter()] | diff --git a/javascript/ql/src/semmle/javascript/StringOps.qll b/javascript/ql/src/semmle/javascript/StringOps.qll index 744f5796f05..6a4a2760524 100644 --- a/javascript/ql/src/semmle/javascript/StringOps.qll +++ b/javascript/ql/src/semmle/javascript/StringOps.qll @@ -67,7 +67,7 @@ module StringOps { inner.getEnclosingExpr() = callee.getAReturnedExpr() and this.getACallee() = callee and count(this.getACallee()) = 1 and - count(callee.getAReturnedExpr()) = 1 and + count(callee.getAReturnedExpr()) = 1 and not this.isImprecise() and inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter() @@ -298,7 +298,7 @@ module StringOps { inner.getEnclosingExpr() = callee.getAReturnedExpr() and this.getACallee() = callee and count(this.getACallee()) = 1 and - count(callee.getAReturnedExpr()) = 1 and + count(callee.getAReturnedExpr()) = 1 and not this.isImprecise() and inner.getBaseString().getALocalSource().getEnclosingExpr() = callee.getAParameter() and inner.getSubstring().getALocalSource().getEnclosingExpr() = callee.getAParameter() @@ -321,7 +321,6 @@ module StringOps { override boolean getPolarity() { result = inner.getPolarity() } } - /** * A call of form `A.endsWith(B)`. */ diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index cee84c9b460..342e828c6ec 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -633,18 +633,19 @@ module NodeJSLib { result = getArgument(1) } - override predicate isSync() { - "Sync" = methodName.suffix(methodName.length() - 4) - } + override predicate isSync() { "Sync" = methodName.suffix(methodName.length() - 4) } override DataFlow::Node getOptionsArg() { not result.getALocalSource() instanceof DataFlow::FunctionNode and // looks like callback not result.getALocalSource() instanceof DataFlow::ArrayCreationNode and // looks like argumentlist not result = getArgument(0) and // fork/spawn and all sync methos always has options as the last argument - if methodName.regexpMatch("fork.*") or methodName.regexpMatch("spawn.*") or methodName.regexpMatch(".*Sync") then - result = getLastArgument() - else + if + methodName.regexpMatch("fork.*") or + methodName.regexpMatch("spawn.*") or + methodName.regexpMatch(".*Sync") + then result = getLastArgument() + else // the rest (exec/execFile) has the options argument as their second last. result = getArgument(this.getNumArgument() - 2) } diff --git a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll index 6944b7e74a3..3fc1644fdfd 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/ShellJS.qll @@ -161,7 +161,7 @@ module ShellJS { override predicate isShellInterpreted(DataFlow::Node arg) { arg = getACommandArgument() } - override predicate isSync() {none ()} + override predicate isSync() { none() } override DataFlow::Node getOptionsArg() { result = getLastArgument() and diff --git a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll index 4c8b3815586..17e14f9e7b1 100644 --- a/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll +++ b/javascript/ql/src/semmle/javascript/security/UselessUseOfCat.qll @@ -23,7 +23,9 @@ private class CommandCall extends DataFlow::InvokeNode { /** * Gets a list that specifies the arguments given to the command. */ - DataFlow::ArrayCreationNode getArgumentList() { result = command.getArgumentList().getALocalSource() } + DataFlow::ArrayCreationNode getArgumentList() { + result = command.getArgumentList().getALocalSource() + } /** * Gets the callback (if it exists) for an async `exec`-like call. @@ -75,8 +77,8 @@ private class CommandCall extends DataFlow::InvokeNode { } /** - * Holds if the input `str` contains some character that might be interpreted in a non-trivial way by a shell. - */ + * Holds if the input `str` contains some character that might be interpreted in a non-trivial way by a shell. + */ bindingset[str] private predicate containsNonTrivialShellChar(string str) { exists(str.regexpFind("\\*|\\||>|<| |\\$|&|,|\\`| |;", _, _)) @@ -142,9 +144,7 @@ class UselessCat extends CommandCall { /** * Gets a string used to call `cat`. */ -private string getACatExecuteable() { - result = "cat" or result = "/bin/cat" -} +private string getACatExecuteable() { result = "cat" or result = "/bin/cat" } /** * Predicates for creating an equivalent call to `fs.readFile` from a command execution of `cat`. @@ -173,11 +173,10 @@ module PrettyPrintCatCall { callback = "" and not exists(cat.getCallback()) ) and fileArg = createFileArgument(cat).trim() and - // sanity check in case of surprising `toString` results, other uses of `containsNonTrivialBashChar` should ensure that this conjunct will hold most of the time - not(containsNonTrivialShellChar(fileArg.regexpReplaceAll("\\$|\\`| ", ""))) // string concat might contain " ", template strings might contain "$" or `, and that is OK. + // sanity check in case of surprising `toString` results, other uses of `containsNonTrivialBashChar` should ensure that this conjunct will hold most of the time + not containsNonTrivialShellChar(fileArg.regexpReplaceAll("\\$|\\`| ", "")) // string concat might contain " ", template strings might contain "$" or `, and that is OK. | - result = - "fs.readFile" + sync + "(" + fileArg + extraArg + callback + ")" + result = "fs.readFile" + sync + "(" + fileArg + extraArg + callback + ")" ) } diff --git a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql index a70f53016bd..781125f3206 100644 --- a/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql +++ b/javascript/ql/test/query-tests/Security/CWE-078/UselessUseOfCat.ql @@ -19,10 +19,6 @@ select msg, comment query string readFile(UselessCat cat) { result = PrettyPrintCatCall::createReadFileCall(cat) } -query SystemCommandExecution syncCommand() { - result.isSync() -} +query SystemCommandExecution syncCommand() { result.isSync() } -query DataFlow::Node options(SystemCommandExecution sys) { - result = sys.getOptionsArg() -} \ No newline at end of file +query DataFlow::Node options(SystemCommandExecution sys) { result = sys.getOptionsArg() } From 646bc29e7616b69147236a262aa8d3ae1fffdc94 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Wed, 4 Mar 2020 17:22:21 -0800 Subject: [PATCH 201/236] Python: Modernizes query --- python/ql/src/Exceptions/RaisingTuple.ql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/Exceptions/RaisingTuple.ql b/python/ql/src/Exceptions/RaisingTuple.ql index 8bf5c7705b5..93b423f20d5 100644 --- a/python/ql/src/Exceptions/RaisingTuple.ql +++ b/python/ql/src/Exceptions/RaisingTuple.ql @@ -12,7 +12,7 @@ import python from Raise r, AstNode origin -where r.getException().refersTo(_, theTupleType(), origin) and +where r.getException().pointsTo(_, ClassValue::tuple(), origin) and major_version() = 2 /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */ select r, "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", origin, "a tuple" \ No newline at end of file From e29f517af2114475b4945f5293169089d98b0146 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Thu, 5 Mar 2020 12:10:10 +0100 Subject: [PATCH 202/236] C++: Move InstructionSanity out of Instruction.qll Having that module in `Instruction.qll` slowed down the parsing of that file both humans and the compiler. This commit moves the `InstructionSanity` module to `IRSanity.qll` without making any changes to its contents apart from adding some imports. --- .../implementation/aliased_ssa/IRSanity.qll | 276 +++++++++++++++++- .../aliased_ssa/Instruction.qll | 268 ----------------- .../cpp/ir/implementation/raw/IRSanity.qll | 276 +++++++++++++++++- .../cpp/ir/implementation/raw/Instruction.qll | 268 ----------------- .../implementation/unaliased_ssa/IRSanity.qll | 276 +++++++++++++++++- .../unaliased_ssa/Instruction.qll | 268 ----------------- .../csharp/ir/implementation/raw/IRSanity.qll | 276 +++++++++++++++++- .../ir/implementation/raw/Instruction.qll | 268 ----------------- .../implementation/unaliased_ssa/IRSanity.qll | 276 +++++++++++++++++- .../unaliased_ssa/Instruction.qll | 268 ----------------- 10 files changed, 1370 insertions(+), 1350 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll index de66a3c99dc..5ec704f533f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } +} \ No newline at end of file diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll index 2d414e0c46e..72c8d7e3b95 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - query predicate switchInstructionWithoutDefaultEdge( - SwitchInstruction switchInstr, string message, IRFunction func, string funcText - ) { - not exists(switchInstr.getDefaultSuccessor()) and - message = - "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and - func = switchInstr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll index de66a3c99dc..5ec704f533f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } +} \ No newline at end of file diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll index 2d414e0c46e..72c8d7e3b95 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - query predicate switchInstructionWithoutDefaultEdge( - SwitchInstruction switchInstr, string message, IRFunction func, string funcText - ) { - not exists(switchInstr.getDefaultSuccessor()) and - message = - "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and - func = switchInstr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll index de66a3c99dc..5ec704f533f 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } +} \ No newline at end of file diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll index 2d414e0c46e..72c8d7e3b95 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - query predicate switchInstructionWithoutDefaultEdge( - SwitchInstruction switchInstr, string message, IRFunction func, string funcText - ) { - not exists(switchInstr.getDefaultSuccessor()) and - message = - "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and - func = switchInstr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll index de66a3c99dc..5ec704f533f 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } +} \ No newline at end of file diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll index 2d414e0c46e..72c8d7e3b95 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/Instruction.qll @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - query predicate switchInstructionWithoutDefaultEdge( - SwitchInstruction switchInstr, string message, IRFunction func, string funcText - ) { - not exists(switchInstr.getDefaultSuccessor()) and - message = - "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and - func = switchInstr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll index de66a3c99dc..5ec704f533f 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll @@ -1,3 +1,275 @@ private import IR -import InstructionSanity -import IRTypeSanity +import InstructionSanity // module is below +import IRTypeSanity // module is in IRType.qll + +module InstructionSanity { + private import internal.InstructionImports as Imports + private import Imports::OperandTag + private import internal.IRInternal + + /** + * Holds if instruction `instr` is missing an expected operand with tag `tag`. + */ + query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { + exists(OperandTag tag | + instr.getOpcode().hasOperand(tag) and + not exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + message = + "Instruction '" + instr.getOpcode().toString() + + "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if instruction `instr` has an unexpected operand with tag `tag`. + */ + query predicate unexpectedOperand(Instruction instr, OperandTag tag) { + exists(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + not instr.getOpcode().hasOperand(tag) and + not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and + not ( + instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag + ) and + not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) + } + + /** + * Holds if instruction `instr` has multiple operands with tag `tag`. + */ + query predicate duplicateOperand( + Instruction instr, string message, IRFunction func, string funcText + ) { + exists(OperandTag tag, int operandCount | + operandCount = + strictcount(NonPhiOperand operand | + operand = instr.getAnOperand() and + operand.getOperandTag() = tag + ) and + operandCount > 1 and + not tag instanceof UnmodeledUseOperandTag and + message = + "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + + " in function '$@'." and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + /** + * Holds if `Phi` instruction `instr` is missing an operand corresponding to + * the predecessor block `pred`. + */ + query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { + pred = instr.getBlock().getAPredecessor() and + not exists(PhiInputOperand operand | + operand = instr.getAnOperand() and + operand.getPredecessorBlock() = pred + ) + } + + query predicate missingOperandType(Operand operand, string message) { + exists(Language::Function func, Instruction use | + not exists(operand.getType()) and + use = operand.getUse() and + func = use.getEnclosingFunction() and + message = + "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + + "' missing type in function '" + Language::getIdentityString(func) + "'." + ) + } + + query predicate duplicateChiOperand( + ChiInstruction chi, string message, IRFunction func, string funcText + ) { + chi.getTotal() = chi.getPartial() and + message = + "Chi instruction for " + chi.getPartial().toString() + + " has duplicate operands in function $@" and + func = chi.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + query predicate sideEffectWithoutPrimary( + SideEffectInstruction instr, string message, IRFunction func, string funcText + ) { + not exists(instr.getPrimaryInstruction()) and + message = "Side effect instruction missing primary instruction in function $@" and + func = instr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } + + /** + * Holds if an instruction, other than `ExitFunction`, has no successors. + */ + query predicate instructionWithoutSuccessor(Instruction instr) { + not exists(instr.getASuccessor()) and + not instr instanceof ExitFunctionInstruction and + // Phi instructions aren't linked into the instruction-level flow graph. + not instr instanceof PhiInstruction and + not instr instanceof UnreachedInstruction + } + + /** + * Holds if there are multiple (`n`) edges of kind `kind` from `source`, + * where `target` is among the targets of those edges. + */ + query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { + n = strictcount(Instruction t | source.getSuccessor(kind) = t) and + n > 1 and + source.getSuccessor(kind) = target + } + + /** + * Holds if `instr` in `f` is part of a loop even though the AST of `f` + * contains no element that can cause loops. + */ + query predicate unexplainedLoop(Language::Function f, Instruction instr) { + exists(IRBlock block | + instr.getBlock() = block and + block.getEnclosingFunction() = f and + block.getASuccessor+() = block + ) and + not Language::hasPotentialLoop(f) + } + + /** + * Holds if a `Phi` instruction is present in a block with fewer than two + * predecessors. + */ + query predicate unnecessaryPhiInstruction(PhiInstruction instr) { + count(instr.getBlock().getAPredecessor()) < 2 + } + + /** + * Holds if operand `operand` consumes a value that was defined in + * a different function. + */ + query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { + operand.getUse() = instr and + operand.getAnyDef() = defInstr and + instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() + } + + /** + * Holds if instruction `instr` is not in exactly one block. + */ + query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { + blockCount = count(instr.getBlock()) and + blockCount != 1 + } + + private predicate forwardEdge(IRBlock b1, IRBlock b2) { + b1.getASuccessor() = b2 and + not b1.getBackEdgeSuccessor(_) = b2 + } + + /** + * Holds if `f` contains a loop in which no edge is a back edge. + * + * This check ensures we don't have too _few_ back edges. + */ + query predicate containsLoopOfForwardEdges(IRFunction f) { + exists(IRBlock block | + forwardEdge+(block, block) and + block.getEnclosingIRFunction() = f + ) + } + + /** + * Holds if `block` is reachable from its function entry point but would not + * be reachable by traversing only forward edges. This check is skipped for + * functions containing `goto` statements as the property does not generally + * hold there. + * + * This check ensures we don't have too _many_ back edges. + */ + query predicate lostReachability(IRBlock block) { + exists(IRFunction f, IRBlock entry | + entry = f.getEntryBlock() and + entry.getASuccessor+() = block and + not forwardEdge+(entry, block) and + not Language::hasGoto(f.getFunction()) + ) + } + + /** + * Holds if the number of back edges differs between the `Instruction` graph + * and the `IRBlock` graph. + */ + query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { + fromInstr = + count(Instruction i1, Instruction i2 | + i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 + ) and + fromBlock = + count(IRBlock b1, IRBlock b2 | + b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 + ) and + fromInstr != fromBlock + } + + /** + * Gets the point in the function at which the specified operand is evaluated. For most operands, + * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point + * of evaluation is at the end of the corresponding predecessor block. + */ + private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { + block = operand.(PhiInputOperand).getPredecessorBlock() and + index = block.getInstructionCount() + or + exists(Instruction use | + use = operand.(NonPhiOperand).getUse() and + block.getInstruction(index) = use + ) + } + + /** + * Holds if `useOperand` has a definition that does not dominate the use. + */ + query predicate useNotDominatedByDefinition( + Operand useOperand, string message, IRFunction func, string funcText + ) { + exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | + not useOperand.getUse() instanceof UnmodeledUseInstruction and + not defInstr instanceof UnmodeledDefinitionInstruction and + pointOfEvaluation(useOperand, useBlock, useIndex) and + defInstr = useOperand.getAnyDef() and + ( + defInstr instanceof PhiInstruction and + defBlock = defInstr.getBlock() and + defIndex = -1 + or + defBlock.getInstruction(defIndex) = defInstr + ) and + not ( + defBlock.strictlyDominates(useBlock) + or + defBlock = useBlock and + defIndex < useIndex + ) and + message = + "Operand '" + useOperand.toString() + + "' is not dominated by its definition in function '$@'." and + func = useOperand.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + ) + } + + query predicate switchInstructionWithoutDefaultEdge( + SwitchInstruction switchInstr, string message, IRFunction func, string funcText + ) { + not exists(switchInstr.getDefaultSuccessor()) and + message = + "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and + func = switchInstr.getEnclosingIRFunction() and + funcText = Language::getIdentityString(func.getFunction()) + } +} \ No newline at end of file diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll index 2d414e0c46e..72c8d7e3b95 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/Instruction.qll @@ -10,274 +10,6 @@ import Imports::MemoryAccessKind import Imports::Opcode private import Imports::OperandTag -module InstructionSanity { - /** - * Holds if instruction `instr` is missing an expected operand with tag `tag`. - */ - query predicate missingOperand(Instruction instr, string message, IRFunction func, string funcText) { - exists(OperandTag tag | - instr.getOpcode().hasOperand(tag) and - not exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - message = - "Instruction '" + instr.getOpcode().toString() + - "' is missing an expected operand with tag '" + tag.toString() + "' in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if instruction `instr` has an unexpected operand with tag `tag`. - */ - query predicate unexpectedOperand(Instruction instr, OperandTag tag) { - exists(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - not instr.getOpcode().hasOperand(tag) and - not (instr instanceof CallInstruction and tag instanceof ArgumentOperandTag) and - not ( - instr instanceof BuiltInOperationInstruction and tag instanceof PositionalArgumentOperandTag - ) and - not (instr instanceof InlineAsmInstruction and tag instanceof AsmOperandTag) - } - - /** - * Holds if instruction `instr` has multiple operands with tag `tag`. - */ - query predicate duplicateOperand( - Instruction instr, string message, IRFunction func, string funcText - ) { - exists(OperandTag tag, int operandCount | - operandCount = - strictcount(NonPhiOperand operand | - operand = instr.getAnOperand() and - operand.getOperandTag() = tag - ) and - operandCount > 1 and - not tag instanceof UnmodeledUseOperandTag and - message = - "Instruction has " + operandCount + " operands with tag '" + tag.toString() + "'" + - " in function '$@'." and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - /** - * Holds if `Phi` instruction `instr` is missing an operand corresponding to - * the predecessor block `pred`. - */ - query predicate missingPhiOperand(PhiInstruction instr, IRBlock pred) { - pred = instr.getBlock().getAPredecessor() and - not exists(PhiInputOperand operand | - operand = instr.getAnOperand() and - operand.getPredecessorBlock() = pred - ) - } - - query predicate missingOperandType(Operand operand, string message) { - exists(Language::Function func, Instruction use | - not exists(operand.getType()) and - use = operand.getUse() and - func = use.getEnclosingFunction() and - message = - "Operand '" + operand.toString() + "' of instruction '" + use.getOpcode().toString() + - "' missing type in function '" + Language::getIdentityString(func) + "'." - ) - } - - query predicate duplicateChiOperand( - ChiInstruction chi, string message, IRFunction func, string funcText - ) { - chi.getTotal() = chi.getPartial() and - message = - "Chi instruction for " + chi.getPartial().toString() + - " has duplicate operands in function $@" and - func = chi.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - query predicate sideEffectWithoutPrimary( - SideEffectInstruction instr, string message, IRFunction func, string funcText - ) { - not exists(instr.getPrimaryInstruction()) and - message = "Side effect instruction missing primary instruction in function $@" and - func = instr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } - - /** - * Holds if an instruction, other than `ExitFunction`, has no successors. - */ - query predicate instructionWithoutSuccessor(Instruction instr) { - not exists(instr.getASuccessor()) and - not instr instanceof ExitFunctionInstruction and - // Phi instructions aren't linked into the instruction-level flow graph. - not instr instanceof PhiInstruction and - not instr instanceof UnreachedInstruction - } - - /** - * Holds if there are multiple (`n`) edges of kind `kind` from `source`, - * where `target` is among the targets of those edges. - */ - query predicate ambiguousSuccessors(Instruction source, EdgeKind kind, int n, Instruction target) { - n = strictcount(Instruction t | source.getSuccessor(kind) = t) and - n > 1 and - source.getSuccessor(kind) = target - } - - /** - * Holds if `instr` in `f` is part of a loop even though the AST of `f` - * contains no element that can cause loops. - */ - query predicate unexplainedLoop(Language::Function f, Instruction instr) { - exists(IRBlock block | - instr.getBlock() = block and - block.getEnclosingFunction() = f and - block.getASuccessor+() = block - ) and - not Language::hasPotentialLoop(f) - } - - /** - * Holds if a `Phi` instruction is present in a block with fewer than two - * predecessors. - */ - query predicate unnecessaryPhiInstruction(PhiInstruction instr) { - count(instr.getBlock().getAPredecessor()) < 2 - } - - /** - * Holds if operand `operand` consumes a value that was defined in - * a different function. - */ - query predicate operandAcrossFunctions(Operand operand, Instruction instr, Instruction defInstr) { - operand.getUse() = instr and - operand.getAnyDef() = defInstr and - instr.getEnclosingIRFunction() != defInstr.getEnclosingIRFunction() - } - - /** - * Holds if instruction `instr` is not in exactly one block. - */ - query predicate instructionWithoutUniqueBlock(Instruction instr, int blockCount) { - blockCount = count(instr.getBlock()) and - blockCount != 1 - } - - private predicate forwardEdge(IRBlock b1, IRBlock b2) { - b1.getASuccessor() = b2 and - not b1.getBackEdgeSuccessor(_) = b2 - } - - /** - * Holds if `f` contains a loop in which no edge is a back edge. - * - * This check ensures we don't have too _few_ back edges. - */ - query predicate containsLoopOfForwardEdges(IRFunction f) { - exists(IRBlock block | - forwardEdge+(block, block) and - block.getEnclosingIRFunction() = f - ) - } - - /** - * Holds if `block` is reachable from its function entry point but would not - * be reachable by traversing only forward edges. This check is skipped for - * functions containing `goto` statements as the property does not generally - * hold there. - * - * This check ensures we don't have too _many_ back edges. - */ - query predicate lostReachability(IRBlock block) { - exists(IRFunction f, IRBlock entry | - entry = f.getEntryBlock() and - entry.getASuccessor+() = block and - not forwardEdge+(entry, block) and - not Language::hasGoto(f.getFunction()) - ) - } - - /** - * Holds if the number of back edges differs between the `Instruction` graph - * and the `IRBlock` graph. - */ - query predicate backEdgeCountMismatch(Language::Function f, int fromInstr, int fromBlock) { - fromInstr = - count(Instruction i1, Instruction i2 | - i1.getEnclosingFunction() = f and i1.getBackEdgeSuccessor(_) = i2 - ) and - fromBlock = - count(IRBlock b1, IRBlock b2 | - b1.getEnclosingFunction() = f and b1.getBackEdgeSuccessor(_) = b2 - ) and - fromInstr != fromBlock - } - - /** - * Gets the point in the function at which the specified operand is evaluated. For most operands, - * this is at the instruction that consumes the use. For a `PhiInputOperand`, the effective point - * of evaluation is at the end of the corresponding predecessor block. - */ - private predicate pointOfEvaluation(Operand operand, IRBlock block, int index) { - block = operand.(PhiInputOperand).getPredecessorBlock() and - index = block.getInstructionCount() - or - exists(Instruction use | - use = operand.(NonPhiOperand).getUse() and - block.getInstruction(index) = use - ) - } - - /** - * Holds if `useOperand` has a definition that does not dominate the use. - */ - query predicate useNotDominatedByDefinition( - Operand useOperand, string message, IRFunction func, string funcText - ) { - exists(IRBlock useBlock, int useIndex, Instruction defInstr, IRBlock defBlock, int defIndex | - not useOperand.getUse() instanceof UnmodeledUseInstruction and - not defInstr instanceof UnmodeledDefinitionInstruction and - pointOfEvaluation(useOperand, useBlock, useIndex) and - defInstr = useOperand.getAnyDef() and - ( - defInstr instanceof PhiInstruction and - defBlock = defInstr.getBlock() and - defIndex = -1 - or - defBlock.getInstruction(defIndex) = defInstr - ) and - not ( - defBlock.strictlyDominates(useBlock) - or - defBlock = useBlock and - defIndex < useIndex - ) and - message = - "Operand '" + useOperand.toString() + - "' is not dominated by its definition in function '$@'." and - func = useOperand.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - ) - } - - query predicate switchInstructionWithoutDefaultEdge( - SwitchInstruction switchInstr, string message, IRFunction func, string funcText - ) { - not exists(switchInstr.getDefaultSuccessor()) and - message = - "SwitchInstruction " + switchInstr.toString() + " without a DefaultEdge in function '$@'." and - func = switchInstr.getEnclosingIRFunction() and - funcText = Language::getIdentityString(func.getFunction()) - } -} - /** * Gets an `Instruction` that is contained in `IRFunction`, and has a location with the specified * `File` and line number. Used for assigning register names when printing IR. From 2face94fa5b8cb4222aa2da533f06fe37170cfc5 Mon Sep 17 00:00:00 2001 From: Taus Brock-Nannestad Date: Thu, 5 Mar 2020 14:00:58 +0100 Subject: [PATCH 203/236] Python: Mitigate CP in `nested_sequence_assign`. The problem here was that in the base case, there was no relationship between `left_parent` and `right_parent`. These could be any two tuples or lists, even if they were not part of an assignment statement. To fix this, we add a bit of manual "magic", requiring that both of these arguments must belong to the left and right-hand sides of the same assignment statement. (Note that this is in principle _still_ a gross overapproximation, but since assignment statements are usually quite restricted in size, I don't expect this to be a major problem.) --- python/ql/src/semmle/python/Flow.qll | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/python/ql/src/semmle/python/Flow.qll b/python/ql/src/semmle/python/Flow.qll index c81e5e040e4..370ced23287 100755 --- a/python/ql/src/semmle/python/Flow.qll +++ b/python/ql/src/semmle/python/Flow.qll @@ -910,6 +910,10 @@ private AstNode assigned_value(Expr lhs) { predicate nested_sequence_assign(Expr left_parent, Expr right_parent, Expr left_result, Expr right_result) { + exists(Assign a | + a.getATarget().getASubExpression*() = left_parent and + a.getValue().getASubExpression*() = right_parent + ) and exists(int i, Expr left_elem, Expr right_elem | ( From 2c8eae22d1427234b8d3d845fe6b49d6ad564a7a Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 5 Mar 2020 16:58:49 +0000 Subject: [PATCH 204/236] JS: Autoformat --- .../ql/src/semmle/javascript/dataflow/Configuration.qll | 4 +++- javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll | 4 +--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 6809bb97718..ae4459e72ba 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -907,7 +907,9 @@ private predicate reachableFromStoreBase( reachableFromStoreBase(_, _, rhs, cfg, s1) | storeStep(rhs, nd, prop, cfg, s2) and - summary = MkPathSummary(false, s1.hasCall().booleanOr(s2.hasCall()), s2.getStartLabel(), s2.getEndLabel()) + summary = + MkPathSummary(false, s1.hasCall().booleanOr(s2.hasCall()), s2.getStartLabel(), + s2.getEndLabel()) ) or exists(DataFlow::Node mid, PathSummary oldSummary, PathSummary newSummary | diff --git a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll index d7bb078b4cf..78393a1aabf 100644 --- a/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll +++ b/javascript/ql/src/semmle/javascript/frameworks/NodeJSLib.qll @@ -952,9 +952,7 @@ module NodeJSLib { * The NodeJS `process` object as an EventEmitter subclass. */ private class ProcessAsNodeJSEventEmitter extends NodeJSEventEmitter { - ProcessAsNodeJSEventEmitter() { - this = process() - } + ProcessAsNodeJSEventEmitter() { this = process() } } /** From a13f355a855f66a6fc724ee84fe653d08b063ad7 Mon Sep 17 00:00:00 2001 From: Jonas Jensen Date: Fri, 6 Mar 2020 08:29:46 +0100 Subject: [PATCH 205/236] C++: autoformat fixup --- .../semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll | 2 +- cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll | 2 +- .../code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll | 2 +- .../src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll | 2 +- .../code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll index 5ec704f533f..d07a99eab37 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll @@ -272,4 +272,4 @@ module InstructionSanity { func = switchInstr.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) } -} \ No newline at end of file +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll index 5ec704f533f..d07a99eab37 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll @@ -272,4 +272,4 @@ module InstructionSanity { func = switchInstr.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) } -} \ No newline at end of file +} diff --git a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll index 5ec704f533f..d07a99eab37 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll @@ -272,4 +272,4 @@ module InstructionSanity { func = switchInstr.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) } -} \ No newline at end of file +} diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll index 5ec704f533f..d07a99eab37 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/raw/IRSanity.qll @@ -272,4 +272,4 @@ module InstructionSanity { func = switchInstr.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) } -} \ No newline at end of file +} diff --git a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll index 5ec704f533f..d07a99eab37 100644 --- a/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll +++ b/csharp/ql/src/semmle/code/csharp/ir/implementation/unaliased_ssa/IRSanity.qll @@ -272,4 +272,4 @@ module InstructionSanity { func = switchInstr.getEnclosingIRFunction() and funcText = Language::getIdentityString(func.getFunction()) } -} \ No newline at end of file +} From 70634fe30eb8d0b6353c971ba5b96b710c10181a Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Fri, 6 Mar 2020 16:20:31 +0100 Subject: [PATCH 206/236] Python: Remove usage of deprecated .getValue() --- python/ql/src/Variables/MonkeyPatched.qll | 2 +- python/ql/src/semmle/python/dataflow/TaintTracking.qll | 4 ++-- python/ql/src/semmle/python/security/strings/Basic.qll | 2 +- python/ql/src/semmle/python/security/strings/External.qll | 2 +- python/ql/src/semmle/python/values/StringAttributes.qll | 2 +- python/ql/src/semmle/python/web/Http.qll | 4 ++-- python/ql/src/semmle/python/web/django/Request.qll | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/python/ql/src/Variables/MonkeyPatched.qll b/python/ql/src/Variables/MonkeyPatched.qll index 41e19a425f2..2a846e3deb1 100644 --- a/python/ql/src/Variables/MonkeyPatched.qll +++ b/python/ql/src/Variables/MonkeyPatched.qll @@ -5,7 +5,7 @@ predicate monkey_patched_builtin(string name) { subscr.isStore() and subscr.getIndex().getNode() = s and s.getText() = name and - subscr.getValue() = attr and + subscr.getObject() = attr and attr.getObject("__dict__").pointsTo(Module::builtinModule()) ) or diff --git a/python/ql/src/semmle/python/dataflow/TaintTracking.qll b/python/ql/src/semmle/python/dataflow/TaintTracking.qll index 6ef9e5ae3e8..88ef3c4e4fc 100755 --- a/python/ql/src/semmle/python/dataflow/TaintTracking.qll +++ b/python/ql/src/semmle/python/dataflow/TaintTracking.qll @@ -286,14 +286,14 @@ module DictKind { pragma[noinline] private predicate subscript_index(ControlFlowNode obj, SubscriptNode sub) { sub.isLoad() and - sub.getValue() = obj and + sub.getObject() = obj and not sub.getNode().getIndex() instanceof Slice } pragma[noinline] private predicate subscript_slice(ControlFlowNode obj, SubscriptNode sub) { sub.isLoad() and - sub.getValue() = obj and + sub.getObject() = obj and sub.getNode().getIndex() instanceof Slice } diff --git a/python/ql/src/semmle/python/security/strings/Basic.qll b/python/ql/src/semmle/python/security/strings/Basic.qll index 72361657676..9dda9c59729 100755 --- a/python/ql/src/semmle/python/security/strings/Basic.qll +++ b/python/ql/src/semmle/python/security/strings/Basic.qll @@ -104,7 +104,7 @@ private predicate slice(ControlFlowNode fromnode, SubscriptNode tonode) { exists(Slice all | all = tonode.getIndex().getNode() and not exists(all.getStart()) and not exists(all.getStop()) and - tonode.getValue() = fromnode + tonode.getObject() = fromnode ) } diff --git a/python/ql/src/semmle/python/security/strings/External.qll b/python/ql/src/semmle/python/security/strings/External.qll index 10687423c29..fca73a2f0b2 100644 --- a/python/ql/src/semmle/python/security/strings/External.qll +++ b/python/ql/src/semmle/python/security/strings/External.qll @@ -134,7 +134,7 @@ private predicate json_subscript_taint( SubscriptNode sub, ControlFlowNode obj, ExternalJsonKind seq, TaintKind key ) { sub.isLoad() and - sub.getValue() = obj and + sub.getObject() = obj and key = seq.getValue() } diff --git a/python/ql/src/semmle/python/values/StringAttributes.qll b/python/ql/src/semmle/python/values/StringAttributes.qll index 30c1de0560c..248a11f6ae2 100644 --- a/python/ql/src/semmle/python/values/StringAttributes.qll +++ b/python/ql/src/semmle/python/values/StringAttributes.qll @@ -82,7 +82,7 @@ private predicate tracking_step(ControlFlowNode src, ControlFlowNode dest) { or src = dest.(AttrNode).getObject() or - src = dest.(SubscriptNode).getValue() + src = dest.(SubscriptNode).getObject() or tracked_call_step(src, dest) or diff --git a/python/ql/src/semmle/python/web/Http.qll b/python/ql/src/semmle/python/web/Http.qll index 80b8885d62e..e12cb6abe96 100644 --- a/python/ql/src/semmle/python/web/Http.qll +++ b/python/ql/src/semmle/python/web/Http.qll @@ -26,7 +26,7 @@ class WsgiEnvironment extends TaintKind { tonode.(CallNode).getFunction().(AttrNode).getObject("get") = fromnode and tonode.(CallNode).getArg(0).pointsTo(key) or - tonode.(SubscriptNode).getValue() = fromnode and tonode.isLoad() and + tonode.(SubscriptNode).getObject() = fromnode and tonode.isLoad() and tonode.(SubscriptNode).getIndex().pointsTo(key) | key = Value::forString(text) and result instanceof ExternalStringKind and @@ -66,7 +66,7 @@ class UntrustedCookie extends TaintKind { } override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) { - tonode.(SubscriptNode).getValue() = fromnode and + tonode.(SubscriptNode).getObject() = fromnode and result instanceof UntrustedMorsel } diff --git a/python/ql/src/semmle/python/web/django/Request.qll b/python/ql/src/semmle/python/web/django/Request.qll index 24a29ea4542..cd1bccd5f7f 100644 --- a/python/ql/src/semmle/python/web/django/Request.qll +++ b/python/ql/src/semmle/python/web/django/Request.qll @@ -21,7 +21,7 @@ class DjangoRequest extends TaintKind { /* Helper for getTaintForStep() */ pragma[noinline] private predicate subscript_taint(SubscriptNode sub, ControlFlowNode obj, TaintKind kind) { - sub.getValue() = obj and + sub.getObject() = obj and kind instanceof ExternalStringKind } From 7b49c8e6f86ddc29a8f0a47c4f8ba5364570f032 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Fri, 6 Mar 2020 12:47:46 -0800 Subject: [PATCH 207/236] Python: Fixes bug in modernization --- python/ql/src/Exceptions/RaisingTuple.ql | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Exceptions/RaisingTuple.ql b/python/ql/src/Exceptions/RaisingTuple.ql index 93b423f20d5..ab197fe1665 100644 --- a/python/ql/src/Exceptions/RaisingTuple.ql +++ b/python/ql/src/Exceptions/RaisingTuple.ql @@ -11,8 +11,9 @@ import python -from Raise r, AstNode origin -where r.getException().pointsTo(_, ClassValue::tuple(), origin) and +from Raise r, Value v, AstNode origin +where r.getException().pointsTo(v, origin) and +v.getClass() = ClassValue::tuple() and major_version() = 2 /* Raising a tuple is a type error in Python 3, so is handled by the IllegalRaise query. */ select r, "Raising $@ will result in the first element (recursively) being raised and all other elements being discarded.", origin, "a tuple" \ No newline at end of file From 3e36c672cf50f79478b2763b71c04d1f8a4c4809 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Fri, 6 Mar 2020 13:06:11 -0800 Subject: [PATCH 208/236] Python: Removes superfluous cast --- python/ql/src/Exceptions/EmptyExcept.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Exceptions/EmptyExcept.ql b/python/ql/src/Exceptions/EmptyExcept.ql index 7cb09203e9d..a753181af71 100755 --- a/python/ql/src/Exceptions/EmptyExcept.ql +++ b/python/ql/src/Exceptions/EmptyExcept.ql @@ -64,9 +64,9 @@ predicate subscript(Stmt s) { s.(Delete).getATarget() instanceof Subscript } -predicate encode_decode(Expr ex, ClassValue type) { +predicate encode_decode(Call ex, ClassValue type) { exists(string name | - ex.(Call).getFunc().(Attribute).getName() = name | + ex.getFunc().(Attribute).getName() = name | name = "encode" and type = ClassValue::unicodeEncodeError() or name = "decode" and type = ClassValue::unicodeDecodeError() From eed4204e04f69dd2a7590537ae29c402043bc10d Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 25 Feb 2020 15:55:53 +0000 Subject: [PATCH 209/236] JS: Lift some internal members to private top-level --- .../javascript/dataflow/Configuration.qll | 77 +++++++++---------- 1 file changed, 38 insertions(+), 39 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index ae4459e72ba..d3398dc49b3 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -149,7 +149,7 @@ abstract class Configuration extends string { predicate isBarrier(DataFlow::Node node) { exists(BarrierGuardNode guard | isBarrierGuardInternal(guard) and - guard.internalBlocks(node, "") + barrierGuardBlocksNode(guard, node, "") ) } @@ -183,7 +183,7 @@ abstract class Configuration extends string { predicate isLabeledBarrier(DataFlow::Node node, FlowLabel lbl) { exists(BarrierGuardNode guard | isBarrierGuardInternal(guard) and - guard.internalBlocks(node, lbl) + barrierGuardBlocksNode(guard, node, lbl) ) or none() // relax type inference to account for overriding @@ -319,43 +319,6 @@ module FlowLabel { * implementations of `blocks` will _both_ apply to any configuration that includes either of them. */ abstract class BarrierGuardNode extends DataFlow::Node { - /** - * Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing - * through an access path. - * - * `label` is bound to the blocked label, or the empty string if all labels should be blocked. - * - * INTERNAL: this predicate should only be used from within `blocks(boolean, Expr)`. - */ - predicate internalBlocks(DataFlow::Node nd, string label) { - // 1) `nd` is a use of a refinement node that blocks its input variable - exists(SsaRefinementNode ref, boolean outcome | - nd = DataFlow::ssaDefinitionNode(ref) and - outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and - ssaRefinementBlocks(outcome, ref, label) - ) - or - // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` - exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome | - nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and - getEnclosingExpr() = cond.getTest() and - outcome = cond.getOutcome() and - barrierGuardBlocksAccessPath(this, outcome, p, label) and - cond.dominates(bb) - ) - } - - /** - * Holds if there exists an input variable of `ref` that blocks the label `label`. - * - * This predicate is outlined to give the optimizer a hint about the join ordering. - */ - private predicate ssaRefinementBlocks(boolean outcome, SsaRefinementNode ref, string label) { - getEnclosingExpr() = ref.getGuard().getTest() and - forex(SsaVariable input | input = ref.getAnInput() | - barrierGuardBlocksExpr(this, outcome, input.getAUse(), label) - ) - } /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. @@ -400,6 +363,42 @@ private predicate barrierGuardBlocksAccessPath( barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label) } +/** + * Holds if there exists an input variable of `ref` that blocks the label `label`. + * + * This predicate is outlined to give the optimizer a hint about the join ordering. + */ +private predicate barrierGuardBlocksSsaRefinement(BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label) { + guard.getEnclosingExpr() = ref.getGuard().getTest() and + forex(SsaVariable input | input = ref.getAnInput() | + barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label) + ) +} + +/** + * Holds if data flow node `nd` acts as a barrier for data flow, possibly due to aliasing + * through an access path. + * + * `label` is bound to the blocked label, or the empty string if all labels should be blocked. + */ +private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node nd, string label) { + // 1) `nd` is a use of a refinement node that blocks its input variable + exists(SsaRefinementNode ref, boolean outcome | + nd = DataFlow::ssaDefinitionNode(ref) and + outcome = ref.getGuard().(ConditionGuardNode).getOutcome() and + barrierGuardBlocksSsaRefinement(guard, outcome, ref, label) + ) + or + // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` + exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome | + nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and + guard.getEnclosingExpr() = cond.getTest() and + outcome = cond.getOutcome() and + barrierGuardBlocksAccessPath(guard, outcome, p, label) and + cond.dominates(bb) + ) +} + /** * Holds if `guard` should block flow along the edge `pred -> succ`. * From fd1a14d3bd81ffabf9c575132f6ccda1faed376f Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 25 Feb 2020 16:01:14 +0000 Subject: [PATCH 210/236] JS: Add qldoc to a private predicate --- .../ql/src/semmle/javascript/dataflow/Configuration.qll | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index d3398dc49b3..65e27cf438d 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -199,6 +199,10 @@ abstract class Configuration extends string { */ predicate isBarrierGuard(BarrierGuardNode guard) { none() } + /** + * Holds if `guard` is a barrier guard for this configuration, added through + * `isBarrierGuard` or `AdditionalBarrierGuardNode`. + */ private predicate isBarrierGuardInternal(BarrierGuardNode guard) { isBarrierGuard(guard) or @@ -319,7 +323,6 @@ module FlowLabel { * implementations of `blocks` will _both_ apply to any configuration that includes either of them. */ abstract class BarrierGuardNode extends DataFlow::Node { - /** * Holds if this node blocks expression `e` provided it evaluates to `outcome`. * @@ -368,7 +371,9 @@ private predicate barrierGuardBlocksAccessPath( * * This predicate is outlined to give the optimizer a hint about the join ordering. */ -private predicate barrierGuardBlocksSsaRefinement(BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label) { +private predicate barrierGuardBlocksSsaRefinement( + BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label +) { guard.getEnclosingExpr() = ref.getGuard().getTest() and forex(SsaVariable input | input = ref.getAnInput() | barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label) From 2ef21ea4b839bc7189b80512c79ce2f530a2bcd6 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Tue, 25 Feb 2020 16:16:34 +0000 Subject: [PATCH 211/236] JS: Only evaluate relevant barrier guards --- .../javascript/dataflow/Configuration.qll | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll index 65e27cf438d..807733f309a 100644 --- a/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll +++ b/javascript/ql/src/semmle/javascript/dataflow/Configuration.qll @@ -353,6 +353,17 @@ private predicate barrierGuardBlocksExpr( guard.(AdditionalBarrierGuardCall).internalBlocksLabel(outcome, test, label) } +/** + * Holds if `guard` blocks the flow of a value reachable through exploratory flow. + */ +pragma[noinline] +private predicate barrierGuardIsRelevant(BarrierGuardNode guard) { + exists(Expr e | + barrierGuardBlocksExpr(guard, _, e, _) and + isRelevantForward(e.flow(), _) + ) +} + /** * Holds if data flow node `nd` acts as a barrier for data flow due to aliasing through * an access path. @@ -363,6 +374,7 @@ pragma[noinline] private predicate barrierGuardBlocksAccessPath( BarrierGuardNode guard, boolean outcome, AccessPath ap, string label ) { + barrierGuardIsRelevant(guard) and barrierGuardBlocksExpr(guard, outcome, ap.getAnInstance(), label) } @@ -374,6 +386,7 @@ private predicate barrierGuardBlocksAccessPath( private predicate barrierGuardBlocksSsaRefinement( BarrierGuardNode guard, boolean outcome, SsaRefinementNode ref, string label ) { + barrierGuardIsRelevant(guard) and guard.getEnclosingExpr() = ref.getGuard().getTest() and forex(SsaVariable input | input = ref.getAnInput() | barrierGuardBlocksExpr(guard, outcome, input.getAUse(), label) @@ -395,6 +408,7 @@ private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node ) or // 2) `nd` is an instance of an access path `p`, and dominated by a barrier for `p` + barrierGuardIsRelevant(guard) and exists(AccessPath p, BasicBlock bb, ConditionGuardNode cond, boolean outcome | nd = DataFlow::valueNode(p.getAnInstanceIn(bb)) and guard.getEnclosingExpr() = cond.getTest() and @@ -412,6 +426,7 @@ private predicate barrierGuardBlocksNode(BarrierGuardNode guard, DataFlow::Node private predicate barrierGuardBlocksEdge( BarrierGuardNode guard, DataFlow::Node pred, DataFlow::Node succ, string label ) { + barrierGuardIsRelevant(guard) and exists( SsaVariable input, SsaPhiNode phi, BasicBlock bb, ConditionGuardNode cond, boolean outcome | @@ -573,11 +588,12 @@ private class FlowStepThroughImport extends AdditionalFlowStep, DataFlow::ValueN /** * Holds if there is a flow step from `pred` to `succ` described by `summary` - * under configuration `cfg`. + * under configuration `cfg`, disregarding barriers. * * Summary steps through function calls are not taken into account. */ -private predicate basicFlowStep( +pragma[inline] +private predicate basicFlowStepNoBarrier( DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg ) { isLive() and @@ -586,8 +602,7 @@ private predicate basicFlowStep( // Local flow exists(FlowLabel predlbl, FlowLabel succlbl | localFlowStep(pred, succ, cfg, predlbl, succlbl) and - not isLabeledBarrierEdge(cfg, pred, succ, predlbl) and - not isBarrierEdge(cfg, pred, succ) and + not cfg.isBarrierEdge(pred, succ) and summary = MkPathSummary(false, false, predlbl, succlbl) ) or @@ -609,6 +624,20 @@ private predicate basicFlowStep( ) } +/** + * Holds if there is a flow step from `pred` to `succ` described by `summary` + * under configuration `cfg`. + * + * Summary steps through function calls are not taken into account. + */ +private predicate basicFlowStep( + DataFlow::Node pred, DataFlow::Node succ, PathSummary summary, DataFlow::Configuration cfg +) { + basicFlowStepNoBarrier(pred, succ, summary, cfg) and + not isLabeledBarrierEdge(cfg, pred, succ, summary.getStartLabel()) and + not isBarrierEdge(cfg, pred, succ) +} + /** * Holds if there is a flow step from `pred` to `succ` under configuration `cfg`, * including both basic flow steps and steps into/out of properties. @@ -619,7 +648,7 @@ private predicate basicFlowStep( private predicate exploratoryFlowStep( DataFlow::Node pred, DataFlow::Node succ, DataFlow::Configuration cfg ) { - basicFlowStep(pred, succ, _, cfg) or + basicFlowStepNoBarrier(pred, succ, _, cfg) or basicStoreStep(pred, succ, _) or basicLoadStep(pred, succ, _) or isAdditionalStoreStep(pred, succ, _, cfg) or @@ -1446,6 +1475,7 @@ private class BarrierGuardFunction extends Function { string label; BarrierGuardFunction() { + barrierGuardIsRelevant(guard) and exists(Expr e | exists(Expr returnExpr | returnExpr = guard.asExpr() From e1657b237bf03ffcabefd948f82f89221728eb0c Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 5 Mar 2020 11:51:43 +0000 Subject: [PATCH 212/236] JS: Extract compiler-restarting into a function --- .../extractor/lib/typescript/src/main.ts | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index 188544a3792..cc4717cd987 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -86,6 +86,8 @@ class State { } let state = new State(); +const reloadMemoryThresholdMb = getEnvironmentVariable("SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD", Number, 1000); + /** * Debugging method for finding cycles in the TypeScript AST. Should not be used in production. * @@ -529,26 +531,36 @@ function getEnvironmentVariable(name: string, parse: (x: string) => T, defaul return value != null ? parse(value) : defaultValue; } +/** + * Whether the memory usage was last observed to be above the threshold for restarting the TypeScript compiler. + */ +let isAboveReloadThreshold = false; + +/** + * If memory usage has moved above a the threshold, reboot the TypeScript compiler instance. + */ +function checkMemoryUsage() { + let bytesUsed = process.memoryUsage().heapUsed; + let megabytesUsed = bytesUsed / 1000000; + if (!isAboveReloadThreshold && megabytesUsed > reloadMemoryThresholdMb && state.project != null) { + console.warn('Restarting TypeScript compiler due to memory usage'); + state.project.reload(); + isAboveReloadThreshold = true; + } + else if (isAboveReloadThreshold && megabytesUsed < reloadMemoryThresholdMb) { + isAboveReloadThreshold = false; + } +} + function runReadLineInterface() { reset(); - let reloadMemoryThresholdMb = getEnvironmentVariable("SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD", Number, 1000); - let isAboveReloadThreshold = false; let rl = readline.createInterface({ input: process.stdin, output: process.stdout }); rl.on("line", (line: string) => { let req: Command = JSON.parse(line); switch (req.command) { case "parse": handleParseCommand(req); - // If memory usage has moved above the threshold, reboot the TypeScript compiler instance. - let bytesUsed = process.memoryUsage().heapUsed; - let megabytesUsed = bytesUsed / 1000000; - if (!isAboveReloadThreshold && megabytesUsed > reloadMemoryThresholdMb && state.project != null) { - console.warn('Restarting TypeScript compiler due to memory usage'); - state.project.reload(); - isAboveReloadThreshold = true; - } else if (isAboveReloadThreshold && megabytesUsed < reloadMemoryThresholdMb) { - isAboveReloadThreshold = false; - } + checkMemoryUsage(); break; case "open-project": handleOpenProjectCommand(req); From 549d4e9b574d58b0bb013687c421f8d52ebc3471 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 5 Mar 2020 11:53:54 +0000 Subject: [PATCH 213/236] JS: Do not restart in the middle of a message --- javascript/extractor/lib/typescript/src/main.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index cc4717cd987..e100f16061c 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -163,6 +163,7 @@ function extractFile(filename: string): string { function prepareNextFile() { if (state.pendingResponse != null) return; if (state.pendingFileIndex < state.pendingFiles.length) { + checkMemoryUsage(); let nextFilename = state.pendingFiles[state.pendingFileIndex]; state.pendingResponse = extractFile(nextFilename); } @@ -538,6 +539,8 @@ let isAboveReloadThreshold = false; /** * If memory usage has moved above a the threshold, reboot the TypeScript compiler instance. + * + * Make sure to call this only when stdout has been flushed. */ function checkMemoryUsage() { let bytesUsed = process.memoryUsage().heapUsed; @@ -560,7 +563,6 @@ function runReadLineInterface() { switch (req.command) { case "parse": handleParseCommand(req); - checkMemoryUsage(); break; case "open-project": handleOpenProjectCommand(req); From c55dcf88d501c0e2f52039d6a9522f0036697f59 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 5 Mar 2020 14:16:20 +0000 Subject: [PATCH 214/236] JS: Improve error reporting --- .../semmle/js/parser/TypeScriptParser.java | 75 ++++++++++++------- 1 file changed, 50 insertions(+), 25 deletions(-) diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index 0f43a2da0b4..44f7c076d89 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -17,6 +17,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -37,7 +38,6 @@ import com.semmle.util.exception.Exceptions; import com.semmle.util.exception.InterruptedError; import com.semmle.util.exception.ResourceError; import com.semmle.util.exception.UserError; -import com.semmle.util.io.WholeIO; import com.semmle.util.logging.LogbackUtils; import com.semmle.util.process.AbstractProcessBuilder; import com.semmle.util.process.Builder; @@ -114,6 +114,18 @@ public class TypeScriptParser { */ public static final String TYPESCRIPT_NODE_FLAGS = "SEMMLE_TYPESCRIPT_NODE_FLAGS"; + /** + * Exit code for Node.js in case of a fatal error from V8. This exit code sometimes occurs + * when the process runs out of memory. + */ + private static final int NODEJS_EXIT_CODE_FATAL_ERROR = 5; + + /** + * Exit code for Node.js in case it exits due to SIGABRT. This exit code sometimes occurs + * when the process runs out of memory. + */ + private static final int NODEJS_EXIT_CODE_SIG_ABORT = 128 + 6; + /** The Node.js parser wrapper process, if it has been started already. */ private Process parserWrapperProcess; @@ -318,15 +330,7 @@ public class TypeScriptParser { if (parserWrapperProcess == null) setupParserWrapper(); if (!parserWrapperProcess.isAlive()) { - int exitCode = 0; - try { - exitCode = parserWrapperProcess.waitFor(); - } catch (InterruptedException e) { - Exceptions.ignore(e, "This is for diagnostic purposes only."); - } - String err = new WholeIO().strictReadString(parserWrapperProcess.getErrorStream()); - throw new CatastrophicError( - "TypeScript parser wrapper terminated with exit code " + exitCode + "; stderr: " + err); + throw getExceptionFromMalformedResponse(null, null); } String response = null; @@ -335,13 +339,14 @@ public class TypeScriptParser { toParserWrapper.newLine(); toParserWrapper.flush(); response = fromParserWrapper.readLine(); - if (response == null) - throw new CatastrophicError( - "Could not communicate with TypeScript parser wrapper " - + "(command: " - + parserWrapperCommand - + ")."); - return new JsonParser().parse(response).getAsJsonObject(); + if (response == null || response.isEmpty()) { + throw getExceptionFromMalformedResponse(response, null); + } + try { + return new JsonParser().parse(response).getAsJsonObject(); + } catch (JsonParseException | IllegalStateException e) { + throw getExceptionFromMalformedResponse(response, e); + } } catch (IOException e) { throw new CatastrophicError( "Could not communicate with TypeScript parser wrapper " @@ -349,17 +354,37 @@ public class TypeScriptParser { + parserWrapperCommand + ").", e); - } catch (JsonParseException | IllegalStateException e) { - throw new CatastrophicError( - "TypeScript parser wrapper sent unexpected response: " - + response - + " (command: " - + parserWrapperCommand - + ").", - e); } } + /** + * Creates an exception object describing the best known reason for the TypeScript parser wrapper + * failing to behave as expected. + * + * Note that the stderr stream is redirected to our stderr so a more descriptive error is likely + * to be found in the log, but we try to make the Java exception descriptive as well. + */ + private RuntimeException getExceptionFromMalformedResponse(String response, Exception e) { + try { + Integer exitCode = null; + if (parserWrapperProcess.waitFor(1L, TimeUnit.SECONDS)) { + exitCode = parserWrapperProcess.waitFor(); + } + if (exitCode != null && (exitCode == NODEJS_EXIT_CODE_FATAL_ERROR || exitCode == NODEJS_EXIT_CODE_SIG_ABORT)) { + return new ResourceError("The TypeScript parser wrapper crashed, possibly from running out of memory.", e); + } + if (exitCode != null) { + return new CatastrophicError("The TypeScript parser wrapper crashed with exit code " + exitCode); + } + } catch (InterruptedException e1) { + Exceptions.ignore(e, "This is for diagnostic purposes only."); + } + if (response == null) { + return new CatastrophicError("No response from TypeScript parser wrapper", e); + } + return new CatastrophicError("Unexpected response from TypeScript parser wrapper:\n" + response, e); + } + /** * Returns the AST for a given source file. * From 759631ae562348f5bf52f5108b9dcf9ff3de8532 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Thu, 5 Mar 2020 15:11:03 +0000 Subject: [PATCH 215/236] JS: Raise default memory limit to 2.4G --- .../extractor/src/com/semmle/js/parser/TypeScriptParser.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java index 44f7c076d89..dfef971b76e 100644 --- a/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java +++ b/javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java @@ -262,7 +262,7 @@ public class TypeScriptParser { int mainMemoryMb = typescriptRam != 0 ? typescriptRam - : getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_SUFFIX, 1000); + : getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_SUFFIX, 2000); int reserveMemoryMb = getMegabyteCountFromPrefixedEnv(TYPESCRIPT_RAM_RESERVE_SUFFIX, 400); File parserWrapper = getParserWrapper(); From a1d479e9751715b63a5ff5e21ae2208221d58254 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Wed, 4 Mar 2020 17:10:04 +0000 Subject: [PATCH 216/236] JS: Declassify sensitive exprs with special characters --- .../ql/src/semmle/javascript/security/SensitiveActions.qll | 5 +++-- javascript/ql/test/library-tests/SensitiveActions/tst.js | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll b/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll index d3bcc289a50..0a5a701ab82 100644 --- a/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll +++ b/javascript/ql/src/semmle/javascript/security/SensitiveActions.qll @@ -63,10 +63,11 @@ module HeuristicNames { /** * Gets a regular expression that identifies strings that may indicate the presence of data - * that is hashed or encrypted, and hence rendered non-sensitive. + * that is hashed or encrypted, and hence rendered non-sensitive, or contains special characters + * suggesting nouns within the string do not represent the meaning of the whole string (e.g. a URL or a SQL query). */ string notSensitive() { - result = "(?is).*(redact|censor|obfuscate|hash|md5|sha|((? Date: Thu, 5 Mar 2020 14:48:29 +0000 Subject: [PATCH 217/236] JS: Change note --- change-notes/1.24/analysis-javascript.md | 1 + 1 file changed, 1 insertion(+) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index 1a666cea01c..bbedb3bc1f4 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -64,6 +64,7 @@ | Uncontrolled data used in path expression (`js/path-injection`) | More results | This query now recognizes additional ways dangerous paths can be constructed and used. | | Uncontrolled command line (`js/command-line-injection`) | More results | This query now recognizes additional ways of constructing arguments to `cmd.exe` and `/bin/sh`. | | Syntax error (`js/syntax-error`) | Lower severity | This results of this query are now displayed with lower severity. | +| Use of password hash with insufficient computational effort (`js/insufficient-password-hash`) | Fewer false positive results | This query now recognizes additional cases that do not require secure hashing. | ## Changes to libraries From cf84a53573351d73cdd155d80d634bea9fe933b1 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 9 Mar 2020 10:39:08 +0100 Subject: [PATCH 218/236] Java/C++/C#: Fix bug in type pruning. --- .../java/dataflow/internal/DataFlowImpl.qll | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..227a4bdbff4 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1406,8 +1413,6 @@ private class AccessPathFrontNilNode extends NormalNodeExt { ( any(Configuration c).isSource(this.getNode()) or - localFlowBigStepExt(_, this, false, _) - or additionalJumpStepExt(_, this, _) ) } @@ -1428,13 +1433,12 @@ private predicate flowCandFwd0( ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1589,13 +1593,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1845,13 +1849,12 @@ private predicate flowFwd0( ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1985,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2357,12 +2360,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2397,7 +2399,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() From 7a74634cfd011b3b62dc4209c9f303b6462ebc54 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 9 Mar 2020 11:02:17 +0100 Subject: [PATCH 219/236] Java/C++/C#: Simplify. --- .../java/dataflow/internal/DataFlowImpl.qll | 43 +++---------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 227a4bdbff4..e6851a9b353 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -1402,32 +1402,13 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( @@ -1451,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1814,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1842,7 +1811,7 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and @@ -1868,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -2167,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2376,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() From f491fcd5aefd9d286e2a150b3e67f2074ab4b28c Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 9 Mar 2020 11:05:13 +0100 Subject: [PATCH 220/236] Java/C++/C#: Sync. --- .../cpp/dataflow/internal/DataFlowImpl.qll | 97 +++++++------------ .../cpp/dataflow/internal/DataFlowImpl2.qll | 97 +++++++------------ .../cpp/dataflow/internal/DataFlowImpl3.qll | 97 +++++++------------ .../cpp/dataflow/internal/DataFlowImpl4.qll | 97 +++++++------------ .../dataflow/internal/DataFlowImplLocal.qll | 97 +++++++------------ .../cpp/ir/dataflow/internal/DataFlowImpl.qll | 97 +++++++------------ .../ir/dataflow/internal/DataFlowImpl2.qll | 97 +++++++------------ .../ir/dataflow/internal/DataFlowImpl3.qll | 97 +++++++------------ .../ir/dataflow/internal/DataFlowImpl4.qll | 97 +++++++------------ .../csharp/dataflow/internal/DataFlowImpl.qll | 97 +++++++------------ .../dataflow/internal/DataFlowImpl2.qll | 97 +++++++------------ .../dataflow/internal/DataFlowImpl3.qll | 97 +++++++------------ .../dataflow/internal/DataFlowImpl4.qll | 97 +++++++------------ .../dataflow/internal/DataFlowImpl5.qll | 97 +++++++------------ .../java/dataflow/internal/DataFlowImpl2.qll | 97 +++++++------------ .../java/dataflow/internal/DataFlowImpl3.qll | 97 +++++++------------ .../java/dataflow/internal/DataFlowImpl4.qll | 97 +++++++------------ .../java/dataflow/internal/DataFlowImpl5.qll | 97 +++++++------------ 18 files changed, 612 insertions(+), 1134 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 5eb98aefe4e..e6851a9b353 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -1307,15 +1307,20 @@ private predicate localFlowExit(Node node, Configuration config) { */ pragma[nomagic] private predicate localFlowStepPlus( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext cc + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext cc ) { not isUnreachableInCall(node2, cc.(LocalCallContextSpecificCall).getCall()) and ( localFlowEntry(node1, config) and ( - localFlowStep(node1, node2, config) and preservesValue = true + localFlowStep(node1, node2, config) and + preservesValue = true and + t = getErasedNodeTypeBound(node1) or - additionalLocalFlowStep(node1, node2, config) and preservesValue = false + additionalLocalFlowStep(node1, node2, config) and + preservesValue = false and + t = getErasedNodeTypeBound(node2) ) and node1 != node2 and cc.relevantFor(node1.getEnclosingCallable()) and @@ -1323,17 +1328,18 @@ private predicate localFlowStepPlus( nodeCand(TNormalNode(node2), unbind(config)) or exists(Node mid | - localFlowStepPlus(node1, mid, preservesValue, config, cc) and + localFlowStepPlus(node1, mid, preservesValue, t, config, cc) and localFlowStep(mid, node2, config) and not mid instanceof CastNode and nodeCand(TNormalNode(node2), unbind(config)) ) or exists(Node mid | - localFlowStepPlus(node1, mid, _, config, cc) and + localFlowStepPlus(node1, mid, _, _, config, cc) and additionalLocalFlowStep(mid, node2, config) and not mid instanceof CastNode and preservesValue = false and + t = getErasedNodeTypeBound(node2) and nodeCand(TNormalNode(node2), unbind(config)) ) ) @@ -1345,17 +1351,18 @@ private predicate localFlowStepPlus( */ pragma[nomagic] private predicate localFlowBigStep( - Node node1, Node node2, boolean preservesValue, Configuration config, LocalCallContext callContext + Node node1, Node node2, boolean preservesValue, DataFlowType t, Configuration config, + LocalCallContext callContext ) { - localFlowStepPlus(node1, node2, preservesValue, config, callContext) and + localFlowStepPlus(node1, node2, preservesValue, t, config, callContext) and localFlowExit(node2, config) } pragma[nomagic] private predicate localFlowBigStepExt( - NodeExt node1, NodeExt node2, boolean preservesValue, Configuration config + NodeExt node1, NodeExt node2, boolean preservesValue, AccessPathFrontNil apf, Configuration config ) { - localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, config, _) + localFlowBigStep(node1.getNode(), node2.getNode(), preservesValue, apf.getType(), config, _) } private newtype TAccessPathFront = @@ -1395,46 +1402,24 @@ private predicate flowCandFwd( else any() } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathFrontNilNode extends NormalNodeExt { - AccessPathFrontNilNode() { - nodeCand(this, _) and - ( - any(Configuration c).isSource(this.getNode()) - or - localFlowBigStepExt(_, this, false, _) - or - additionalJumpStepExt(_, this, _) - ) - } - - /** Gets the `nil` path front for this node. */ - AccessPathFrontNil getApf() { result = TFrontNil(this.getErasedNodeTypeBound()) } -} - private predicate flowCandFwd0( NodeExt node, boolean fromArg, AccessPathFront apf, Configuration config ) { nodeCand2(node, _, false, config) and config.isSource(node.getNode()) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) or nodeCand(node, unbind(config)) and ( exists(NodeExt mid | flowCandFwd(mid, fromArg, apf, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(mid, fromArg, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - apf = node.(AccessPathFrontNilNode).getApf() + localFlowBigStepExt(mid, node, false, apf, config) ) or exists(NodeExt mid | @@ -1447,7 +1432,7 @@ private predicate flowCandFwd0( flowCandFwd(mid, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - apf = node.(AccessPathFrontNilNode).getApf() + apf = TFrontNil(node.getErasedNodeTypeBound()) ) or exists(NodeExt mid, boolean allowsFieldFlow | @@ -1589,13 +1574,13 @@ private predicate flowCand0( apf instanceof AccessPathFrontNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flowCand(mid, toReturn, apf, config) ) or exists(NodeExt mid, AccessPathFrontNil nil | flowCandFwd(node, _, apf, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flowCand(mid, toReturn, nil, config) and apf instanceof AccessPathFrontNil ) @@ -1810,18 +1795,6 @@ private predicate popWithFront(AccessPath ap0, Content f, AccessPathFront apf, A /** Gets the access path obtained by pushing `f` onto `ap`. */ private AccessPath push(Content f, AccessPath ap) { ap = pop(f, result) } -/** - * A node that requires an empty access path and should have its tracked type - * (re-)computed. This is either a source or a node reached through an - * additional step. - */ -private class AccessPathNilNode extends NormalNodeExt { - AccessPathNilNode() { flowCand(this.(AccessPathFrontNilNode), _, _, _) } - - /** Gets the `nil` path for this node. */ - AccessPathNil getAp() { result = TNil(this.getErasedNodeTypeBound()) } -} - /** * Holds if data can flow from a source to `node` with the given `ap`. */ @@ -1838,20 +1811,19 @@ private predicate flowFwd0( flowCand(node, _, _, config) and config.isSource(node.getNode()) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() or flowCand(node, _, _, unbind(config)) and ( exists(NodeExt mid | flowFwd(mid, fromArg, apf, ap, config) and - localFlowBigStepExt(mid, node, true, config) + localFlowBigStepExt(mid, node, true, _, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(mid, fromArg, _, nil, config) and - localFlowBigStepExt(mid, node, false, config) and - ap = node.(AccessPathNilNode).getAp() and + localFlowBigStepExt(mid, node, false, apf, config) and apf = ap.(AccessPathNil).getFront() ) or @@ -1865,7 +1837,7 @@ private predicate flowFwd0( flowFwd(mid, _, _, nil, config) and additionalJumpStepExt(mid, node, config) and fromArg = false and - ap = node.(AccessPathNilNode).getAp() and + ap = TNil(node.getErasedNodeTypeBound()) and apf = ap.(AccessPathNil).getFront() ) or @@ -1982,13 +1954,13 @@ private predicate flow0(NodeExt node, boolean toReturn, AccessPath ap, Configura ap instanceof AccessPathNil or exists(NodeExt mid | - localFlowBigStepExt(node, mid, true, config) and + localFlowBigStepExt(node, mid, true, _, config) and flow(mid, toReturn, ap, config) ) or exists(NodeExt mid, AccessPathNil nil | flowFwd(node, _, _, ap, config) and - localFlowBigStepExt(node, mid, false, config) and + localFlowBigStepExt(node, mid, false, _, config) and flow(mid, toReturn, nil, config) and ap instanceof AccessPathNil ) @@ -2164,7 +2136,7 @@ private newtype TPathNode = config.isSource(node) and cc instanceof CallContextAny and sc instanceof SummaryCtxNone and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or // ... or a step from an existing PathNode to another node. exists(PathNodeMid mid | @@ -2357,12 +2329,11 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt pathIntoLocalStep(mid, midnode, cc, enclosing, sc, ap0, conf) and localCC = getLocalCallContext(cc, enclosing) | - localFlowBigStep(midnode, node, true, conf, localCC) and + localFlowBigStep(midnode, node, true, _, conf, localCC) and ap = ap0 or - localFlowBigStep(midnode, node, false, conf, localCC) and - ap0 instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + localFlowBigStep(midnode, node, false, ap.(AccessPathNil).getType(), conf, localCC) and + ap0 instanceof AccessPathNil ) or jumpStep(mid.getNode(), node, mid.getConfiguration()) and @@ -2374,7 +2345,7 @@ private predicate pathStep(PathNodeMid mid, Node node, CallContext cc, SummaryCt cc instanceof CallContextAny and sc instanceof SummaryCtxNone and mid.getAp() instanceof AccessPathNil and - ap = any(AccessPathNilNode nil | nil.getNode() = node).getAp() + ap = TNil(getErasedNodeTypeBound(node)) or exists(Content f, AccessPath ap0 | pathReadStep(mid, node, ap0, f, cc) and ap = pop(f, ap0)) and sc = mid.getSummaryCtx() @@ -2397,7 +2368,7 @@ private predicate pathIntoLocalStep( midnode = mid.getNode() and cc = mid.getCallContext() and conf = mid.getConfiguration() and - localFlowBigStep(midnode, _, _, conf, _) and + localFlowBigStep(midnode, _, _, _, conf, _) and enclosing = midnode.getEnclosingCallable() and sc = mid.getSummaryCtx() and ap0 = mid.getAp() From 4298a3a9313b1dd3fe0fb41f9fd40a80ff8bf3f8 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 9 Mar 2020 11:16:59 +0100 Subject: [PATCH 221/236] Java: Add test. --- .../library-tests/dataflow/typepruning/A.java | 18 +++++++++++++ .../dataflow/typepruning/test.expected | 1 + .../dataflow/typepruning/test.ql | 27 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 java/ql/test/library-tests/dataflow/typepruning/A.java create mode 100644 java/ql/test/library-tests/dataflow/typepruning/test.expected create mode 100644 java/ql/test/library-tests/dataflow/typepruning/test.ql diff --git a/java/ql/test/library-tests/dataflow/typepruning/A.java b/java/ql/test/library-tests/dataflow/typepruning/A.java new file mode 100644 index 00000000000..656a602ba54 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/typepruning/A.java @@ -0,0 +1,18 @@ +public class A { + Object customStep(Object o) { return null; } + + Object source() { return null; } + + void sink(Object o) { } + + Object through1(Object o) { + Object o2 = customStep(o); + String s = (String)o2; + return s; + } + + void foo() { + Object x = through1(source()); + sink(x); + } +} diff --git a/java/ql/test/library-tests/dataflow/typepruning/test.expected b/java/ql/test/library-tests/dataflow/typepruning/test.expected new file mode 100644 index 00000000000..4c92d2af6f0 --- /dev/null +++ b/java/ql/test/library-tests/dataflow/typepruning/test.expected @@ -0,0 +1 @@ +| A.java:15:25:15:32 | source(...) | A.java:16:10:16:10 | x | A.java:14:8:14:10 | foo | diff --git a/java/ql/test/library-tests/dataflow/typepruning/test.ql b/java/ql/test/library-tests/dataflow/typepruning/test.ql new file mode 100644 index 00000000000..c86476afeed --- /dev/null +++ b/java/ql/test/library-tests/dataflow/typepruning/test.ql @@ -0,0 +1,27 @@ +import java +import semmle.code.java.dataflow.DataFlow +import DataFlow + +class Conf extends Configuration { + Conf() { this = "test types" } + + override predicate isSource(Node n) { n.asExpr().(MethodAccess).getMethod().hasName("source") } + + override predicate isSink(Node n) { + exists(MethodAccess sink | + sink.getAnArgument() = n.asExpr() and sink.getMethod().hasName("sink") + ) + } + + override predicate isAdditionalFlowStep(Node n1, Node n2) { + exists(MethodAccess ma | + ma.getMethod().hasName("customStep") and + ma.getAnArgument() = n1.asExpr() and + ma = n2.asExpr() + ) + } +} + +from Node src, Node sink, Conf conf +where conf.hasFlow(src, sink) +select src, sink, sink.getEnclosingCallable() From a2bbacf58d1c208896fd6505f8d4d372a6b0bdcb Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Mon, 9 Mar 2020 10:36:46 +0100 Subject: [PATCH 222/236] Java/C++/C#: Fix performance issue in partial paths exploration. --- cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll | 2 +- cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll | 2 +- cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll | 2 +- cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll | 2 +- .../src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll | 2 +- .../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll | 2 +- .../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll | 2 +- .../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll | 2 +- .../src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll | 2 +- .../src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll | 2 +- .../src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll | 2 +- .../src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll | 2 +- .../src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll | 2 +- .../src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll | 2 +- java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll | 2 +- .../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll | 2 +- .../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll | 2 +- .../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll | 2 +- .../ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll +++ b/cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplLocal.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll +++ b/cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl2.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl3.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl4.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll +++ b/csharp/ql/src/semmle/code/csharp/dataflow/internal/DataFlowImpl5.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config diff --git a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll index 5eb98aefe4e..0150f4e0aa0 100644 --- a/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll +++ b/java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll @@ -2949,7 +2949,7 @@ private module FlowExploration { config = mid.getConfiguration() } - pragma[noinline] + pragma[nomagic] private predicate partialPathOutOfCallable1( PartialPathNodePriv mid, DataFlowCall call, ReturnKindExt kind, CallContext cc, PartialAccessPath ap, Configuration config From 6c1f98a5ae583240d3b45fb7cb3b0128c2025e78 Mon Sep 17 00:00:00 2001 From: Asger Feldthaus Date: Mon, 9 Mar 2020 11:58:38 +0000 Subject: [PATCH 223/236] JS: Update vague variable name --- javascript/extractor/lib/typescript/src/main.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/javascript/extractor/lib/typescript/src/main.ts b/javascript/extractor/lib/typescript/src/main.ts index e100f16061c..3aa1d6f0f4e 100644 --- a/javascript/extractor/lib/typescript/src/main.ts +++ b/javascript/extractor/lib/typescript/src/main.ts @@ -534,8 +534,11 @@ function getEnvironmentVariable(name: string, parse: (x: string) => T, defaul /** * Whether the memory usage was last observed to be above the threshold for restarting the TypeScript compiler. + * + * This is to prevent repeatedly restarting the compiler if the GC does not immediately bring us below the + * threshold again. */ -let isAboveReloadThreshold = false; +let hasReloadedSinceExceedingThreshold = false; /** * If memory usage has moved above a the threshold, reboot the TypeScript compiler instance. @@ -545,13 +548,13 @@ let isAboveReloadThreshold = false; function checkMemoryUsage() { let bytesUsed = process.memoryUsage().heapUsed; let megabytesUsed = bytesUsed / 1000000; - if (!isAboveReloadThreshold && megabytesUsed > reloadMemoryThresholdMb && state.project != null) { + if (!hasReloadedSinceExceedingThreshold && megabytesUsed > reloadMemoryThresholdMb && state.project != null) { console.warn('Restarting TypeScript compiler due to memory usage'); state.project.reload(); - isAboveReloadThreshold = true; + hasReloadedSinceExceedingThreshold = true; } - else if (isAboveReloadThreshold && megabytesUsed < reloadMemoryThresholdMb) { - isAboveReloadThreshold = false; + else if (hasReloadedSinceExceedingThreshold && megabytesUsed < reloadMemoryThresholdMb) { + hasReloadedSinceExceedingThreshold = false; } } From 3c785ecaa7439163aea585bcd559693df2d24adb Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 9 Mar 2020 12:57:20 +0000 Subject: [PATCH 224/236] JavaScript: Move flow summaries to `experimental`. Also update description and change note to call out their experimental character more clearly. --- change-notes/1.24/analysis-javascript.md | 2 ++ javascript/config/suites/javascript/flow-summaries | 4 ++-- javascript/documentation/flow-summaries.rst | 12 ++++++++---- .../Summaries/Configurations.qll | 0 .../Summaries/ExtractFlowStepSummaries.ql | 0 .../Summaries/ExtractSinkSummaries.ql | 0 .../Summaries/ExtractSourceSummaries.ql | 0 .../Summaries/ImportFromCsv.qll | 0 .../Summaries/ImportFromExternalPredicates.qll | 0 .../Summaries/PortalEntrySink.qll | 0 .../Summaries/PortalExitSource.qll | 0 .../{Security => experimental}/Summaries/Shared.qll | 0 .../Summaries/SinkFromAnnotation.qll | 0 .../Summaries/SourceFromAnnotation.qll | 0 .../Summaries/ExtractFlowStepSummaries.qlref | 2 +- .../Security/Summaries/ExtractSinkSummaries.qlref | 2 +- .../Security/Summaries/ExtractSourceSummaries.qlref | 2 +- 17 files changed, 15 insertions(+), 9 deletions(-) rename javascript/ql/src/{Security => experimental}/Summaries/Configurations.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/ExtractFlowStepSummaries.ql (100%) rename javascript/ql/src/{Security => experimental}/Summaries/ExtractSinkSummaries.ql (100%) rename javascript/ql/src/{Security => experimental}/Summaries/ExtractSourceSummaries.ql (100%) rename javascript/ql/src/{Security => experimental}/Summaries/ImportFromCsv.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/ImportFromExternalPredicates.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/PortalEntrySink.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/PortalExitSource.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/Shared.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/SinkFromAnnotation.qll (100%) rename javascript/ql/src/{Security => experimental}/Summaries/SourceFromAnnotation.qll (100%) diff --git a/change-notes/1.24/analysis-javascript.md b/change-notes/1.24/analysis-javascript.md index bbedb3bc1f4..989750a6468 100644 --- a/change-notes/1.24/analysis-javascript.md +++ b/change-notes/1.24/analysis-javascript.md @@ -19,6 +19,8 @@ - Calls can now be resolved to indirectly-defined class members in more cases. - Calls through partial invocations such as `.bind` can now be resolved in more cases. +* Support for flow summaries has been more clearly marked as being experimental and moved to the new `experimental` folder. + * Support for the following frameworks and libraries has been improved: - [Electron](https://electronjs.org/) - [Handlebars](https://www.npmjs.com/package/handlebars) diff --git a/javascript/config/suites/javascript/flow-summaries b/javascript/config/suites/javascript/flow-summaries index 21573fd9bf3..481c69829ad 100644 --- a/javascript/config/suites/javascript/flow-summaries +++ b/javascript/config/suites/javascript/flow-summaries @@ -1,2 +1,2 @@ -+ semmlecode-javascript-queries/Security/Summaries/ExtractSourceSummaries.ql -+ semmlecode-javascript-queries/Security/Summaries/ExtractSinkSummaries.ql ++ semmlecode-javascript-queries/experimental/Summaries/ExtractSourceSummaries.ql ++ semmlecode-javascript-queries/experimental/Summaries/ExtractSinkSummaries.ql diff --git a/javascript/documentation/flow-summaries.rst b/javascript/documentation/flow-summaries.rst index a1e97cb7815..edd6cb027b0 100644 --- a/javascript/documentation/flow-summaries.rst +++ b/javascript/documentation/flow-summaries.rst @@ -14,6 +14,10 @@ added as external data), and are picked up by the standard security queries, all about flow into, out of and through the npm packages as though they had been included as part of the build. +Note that flow summaries are an experimental technology, and not ready to be used in production +queries or libraries. Also note that flow summaries do not currently work with CodeQL, but require +the legacy Semmle Core toolchain. + Motivating example ------------------ @@ -87,11 +91,11 @@ package. (Note that this requires a working installation of Semmle Core.) There are three default summary extraction queries: - Extract flow step summaries (``js/step-summary-extraction``, - ``Security/Summaries/ExtractSourceSummaries.ql``) + ``experimental/Summaries/ExtractSourceSummaries.ql``) - Extract sink summaries (``js/sink-summary-extraction``, - ``Security/Summaries/ExtractSinkSummaries.ql``) + ``experimental/Summaries/ExtractSinkSummaries.ql``) - Extract source summaries (``js/source-summary-extraction``, - ``Security/Summaries/ExtractSourceSummaries.ql``) + ``experimental/Summaries/ExtractSourceSummaries.ql``) You can run these queries individually against a snapshot of the npm package you want to create flow summaries for using ``odasa runQuery``, and store the output as CSV files named @@ -103,7 +107,7 @@ project, we can extract sink summaries using the command .. code-block:: bash odasa runQuery \ - --query $SEMMLE_DIST/queries/semmlecode-javascript-queries/Security/Summaries/ExtractSinkSummaries.ql \ + --query $SEMMLE_DIST/queries/semmlecode-javascript-queries/experimental/Summaries/ExtractSinkSummaries.ql \ --output-file additional-sinks.csv --snapshot mkdirp-snapshot diff --git a/javascript/ql/src/Security/Summaries/Configurations.qll b/javascript/ql/src/experimental/Summaries/Configurations.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/Configurations.qll rename to javascript/ql/src/experimental/Summaries/Configurations.qll diff --git a/javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql b/javascript/ql/src/experimental/Summaries/ExtractFlowStepSummaries.ql similarity index 100% rename from javascript/ql/src/Security/Summaries/ExtractFlowStepSummaries.ql rename to javascript/ql/src/experimental/Summaries/ExtractFlowStepSummaries.ql diff --git a/javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql b/javascript/ql/src/experimental/Summaries/ExtractSinkSummaries.ql similarity index 100% rename from javascript/ql/src/Security/Summaries/ExtractSinkSummaries.ql rename to javascript/ql/src/experimental/Summaries/ExtractSinkSummaries.ql diff --git a/javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql b/javascript/ql/src/experimental/Summaries/ExtractSourceSummaries.ql similarity index 100% rename from javascript/ql/src/Security/Summaries/ExtractSourceSummaries.ql rename to javascript/ql/src/experimental/Summaries/ExtractSourceSummaries.ql diff --git a/javascript/ql/src/Security/Summaries/ImportFromCsv.qll b/javascript/ql/src/experimental/Summaries/ImportFromCsv.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/ImportFromCsv.qll rename to javascript/ql/src/experimental/Summaries/ImportFromCsv.qll diff --git a/javascript/ql/src/Security/Summaries/ImportFromExternalPredicates.qll b/javascript/ql/src/experimental/Summaries/ImportFromExternalPredicates.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/ImportFromExternalPredicates.qll rename to javascript/ql/src/experimental/Summaries/ImportFromExternalPredicates.qll diff --git a/javascript/ql/src/Security/Summaries/PortalEntrySink.qll b/javascript/ql/src/experimental/Summaries/PortalEntrySink.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/PortalEntrySink.qll rename to javascript/ql/src/experimental/Summaries/PortalEntrySink.qll diff --git a/javascript/ql/src/Security/Summaries/PortalExitSource.qll b/javascript/ql/src/experimental/Summaries/PortalExitSource.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/PortalExitSource.qll rename to javascript/ql/src/experimental/Summaries/PortalExitSource.qll diff --git a/javascript/ql/src/Security/Summaries/Shared.qll b/javascript/ql/src/experimental/Summaries/Shared.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/Shared.qll rename to javascript/ql/src/experimental/Summaries/Shared.qll diff --git a/javascript/ql/src/Security/Summaries/SinkFromAnnotation.qll b/javascript/ql/src/experimental/Summaries/SinkFromAnnotation.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/SinkFromAnnotation.qll rename to javascript/ql/src/experimental/Summaries/SinkFromAnnotation.qll diff --git a/javascript/ql/src/Security/Summaries/SourceFromAnnotation.qll b/javascript/ql/src/experimental/Summaries/SourceFromAnnotation.qll similarity index 100% rename from javascript/ql/src/Security/Summaries/SourceFromAnnotation.qll rename to javascript/ql/src/experimental/Summaries/SourceFromAnnotation.qll diff --git a/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref b/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref index 8491bf549de..da6baf2b26d 100644 --- a/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref +++ b/javascript/ql/test/query-tests/Security/Summaries/ExtractFlowStepSummaries.qlref @@ -1 +1 @@ -Security/Summaries/ExtractFlowStepSummaries.ql +experimental/Summaries/ExtractFlowStepSummaries.ql diff --git a/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref b/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref index 9b5d6745f2c..90b31eefa80 100644 --- a/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref +++ b/javascript/ql/test/query-tests/Security/Summaries/ExtractSinkSummaries.qlref @@ -1 +1 @@ -Security/Summaries/ExtractSinkSummaries.ql +experimental/Summaries/ExtractSinkSummaries.ql diff --git a/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref b/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref index 36bf7bd8369..26661a51f11 100644 --- a/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref +++ b/javascript/ql/test/query-tests/Security/Summaries/ExtractSourceSummaries.qlref @@ -1 +1 @@ -Security/Summaries/ExtractSourceSummaries.ql +experimental/Summaries/ExtractSourceSummaries.ql From 6dee7061a03e5f8660f6f020a65aa0664b8b13c4 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 9 Mar 2020 15:44:32 +0100 Subject: [PATCH 225/236] C++: Handle constant variable accesses in SimpleRangeAnalysis.qll --- .../cpp/rangeanalysis/SimpleRangeAnalysis.qll | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll index 4a8f39d79f2..c126e984f1e 100644 --- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll @@ -91,13 +91,30 @@ private float wideningUpperBounds(ArithmeticType t) { result = 1.0 / 0.0 // +Inf } +/** + * Gets the value of the expression `e`, if it is a constant. + * This predicate also handles the case of constant variables initialized in compilation units, + * which doesn't necessarily have a getValue() result from the extractor. + */ +private string getValue(Expr e) { + if exists(e.getValue()) + then result = e.getValue() + else + exists(VariableAccess access, Variable v | + e = access and + v = access.getTarget() and + v.getType().isConst() and + result = getValue(v.getAnAssignedValue()) + ) +} + /** Set of expressions which we know how to analyze. */ private predicate analyzableExpr(Expr e) { // The type of the expression must be arithmetic. We reuse the logic in // `exprMinVal` to check this. exists(exprMinVal(e)) and ( - exists(e.getValue().toFloat()) or + exists(getValue(e).toFloat()) or e instanceof UnaryPlusExpr or e instanceof UnaryMinusExpr or e instanceof MinExpr or From a9674ef6e865efe1230fd102109ce647c5c93c5e Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 9 Mar 2020 16:54:55 +0100 Subject: [PATCH 226/236] Python: Resolve autoformat ugliness --- python/ql/src/Statements/StatementNoEffect.ql | 9 +++------ python/ql/src/Statements/UnnecessaryDelete.ql | 7 ++----- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/python/ql/src/Statements/StatementNoEffect.ql b/python/ql/src/Statements/StatementNoEffect.ql index 6e5fc9d88e8..dde0b5d7cde 100644 --- a/python/ql/src/Statements/StatementNoEffect.ql +++ b/python/ql/src/Statements/StatementNoEffect.ql @@ -36,12 +36,9 @@ predicate maybe_side_effecting_attribute(Attribute attr) { predicate side_effecting_descriptor_type(ClassValue descriptor) { descriptor.isDescriptorType() and - /* - * Technically all descriptor gets have side effects, - * but some are indicative of a missing call and - * we want to treat them as having no effect. - */ - + // Technically all descriptor gets have side effects, + // but some are indicative of a missing call and + // we want to treat them as having no effect. not descriptor = ClassValue::functionType() and not descriptor = ClassValue::staticmethod() and not descriptor = ClassValue::classmethod() diff --git a/python/ql/src/Statements/UnnecessaryDelete.ql b/python/ql/src/Statements/UnnecessaryDelete.ql index 48f167ca706..d10bcc2ed20 100644 --- a/python/ql/src/Statements/UnnecessaryDelete.ql +++ b/python/ql/src/Statements/UnnecessaryDelete.ql @@ -22,11 +22,8 @@ where not e instanceof Subscript and not e instanceof Attribute and not exists(Stmt s | s.(While).contains(del) or s.(For).contains(del)) and - /* - * False positive: calling `sys.exc_info` within a function results in a - * reference cycle, and an explicit call to `del` helps break this cycle. - */ - + // False positive: calling `sys.exc_info` within a function results in a + // reference cycle, and an explicit call to `del` helps break this cycle. not exists(FunctionValue ex | ex = Value::named("sys.exc_info") and ex.getACall().getScope() = f From a38fd2d3d173bb005756b1dc2b01e9b9a2246df8 Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Mon, 9 Mar 2020 17:05:00 +0100 Subject: [PATCH 227/236] Python: Use unambiguous name getCallNode --- python/ql/src/semmle/python/objects/Descriptors.qll | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/src/semmle/python/objects/Descriptors.qll b/python/ql/src/semmle/python/objects/Descriptors.qll index 31353f92b10..dbc3e59afa5 100644 --- a/python/ql/src/semmle/python/objects/Descriptors.qll +++ b/python/ql/src/semmle/python/objects/Descriptors.qll @@ -20,7 +20,7 @@ class PropertyInternal extends ObjectInternal, TProperty { this = TProperty(_, _, result) } - private CallNode getCall() { this = TProperty(result, _, _) } + private CallNode getCallNode() { this = TProperty(result, _, _) } /** Gets the setter function of this property */ CallableObjectInternal getSetter() { @@ -33,7 +33,7 @@ class PropertyInternal extends ObjectInternal, TProperty { or // x = property(getter, setter, deleter) exists(ControlFlowNode setter_arg | - setter_arg = getCall().getArg(1) or setter_arg = getCall().getArgByName("fset") + setter_arg = getCallNode().getArg(1) or setter_arg = getCallNode().getArgByName("fset") | PointsToInternal::pointsTo(setter_arg, this.getContext(), result, _) ) @@ -49,7 +49,7 @@ class PropertyInternal extends ObjectInternal, TProperty { or // x = property(getter, setter, deleter) exists(ControlFlowNode deleter_arg | - deleter_arg = getCall().getArg(2) or deleter_arg = getCall().getArgByName("fdel") + deleter_arg = getCallNode().getArg(2) or deleter_arg = getCallNode().getArgByName("fdel") | PointsToInternal::pointsTo(deleter_arg, this.getContext(), result, _) ) From 525a00098e75e9eaa624ea49ac2f896866eb3822 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 9 Mar 2020 22:16:05 +0100 Subject: [PATCH 228/236] C++: Address review comments --- .../cpp/rangeanalysis/SimpleRangeAnalysis.qll | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll index c126e984f1e..3aa17e154ae 100644 --- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll @@ -96,18 +96,23 @@ private float wideningUpperBounds(ArithmeticType t) { * This predicate also handles the case of constant variables initialized in compilation units, * which doesn't necessarily have a getValue() result from the extractor. */ -private string getValue(Expr e) { +private string getValue0(Expr e) { if exists(e.getValue()) then result = e.getValue() else exists(VariableAccess access, Variable v | e = access and v = access.getTarget() and - v.getType().isConst() and - result = getValue(v.getAnAssignedValue()) + v.getUnderlyingType().isConst() and + result = getValue0(v.getAnAssignedValue()) ) } +private string getValue(Expr e) { + result = min(getValue0(e)) and + result = max(getValue0(e)) +} + /** Set of expressions which we know how to analyze. */ private predicate analyzableExpr(Expr e) { // The type of the expression must be arithmetic. We reuse the logic in @@ -382,8 +387,8 @@ private float getTruncatedLowerBounds(Expr expr) { then // If the expression evaluates to a constant, then there is no // need to call getLowerBoundsImpl. - if exists(expr.getValue().toFloat()) - then result = expr.getValue().toFloat() + if exists(getValue(expr).toFloat()) + then result = getValue(expr).toFloat() else ( // Some of the bounds computed by getLowerBoundsImpl might // overflow, so we replace invalid bounds with exprMinVal. @@ -435,8 +440,8 @@ private float getTruncatedUpperBounds(Expr expr) { then // If the expression evaluates to a constant, then there is no // need to call getUpperBoundsImpl. - if exists(expr.getValue().toFloat()) - then result = expr.getValue().toFloat() + if exists(getValue(expr).toFloat()) + then result = getValue(expr).toFloat() else ( // Some of the bounds computed by `getUpperBoundsImpl` // might overflow, so we replace invalid bounds with From 1a5282ae216f84f0239c3d9afa9668df4bedd7ae Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Mon, 9 Mar 2020 22:33:59 +0100 Subject: [PATCH 229/236] C++: Add testcase that previously resulted in a false positive --- .../Security/CWE/CWE-190/semmle/wider_type/test.c | 7 +++++++ .../Security/CWE/CWE-190/semmle/wider_type/test2.c | 1 + 2 files changed, 8 insertions(+) create mode 100644 cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c index 88fe22c3b31..5802ca3eb4f 100644 --- a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test.c @@ -69,3 +69,10 @@ void test10(int x) { } while (0); } } + +extern const int const256; + +void test11() { + short s; + for(s = 0; s < const256; ++s) {} +} \ No newline at end of file diff --git a/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c new file mode 100644 index 00000000000..7a9609c9a3b --- /dev/null +++ b/cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/wider_type/test2.c @@ -0,0 +1 @@ +const int const256 = 256; \ No newline at end of file From 48e67bca51e84beda375939e60bc2e73c1ad78eb Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 9 Mar 2020 18:57:42 -0700 Subject: [PATCH 230/236] Python: Modernizes query --- python/ql/src/Functions/UseImplicitNoneReturnValue.ql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/ql/src/Functions/UseImplicitNoneReturnValue.ql b/python/ql/src/Functions/UseImplicitNoneReturnValue.ql index d2c954cb4c9..ab797844533 100644 --- a/python/ql/src/Functions/UseImplicitNoneReturnValue.ql +++ b/python/ql/src/Functions/UseImplicitNoneReturnValue.ql @@ -23,12 +23,12 @@ predicate is_used(Call c) { ) } -from Call c, FunctionObject func +from Call c, FunctionValue func where /* Call result is used, but callee is a procedure */ -is_used(c) and c.getFunc().refersTo(func) and func.getFunction().isProcedure() and +is_used(c) and c.getFunc().pointsTo(func) and func.getScope().isProcedure() and /* All callees are procedures */ -forall(FunctionObject callee | c.getFunc().refersTo(callee) | callee.getFunction().isProcedure()) and +forall(FunctionValue callee | c.getFunc().pointsTo(callee) | callee.getScope().isProcedure()) and /* Mox return objects have an `AndReturn` method */ not useOfMoxInModule(c.getEnclosingModule()) select c, "The result of '$@' is used even though it is always None.", func, func.getQualifiedName() From e8708a083f8c499a8b3fccff826165850342570d Mon Sep 17 00:00:00 2001 From: Rebecca Valentine Date: Mon, 9 Mar 2020 19:13:54 -0700 Subject: [PATCH 231/236] Python: Modernizes query and expecteds --- .../src/Functions/SignatureSpecialMethods.ql | 18 +++++++++--------- .../general/SignatureSpecialMethods.expected | 14 +++++++------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/python/ql/src/Functions/SignatureSpecialMethods.ql b/python/ql/src/Functions/SignatureSpecialMethods.ql index 1301949768a..3e718ce2a3b 100644 --- a/python/ql/src/Functions/SignatureSpecialMethods.ql +++ b/python/ql/src/Functions/SignatureSpecialMethods.ql @@ -112,7 +112,7 @@ predicate is_quad_op(string name) { name = "__setslice__" or name = "__exit__" } -int argument_count(PyFunctionObject f, string name, ClassObject cls) { +int argument_count(PythonFunctionValue f, string name, ClassValue cls) { cls.declaredAttribute(name) = f and ( is_unary_op(name) and result = 1 @@ -125,7 +125,7 @@ int argument_count(PyFunctionObject f, string name, ClassObject cls) { ) } -predicate incorrect_special_method_defn(PyFunctionObject func, string message, boolean show_counts, string name, ClassObject owner) { +predicate incorrect_special_method_defn(PythonFunctionValue func, string message, boolean show_counts, string name, ClassValue owner) { exists(int required | required = argument_count(func, name, owner) | /* actual_non_default <= actual */ @@ -133,14 +133,14 @@ predicate incorrect_special_method_defn(PyFunctionObject func, string message, b (message = "Too few parameters" and show_counts = true) else if required < func.minParameters() then (message = "Too many parameters" and show_counts = true) - else if (func.minParameters() < required and not func.getFunction().hasVarArg()) then + else if (func.minParameters() < required and not func.getScope().hasVarArg()) then (message = (required -func.minParameters()) + " default values(s) will never be used" and show_counts = false) else none() ) } -predicate incorrect_pow(FunctionObject func, string message, boolean show_counts, ClassObject owner) { +predicate incorrect_pow(FunctionValue func, string message, boolean show_counts, ClassValue owner) { owner.declaredAttribute("__pow__") = func and ( func.maxParameters() < 2 and message = "Too few parameters" and show_counts = true @@ -153,19 +153,19 @@ predicate incorrect_pow(FunctionObject func, string message, boolean show_counts ) } -predicate incorrect_get(FunctionObject func, string message, boolean show_counts, ClassObject owner) { +predicate incorrect_get(FunctionValue func, string message, boolean show_counts, ClassValue owner) { owner.declaredAttribute("__get__") = func and ( func.maxParameters() < 3 and message = "Too few parameters" and show_counts = true or func.minParameters() > 3 and message = "Too many parameters" and show_counts = true or - func.minParameters() < 2 and not func.getFunction().hasVarArg() and + func.minParameters() < 2 and not func.getScope().hasVarArg() and message = (2 - func.minParameters()) + " default value(s) will never be used" and show_counts = false ) } -string should_have_parameters(PyFunctionObject f, string name, ClassObject owner) { +string should_have_parameters(PythonFunctionValue f, string name, ClassValue owner) { exists(int i | i = argument_count(f, name, owner) | result = i.toString() ) @@ -173,7 +173,7 @@ string should_have_parameters(PyFunctionObject f, string name, ClassObject owner owner.declaredAttribute(name) = f and (name = "__get__" or name = "__pow__") and result = "2 or 3" } -string has_parameters(PyFunctionObject f) { +string has_parameters(PythonFunctionValue f) { exists(int i | i = f.minParameters() | i = 0 and result = "no parameters" or @@ -183,7 +183,7 @@ string has_parameters(PyFunctionObject f) { ) } -from PyFunctionObject f, string message, string sizes, boolean show_counts, string name, ClassObject owner +from PythonFunctionValue f, string message, string sizes, boolean show_counts, string name, ClassValue owner where ( incorrect_special_method_defn(f, message, show_counts, name, owner) diff --git a/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected index bda8b8e6472..8f25a085f4f 100644 --- a/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected +++ b/python/ql/test/query-tests/Functions/general/SignatureSpecialMethods.expected @@ -1,9 +1,9 @@ -| om_test.py:59:5:59:28 | Function __div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:62:5:62:22 | Function __mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:65:5:65:29 | Function __neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:68:5:68:35 | Function __exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:71:5:71:19 | Function __repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:74:5:74:46 | Function __add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | -| om_test.py:97:15:97:34 | Function lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials | +| om_test.py:59:5:59:28 | Function WrongSpecials.__div__ | Too many parameters for special method __div__, which has 3 parameters, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:62:5:62:22 | Function WrongSpecials.__mul__ | Too few parameters for special method __mul__, which has 1 parameter, but should have 2, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:65:5:65:29 | Function WrongSpecials.__neg__ | Too many parameters for special method __neg__, which has 2 parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:68:5:68:35 | Function WrongSpecials.__exit__ | Too few parameters for special method __exit__, which has 3 parameters, but should have 4, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:71:5:71:19 | Function WrongSpecials.__repr__ | Too few parameters for special method __repr__, which has no parameters, but should have 1, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:74:5:74:46 | Function WrongSpecials.__add__ | 1 default values(s) will never be used for special method __add__, in class $@. | om_test.py:57:1:57:28 | class WrongSpecials | WrongSpecials | +| om_test.py:97:15:97:34 | Function NotOKSpecials.lambda | Too few parameters for special method __sub__, which has 1 parameter, but should have 2, in class $@. | om_test.py:95:1:95:28 | class NotOKSpecials | NotOKSpecials | | protocols.py:104:1:104:12 | Function f | Too few parameters for special method __add__, which has 1 parameter, but should have 2, in class $@. | protocols.py:107:1:107:29 | class MissingMethods | MissingMethods | | protocols.py:104:1:104:12 | Function f | Too few parameters for special method __set__, which has 1 parameter, but should have 3, in class $@. | protocols.py:107:1:107:29 | class MissingMethods | MissingMethods | From 047c328c58b36483ff01f8b3b0613548a2ddf516 Mon Sep 17 00:00:00 2001 From: Rebecca Valentine <171941+BekaValentine@users.noreply.github.com> Date: Mon, 9 Mar 2020 19:20:08 -0700 Subject: [PATCH 232/236] Update python/ql/src/semmle/python/objects/ObjectAPI.qll Co-Authored-By: Rasmus Wriedt Larsen --- python/ql/src/semmle/python/objects/ObjectAPI.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index 844d366b07f..c6dbc94e310 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -517,7 +517,7 @@ abstract class FunctionValue extends CallableValue { } /** Whether this is a "normal" method, that is, it is exists as a class attribute - * which is not wrapped and not the __new__ method. */ + * which is not a lambda and not the __new__ method. */ predicate isNormalMethod() { exists(ClassValue cls, string name | cls.declaredAttribute(name) = this and From 8b467eb9a709003ac68f6615a4d0ec3a6b8673a8 Mon Sep 17 00:00:00 2001 From: Mathias Vorreiter Pedersen Date: Tue, 10 Mar 2020 09:50:28 +0100 Subject: [PATCH 233/236] C++: Allow multiple results in getValue --- .../code/cpp/rangeanalysis/SimpleRangeAnalysis.qll | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll index 3aa17e154ae..fe89d8ebbe5 100644 --- a/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll +++ b/cpp/ql/src/semmle/code/cpp/rangeanalysis/SimpleRangeAnalysis.qll @@ -96,7 +96,7 @@ private float wideningUpperBounds(ArithmeticType t) { * This predicate also handles the case of constant variables initialized in compilation units, * which doesn't necessarily have a getValue() result from the extractor. */ -private string getValue0(Expr e) { +private string getValue(Expr e) { if exists(e.getValue()) then result = e.getValue() else @@ -104,15 +104,10 @@ private string getValue0(Expr e) { e = access and v = access.getTarget() and v.getUnderlyingType().isConst() and - result = getValue0(v.getAnAssignedValue()) + result = getValue(v.getAnAssignedValue()) ) } -private string getValue(Expr e) { - result = min(getValue0(e)) and - result = max(getValue0(e)) -} - /** Set of expressions which we know how to analyze. */ private predicate analyzableExpr(Expr e) { // The type of the expression must be arithmetic. We reuse the logic in From fc87f1eb1ba86cbaeb79879f27b22beb9e057657 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Tue, 10 Mar 2020 10:54:48 +0100 Subject: [PATCH 234/236] C#: Fix tests. --- .../semmle/code/csharp/frameworks/JsonNET.qll | 2 +- .../global/TaintTrackingEdges.expected | 166 +++++++++--------- .../global/TaintTrackingPath.expected | 30 ++-- 3 files changed, 99 insertions(+), 99 deletions(-) diff --git a/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll b/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll index d61cbd878a8..68dfc910911 100644 --- a/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll +++ b/csharp/ql/src/semmle/code/csharp/frameworks/JsonNET.qll @@ -50,7 +50,7 @@ module JsonNET { ) { // ToString methods c = getAToStringMethod() and - preservesValue = true and + preservesValue = false and source = any(CallableFlowSourceArg arg | arg.getArgumentIndex() = 0) and sink instanceof CallableFlowSinkReturn or diff --git a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected index acfcdbbf147..bd9d777b9dd 100644 --- a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected +++ b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingEdges.expected @@ -1381,7 +1381,7 @@ | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : Object | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1391,7 +1391,7 @@ | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1401,7 +1401,7 @@ | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : T | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1413,7 +1413,7 @@ | GlobalDataFlow.cs:78:41:78:45 | access to local variable sink3 : String | GlobalDataFlow.cs:287:50:287:50 | z | | GlobalDataFlow.cs:78:41:78:45 | access to local variable sink3 : String | GlobalDataFlow.cs:287:50:287:50 | z : String | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1421,7 +1421,7 @@ | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : Object | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1429,7 +1429,7 @@ | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : String | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 : T | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 | @@ -1439,7 +1439,7 @@ | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1449,7 +1449,7 @@ | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:13:80:85 | SSA def(sink13) : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1461,7 +1461,7 @@ | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1473,7 +1473,7 @@ | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1491,19 +1491,19 @@ | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | GlobalDataFlow.cs:80:23:80:65 | (...) ... | | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : Object | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : Object | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : String | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... | -| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:80:44:80:65 | array creation of type String[] : String[] | | GlobalDataFlow.cs:80:59:80:63 | access to local variable sink3 : T | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 | @@ -1521,7 +1521,7 @@ | GlobalDataFlow.cs:80:84:80:84 | access to parameter x : String | GlobalDataFlow.cs:431:44:431:47 | delegate call | | GlobalDataFlow.cs:80:84:80:84 | access to parameter x : String | GlobalDataFlow.cs:431:44:431:47 | delegate call : String | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1529,7 +1529,7 @@ | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 | @@ -1539,7 +1539,7 @@ | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1553,7 +1553,7 @@ | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1569,7 +1569,7 @@ | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1583,7 +1583,7 @@ | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:22:82:95 | call to method Select : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1605,19 +1605,19 @@ | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:82:23:82:74 | (...) ... | | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First | | GlobalDataFlow.cs:82:59:82:64 | access to local variable sink13 : IEnumerable | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:23:82:74 | (...) ... | -| GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] | | GlobalDataFlow.cs:82:59:82:72 | call to method First : String | GlobalDataFlow.cs:82:44:82:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:82:13:82:95 | SSA def(sink14) | @@ -1627,7 +1627,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1641,7 +1641,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : String | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1659,7 +1659,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1673,7 +1673,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1685,7 +1685,7 @@ | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 | @@ -1699,7 +1699,7 @@ | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1713,7 +1713,7 @@ | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1721,7 +1721,7 @@ | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1733,7 +1733,7 @@ | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1741,7 +1741,7 @@ | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:22:84:136 | call to method Zip : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1759,7 +1759,7 @@ | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:23:84:74 | (...) ... | | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:84:59:84:72 | call to method First | @@ -1771,7 +1771,7 @@ | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -1783,13 +1783,13 @@ | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 | | GlobalDataFlow.cs:84:59:84:64 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... | -| GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] | | GlobalDataFlow.cs:84:59:84:72 | call to method First : String | GlobalDataFlow.cs:84:44:84:74 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:82:84:121 | (...) ... | | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:84:82:84:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:82:84:121 | (...) ... | -| GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:82:84:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:82:84:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] | | GlobalDataFlow.cs:84:118:84:119 | "" : String | GlobalDataFlow.cs:84:103:84:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:84:13:84:136 | SSA def(sink15) | @@ -1799,7 +1799,7 @@ | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1807,7 +1807,7 @@ | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1835,7 +1835,7 @@ | GlobalDataFlow.cs:84:135:84:135 | access to parameter x : String | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... | | GlobalDataFlow.cs:84:135:84:135 | access to parameter x : String | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 | @@ -1843,7 +1843,7 @@ | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1859,7 +1859,7 @@ | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:23:86:62 | (...) ... | | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:23:86:62 | (...) ... : String[] | | GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:23:86:62 | (...) ... | -| GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:23:86:62 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:23:86:62 | (...) ... : String[] | | GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] | | GlobalDataFlow.cs:86:59:86:60 | "" : String | GlobalDataFlow.cs:86:44:86:62 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... | @@ -1873,13 +1873,13 @@ | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:70:86:121 | (...) ... | | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 | @@ -1887,7 +1887,7 @@ | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First | | GlobalDataFlow.cs:86:106:86:111 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... | -| GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] | | GlobalDataFlow.cs:86:106:86:119 | call to method First : String | GlobalDataFlow.cs:86:91:86:121 | array creation of type String[] : String[] | | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:13:86:136 | SSA def(sink16) | @@ -1927,7 +1927,7 @@ | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -2069,13 +2069,13 @@ | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:90:23:90:62 | (...) ... | | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] : String[] | GlobalDataFlow.cs:90:23:90:62 | (...) ... : String[] | | GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:23:90:62 | (...) ... | -| GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:23:90:62 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:23:90:62 | (...) ... : String[] | | GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] | | GlobalDataFlow.cs:90:59:90:60 | "" : String | GlobalDataFlow.cs:90:44:90:62 | array creation of type String[] : String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:90:75:90:80 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 | @@ -2365,13 +2365,13 @@ | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2383,13 +2383,13 @@ | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:108:27:108:34 | SSA def(nonSink0) : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2407,13 +2407,13 @@ | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2423,13 +2423,13 @@ | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:109:15:109:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2461,13 +2461,13 @@ | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2477,13 +2477,13 @@ | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2491,25 +2491,25 @@ | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:287:50:287:50 | z | | GlobalDataFlow.cs:110:41:110:48 | access to local variable nonSink0 : T | GlobalDataFlow.cs:287:50:287:50 | z : T | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:111:15:111:22 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2537,21 +2537,21 @@ | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | GlobalDataFlow.cs:112:25:112:70 | (...) ... | | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:25:112:70 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:112:46:112:70 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:112:61:112:68 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 | @@ -2585,11 +2585,11 @@ | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | GlobalDataFlow.cs:114:21:114:66 | (...) ... | | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : String | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... | -| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:21:114:66 | (...) ... : String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] | | GlobalDataFlow.cs:114:57:114:64 | access to local variable nonSink0 : T | GlobalDataFlow.cs:114:42:114:66 | array creation of type String[] : String[] | | GlobalDataFlow.cs:114:76:114:76 | x : IEnumerable | GlobalDataFlow.cs:114:76:114:76 | x | @@ -2627,7 +2627,7 @@ | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:21:116:72 | (...) ... | | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:116:57:116:70 | call to method First | @@ -2637,7 +2637,7 @@ | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 | | GlobalDataFlow.cs:116:57:116:62 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:122:20:122:25 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... | -| GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:21:116:72 | (...) ... : String[] | | GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] | | GlobalDataFlow.cs:116:57:116:70 | call to method First : String | GlobalDataFlow.cs:116:42:116:72 | array creation of type String[] : String[] | | GlobalDataFlow.cs:116:80:116:119 | (...) ... : IEnumerable | GlobalDataFlow.cs:116:123:116:133 | [output] (...) => ... | @@ -2651,7 +2651,7 @@ | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:80:116:119 | (...) ... | | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:116:80:116:119 | (...) ... : String[] | | GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:80:116:119 | (...) ... | -| GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:80:116:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:80:116:119 | (...) ... : String[] | | GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] | | GlobalDataFlow.cs:116:116:116:117 | "" : String | GlobalDataFlow.cs:116:101:116:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:116:123:116:133 | [output] (...) => ... : String | GlobalDataFlow.cs:116:9:116:134 | SSA def(nonSink1) | @@ -2701,19 +2701,19 @@ | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:21:118:60 | (...) ... | | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:21:118:60 | (...) ... : String[] | | GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:21:118:60 | (...) ... | -| GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:21:118:60 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:21:118:60 | (...) ... : String[] | | GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] | | GlobalDataFlow.cs:118:57:118:58 | "" : String | GlobalDataFlow.cs:118:42:118:60 | array creation of type String[] : String[] | | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:68:118:119 | (...) ... | | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First | | GlobalDataFlow.cs:118:104:118:109 | access to local variable sink15 : IEnumerable | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... | -| GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:68:118:119 | (...) ... : String[] | | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] | | GlobalDataFlow.cs:118:104:118:117 | call to method First : String | GlobalDataFlow.cs:118:89:118:119 | array creation of type String[] : String[] | | GlobalDataFlow.cs:118:123:118:133 | [output] (...) => ... : String | GlobalDataFlow.cs:118:9:118:134 | SSA def(nonSink1) | @@ -3833,17 +3833,17 @@ | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink | | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:19:222:39 | call to method Select | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : String | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:9:222:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:19:222:39 | call to method Select | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:222:19:222:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink | -| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:222:37:222:38 | [output] access to local variable f2 : T | GlobalDataFlow.cs:223:15:223:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:224:9:224:39 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:225:15:225:21 | access to local variable nonSink | | GlobalDataFlow.cs:224:9:224:39 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:225:15:225:21 | access to local variable nonSink : IEnumerable | | GlobalDataFlow.cs:224:19:224:28 | access to parameter notTainted : IQueryable | GlobalDataFlow.cs:218:35:218:46 | nonSinkParam | @@ -3879,17 +3879,17 @@ | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink | | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:19:226:39 | call to method Select | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : String | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:9:226:39 | SSA def(nonSink) : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:19:226:39 | call to method Select | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:226:19:226:39 | call to method Select : IQueryable | | GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink | -| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IEnumerable | +| GlobalDataFlow.cs:226:37:226:38 | [output] access to local variable f4 : T | GlobalDataFlow.cs:227:15:227:21 | access to local variable nonSink : IQueryable | | GlobalDataFlow.cs:228:9:228:49 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:229:15:229:21 | access to local variable nonSink | | GlobalDataFlow.cs:228:9:228:49 | SSA def(nonSink) : IEnumerable | GlobalDataFlow.cs:229:15:229:21 | access to local variable nonSink : IEnumerable | | GlobalDataFlow.cs:228:19:228:28 | access to parameter notTainted : IQueryable | GlobalDataFlow.cs:228:37:228:48 | [output] delegate creation of type Func | diff --git a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected index 59bb9b3d220..c612897d3ee 100644 --- a/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected +++ b/csharp/ql/test/library-tests/dataflow/global/TaintTrackingPath.expected @@ -137,21 +137,21 @@ edges | GlobalDataFlow.cs:75:30:75:34 | SSA def(sink2) : String | GlobalDataFlow.cs:78:19:78:23 | access to local variable sink2 : String | | GlobalDataFlow.cs:78:19:78:23 | access to local variable sink2 : String | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | -| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | GlobalDataFlow.cs:135:29:135:33 | access to local variable sink3 : String | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | -| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | -| GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | -| GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | -| GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : IEnumerable | +| GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | +| GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | +| GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | +| GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | -| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | GlobalDataFlow.cs:90:75:90:88 | call to method First : String | -| GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | +| GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | -| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | -| GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | +| GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | +| GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | GlobalDataFlow.cs:87:15:87:20 | access to local variable sink16 | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | GlobalDataFlow.cs:88:43:88:61 | [output] (...) => ... : String | | GlobalDataFlow.cs:88:43:88:61 | [output] (...) => ... : String | GlobalDataFlow.cs:88:64:88:69 | [output] (...) => ... : String | @@ -199,7 +199,7 @@ edges | GlobalDataFlow.cs:255:26:255:35 | sinkParam5 : String | GlobalDataFlow.cs:257:15:257:24 | access to parameter sinkParam5 | | GlobalDataFlow.cs:260:26:260:35 | sinkParam6 : String | GlobalDataFlow.cs:262:15:262:24 | access to parameter sinkParam6 | | GlobalDataFlow.cs:265:26:265:35 | sinkParam7 : String | GlobalDataFlow.cs:267:15:267:24 | access to parameter sinkParam7 | -| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : IEnumerable | GlobalDataFlow.cs:294:15:294:24 | access to parameter sinkParam8 | +| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : String[] | GlobalDataFlow.cs:294:15:294:24 | access to parameter sinkParam8 | | GlobalDataFlow.cs:298:32:298:41 | sinkParam9 : String | GlobalDataFlow.cs:300:15:300:24 | access to parameter sinkParam9 | | GlobalDataFlow.cs:304:32:304:42 | sinkParam11 : IQueryable | GlobalDataFlow.cs:306:15:306:25 | access to parameter sinkParam11 | | GlobalDataFlow.cs:318:16:318:29 | "taint source" : String | GlobalDataFlow.cs:153:21:153:25 | call to method Out : String | @@ -331,15 +331,15 @@ nodes | GlobalDataFlow.cs:78:30:78:34 | SSA def(sink3) : String | semmle.label | SSA def(sink3) : String | | GlobalDataFlow.cs:79:15:79:19 | access to local variable sink3 | semmle.label | access to local variable sink3 | | GlobalDataFlow.cs:80:22:80:85 | call to method SelectEven : IEnumerable | semmle.label | call to method SelectEven : IEnumerable | -| GlobalDataFlow.cs:80:23:80:65 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:80:23:80:65 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:81:15:81:20 | access to local variable sink13 | semmle.label | access to local variable sink13 | -| GlobalDataFlow.cs:82:23:82:74 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:82:23:82:74 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:82:84:82:94 | [output] delegate creation of type Func : T | semmle.label | [output] delegate creation of type Func : T | | GlobalDataFlow.cs:83:15:83:20 | access to local variable sink14 | semmle.label | access to local variable sink14 | -| GlobalDataFlow.cs:84:23:84:74 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:84:23:84:74 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:84:125:84:135 | [output] (...) => ... : String | semmle.label | [output] (...) => ... : String | | GlobalDataFlow.cs:85:15:85:20 | access to local variable sink15 | semmle.label | access to local variable sink15 | -| GlobalDataFlow.cs:86:70:86:121 | (...) ... : IEnumerable | semmle.label | (...) ... : IEnumerable | +| GlobalDataFlow.cs:86:70:86:121 | (...) ... : String[] | semmle.label | (...) ... : String[] | | GlobalDataFlow.cs:86:125:86:135 | [output] (...) => ... : String | semmle.label | [output] (...) => ... : String | | GlobalDataFlow.cs:87:15:87:20 | access to local variable sink16 | semmle.label | access to local variable sink16 | | GlobalDataFlow.cs:88:22:88:27 | access to local variable sink14 : IEnumerable | semmle.label | access to local variable sink14 : IEnumerable | @@ -404,7 +404,7 @@ nodes | GlobalDataFlow.cs:262:15:262:24 | access to parameter sinkParam6 | semmle.label | access to parameter sinkParam6 | | GlobalDataFlow.cs:265:26:265:35 | sinkParam7 : String | semmle.label | sinkParam7 : String | | GlobalDataFlow.cs:267:15:267:24 | access to parameter sinkParam7 | semmle.label | access to parameter sinkParam7 | -| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : IEnumerable | semmle.label | sinkParam8 : IEnumerable | +| GlobalDataFlow.cs:292:31:292:40 | sinkParam8 : String[] | semmle.label | sinkParam8 : String[] | | GlobalDataFlow.cs:294:15:294:24 | access to parameter sinkParam8 | semmle.label | access to parameter sinkParam8 | | GlobalDataFlow.cs:298:32:298:41 | sinkParam9 : String | semmle.label | sinkParam9 : String | | GlobalDataFlow.cs:300:15:300:24 | access to parameter sinkParam9 | semmle.label | access to parameter sinkParam9 | From 5439059b0daa62364b3b695bf89510999f3f50fc Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 10 Mar 2020 14:00:30 +0100 Subject: [PATCH 235/236] Python: Minor cleanup in regex.qll --- python/ql/src/semmle/python/regex.qll | 28 +++++++++++++-------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/python/ql/src/semmle/python/regex.qll b/python/ql/src/semmle/python/regex.qll index 83f3251ff80..2e83d8fca35 100644 --- a/python/ql/src/semmle/python/regex.qll +++ b/python/ql/src/semmle/python/regex.qll @@ -19,22 +19,20 @@ private predicate re_module_function(string name, int flags) { predicate used_as_regex(Expr s, string mode) { (s instanceof Bytes or s instanceof Unicode) and - exists(ModuleValue re | re = Module::named("re") | - /* Call to re.xxx(regex, ... [mode]) */ - exists(CallNode call, string name | - call.getArg(0).refersTo(_, _, s.getAFlowNode()) and - call.getFunction().pointsTo(re.attr(name)) | - mode = "None" - or - exists(Value obj | - mode = mode_from_mode_object(obj) | - exists(int flags_arg | - re_module_function(name, flags_arg) and - call.getArg(flags_arg).pointsTo(obj) - ) - or - call.getArgByName("flags").pointsTo(obj) + /* Call to re.xxx(regex, ... [mode]) */ + exists(CallNode call, string name | + call.getArg(0).refersTo(_, _, s.getAFlowNode()) and + call.getFunction().pointsTo(Module::named("re").attr(name)) | + mode = "None" + or + exists(Value obj | + mode = mode_from_mode_object(obj) | + exists(int flags_arg | + re_module_function(name, flags_arg) and + call.getArg(flags_arg).pointsTo(obj) ) + or + call.getArgByName("flags").pointsTo(obj) ) ) } From 2382b42bbe632cdac4395077533c5f8b014a96ee Mon Sep 17 00:00:00 2001 From: Rasmus Wriedt Larsen Date: Tue, 10 Mar 2020 14:47:49 +0100 Subject: [PATCH 236/236] Python: Rewrite helper predicate has_string_type --- python/ql/src/Statements/IterableStringOrSequence.ql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/ql/src/Statements/IterableStringOrSequence.ql b/python/ql/src/Statements/IterableStringOrSequence.ql index 7602f98bce7..76d601dc600 100644 --- a/python/ql/src/Statements/IterableStringOrSequence.ql +++ b/python/ql/src/Statements/IterableStringOrSequence.ql @@ -14,9 +14,9 @@ import python predicate has_string_type(Value v) { - v.getClass() = ClassValue::bytes() and major_version() = 2 + v.getClass() = ClassValue::str() or - v.getClass() = ClassValue::unicode() + v.getClass() = ClassValue::unicode() and major_version() = 2 } from