diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.qhelp b/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.qhelp index d0d888a9aac7..0c1e345c04fa 100644 --- a/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.qhelp +++ b/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.qhelp @@ -5,18 +5,25 @@ -

A function is called with arguments despite having an empty parameter list. This may indicate -that the incorrect function is being called, or that the author misunderstood the function.

+

A function is called with arguments despite having been declared with an empty parameter list. +In addition, we were either unable to find a definition of the function, or found one with +an incompatible number of parameters.

+ +

This may indicate that an incorrect function is being called, or that the signature (parameter list) +of the called function is not known to the author.

In C, a function declared with an empty parameter list () is considered to have an unknown parameter list, and therefore can be called with any set of arguments. To declare a function which takes no arguments, you must use (void) as the parameter list in any forward declarations. -In C++, either style of declaration indicates that the function accepts no arguments.

+In C++, () means the same as (void), i.e., indicates that the function accepts no +arguments.

Call the function without arguments, or call a different function that expects the arguments -being passed.

+being passed. If possible, change the forward declaration of the ()-function to reflect the +arguments being passed, or change the () to (void) to indicate that no arguments +shall be passed.

diff --git a/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.ql b/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.ql index bb5a99a69bc9..c21afaf4ac9a 100644 --- a/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.ql +++ b/cpp/ql/src/Likely Bugs/Likely Typos/FutileParams.ql @@ -12,11 +12,16 @@ import cpp -from Function f, FunctionCall fc -where fc.getTarget() = f - and f.getNumberOfParameters() = 0 - and not f.isVarargs() - and fc.getNumberOfArguments() != 0 - and not f instanceof BuiltInFunction - and exists(FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() | not fde.isImplicit()) -select fc, "This call has arguments, but $@ is not declared with any parameters.", f, f.toString() +from FunctionCall fc, Function f + where f = fc.getTarget() and not f.isVarargs() + /* There must be a zero-parameter declaration */ + and exists ( FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() | fde.getNumberOfParameters() = 0) + /* There must be a mismatch between number of call arguments and number of parameters in some + * declaration of Function f + */ + and exists ( FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() | fde.getNumberOfParameters() != fc.getNumberOfArguments()) + /* There must be no actual declaration of Function f whose number of parameters matches number of call arguments */ + and not exists ( FunctionDeclarationEntry fde | fde = f.getADeclarationEntry() | not fde.isImplicit() and fde.getNumberOfParameters() = fc.getNumberOfArguments()) +select fc, "This call has arguments, but $@ is not declared with any parameters.", f, f.toString() + + \ No newline at end of file diff --git a/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql b/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql index e4a3163d4d1a..635a1013b716 100644 --- a/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql +++ b/cpp/ql/src/Likely Bugs/Memory Management/AllocaInLoop.ql @@ -8,6 +8,7 @@ * correctness * external/cwe/cwe-770 */ + import cpp Loop getAnEnclosingLoopOfExpr(Expr e) { @@ -21,7 +22,15 @@ Loop getAnEnclosingLoopOfStmt(Stmt s) { } from Loop l, FunctionCall fc -where getAnEnclosingLoopOfExpr(fc) = l - and fc.getTarget().getName() = "__builtin_alloca" - and not l.(DoStmt).getCondition().getValue() = "0" -select fc, "Stack allocation is inside a $@ and could lead to overflow.", l, l.toString() +where + getAnEnclosingLoopOfExpr(fc) = l and + ( + fc.getTarget().getName() = "__builtin_alloca" + or + ( + (fc.getTarget().getName() = "_alloca" or fc.getTarget().getName() = "_malloca") and + fc.getTarget().getADeclarationEntry().getFile().getBaseName() = "malloc.h" + ) + ) and + not l.(DoStmt).getCondition().getValue() = "0" +select fc, "Stack allocation is inside a $@ and could lead to stack overflow.", l, l.toString() diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/FutileParams.expected b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/FutileParams.expected index d6dc2222be42..0021164c8614 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/FutileParams.expected +++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/FutileParams.expected @@ -1,3 +1,6 @@ -| test.c:8:3:8:16 | call to declared_empty | This call has arguments, but $@ is not declared with any parameters. | test.c:1:6:1:19 | declared_empty | declared_empty | +| test.c:7:3:7:16 | call to declared_empty | This call has arguments, but $@ is not declared with any parameters. | test.c:1:6:1:19 | declared_empty | declared_empty | +| test.c:12:3:12:12 | call to undeclared | This call has arguments, but $@ is not declared with any parameters. | test.c:11:3:11:3 | undeclared | undeclared | | test.c:14:3:14:19 | call to not_yet_declared1 | This call has arguments, but $@ is not declared with any parameters. | test.c:14:3:14:3 | not_yet_declared1 | not_yet_declared1 | | test.c:14:3:14:19 | call to not_yet_declared1 | This call has arguments, but $@ is not declared with any parameters. | test.c:25:6:25:22 | not_yet_declared1 | not_yet_declared1 | +| test.c:17:3:17:29 | call to declared_empty_defined_with | This call has arguments, but $@ is not declared with any parameters. | test.c:27:6:27:32 | declared_empty_defined_with | declared_empty_defined_with | +| test.c:22:3:22:29 | call to declared_empty_defined_with | This call has arguments, but $@ is not declared with any parameters. | test.c:27:6:27:32 | declared_empty_defined_with | declared_empty_defined_with | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/test.c b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/test.c index 9589d2a9ffc9..698644c5c1cf 100644 --- a/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/test.c +++ b/cpp/ql/test/query-tests/Likely Bugs/Likely Typos/FutileParams/test.c @@ -1,7 +1,6 @@ void declared_empty(); void declared_void(void); void declared_with(int); -void declared_empty_defined_with(); void test() { declared_empty(); // GOOD @@ -9,17 +8,18 @@ void test() { declared_void(); // GOOD declared_with(1); // GOOD - undeclared(1); // GOOD + undeclared(); // GOOD + undeclared(1); // BAD not_yet_declared1(1); // BAD not_yet_declared2(1); // GOOD - declared_empty_defined_with(); // BAD [NOT DETECTED] + declared_empty_defined_with(); // BAD declared_empty_defined_with(1); // GOOD int x; - declared_empty_defined_with(&x); // BAD [NOT DETECTED] - declared_empty_defined_with(x, x); // BAD [NOT DETECTED] + declared_empty_defined_with(&x); // GOOD + declared_empty_defined_with(x, x); // BAD } void not_yet_declared1(); diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop.expected b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop.expected new file mode 100644 index 000000000000..df4910ac5aaf --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop.expected @@ -0,0 +1,8 @@ +| AllocaInLoop1.cpp:31:18:31:23 | call to __builtin_alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop1.cpp:22:2:39:2 | for(...;...;...) ... | for(...;...;...) ... | +| AllocaInLoop1.cpp:55:19:55:24 | call to __builtin_alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop1.cpp:45:2:64:2 | for(...;...;...) ... | for(...;...;...) ... | +| AllocaInLoop1.cpp:80:19:80:24 | call to __builtin_alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop1.cpp:71:3:88:3 | for(...;...;...) ... | for(...;...;...) ... | +| AllocaInLoop1ms.cpp:28:18:28:24 | call to _alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop1ms.cpp:19:2:36:2 | for(...;...;...) ... | for(...;...;...) ... | +| AllocaInLoop1ms.cpp:52:19:52:26 | call to _malloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop1ms.cpp:42:2:63:2 | for(...;...;...) ... | for(...;...;...) ... | +| AllocaInLoop1ms.cpp:79:19:79:25 | call to _alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop1ms.cpp:70:3:87:3 | for(...;...;...) ... | for(...;...;...) ... | +| AllocaInLoop2.c:39:30:39:35 | call to __builtin_alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop2.c:29:5:48:19 | do (...) ... | do (...) ... | +| AllocaInLoop3.cpp:45:23:45:28 | call to __builtin_alloca | Stack allocation is inside a $@ and could lead to stack overflow. | AllocaInLoop3.cpp:43:2:49:19 | do (...) ... | do (...) ... | diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop.qlref b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop.qlref new file mode 100644 index 000000000000..d5227c40ee4c --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop.qlref @@ -0,0 +1 @@ +Likely Bugs/Memory Management/AllocaInLoop.ql diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop1.cpp b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop1.cpp new file mode 100644 index 000000000000..c3866a9c649b --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop1.cpp @@ -0,0 +1,90 @@ +// semmle-extractor-options: --clang +struct vtype { + int i1, i2; +}; +extern int w1, w2; + +#ifdef _MSC_VER +#define restrict __restrict +#else +#define restrict __restrict__ +#endif + +void *__builtin_alloca(int sz); +#define alloca __builtin_alloca +typedef unsigned long size_t; + +int printf(const char *restrict format, ...); +void *memcpy(void *restrict s1, const void *restrict s2, size_t n); + +// case 1: alloca directly contained in an unbounded loop +void foo(const struct vtype* vec, int count) { + for (int i = 0; i < count; i++) { + const vtype* v = vec + i; + char *b1 = 0; + if (b1 == nullptr) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = new char[w1]; + } else { + // Allocate the buffer on stack + b1 = (char*) alloca(w1); // BAD + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + delete b1; + } + } +} + +// case 2: alloca contained in a do-while(0) that is in turn contained +// in an unbounded loop +void bar(const struct vtype* vec, int count) { + for (int i = 0; i < count; i++) { + const vtype* v = vec + i; + char *b1 = 0; + do { + if (b1 == nullptr) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = new char[w1]; + } else { + // Allocate the buffer on stack + b1 = (char*) alloca(w1); // BAD + } + } + } while (0); + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + delete b1; + } + } +} + +// case 3: alloca contained in an unbounded loop that is in turn contained +// in a do-while(0) +void baz(const struct vtype* vec, int count) { + do { + for (int i = 0; i < count; i++) { + const vtype* v = vec + i; + char *b1 = 0; + if (b1 == nullptr) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = new char[w1]; + } else { + // Allocate the buffer on stack + b1 = (char*) alloca(w1); // BAD + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + delete b1; + } + } + } while (0); +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop1ms.cpp b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop1ms.cpp new file mode 100644 index 000000000000..9ebf4f17ba16 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop1ms.cpp @@ -0,0 +1,89 @@ +// semmle-extractor-options: --clang +#include "malloc.h" +struct vtype { + int i1, i2; +}; +extern int w1, w2; + +#ifdef _MSC_VER +#define restrict __restrict +#else +#define restrict __restrict__ +#endif + +int printf(const char *restrict format, ...); +void *memcpy(void *restrict s1, const void *restrict s2, size_t n); + +// case 1: _alloca directly contained in an unbounded loop +void foo(const struct vtype* vec, int count) { + for (int i = 0; i < count; i++) { + const vtype* v = vec + i; + char *b1 = 0; + if (b1 == nullptr) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = new char[w1]; + } else { + // Allocate the buffer on stack + b1 = (char*) _alloca(w1); // BAD + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + delete b1; + } + } +} + +// case 2: _malloca contained in a do-while(0) that is in turn contained +// in an unbounded loop +void bar(const struct vtype* vec, int count) { + for (int i = 0; i < count; i++) { + const vtype* v = vec + i; + char *b1 = 0; + do { + if (b1 == nullptr) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = new char[w1]; + } else { + // Allocate the buffer on stack + b1 = (char*) _malloca(w1); // BAD + } + } + } while (0); + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + delete b1; + } else { + _freea(b1); + } + } +} + +// case 3: _alloca contained in an unbounded loop that is in turn contained +// in a do-while(0) +void baz(const struct vtype* vec, int count) { + do { + for (int i = 0; i < count; i++) { + const vtype* v = vec + i; + char *b1 = 0; + if (b1 == nullptr) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = new char[w1]; + } else { + // Allocate the buffer on stack + b1 = (char*) _alloca(w1); // BAD + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + delete b1; + } + } + } while (0); +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop2.c b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop2.c new file mode 100644 index 000000000000..60a4986441dc --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop2.c @@ -0,0 +1,158 @@ +// semmle-extractor-options: --clang +int printf(const char *restrict format, ...); +int sprintf(char *restrict buf, const char *restrict format, ...); +typedef unsigned long size_t; +void *memcpy(void *restrict s1, const void *restrict s2, size_t n); +void *malloc(size_t sz); +void free(void *ptr); + +struct vtype { int i1, i2; }; +extern int w1, w2; + +void *_builtin_alloca(int sz); +#define alloca __builtin_alloca + +// We forward-declare the Microsoft routines +//_alloca and _malloca here. Since they do not +// originate from the header, they +// should not be flagged by our queries +void *_alloca(size_t sz); +void *_malloca(size_t sz); +void _freea(void *ptr); + +#define NULL (void *)0 + +// case 1: alloca called within a provably infinite loop +void foo(const struct vtype* vec, int count) { + char iter; + + do { + const struct vtype* v = vec + 2; + char *b1 = 0; + iter = 0; + if (b1 == NULL) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = (char *)malloc(w1); + } else { + // Allocate the buffer on stack + b1 = (char*) alloca(w1); // BAD + iter = 1; + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + free(b1); + } + } while (iter); +} + +// case 2: alloca called within nested do-while(0) loops +void bar(const struct vtype* vec, int count) { + + do { + const struct vtype* v = vec + 2; + char *b1 = 0; + do { + if (b1 == NULL) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = (char *)malloc(w1); + } else { + // Allocate the buffer on stack + b1 = (char*) alloca(w1); // GOOD + } + } + } while (0); + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + free(b1); + } + } while (0); +} + +// case 3: alloca called outside any loops +void baz(int count) { + + char *buf = (char *)alloca(32); // GOOD + sprintf(buf, "Value is %d\n", count); + printf("%s", buf); +} + +////// Negative Microsoft test cases + +// case 4: _alloca directly contained in an unbounded loop +void foo_ms(const struct vtype* vec, int count) { + for (int i = 0; i < count; i++) { + const struct vtype* v = vec + i; + char *b1 = 0; + if (b1 == NULL) { + if (w1 > w2) { + // Allocate the buffer on heap + (char *)malloc(w1); + } else { + // Allocate the buffer on stack + b1 = (char*) _alloca(w1); // GOOD + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + free(b1); + } + } +} + +// case 5: _malloca contained in a do-while(0) that is in turn contained +// in an unbounded loop +void bar_ms(const struct vtype* vec, int count) { + for (int i = 0; i < count; i++) { + const struct vtype* v = vec + i; + char *b1 = 0; + do { + if (b1 == NULL) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = (char *)malloc(w1); + } else { + // Allocate the buffer on stack + b1 = (char*) _malloca(w1); // GOOD + } + } + } while (0); + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + free(b1); + } else { + _freea(b1); + } + } +} + +// case 6: _alloca contained in an unbounded loop that is in turn contained +// in a do-while(0) +void baz_ms(const struct vtype* vec, int count) { + do { + for (int i = 0; i < count; i++) { + const struct vtype* v = vec + i; + char *b1 = 0; + if (b1 == NULL) { + if (w1 > w2) { + // Allocate the buffer on heap + b1 = (char *)malloc(w1); + } else { + // Allocate the buffer on stack + b1 = (char*) _alloca(w1); // GOOD + } + } + memcpy(b1, v, w1); + printf("%s\n", b1); + if (w1 > w2) { + free(b1); + } + } + } while (0); +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop3.cpp b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop3.cpp new file mode 100644 index 000000000000..8a14c58355c0 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/AllocaInLoop3.cpp @@ -0,0 +1,51 @@ +// semmle-extractor-options: --clang + +#ifdef _MSC_VER +#define restrict __restrict +#else +#define restrict __restrict__ +#endif + +int sprintf(char *restrict buf, const char *restrict format, ...); +char * strdup(const char *restrict str1); + +void *__builtin_alloca(int sz); +#define alloca __builtin_alloca + +// case 1: a GNU c/c++ lambda with an alloca in it +char *foo(int count) { + char *buf = ({ + char *b = (char *)alloca(32); // GOOD + sprintf(b, "Value is %d\n", count); + b; + }); + return strdup(buf); +} + +// case 1: a GNU expression statement with an alloca in it +// nested inside a do-while(0) +char *bar(int count) { + char *buf; + do { + buf = ({ + char *b = (char *)alloca(32); // GOOD + sprintf(b, "Value is %d\n", count); + b; + }); + } while (0); + return strdup(buf); +} + +// case 2: a GNU expression statement with an alloca in it +// nested inside an unbounded loop +char *baz(int count) { + char *buf; + do { + buf = ({ + char *b = (char *)alloca(32); // BAD + sprintf(b, "Value is %d\n", count); + b; + }); + } while (count++); + return strdup(buf); +} diff --git a/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/malloc.h b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/malloc.h new file mode 100644 index 000000000000..24344f4719f1 --- /dev/null +++ b/cpp/ql/test/query-tests/Likely Bugs/Memory Management/AllocaInLoop/malloc.h @@ -0,0 +1,7 @@ +typedef unsigned long size_t; +// These are Microsoft routines for stack allocation +// but should only be treated as such if they are declared +// in the header (i.e., this file) +void *_alloca(size_t sz); +void *_malloca(size_t sz); +void _freea(void *ptr);