https://github.com/vbvictor updated https://github.com/llvm/llvm-project/pull/145229
>From ba9f1edfc2ea1fb1e41366721ccbf795ec998707 Mon Sep 17 00:00:00 2001 From: Victor Baranov <bar.victor.2...@gmail.com> Date: Mon, 23 Jun 2025 23:55:57 +0300 Subject: [PATCH 1/2] [clang][analyzer] fix crash when modelling 'getline' function in checkers --- clang/docs/ReleaseNotes.rst | 3 + .../StaticAnalyzer/Checkers/MallocChecker.cpp | 13 +- .../Checkers/UnixAPIChecker.cpp | 39 ++++-- .../getline-unixapi-invalid-signatures.c | 127 ++++++++++++++++++ 4 files changed, 169 insertions(+), 13 deletions(-) create mode 100644 clang/test/Analysis/getline-unixapi-invalid-signatures.c diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 12816eed2e8b5..b6f6dce29a87b 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -1060,6 +1060,9 @@ impact the linker behaviour like the other `-static-*` flags. Crash and bug fixes ^^^^^^^^^^^^^^^^^^^ +- Fixed a crash in ``UnixAPIMisuseChecker`` and ``MallocChecker`` when analyzing + code with non-standard ``getline`` or ``getdelim`` function signatures. + Improvements ^^^^^^^^^^^^ diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 35e98a5e2719a..cf9837a378430 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1518,10 +1518,15 @@ void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call, if (!CE) return; - const auto LinePtr = - getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>(); - const auto Size = - getPointeeVal(Call.getArgSVal(1), State)->getAs<DefinedSVal>(); + auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State); + if (!LinePtrOpt) + return; + const auto LinePtr = LinePtrOpt->getAs<DefinedSVal>(); + + auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State); + if (!SizeOpt) + return; + const auto Size = SizeOpt->getAs<DefinedSVal>(); if (!LinePtr || !Size || !LinePtr->getAsRegion()) return; diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index 79d10d99e11d0..77bc9e92e3520 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -332,13 +332,21 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( // We have a pointer to a pointer to the buffer, and a pointer to the size. // We want what they point at. - auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>(); + auto LinePtrValOpt = getPointeeVal(LinePtrPtrSVal, State); + if (!LinePtrValOpt) + return nullptr; + + auto LinePtrSVal = LinePtrValOpt->getAs<DefinedSVal>(); auto NSVal = getPointeeVal(SizePtrSVal, State); if (!LinePtrSVal || !NSVal || NSVal->isUnknown()) return nullptr; assert(LinePtrPtrExpr && SizePtrExpr); + // Add defensive check to ensure we can handle the assume operation + if (!LinePtrSVal->getAs<DefinedSVal>()) + return nullptr; + const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal); if (LinePtrNotNull && !LinePtrNull) { // If `*lineptr` is not null, but `*n` is undefined, there is UB. @@ -350,9 +358,16 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( // If it is defined, and known, its size must be less than or equal to // the buffer size. auto NDefSVal = NSVal->getAs<DefinedSVal>(); + if (!NDefSVal) + return LinePtrNotNull; + auto &SVB = C.getSValBuilder(); - auto LineBufSize = - getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB); + + const MemRegion *LinePtrRegion = LinePtrSVal->getAsRegion(); + if (!LinePtrRegion) + return LinePtrNotNull; + + auto LineBufSize = getDynamicExtent(LinePtrNotNull, LinePtrRegion, SVB); auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize, *NDefSVal, SVB.getConditionType()) .getAs<DefinedOrUnknownSVal>(); @@ -370,23 +385,29 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C, const CallEvent &Call) const { ProgramStateRef State = C.getState(); + if (Call.getNumArgs() < 2) + return; + + const Expr *Arg0 = Call.getArgExpr(0); + const Expr *Arg1 = Call.getArgExpr(1); + + if (!Arg0->getType()->isPointerType() || !Arg1->getType()->isPointerType()) + return; // The parameter `n` must not be NULL. SVal SizePtrSval = Call.getArgSVal(1); - State = EnsurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size"); + State = EnsurePtrNotNull(SizePtrSval, Arg1, C, State, "Size"); if (!State) return; // The parameter `lineptr` must not be NULL. SVal LinePtrPtrSVal = Call.getArgSVal(0); - State = - EnsurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line"); + State = EnsurePtrNotNull(LinePtrPtrSVal, Arg0, C, State, "Line"); if (!State) return; - State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval, - Call.getArgExpr(0), - Call.getArgExpr(1), C, State); + State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval, Arg0, + Arg1, C, State); if (!State) return; diff --git a/clang/test/Analysis/getline-unixapi-invalid-signatures.c b/clang/test/Analysis/getline-unixapi-invalid-signatures.c new file mode 100644 index 0000000000000..c906625b47634 --- /dev/null +++ b/clang/test/Analysis/getline-unixapi-invalid-signatures.c @@ -0,0 +1,127 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_CORRECT +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_1 +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_2 +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_3 +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_4 +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_5 +// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_GH144884 + +// emulator of "system-header-simulator.h" because of redefinition of 'getline' function +typedef struct _FILE FILE; +typedef __typeof(sizeof(int)) size_t; +typedef long ssize_t; +#define NULL 0 + +int fclose(FILE *fp); +FILE *tmpfile(void); + +#ifdef TEST_CORRECT +ssize_t getline(char **lineptr, size_t *n, FILE *stream); +ssize_t getdelim(char **lineptr, size_t *n, int delimiter, FILE *stream); + +void test_correct() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getline(&buffer, NULL, F1); // expected-warning {{Size pointer might be NULL}} + fclose(F1); +} + +void test_delim_correct() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getdelim(&buffer, NULL, ',', F1); // expected-warning {{Size pointer might be NULL}} + fclose(F1); +} +#endif + +#ifdef TEST_GETLINE_1 +// expected-no-diagnostics +ssize_t getline(int lineptr); + +void test() { + FILE *F1 = tmpfile(); + if (!F1) + return; + int buffer = 0; + getline(buffer); + fclose(F1); +} +#endif + +#ifdef TEST_GETLINE_2 +ssize_t getline(char **lineptr, size_t *n); + +void test() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getline(&buffer, NULL); // expected-warning {{Size pointer might be NULL}} + fclose(F1); +} +#endif + +#ifdef TEST_GETLINE_3 +// expected-no-diagnostics +ssize_t getline(char **lineptr, size_t n, FILE *stream); + +void test() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getline(&buffer, 0, F1); + fclose(F1); +} +#endif + +#ifdef TEST_GETLINE_4 +ssize_t getline(char **lineptr, size_t *n, int stream); +ssize_t getdelim(char **lineptr, size_t *n, int delimiter, int stream); + +void test() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getline(&buffer, NULL, 1); // expected-warning {{Size pointer might be NULL}} + fclose(F1); +} + +void test_delim() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getdelim(&buffer, NULL, ',', 1); // expected-warning {{Size pointer might be NULL}} + fclose(F1); +} +#endif + +#ifdef TEST_GETLINE_5 +ssize_t getdelim(char **lineptr, size_t *n, const char* delimiter, FILE *stream); + +void test_delim() { + FILE *F1 = tmpfile(); + if (!F1) + return; + char *buffer = NULL; + getdelim(&buffer, NULL, ",", F1); // expected-warning {{Size pointer might be NULL}} + fclose(F1); +} +#endif + +#ifdef TEST_GETLINE_GH144884 +// expected-no-diagnostics +struct AW_string {}; +void getline(int *, struct AW_string); +void top() { + struct AW_string line; + int getline_file_info; + getline(&getline_file_info, line); +} +#endif >From f871058da1b656c5f44e8cb0961fd0db54263811 Mon Sep 17 00:00:00 2001 From: Victor Baranov <bar.victor.2...@gmail.com> Date: Tue, 24 Jun 2025 16:32:22 +0300 Subject: [PATCH 2/2] fix pr comments --- .../StaticAnalyzer/Checkers/MallocChecker.cpp | 15 ++++++------ .../Checkers/UnixAPIChecker.cpp | 23 +++++++------------ 2 files changed, 15 insertions(+), 23 deletions(-) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index cf9837a378430..783c35654fb44 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1518,19 +1518,18 @@ void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call, if (!CE) return; - auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State); - if (!LinePtrOpt) + const auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State); + const auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State); + if (!LinePtrOpt || !SizeOpt) return; - const auto LinePtr = LinePtrOpt->getAs<DefinedSVal>(); - auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State); - if (!SizeOpt) - return; + const auto LinePtr = LinePtrOpt->getAs<DefinedSVal>(); const auto Size = SizeOpt->getAs<DefinedSVal>(); - if (!LinePtr || !Size || !LinePtr->getAsRegion()) + const MemRegion *LinePtrReg = LinePtr->getAsRegion(); + if (!LinePtr || !Size || !LinePtrReg) return; - State = setDynamicExtent(State, LinePtr->getAsRegion(), *Size); + State = setDynamicExtent(State, LinePtrReg, *Size); C.addTransition(MallocUpdateRefState(C, CE, State, AllocationFamily(AF_Malloc), *LinePtr)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index 77bc9e92e3520..81fff566aac66 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -128,7 +128,7 @@ ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull( const StringRef PtrDescr, std::optional<std::reference_wrapper<const BugType>> BT) const { const auto Ptr = PtrVal.getAs<DefinedSVal>(); - if (!Ptr) + if (!Ptr || !PtrExpr->getType()->isPointerType()) return State; const auto [PtrNotNull, PtrNull] = State->assume(*Ptr); @@ -343,10 +343,6 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( assert(LinePtrPtrExpr && SizePtrExpr); - // Add defensive check to ensure we can handle the assume operation - if (!LinePtrSVal->getAs<DefinedSVal>()) - return nullptr; - const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal); if (LinePtrNotNull && !LinePtrNull) { // If `*lineptr` is not null, but `*n` is undefined, there is UB. @@ -384,30 +380,27 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C, const CallEvent &Call) const { - ProgramStateRef State = C.getState(); if (Call.getNumArgs() < 2) return; - const Expr *Arg0 = Call.getArgExpr(0); - const Expr *Arg1 = Call.getArgExpr(1); - - if (!Arg0->getType()->isPointerType() || !Arg1->getType()->isPointerType()) - return; + ProgramStateRef State = C.getState(); // The parameter `n` must not be NULL. SVal SizePtrSval = Call.getArgSVal(1); - State = EnsurePtrNotNull(SizePtrSval, Arg1, C, State, "Size"); + State = EnsurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size"); if (!State) return; // The parameter `lineptr` must not be NULL. SVal LinePtrPtrSVal = Call.getArgSVal(0); - State = EnsurePtrNotNull(LinePtrPtrSVal, Arg0, C, State, "Line"); + State = + EnsurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line"); if (!State) return; - State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval, Arg0, - Arg1, C, State); + State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval, + Call.getArgExpr(0), + Call.getArgExpr(1), C, State); if (!State) return; _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits