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()