Index: clang/docs/analyzer/checkers.rst =================================================================== --- clang/docs/analyzer/checkers.rst +++ clang/docs/analyzer/checkers.rst @@ -1961,6 +1961,16 @@ return putenv(env); // putenv function should not be called with auto variables } +.. _alpha-security-cert-str-30c: + +alpha.security.cert.str.30c +""""""""""""""""""""""""""" + +Checker for `STR30-C rule`_. + +It warns on misusing the following functions: +``strpbrk()``, ``strchr()``, ``strrchr()``, ``strstr()``, ``memchr()``. + .. _alpha-security-cert-str-31c: alpha.security.cert.str.31c Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -856,6 +856,11 @@ Documentation, Hidden; +def Str30cChecker : Checker<"30c">, + HelpText<"SEI CERT checker of rules defined in STR30-C">, + Dependencies<[StrCheckerBase]>, + Documentation; + def Str31cChecker : Checker<"31c">, HelpText<"SEI CERT checker of rules defined in STR31-C">, Dependencies<[StrCheckerBase]>, Index: clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -122,6 +122,7 @@ {{CDF_MaybeBuiltin, "mempcpy", 3}, &CStringChecker::evalMempcpy}, {{CDF_MaybeBuiltin, "memcmp", 3}, &CStringChecker::evalMemcmp}, {{CDF_MaybeBuiltin, "memmove", 3}, &CStringChecker::evalMemmove}, + {{CDF_MaybeBuiltin, "memchr", 3}, &CStringChecker::evalMemchr}, {{CDF_MaybeBuiltin, "memset", 3}, &CStringChecker::evalMemset}, {{CDF_MaybeBuiltin, "explicit_memset", 3}, &CStringChecker::evalMemset}, {{CDF_MaybeBuiltin, "strcpy", 2}, &CStringChecker::evalStrcpy}, @@ -138,6 +139,10 @@ {{CDF_MaybeBuiltin, "strcasecmp", 2}, &CStringChecker::evalStrcasecmp}, {{CDF_MaybeBuiltin, "strncasecmp", 3}, &CStringChecker::evalStrncasecmp}, {{CDF_MaybeBuiltin, "strsep", 2}, &CStringChecker::evalStrsep}, + {{CDF_MaybeBuiltin, "strpbrk", 2}, &CStringChecker::evalStrpbrk}, + {{CDF_MaybeBuiltin, "strchr", 2}, &CStringChecker::evalStrchr}, + {{CDF_MaybeBuiltin, "strrchr", 2}, &CStringChecker::evalStrrchr}, + {{CDF_MaybeBuiltin, "strstr", 2}, &CStringChecker::evalStrstr}, {{CDF_MaybeBuiltin, "bcopy", 3}, &CStringChecker::evalBcopy}, {{CDF_MaybeBuiltin, "bcmp", 3}, &CStringChecker::evalMemcmp}, {{CDF_MaybeBuiltin, "bzero", 2}, &CStringChecker::evalBzero}, @@ -189,6 +194,14 @@ void evalStrsep(CheckerContext &C, const CallExpr *CE) const; + // Evaluate function calls which returns a pointer to the string argument. + void evalCharPtrCommon(CheckerContext &C, const CallExpr *CE) const; + void evalMemchr(CheckerContext &C, const CallExpr *CE) const; + void evalStrchr(CheckerContext &C, const CallExpr *CE) const; + void evalStrrchr(CheckerContext &C, const CallExpr *CE) const; + void evalStrstr(CheckerContext &C, const CallExpr *CE) const; + void evalStrpbrk(CheckerContext &C, const CallExpr *CE) const; + void evalStdCopy(CheckerContext &C, const CallExpr *CE) const; void evalStdCopyBackward(CheckerContext &C, const CallExpr *CE) const; void evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const; @@ -2120,6 +2133,58 @@ C.addTransition(State); } +void CStringChecker::evalCharPtrCommon(CheckerContext &C, + const CallExpr *CE) const { + CurrentFunctionDescription = "char pointer returning function"; + + SValBuilder &SVB = C.getSValBuilder(); + ProgramStateRef State = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + + const Expr *SrcExpr = CE->getArg(0); + + // Add a conjured index to the source string. + SVal SrcV = C.getSVal(SrcExpr); + DefinedOrUnknownSVal Index = SVB.conjureSymbolVal( + SrcExpr, LCtx, SVB.getArrayIndexType(), C.blockCount()); + + SVal Offset = SVB.evalBinOp(State, BO_Add, SrcV, Index, SrcExpr->getType()); + + State = State->BindExpr(CE, LCtx, Offset); + C.addTransition(State); +} + +void CStringChecker::evalMemchr(CheckerContext &C, const CallExpr *CE) const { + // void *memchr(const void *buffer, int c, size_t count); + const Expr *BufferExpr = CE->getArg(0); + QualType Ty = BufferExpr->getType().getUnqualifiedType(); + if (Ty->isPointerType()) + Ty = Ty->getPointeeType(); + + if (Ty->isAnyCharacterType()) + evalCharPtrCommon(C, CE); +} + +void CStringChecker::evalStrchr(CheckerContext &C, const CallExpr *CE) const { + // char *strchr(const char *str, int c); + evalCharPtrCommon(C, CE); +} + +void CStringChecker::evalStrrchr(CheckerContext &C, const CallExpr *CE) const { + // char *strrchr(const char *str, int c); + evalCharPtrCommon(C, CE); +} + +void CStringChecker::evalStrstr(CheckerContext &C, const CallExpr *CE) const { + // char *strstr(const char *str, const char *strSearch); + evalCharPtrCommon(C, CE); +} + +void CStringChecker::evalStrpbrk(CheckerContext &C, const CallExpr *CE) const { + // char *strpbrk(const char *str, const char *strCharSet); + evalCharPtrCommon(C, CE); +} + // These should probably be moved into a C++ standard library checker. void CStringChecker::evalStdCopy(CheckerContext &C, const CallExpr *CE) const { evalStdCopyCommon(C, CE); Index: clang/lib/StaticAnalyzer/Checkers/cert/StrChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/cert/StrChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/cert/StrChecker.cpp @@ -12,6 +12,9 @@ // - https://wiki.sei.cmu.edu/confluence/x/ptUxBQ // // This checker is a base checker which consist of the following checkers: +// - STR30-C: Do not attempt to modify string literals: +// - https://wiki.sei.cmu.edu/confluence/x/VtYxBQ +// // - STR31-C: Guarantee that storage for strings has sufficient space for // character data and the null terminator: // - https://wiki.sei.cmu.edu/confluence/x/sNUxBQ @@ -83,6 +86,17 @@ /// \{ /// Check the function calls defined in 'CDM'. + /// STR30-C. + void checkMemchr(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) const; + void checkStrchr(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) const; + void checkStrrchr(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) const; + void checkStrstr(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) const; + void checkStrpbrk(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) const; /// STR31-C. void checkGets(const CallEvent &Call, const CallContext &CallC, CheckerContext &C) const; @@ -105,6 +119,7 @@ void createCallOverflowReport(const CallEvent &Call, const CallContext &CallC, CheckerContext &C) const; + bool EnableStr30cChecker = false; bool EnableStr31cChecker = false; bool EnableStr32cChecker = false; @@ -113,6 +128,18 @@ const CallContext &, CheckerContext &)>; const CallDescriptionMap> CDM = { + // The following checks STR30-C rules. + // void *memchr(const void *buffer, int c, size_t count); + {{"memchr", 3}, {&StrCheckerBase::checkMemchr, {None, 0}}}, + // char *strchr(const char *str, int c); + {{"strchr", 2}, {&StrCheckerBase::checkStrchr, {None, 0}}}, + // char *strrchr(const char *str, int c); + {{"strrchr", 2}, {&StrCheckerBase::checkStrrchr, {None, 0}}}, + // char *strstr(const char *str, const char *strSearch); + {{"strstr", 2}, {&StrCheckerBase::checkStrstr, {None, 0}}}, + // char *strpbrk(const char *str, const char *strCharSet); + {{"strpbrk", 2}, {&StrCheckerBase::checkStrpbrk, {None, 0}}}, + // The following checks STR31-C rules. // char *gets(char *dest); {{"gets", 1}, {&StrCheckerBase::checkGets, {0}}}, @@ -138,6 +165,9 @@ /// It stores the allocations which we emit notes on. REGISTER_LIST_WITH_PROGRAMSTATE(AllocationList, const MemRegion *) +/// It stores the constant strings which we have pointers to them. +REGISTER_LIST_WITH_PROGRAMSTATE(PointerList, const MemRegion *) + //===----------------------------------------------------------------------===// // Helper functions. //===----------------------------------------------------------------------===// @@ -356,6 +386,131 @@ return true; } +//===----------------------------------------------------------------------===// +// The following checks STR30-C rules. +//===----------------------------------------------------------------------===// + +/// Check whether a constant string is being modified. If so, emit a report. +static bool isConstantStringModify(SVal L, SVal V, const Stmt *S, + CheckerContext &C, const BugType &BT) { + // FIXME: Handle more complex modifications. + const auto *BO = dyn_cast(S); + if (!BO) + return false; + + const MemRegion *MR = L.getAsRegion(); + if (!MR) + return false; + + if (!C.getState()->contains(MR)) + return false; + + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << '\'' << printPretty(BO->getLHS(), C) + << "' is pointing to a constant string"; + + auto Report = std::make_unique(BT, Out.str(), + C.generateErrorNode()); + Report->addRange(BO->getLHS()->getSourceRange()); + Report->markInteresting(MR); + + // Track the allocation. + bugreporter::trackExpressionValue(Report->getErrorNode(), BO->getLHS(), + *Report); + + C.emitReport(std::move(Report)); + return true; +} + +/// Check whether a constant string is being stored. If so, emit a report. +static void checkCharPtr(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) { + const MemRegion *MR = Call.getReturnValue().getAsRegion(); + if (!MR) + return; + + ProgramStateRef State = C.getState(); + const Expr *SrcArg = Call.getArgExpr(*CallC.SourcePos); + const VarRegion *SrcVR = nullptr; + if (const auto *DRE = dyn_cast(SrcArg->IgnoreImpCasts())) + if (const auto *VD = dyn_cast(DRE->getDecl())) + SrcVR = State->getRegion(VD, C.getLocationContext()); + + if (!SrcVR) + return; + + const MemRegion *PtrMR = MR->getBaseRegion(); + if (!PtrMR) + return; + + const auto *PtrTR = PtrMR->getAs(); + if (!PtrTR) + return; + + QualType Ty = PtrTR->getLocationType(); + if (Ty->isPointerType()) + Ty = Ty->getPointeeType(); + + if (!Ty.isConstQualified()) + return; + + std::string CallName = Call.getCalleeIdentifier()->getName().str(); + std::string ArgName = SrcVR->getDecl()->getNameAsString(); + + const NoteTag *Tag = C.getNoteTag( + [=](PathSensitiveBugReport &BR) -> std::string { + if (!BR.isInteresting(MR)) + return ""; + + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + Out << '\'' << CallName << "' returns a pointer to the constant '" + << ArgName << '\''; + + return Out.str().str(); + }, + /*IsPrunable=*/false); + + State = State->add(MR); + C.addTransition(State, Tag); +} + +void StrCheckerBase::checkMemchr(const CallEvent &Call, + const CallContext &CallC, + CheckerContext &C) const { + if (EnableStr30cChecker) + checkCharPtr(Call, CallC, C); +} + +void StrCheckerBase::checkStrchr(const CallEvent &Call, + const CallContext &CallC, + CheckerContext &C) const { + if (EnableStr30cChecker) + checkCharPtr(Call, CallC, C); +} +void StrCheckerBase::checkStrrchr(const CallEvent &Call, + const CallContext &CallC, + CheckerContext &C) const { + if (EnableStr30cChecker) + checkCharPtr(Call, CallC, C); +} + +void StrCheckerBase::checkStrstr(const CallEvent &Call, + const CallContext &CallC, + CheckerContext &C) const { + if (EnableStr30cChecker) + checkCharPtr(Call, CallC, C); +} + +void StrCheckerBase::checkStrpbrk(const CallEvent &Call, + const CallContext &CallC, + CheckerContext &C) const { + if (EnableStr30cChecker) + checkCharPtr(Call, CallC, C); +} + //===----------------------------------------------------------------------===// // The following checks STR31-C rules. //===----------------------------------------------------------------------===// @@ -413,6 +568,10 @@ void StrCheckerBase::checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const { + if (EnableStr30cChecker) + if (isConstantStringModify(L, V, S, C, BT)) + return; + if (!EnableStr32cChecker) return; @@ -521,5 +680,6 @@ \ bool ento::shouldRegister##Name(const CheckerManager &) { return true; } +REGISTER_CHECKER(Str30cChecker) REGISTER_CHECKER(Str31cChecker) REGISTER_CHECKER(Str32cChecker) Index: clang/test/Analysis/cert/str30-c-notes.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/cert/str30-c-notes.cpp @@ -0,0 +1,43 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,unix,alpha.security.cert.str.30c \ +// RUN: -analyzer-output=text -verify %s + +// See the examples on the page of STR30-C: +// https://wiki.sei.cmu.edu/confluence/x/VtYxBQ + +#include "../Inputs/system-header-simulator.h" + +typedef __SIZE_TYPE__ size_t; + +void free(void *memblock); +void *malloc(size_t size); + +char *strrchr(const char *str, int c); +int puts(const char *str); + +namespace test_strrchr_bad { +const char *get_dirname(const char *pathname) { + char *slash; + slash = strrchr(pathname, '/'); + // expected-note@-1 {{'strrchr' returns a pointer to the constant 'pathname'}} + + slash = strrchr(slash, '/'); + // expected-note@-1 {{'strrchr' returns a pointer to the constant 'slash'}} + + if (slash) { + // expected-note@-1 {{'slash' is non-null}} + // expected-note@-2 {{Taking true branch}} + + *slash = '\0'; /* Undefined behavior */ + // expected-note@-1 {{'*slash' is pointing to a constant string}} + // expected-warning@-2 {{'*slash' is pointing to a constant string}} + } + return pathname; +} + +int main(void) { + puts(get_dirname(__FILE__)); + // expected-note@-1 {{Calling 'get_dirname'}} + return 0; +} +} // namespace test_strrchr_bad Index: clang/test/Analysis/cert/str30-c.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/cert/str30-c.cpp @@ -0,0 +1,58 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,unix,alpha.security.cert.str.30c \ +// RUN: -verify %s + +// See the examples on the page of STR30-C: +// https://wiki.sei.cmu.edu/confluence/x/VtYxBQ + +#include "../Inputs/system-header-simulator.h" + +typedef __SIZE_TYPE__ size_t; +typedef __typeof__((char *)0 - (char *)0) ptrdiff_t; + +void free(void *memblock); +void *malloc(size_t size); + +char *strrchr(const char *str, int c); +int puts(const char *str); + +namespace test_strrchr_bad { +const char *get_dirname(const char *pathname) { + char *slash; + slash = strrchr(pathname, '/'); + if (slash) { + *slash = '\0'; /* Undefined behavior */ + // expected-warning@-1 {{'*slash' is pointing to a constant string}} + } + return pathname; +} + +int main(void) { + puts(get_dirname(__FILE__)); + return 0; +} +} // namespace test_strrchr_bad + +namespace test_strrchr_good { +char *get_dirname(const char *pathname, char *dirname, size_t size) { + const char *slash; + slash = strrchr(pathname, '/'); + if (slash) { + ptrdiff_t slash_idx = slash - pathname; + if ((size_t)slash_idx < size) { + memcpy(dirname, pathname, slash_idx); + dirname[slash_idx] = '\0'; + return dirname; + } + } + return 0; +} + +int main(void) { + char dirname[260]; + if (get_dirname(__FILE__, dirname, sizeof(dirname))) { + puts(dirname); + } + return 0; +} +} // namespace test_strrchr_good