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