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/6] 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/6] 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, >From 0ab7a5a7ee72a60b3a478a7c508779458348f993 Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Sat, 31 Aug 2024 19:41:32 +0300 Subject: [PATCH 3/6] rework ReleaseFunctionLC logic --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 62 ++++++++++++------- clang/test/Analysis/NewDelete-atomics.cpp | 46 ++++++++++++++ 2 files changed, 85 insertions(+), 23 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 77b9913a904700..f1194eb20fe564 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -3551,12 +3551,13 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, const LocationContext *CurrentLC = N->getLocationContext(); - // If we find an atomic fetch_add or fetch_sub within the destructor in which - // the pointer was released (before the release), this is likely a destructor - // of a shared pointer. + // If we find an atomic fetch_add or fetch_sub within the function in which + // the pointer was released (before the release), this is likely a release point + // of reference-counted object (like shared pointer). + // // Because we don't model atomics, and also because we don't know that the // original reference count is positive, we should not report use-after-frees - // on objects deleted in such destructors. This can probably be improved + // on objects deleted in such functions. This can probably be improved // through better shared pointer modeling. if (ReleaseFunctionLC && (ReleaseFunctionLC == CurrentLC || ReleaseFunctionLC->isParentOf(CurrentLC))) { @@ -3566,6 +3567,8 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, if (Op == AtomicExpr::AO__c11_atomic_fetch_add || Op == AtomicExpr::AO__c11_atomic_fetch_sub) { BR.markInvalid(getTag(), S); + // After report is considered invalid there is no need to proceed futher. + return nullptr; } } else if (const auto *CE = dyn_cast<CallExpr>(S)) { // Check for `std::atomic` and such. This covers both regular method calls @@ -3648,10 +3651,13 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, return nullptr; } - // See if we're releasing memory while inlining a destructor or - // functions that decrement reference counters (or one of its callees). + // Save the first destructor/function as release point. + assert(!ReleaseFunctionLC && "There should be only one release point"); + ReleaseFunctionLC = CurrentLC->getStackFrame(); + + // See if we're releasing memory while inlining a destructor 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)) { @@ -3659,27 +3665,37 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, // We're bad at guessing the original reference count of the // object, so suppress the report for now. BR.markInvalid(getTag(), DD); - continue; + + // After report is considered invalid there is no need to proceed futher. + return nullptr; } - } - if (!FoundAnyReleaseFunction) { - assert(!ReleaseFunctionLC && - "There can be only one release point!"); - // 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. + // Switch suspection to outer destructor to catch patterns like: + // + // SmartPointr::~SmartPointr() { + // if (__c11_atomic_fetch_sub(refcount, 1, memory_order_relaxed) == 1) + // release_resources(); + // } + // void SmartPointr::release_resources() { + // free(buffer); + // } + // + // This way ReleaseFunctionLC will point to outermost destructor and + // it would be possible to catch wider range of FP. + // + // NOTE: it would be great to support smth like that in C, since currently + // patterns like following won't be supressed: + // + // void doFree(struct Data *data) { free(data); } + // void putData(struct Data *data) + // { + // if (refPut(data)) + // doFree(data); + // } 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 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/NewDelete-atomics.cpp b/clang/test/Analysis/NewDelete-atomics.cpp index 1425acab7489ba..cb0d5aaf1644b6 100644 --- a/clang/test/Analysis/NewDelete-atomics.cpp +++ b/clang/test/Analysis/NewDelete-atomics.cpp @@ -97,6 +97,32 @@ class DifferentlyNamed { T *getPtr() const { return Ptr; } // no-warning }; +// Also IntrusivePtr with different name and outline release in destructor +template <typename T> +class DifferentlyNamedOutlineRelease { + T *Ptr; + +public: + DifferentlyNamedOutlineRelease(T *Ptr) : Ptr(Ptr) { + Ptr->incRef(); + } + + DifferentlyNamedOutlineRelease(const DifferentlyNamedOutlineRelease &Other) : Ptr(Other.Ptr) { + Ptr->incRef(); + } + + void releasePtr(void) { + delete Ptr; + } + + ~DifferentlyNamedOutlineRelease() { + if (Ptr->decRef() == 1) + releasePtr(); + } + + T *getPtr() const { return Ptr; } // no-warning +}; + void testDestroyLocalRefPtr() { IntrusivePtr<RawObj> p1(new RawObj()); { @@ -176,3 +202,23 @@ void testDestroyLocalRefPtrWithAtomicsDifferentlyNamed( // p1 still maintains ownership. The object is not deleted. p1.getPtr()->foo(); // no-warning } + +void testDestroyLocalRefPtrWithOutlineRelease() { + DifferentlyNamedOutlineRelease <RawObj> p1(new RawObj()); + { + DifferentlyNamedOutlineRelease <RawObj> p2(p1); + } + + // p1 still maintains ownership. The object is not deleted. + p1.getPtr()->foo(); // no-warning +} + +void testDestroySymbolicRefPtrWithOutlineRelease( + const DifferentlyNamedOutlineRelease<RawObj> &p1) { + { + DifferentlyNamedOutlineRelease <RawObj> p2(p1); + } + + // p1 still maintains ownership. The object is not deleted. + p1.getPtr()->foo(); // no-warning +} >From 6c1467504616690ac4c627decb29f0df868b6d02 Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Sat, 31 Aug 2024 19:51:31 +0300 Subject: [PATCH 4/6] fix style --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index f1194eb20fe564..1f2dd42ed915ca 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -3552,8 +3552,8 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, const LocationContext *CurrentLC = N->getLocationContext(); // If we find an atomic fetch_add or fetch_sub within the function in which - // the pointer was released (before the release), this is likely a release point - // of reference-counted object (like shared pointer). + // the pointer was released (before the release), this is likely a release + // point of reference-counted object (like shared pointer). // // Because we don't model atomics, and also because we don't know that the // original reference count is positive, we should not report use-after-frees @@ -3567,7 +3567,8 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, if (Op == AtomicExpr::AO__c11_atomic_fetch_add || Op == AtomicExpr::AO__c11_atomic_fetch_sub) { BR.markInvalid(getTag(), S); - // After report is considered invalid there is no need to proceed futher. + // After report is considered invalid there is no need to proceed + // futher. return nullptr; } } else if (const auto *CE = dyn_cast<CallExpr>(S)) { @@ -3666,14 +3667,16 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, // object, so suppress the report for now. BR.markInvalid(getTag(), DD); - // After report is considered invalid there is no need to proceed futher. + // After report is considered invalid there is no need to proceed + // futher. return nullptr; } // Switch suspection to outer destructor to catch patterns like: // // SmartPointr::~SmartPointr() { - // if (__c11_atomic_fetch_sub(refcount, 1, memory_order_relaxed) == 1) + // if (__c11_atomic_fetch_sub(refcount, 1, memory_order_relaxed) == + // 1) // release_resources(); // } // void SmartPointr::release_resources() { @@ -3683,15 +3686,15 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, // This way ReleaseFunctionLC will point to outermost destructor and // it would be possible to catch wider range of FP. // - // NOTE: it would be great to support smth like that in C, since currently - // patterns like following won't be supressed: - // - // void doFree(struct Data *data) { free(data); } - // void putData(struct Data *data) - // { - // if (refPut(data)) - // doFree(data); - // } + // NOTE: it would be great to support smth like that in C, since + // currently patterns like following won't be supressed: + // + // void doFree(struct Data *data) { free(data); } + // void putData(struct Data *data) + // { + // if (refPut(data)) + // doFree(data); + // } ReleaseFunctionLC = LC->getStackFrame(); } } >From 8ac3af5c3e5ca4d17508f362e651701860299925 Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Mon, 2 Sep 2024 23:49:46 +0300 Subject: [PATCH 5/6] add missing return and make comment more c++ish --- clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 1f2dd42ed915ca..fb2856f01b26fb 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -3581,6 +3581,9 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, // "__atomic_base" or something. if (StringRef(RD->getNameAsString()).contains("atomic")) { BR.markInvalid(getTag(), S); + // After report is considered invalid there is no need to proceed + // futher. + return nullptr; } } } @@ -3673,10 +3676,11 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, } // Switch suspection to outer destructor to catch patterns like: + // (note that class name is special to bypass + // isReferenceCountingPointerDestructor() logic) // // SmartPointr::~SmartPointr() { - // if (__c11_atomic_fetch_sub(refcount, 1, memory_order_relaxed) == - // 1) + // if (refcount.fetch_sub(1) == 1) // release_resources(); // } // void SmartPointr::release_resources() { >From 87de191ab83d7f00b4a92c68a45687b2327593cf Mon Sep 17 00:00:00 2001 From: Pavel Skripkin <paskrip...@gmail.com> Date: Tue, 3 Sep 2024 13:01:28 +0300 Subject: [PATCH 6/6] fix comment --- clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index fb2856f01b26fb..6525bf6de8ea8c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -3676,7 +3676,7 @@ PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, } // Switch suspection to outer destructor to catch patterns like: - // (note that class name is special to bypass + // (note that class name is distorted to bypass // isReferenceCountingPointerDestructor() logic) // // SmartPointr::~SmartPointr() { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits