Semmle 1.20
Skip to end of metadata
Go to start of metadata

Name: Potential use after free

Description: An allocated memory block is used after it has been freed. Behavior in such cases is undefined and can cause memory corruption.

ID: cpp/use-after-free

Kind: problem

Severity: warning

Query: UseAfterFree.ql
/**
 * @name Potential use after free
 * @description An allocated memory block is used after it has been freed. Behavior in such cases is undefined and can cause memory corruption.
 * @kind problem
 * @id cpp/use-after-free
 * @problem.severity warning
 * @tags reliability
 *       security
 *       external/cwe/cwe-416
 */
import cpp
import semmle.code.cpp.controlflow.LocalScopeVariableReachability

/** `e` is an expression that frees the memory pointed to by `v`. */
predicate isFreeExpr(Expr e, LocalScopeVariable v) {
  exists(VariableAccess va |
    va.getTarget() = v |
    exists(FunctionCall fc |
      fc = e |
      fc.getTarget().hasQualifiedName("free")
      and va = fc.getArgument(0)
    )
    or
    e.(DeleteExpr).getExpr() = va
    or
    e.(DeleteArrayExpr).getExpr() = va
  )
}

/** `e` is an expression that (may) dereference `v`. */
predicate isDerefExpr(Expr e, LocalScopeVariable v) {
  v.getAnAccess() = e and dereferenced(e)
  or
  isDerefByCallExpr(_, _, e, v)
}

/**
 * `va` is passed by value as (part of) the `i`th argument in
 * call `c`. The target function is either a library function
 * or a source code function that dereferences the relevant
 * parameter.
 */
predicate isDerefByCallExpr(Call c, int i, VariableAccess va, LocalScopeVariable v) {
  v.getAnAccess() = va
  and
  va = c.getAnArgumentSubExpr(i)
  and
  not c.passesByReference(i, va)
  and
  (c.getTarget().hasEntryPoint() implies isDerefExpr(_, c.getTarget().getParameter(i)))
}

class UseAfterFreeReachability extends LocalScopeVariableReachability {
  UseAfterFreeReachability() { this = "UseAfterFree" }

  override predicate isSource(ControlFlowNode node, LocalScopeVariable v) {
    isFreeExpr(node, v)
  }

  override predicate isSink(ControlFlowNode node, LocalScopeVariable v) {
    isDerefExpr(node, v)
  }

  override predicate isBarrier(ControlFlowNode node, LocalScopeVariable v) {
    definitionBarrier(v, node) or
    isFreeExpr(node, v)
  }
}

from UseAfterFreeReachability r, LocalScopeVariable v, Expr free, Expr e
where r.reaches(free, v, e)
select e,
  "Memory pointed to by '" + v.getName().toString() +
  "' may have been previously freed $@", free, "here"

This rule finds accesses through a pointer of a memory location that has already been freed (i.e. through a dangling pointer). Such memory blocks have already been released to the dynamic memory manager, and modifying them can lead to anything from a segfault to memory corruption that would cause subsequent calls to the dynamic memory manger to behave erratically, to a possible security vulnerability.

This check is an approximation, so some results may not be actual defects in the program. It is not possible in general to compute the values of pointers without running the program with all input data.

Recommendation

Ensure that all execution paths that access memory through a pointer never access that pointer after it is freed.

Example

int f() {
	char* buf = new char[SIZE];
	....
	if (error) {
		free(buf); //error handling has freed the buffer
	}
	...
	log_contents(buf); //but it is still used here for logging
}

References
  • I. Gerg. An Overview and Example of the Buffer-Overflow Exploit. IANewsletter vol 7 no 4. 2005.
  • M. Donaldson. Inside the Buffer Overflow Attack: Mechanism, Method & Prevention. SANS Institute InfoSec Reading Room. 2002.
  • Common Weakness Enumeration: CWE-416.