https://github.com/pskrgag updated https://github.com/llvm/llvm-project/pull/104599
>From 913036ab795d6b91d6bb74d82aa2d329fe689535 Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Fri, 16 Aug 2024 17:45:57 +0300 Subject: [PATCH 1/2] clang/csa: suspect all functions as those that may do refcount release --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 57 ++++++------- clang/test/Analysis/malloc-refcounted.c | 80 +++++++++++++++++++ 2 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 clang/test/Analysis/malloc-refcounted.c diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 3ddcb7e94ae5d6..77b9913a904700 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -904,16 +904,16 @@ class MallocBugVisitor final : public BugReporterVisitor { // A symbol from when the primary region should have been reallocated. SymbolRef FailedReallocSymbol; - // A C++ destructor stack frame in which memory was released. Used for + // A release function stack frame in which memory was released. Used for // miscellaneous false positive suppression. - const StackFrameContext *ReleaseDestructorLC; + const StackFrameContext *ReleaseFunctionLC; bool IsLeak; public: MallocBugVisitor(SymbolRef S, bool isLeak = false) : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), - ReleaseDestructorLC(nullptr), IsLeak(isLeak) {} + ReleaseFunctionLC(nullptr), IsLeak(isLeak) {} static void *getTag() { static int Tag = 0; @@ -3558,8 +3558,8 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, // original reference count is positive, we should not report use-after-frees // on objects deleted in such destructors. This can probably be improved // through better shared pointer modeling. - if (ReleaseDestructorLC && (ReleaseDestructorLC == CurrentLC || - ReleaseDestructorLC->isParentOf(CurrentLC))) { + if (ReleaseFunctionLC && (ReleaseFunctionLC == CurrentLC || + ReleaseFunctionLC->isParentOf(CurrentLC))) { if (const auto *AE = dyn_cast<AtomicExpr>(S)) { // Check for manual use of atomic builtins. AtomicExpr::AtomicOp Op = AE->getOp(); @@ -3648,35 +3648,38 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, return nullptr; } - // See if we're releasing memory while inlining a destructor - // (or one of its callees). This turns on various common - // false positive suppressions. - bool FoundAnyDestructor = false; - for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent()) { - if (const auto *DD = dyn_cast<CXXDestructorDecl>(LC->getDecl())) { - if (isReferenceCountingPointerDestructor(DD)) { - // This immediately looks like a reference-counting destructor. - // We're bad at guessing the original reference count of the object, - // so suppress the report for now. - BR.markInvalid(getTag(), DD); - } else if (!FoundAnyDestructor) { - assert(!ReleaseDestructorLC && + // See if we're releasing memory while inlining a destructor or + // functions that decrement reference counters (or one of its callees). + // This turns on various common false positive suppressions. + bool FoundAnyReleaseFunction = false; + for (const LocationContext *LC = CurrentLC; LC; LC = LC->getParent()) { + if (const auto *DD = dyn_cast<CXXDestructorDecl>(LC->getDecl())) { + if (isReferenceCountingPointerDestructor(DD)) { + // This immediately looks like a reference-counting destructor. + // We're bad at guessing the original reference count of the + // object, so suppress the report for now. + BR.markInvalid(getTag(), DD); + continue; + } + } + + if (!FoundAnyReleaseFunction) { + assert(!ReleaseFunctionLC && "There can be only one release point!"); - // Suspect that it's a reference counting pointer destructor. - // On one of the next nodes might find out that it has atomic - // reference counting operations within it (see the code above), - // and if so, we'd conclude that it likely is a reference counting - // pointer destructor. - ReleaseDestructorLC = LC->getStackFrame(); + // Suspect that it's a reference counting pointer + // destructor/function. On one of the next nodes might find out that + // it has atomic reference counting operations within it (see the + // code above), and if so, we'd conclude that it likely is a + // reference counting pointer destructor. + ReleaseFunctionLC = LC->getStackFrame(); // It is unlikely that releasing memory is delegated to a destructor // inside a destructor of a shared pointer, because it's fairly hard // to pass the information that the pointer indeed needs to be // released into it. So we're only interested in the innermost - // destructor. - FoundAnyDestructor = true; + // destructor or function. + FoundAnyReleaseFunction = true; } } - } } else if (isRelinquished(RSCurr, RSPrev, S)) { Msg = "Memory ownership is transferred"; StackHint = std::make_unique<StackHintGeneratorForSymbol>(Sym, ""); diff --git a/clang/test/Analysis/malloc-refcounted.c b/clang/test/Analysis/malloc-refcounted.c new file mode 100644 index 00000000000000..5897b6c3186bd5 --- /dev/null +++ b/clang/test/Analysis/malloc-refcounted.c @@ -0,0 +1,80 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -verify %s +// + +typedef unsigned long size_t; + +typedef enum memory_order { + memory_order_relaxed = __ATOMIC_RELAXED, +} memory_order; + +void *calloc(size_t, size_t); +void free(void *); + +struct SomeData { + int i; + _Atomic int ref; +}; + +static struct SomeData *alloc_data(void) +{ + struct SomeData *data = calloc(sizeof(*data), 1); + + __c11_atomic_store(&data->ref, 2, memory_order_relaxed); + return data; +} + +static void put_data(struct SomeData *data) +{ + if (__c11_atomic_fetch_sub(&data->ref, 1, memory_order_relaxed) == 1) + free(data); +} + +static int dec_refcounter(struct SomeData *data) +{ + return __c11_atomic_fetch_sub(&data->ref, 1, memory_order_relaxed) == 1; +} + +static void put_data_nested(struct SomeData *data) +{ + if (dec_refcounter(data)) + free(data); +} + +static void put_data_uncond(struct SomeData *data) +{ + free(data); +} + +static void put_data_unrelated_atomic(struct SomeData *data) +{ + free(data); + __c11_atomic_fetch_sub(&data->ref, 1, memory_order_relaxed); +} + +void test_no_uaf(void) +{ + struct SomeData *data = alloc_data(); + put_data(data); + data->i += 1; // no warning +} + +void test_no_uaf_nested(void) +{ + struct SomeData *data = alloc_data(); + put_data_nested(data); + data->i += 1; // no warning +} + +void test_uaf(void) +{ + struct SomeData *data = alloc_data(); + put_data_uncond(data); + data->i += 1; // expected-warning{{Use of memory after it is freed}} +} + +void test_no_uaf_atomic_after(void) +{ + struct SomeData *data = alloc_data(); + put_data_unrelated_atomic(data); + data->i += 1; // expected-warning{{Use of memory after it is freed}} +} >From a9d23cfb446c45c1b85e9a881ec2102b549b5f1b Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Fri, 16 Aug 2024 19:02:57 +0300 Subject: [PATCH 2/2] fix windows CI --- clang/test/Analysis/malloc-refcounted.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/test/Analysis/malloc-refcounted.c b/clang/test/Analysis/malloc-refcounted.c index 5897b6c3186bd5..bfbe91d6ecaf92 100644 --- a/clang/test/Analysis/malloc-refcounted.c +++ b/clang/test/Analysis/malloc-refcounted.c @@ -1,7 +1,7 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -verify %s // -typedef unsigned long size_t; +typedef __SIZE_TYPE__ size_t; typedef enum memory_order { memory_order_relaxed = __ATOMIC_RELAXED, _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits