Fix audit findings: soundness, coverage reporting, docs, and review follow-ups #1

Merged
trevon merged 18 commits from fix/audit-findings into master 2026-09-25 06:48:08 +00:00
4 changed files with 30 additions and 5 deletions
Showing only changes of commit 00082448f4 - Show all commits

fix(encoder): a global C++ lookalike is not a C library function

Problem: isCLibraryFunction accepted any global-scope non-member function,
so a project's `void free(Obj*)` overload was modeled as libc free: calling
it twice was a double free, and reading after it a use-after-free.

Fix: a global function counts only when it is extern "C", in namespace std,
or declared in a system header. extern "C" also covers C translation units,
where external functions have C language linkage.

Test: test_library_callee_identity "[global]" (DoubleFree and UseAfterFree
now 0; were 1 each); test_encoder_callee_names flips the global C++ helper
to not-a-library-callee. Full suite: 140/140.
trvon 2026-09-24 21:34:16 -06:00
Signed by: trevon
GPG key ID: 38686EA797D7A8A7

View file

@ -38,9 +38,10 @@ namespace fold {
[[nodiscard]] std::string summaryStoreKeyForCall(const clang::CallExpr* ce);
// True when `fd` can be the C library function its name suggests: not a class
// member, and declared at global scope, in namespace std, or extern "C".
// Library models (malloc/free, taint sources and sinks) must check this; the
// bare identifier alone also matches `mine::free` or `Pool::free`.
// member, and extern "C", in namespace std, or declared at global scope in a
// system header. Library models (malloc/free, taint sources and sinks) must
// check this; the bare identifier alone also matches `mine::free`,
// `Pool::free` or a project's global `free(Obj*)` overload.
[[nodiscard]] bool isCLibraryFunction(const clang::FunctionDecl* fd);
// isCLibraryFunction of a call's direct callee; false for indirect calls.

View file

@ -76,7 +76,12 @@ bool isCLibraryFunction(const clang::FunctionDecl* fd) {
return false;
if (fd->isExternC() || fd->isInStdNamespace())
return true;
return fd->getDeclContext()->getRedeclContext()->isTranslationUnit();
// A global C++ function is a library function only when a system header
// declares it; a project overload that shares a libc name is not.
if (!fd->getDeclContext()->getRedeclContext()->isTranslationUnit())
return false;
const auto& sm = fd->getASTContext().getSourceManager();
return sm.isInSystemHeader(fd->getCanonicalDecl()->getLocation());
}
bool isCLibraryCallee(const clang::CallExpr* ce) {

View file

@ -176,7 +176,7 @@ TEST_CASE("isCLibraryCallee: only C library functions, never members or namespac
CHECK(fold::isCLibraryCallee(calls[1])); // std::free resolves to ::free
CHECK_FALSE(fold::isCLibraryCallee(calls[2])); // mine::free
CHECK_FALSE(fold::isCLibraryCallee(calls[3])); // Pool::free
CHECK(fold::isCLibraryCallee(calls[4])); // global scope: C semantics
CHECK_FALSE(fold::isCLibraryCallee(calls[4])); // global C++ function, not extern "C"
CHECK_FALSE(fold::isCLibraryCallee(calls[5])); // indirect call
CHECK_FALSE(fold::isCLibraryCallee(nullptr));
}

View file

@ -115,3 +115,22 @@ TEST_CASE("taint: libc system with tainted input is still a sink", "[library-cal
"int f() { return system(getenv(\"CMD\")); }\n";
CHECK(countKind(runTaint(code), fold::FindingKind::TaintTrack) == 1);
}
TEST_CASE("heap model: a global C++ function named free is not libc free",
"[library-callee][double-free][global]") {
// A global-scope C++ overload (no extern "C", not in a system header) is a
// project function that shares libc's name, not the library function.
const std::string code = kLibc + "struct Obj { int v; };\n"
"void free(Obj*) {}\n"
"int f() {\n"
" Obj* o = static_cast<Obj*>(malloc(sizeof(Obj)));\n"
" if (!o) return 0;\n"
" o->v = 1;\n"
" free(o);\n"
" int v = o->v;\n"
" free(o);\n"
" return v;\n"
"}\n";
CHECK(countKind(runDoubleFree(code), fold::FindingKind::DoubleFree) == 0);
CHECK(countKind(runUseAfterFree(code), fold::FindingKind::UseAfterFree) == 0);
}