|
|
17aa40 |
From cecb3cc06f6025835324c1837c03def1d9be8eb1 Mon Sep 17 00:00:00 2001
|
|
|
17aa40 |
From: Frantisek Sumsal <frantisek@sumsal.cz>
|
|
|
17aa40 |
Date: Wed, 1 Dec 2021 21:31:43 +0100
|
|
|
17aa40 |
Subject: [PATCH] lgtm: detect uninitialized variables using the __cleanup__
|
|
|
17aa40 |
attribute
|
|
|
17aa40 |
|
|
|
17aa40 |
This is a slightly modified version of the original
|
|
|
17aa40 |
`cpp/uninitialized-local` CodeQL query which focuses only on variables
|
|
|
17aa40 |
using the cleanup macros. Since this has proven to cause issues in the
|
|
|
17aa40 |
past, let's panic on every uninitialized variable using any of the
|
|
|
17aa40 |
cleanup macros (as long as they're written using the __cleanup__
|
|
|
17aa40 |
attribute).
|
|
|
17aa40 |
|
|
|
17aa40 |
Some test results from a test I used when writing the query:
|
|
|
17aa40 |
|
|
|
17aa40 |
```
|
|
|
17aa40 |
#define _cleanup_foo_ __attribute__((__cleanup__(foo)))
|
|
|
17aa40 |
#define _cleanup_(x) __attribute__((__cleanup__(x)))
|
|
|
17aa40 |
|
|
|
17aa40 |
static inline void freep(void *p) {
|
|
|
17aa40 |
*(void**)p = mfree(*(void**) p);
|
|
|
17aa40 |
}
|
|
|
17aa40 |
|
|
|
17aa40 |
#define _cleanup_free_ _cleanup_(freep)
|
|
|
17aa40 |
|
|
|
17aa40 |
static inline void foo(char **p) {
|
|
|
17aa40 |
if (*p)
|
|
|
17aa40 |
*p = free(*p);
|
|
|
17aa40 |
}
|
|
|
17aa40 |
|
|
|
17aa40 |
int main(void) {
|
|
|
17aa40 |
__attribute__((__cleanup__(foo))) char *a;
|
|
|
17aa40 |
char *b;
|
|
|
17aa40 |
_cleanup_foo_ char *c;
|
|
|
17aa40 |
char **d;
|
|
|
17aa40 |
_cleanup_free_ char *e;
|
|
|
17aa40 |
int r;
|
|
|
17aa40 |
|
|
|
17aa40 |
r = fun(&e);
|
|
|
17aa40 |
if (r < 0)
|
|
|
17aa40 |
return 1;
|
|
|
17aa40 |
|
|
|
17aa40 |
puts(a);
|
|
|
17aa40 |
puts(b);
|
|
|
17aa40 |
puts(c);
|
|
|
17aa40 |
puts(*d);
|
|
|
17aa40 |
puts(e);
|
|
|
17aa40 |
|
|
|
17aa40 |
return 0;
|
|
|
17aa40 |
}
|
|
|
17aa40 |
```
|
|
|
17aa40 |
|
|
|
17aa40 |
```
|
|
|
17aa40 |
+| test.c:23:14:23:14 | e | The variable $@ may not be initialized here, but has a cleanup handler. | test.c:20:26:20:26 | e | e |
|
|
|
17aa40 |
+| test.c:27:10:27:10 | a | The variable $@ may not be initialized here, but has a cleanup handler. | test.c:16:45:16:45 | a | a |
|
|
|
17aa40 |
+| test.c:29:10:29:10 | c | The variable $@ may not be initialized here, but has a cleanup handler. | test.c:18:25:18:25 | c | c |
|
|
|
17aa40 |
```
|
|
|
17aa40 |
|
|
|
17aa40 |
(cherry picked from commit 863bff75488d33f519deea6390988f3d9d72e6de)
|
|
|
17aa40 |
|
|
|
17aa40 |
Related: #2017033
|
|
|
17aa40 |
---
|
|
|
17aa40 |
.../UninitializedVariableWithCleanup.ql | 99 +++++++++++++++++++
|
|
|
17aa40 |
1 file changed, 99 insertions(+)
|
|
|
17aa40 |
create mode 100644 .lgtm/cpp-queries/UninitializedVariableWithCleanup.ql
|
|
|
17aa40 |
|
|
|
17aa40 |
diff --git a/.lgtm/cpp-queries/UninitializedVariableWithCleanup.ql b/.lgtm/cpp-queries/UninitializedVariableWithCleanup.ql
|
|
|
17aa40 |
new file mode 100644
|
|
|
17aa40 |
index 0000000000..6bf0ae01eb
|
|
|
17aa40 |
--- /dev/null
|
|
|
17aa40 |
+++ b/.lgtm/cpp-queries/UninitializedVariableWithCleanup.ql
|
|
|
17aa40 |
@@ -0,0 +1,99 @@
|
|
|
17aa40 |
+/**
|
|
|
17aa40 |
+ * vi: sw=2 ts=2 et syntax=ql:
|
|
|
17aa40 |
+ *
|
|
|
17aa40 |
+ * Based on cpp/uninitialized-local.
|
|
|
17aa40 |
+ *
|
|
|
17aa40 |
+ * @name Potentially uninitialized local variable using the cleanup attribute
|
|
|
17aa40 |
+ * @description Running the cleanup handler on a possibly uninitialized variable
|
|
|
17aa40 |
+ * is generally a bad idea.
|
|
|
17aa40 |
+ * @id cpp/uninitialized-local-with-cleanup
|
|
|
17aa40 |
+ * @kind problem
|
|
|
17aa40 |
+ * @problem.severity error
|
|
|
17aa40 |
+ * @precision high
|
|
|
17aa40 |
+ * @tags security
|
|
|
17aa40 |
+ */
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+import cpp
|
|
|
17aa40 |
+import semmle.code.cpp.controlflow.StackVariableReachability
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+/**
|
|
|
17aa40 |
+ * Auxiliary predicate: Types that don't require initialization
|
|
|
17aa40 |
+ * before they are used, since they're stack-allocated.
|
|
|
17aa40 |
+ */
|
|
|
17aa40 |
+predicate allocatedType(Type t) {
|
|
|
17aa40 |
+ /* Arrays: "int foo[1]; foo[0] = 42;" is ok. */
|
|
|
17aa40 |
+ t instanceof ArrayType
|
|
|
17aa40 |
+ or
|
|
|
17aa40 |
+ /* Structs: "struct foo bar; bar.baz = 42" is ok. */
|
|
|
17aa40 |
+ t instanceof Class
|
|
|
17aa40 |
+ or
|
|
|
17aa40 |
+ /* Typedefs to other allocated types are fine. */
|
|
|
17aa40 |
+ allocatedType(t.(TypedefType).getUnderlyingType())
|
|
|
17aa40 |
+ or
|
|
|
17aa40 |
+ /* Type specifiers don't affect whether or not a type is allocated. */
|
|
|
17aa40 |
+ allocatedType(t.getUnspecifiedType())
|
|
|
17aa40 |
+}
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+/**
|
|
|
17aa40 |
+ * A declaration of a local variable using __attribute__((__cleanup__(x)))
|
|
|
17aa40 |
+ * that leaves the variable uninitialized.
|
|
|
17aa40 |
+ */
|
|
|
17aa40 |
+DeclStmt declWithNoInit(LocalVariable v) {
|
|
|
17aa40 |
+ result.getADeclaration() = v and
|
|
|
17aa40 |
+ not exists(v.getInitializer()) and
|
|
|
17aa40 |
+ /* The variable has __attribute__((__cleanup__(...))) set */
|
|
|
17aa40 |
+ v.getAnAttribute().hasName("cleanup") and
|
|
|
17aa40 |
+ /* The type of the variable is not stack-allocated. */
|
|
|
17aa40 |
+ exists(Type t | t = v.getType() | not allocatedType(t))
|
|
|
17aa40 |
+}
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+class UninitialisedLocalReachability extends StackVariableReachability {
|
|
|
17aa40 |
+ UninitialisedLocalReachability() { this = "UninitialisedLocal" }
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+ override predicate isSource(ControlFlowNode node, StackVariable v) { node = declWithNoInit(v) }
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+ /* Note: _don't_ use the `useOfVarActual()` predicate here (and a couple of lines
|
|
|
17aa40 |
+ * below), as it assumes that the callee always modifies the variable if
|
|
|
17aa40 |
+ * it's passed to the function.
|
|
|
17aa40 |
+ *
|
|
|
17aa40 |
+ * i.e.:
|
|
|
17aa40 |
+ * _cleanup_free char *x;
|
|
|
17aa40 |
+ * fun(&x);
|
|
|
17aa40 |
+ * puts(x);
|
|
|
17aa40 |
+ *
|
|
|
17aa40 |
+ * `useOfVarActual()` won't treat this an an uninitialized read even if the callee
|
|
|
17aa40 |
+ * doesn't modify the argument, however, `useOfVar()` will
|
|
|
17aa40 |
+ */
|
|
|
17aa40 |
+ override predicate isSink(ControlFlowNode node, StackVariable v) { useOfVar(v, node) }
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+ override predicate isBarrier(ControlFlowNode node, StackVariable v) {
|
|
|
17aa40 |
+ // only report the _first_ possibly uninitialized use
|
|
|
17aa40 |
+ useOfVar(v, node) or
|
|
|
17aa40 |
+ definitionBarrier(v, node)
|
|
|
17aa40 |
+ }
|
|
|
17aa40 |
+}
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+pragma[noinline]
|
|
|
17aa40 |
+predicate containsInlineAssembly(Function f) { exists(AsmStmt s | s.getEnclosingFunction() = f) }
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+/**
|
|
|
17aa40 |
+ * Auxiliary predicate: List common exceptions or false positives
|
|
|
17aa40 |
+ * for this check to exclude them.
|
|
|
17aa40 |
+ */
|
|
|
17aa40 |
+VariableAccess commonException() {
|
|
|
17aa40 |
+ // If the uninitialized use we've found is in a macro expansion, it's
|
|
|
17aa40 |
+ // typically something like va_start(), and we don't want to complain.
|
|
|
17aa40 |
+ result.getParent().isInMacroExpansion()
|
|
|
17aa40 |
+ or
|
|
|
17aa40 |
+ result.getParent() instanceof BuiltInOperation
|
|
|
17aa40 |
+ or
|
|
|
17aa40 |
+ // Finally, exclude functions that contain assembly blocks. It's
|
|
|
17aa40 |
+ // anyone's guess what happens in those.
|
|
|
17aa40 |
+ containsInlineAssembly(result.getEnclosingFunction())
|
|
|
17aa40 |
+}
|
|
|
17aa40 |
+
|
|
|
17aa40 |
+from UninitialisedLocalReachability r, LocalVariable v, VariableAccess va
|
|
|
17aa40 |
+where
|
|
|
17aa40 |
+ r.reaches(_, v, va) and
|
|
|
17aa40 |
+ not va = commonException()
|
|
|
17aa40 |
+select va, "The variable $@ may not be initialized here, but has a cleanup handler.", v, v.getName()
|