diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 12816eed2e8b5..576abcfe8aa4f 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. (#GH144884) + Improvements ^^^^^^^^^^^^ diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 35e98a5e2719a..fa6e8e4146804 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -430,8 +430,8 @@ class MallocChecker CHECK_FN(checkGMemdup) CHECK_FN(checkGMallocN) CHECK_FN(checkGMallocN0) - CHECK_FN(preGetdelim) - CHECK_FN(checkGetdelim) + CHECK_FN(preGetDelimOrGetLine) + CHECK_FN(checkGetDelimOrGetLine) CHECK_FN(checkReallocN) CHECK_FN(checkOwnershipAttr) @@ -445,15 +445,16 @@ class MallocChecker const CallDescriptionMap PreFnMap{ // NOTE: the following CallDescription also matches the C++ standard // library function std::getline(); the callback will filter it out. - {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetdelim}, - {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetdelim}, + {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::preGetDelimOrGetLine}, + {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::preGetDelimOrGetLine}, }; const CallDescriptionMap PostFnMap{ // NOTE: the following CallDescription also matches the C++ standard // library function std::getline(); the callback will filter it out. - {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetdelim}, - {{CDM::CLibrary, {"getdelim"}, 4}, &MallocChecker::checkGetdelim}, + {{CDM::CLibrary, {"getline"}, 3}, &MallocChecker::checkGetDelimOrGetLine}, + {{CDM::CLibrary, {"getdelim"}, 4}, + &MallocChecker::checkGetDelimOrGetLine}, }; const CallDescriptionMap FreeingMemFnMap{ @@ -1482,8 +1483,9 @@ static bool isFromStdNamespace(const CallEvent &Call) { return FD->isInStdNamespace(); } -void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call, - CheckerContext &C) const { +void MallocChecker::preGetDelimOrGetLine(ProgramStateRef State, + const CallEvent &Call, + CheckerContext &C) const { // Discard calls to the C++ standard library function std::getline(), which // is completely unrelated to the POSIX getline() that we're checking. if (isFromStdNamespace(Call)) @@ -1505,8 +1507,9 @@ void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call, C.addTransition(State); } -void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call, - CheckerContext &C) const { +void MallocChecker::checkGetDelimOrGetLine(ProgramStateRef State, + const CallEvent &Call, + CheckerContext &C) const { // Discard calls to the C++ standard library function std::getline(), which // is completely unrelated to the POSIX getline() that we're checking. if (isFromStdNamespace(Call)) @@ -1518,14 +1521,19 @@ void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call, if (!CE) return; - const auto LinePtr = - getPointeeVal(Call.getArgSVal(0), State)->getAs(); - const auto Size = - getPointeeVal(Call.getArgSVal(1), State)->getAs(); - if (!LinePtr || !Size || !LinePtr->getAsRegion()) + const auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State); + const auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State); + if (!LinePtrOpt || !SizeOpt || LinePtrOpt->isUnknownOrUndef() || + SizeOpt->isUnknownOrUndef()) + return; + + const auto LinePtr = LinePtrOpt->getAs(); + const auto Size = SizeOpt->getAs(); + const MemRegion *LinePtrReg = LinePtr->getAsRegion(); + if (!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 79d10d99e11d0..ce5887d56b181 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -83,7 +83,7 @@ class UnixAPIMisuseChecker : public Checker { void CheckOpen(CheckerContext &C, const CallEvent &Call) const; void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const; - void CheckGetDelim(CheckerContext &C, const CallEvent &Call) const; + void CheckGetDelimOrGetline(CheckerContext &C, const CallEvent &Call) const; void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const; void CheckOpenVariant(CheckerContext &C, const CallEvent &Call, @@ -128,7 +128,7 @@ ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull( const StringRef PtrDescr, std::optional> BT) const { const auto Ptr = PtrVal.getAs(); - if (!Ptr) + if (!Ptr || !PtrExpr->getType()->isPointerType()) return State; const auto [PtrNotNull, PtrNull] = State->assume(*Ptr); @@ -177,7 +177,7 @@ void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call, CheckPthreadOnce(C, Call); else if (is_contained({"getdelim", "getline"}, FName)) - CheckGetDelim(C, Call); + CheckGetDelimOrGetline(C, Call); } void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C, ProgramStateRef State, @@ -332,8 +332,12 @@ 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(); - auto NSVal = getPointeeVal(SizePtrSVal, State); + const auto LinePtrValOpt = getPointeeVal(LinePtrPtrSVal, State); + if (!LinePtrValOpt) + return nullptr; + + const auto LinePtrSVal = LinePtrValOpt->getAs(); + const auto NSVal = getPointeeVal(SizePtrSVal, State); if (!LinePtrSVal || !NSVal || NSVal->isUnknown()) return nullptr; @@ -350,9 +354,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(); + 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(); @@ -367,8 +378,11 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect( return State; } -void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C, - const CallEvent &Call) const { +void UnixAPIMisuseChecker::CheckGetDelimOrGetline(CheckerContext &C, + const CallEvent &Call) const { + if (Call.getNumArgs() < 2) + return; + ProgramStateRef State = C.getState(); // The parameter `n` must not be NULL. 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