2021-12-02 04:31:43 +08:00
|
|
|
/**
|
|
|
|
* vi: sw=2 ts=2 et syntax=ql:
|
|
|
|
*
|
|
|
|
* Based on cpp/uninitialized-local.
|
|
|
|
*
|
|
|
|
* @name Potentially uninitialized local variable using the cleanup attribute
|
|
|
|
* @description Running the cleanup handler on a possibly uninitialized variable
|
|
|
|
* is generally a bad idea.
|
|
|
|
* @id cpp/uninitialized-local-with-cleanup
|
|
|
|
* @kind problem
|
|
|
|
* @problem.severity error
|
|
|
|
* @precision high
|
|
|
|
* @tags security
|
|
|
|
*/
|
|
|
|
|
|
|
|
import cpp
|
|
|
|
import semmle.code.cpp.controlflow.StackVariableReachability
|
|
|
|
|
2021-12-05 17:25:28 +08:00
|
|
|
/** Auxiliary predicate: List cleanup functions we want to explicitly ignore
|
|
|
|
* since they don't do anything illegal even when the variable is uninitialized
|
|
|
|
*/
|
|
|
|
predicate cleanupFunctionDenyList(string fun) {
|
2023-01-08 05:16:52 +08:00
|
|
|
fun = "erase_char"
|
2021-12-05 17:25:28 +08:00
|
|
|
}
|
|
|
|
|
2021-12-02 04:31:43 +08:00
|
|
|
/**
|
|
|
|
* A declaration of a local variable using __attribute__((__cleanup__(x)))
|
|
|
|
* that leaves the variable uninitialized.
|
|
|
|
*/
|
|
|
|
DeclStmt declWithNoInit(LocalVariable v) {
|
|
|
|
result.getADeclaration() = v and
|
2021-12-05 23:11:35 +08:00
|
|
|
not v.hasInitializer() and
|
2021-12-02 04:31:43 +08:00
|
|
|
/* The variable has __attribute__((__cleanup__(...))) set */
|
|
|
|
v.getAnAttribute().hasName("cleanup") and
|
2021-12-05 17:25:28 +08:00
|
|
|
/* Check if the cleanup function is not on a deny list */
|
2021-12-05 23:11:35 +08:00
|
|
|
not cleanupFunctionDenyList(v.getAnAttribute().getAnArgument().getValueText())
|
2021-12-02 04:31:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
class UninitialisedLocalReachability extends StackVariableReachability {
|
|
|
|
UninitialisedLocalReachability() { this = "UninitialisedLocal" }
|
|
|
|
|
|
|
|
override predicate isSource(ControlFlowNode node, StackVariable v) { node = declWithNoInit(v) }
|
|
|
|
|
|
|
|
/* Note: _don't_ use the `useOfVarActual()` predicate here (and a couple of lines
|
|
|
|
* below), as it assumes that the callee always modifies the variable if
|
|
|
|
* it's passed to the function.
|
|
|
|
*
|
|
|
|
* i.e.:
|
|
|
|
* _cleanup_free char *x;
|
|
|
|
* fun(&x);
|
|
|
|
* puts(x);
|
|
|
|
*
|
2022-09-15 04:04:15 +08:00
|
|
|
* `useOfVarActual()` won't treat this as an uninitialized read even if the callee
|
2021-12-02 04:31:43 +08:00
|
|
|
* doesn't modify the argument, however, `useOfVar()` will
|
|
|
|
*/
|
|
|
|
override predicate isSink(ControlFlowNode node, StackVariable v) { useOfVar(v, node) }
|
|
|
|
|
|
|
|
override predicate isBarrier(ControlFlowNode node, StackVariable v) {
|
2022-09-15 04:04:15 +08:00
|
|
|
/* only report the _first_ possibly uninitialized use */
|
2021-12-02 04:31:43 +08:00
|
|
|
useOfVar(v, node) or
|
2021-12-05 23:11:35 +08:00
|
|
|
(
|
2022-09-15 04:04:15 +08:00
|
|
|
/* If there's a return statement somewhere between the variable declaration
|
2021-12-05 23:11:35 +08:00
|
|
|
* and a possible definition, don't accept is as a valid initialization.
|
|
|
|
*
|
|
|
|
* E.g.:
|
|
|
|
* _cleanup_free_ char *x;
|
|
|
|
* ...
|
|
|
|
* if (...)
|
|
|
|
* return;
|
|
|
|
* ...
|
|
|
|
* x = malloc(...);
|
|
|
|
*
|
|
|
|
* is not a valid initialization, since we might return from the function
|
2022-09-15 04:04:15 +08:00
|
|
|
* _before_ the actual initialization (emphasis on _might_, since we
|
2021-12-05 23:11:35 +08:00
|
|
|
* don't know if the return statement might ever evaluate to true).
|
|
|
|
*/
|
|
|
|
definitionBarrier(v, node) and
|
|
|
|
not exists(ReturnStmt rs |
|
|
|
|
/* The attribute check is "just" a complexity optimization */
|
|
|
|
v.getFunction() = rs.getEnclosingFunction() and v.getAnAttribute().hasName("cleanup") |
|
|
|
|
rs.getLocation().isBefore(node.getLocation())
|
|
|
|
)
|
|
|
|
)
|
2021-12-02 04:31:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pragma[noinline]
|
|
|
|
predicate containsInlineAssembly(Function f) { exists(AsmStmt s | s.getEnclosingFunction() = f) }
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Auxiliary predicate: List common exceptions or false positives
|
|
|
|
* for this check to exclude them.
|
|
|
|
*/
|
|
|
|
VariableAccess commonException() {
|
2022-09-15 04:04:15 +08:00
|
|
|
/* If the uninitialized use we've found is in a macro expansion, it's
|
|
|
|
* typically something like va_start(), and we don't want to complain. */
|
2021-12-02 04:31:43 +08:00
|
|
|
result.getParent().isInMacroExpansion()
|
|
|
|
or
|
|
|
|
result.getParent() instanceof BuiltInOperation
|
|
|
|
or
|
2022-09-15 04:04:15 +08:00
|
|
|
/* Finally, exclude functions that contain assembly blocks. It's
|
|
|
|
* anyone's guess what happens in those. */
|
2021-12-02 04:31:43 +08:00
|
|
|
containsInlineAssembly(result.getEnclosingFunction())
|
|
|
|
}
|
|
|
|
|
|
|
|
from UninitialisedLocalReachability r, LocalVariable v, VariableAccess va
|
|
|
|
where
|
|
|
|
r.reaches(_, v, va) and
|
|
|
|
not va = commonException()
|
|
|
|
select va, "The variable $@ may not be initialized here, but has a cleanup handler.", v, v.getName()
|