Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -800,6 +800,11 @@ Dependencies<[StrCheckerBase]>, Documentation; +def Str32cChecker : Checker<"32.c">, + HelpText<"SEI CERT checker of rules defined in STR32-C">, + Dependencies<[StrCheckerBase]>, + Documentation; + } // end "cert.str" let ParentPackage = SecurityAlpha in { Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h @@ -13,11 +13,13 @@ #ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICSIZE_H #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICSIZE_H +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSizeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "llvm/ADT/Optional.h" namespace clang { namespace ento { @@ -26,6 +28,12 @@ DefinedOrUnknownSVal getDynamicSize(ProgramStateRef State, const MemRegion *MR, SValBuilder &SVB); +/// \returns The stored dynamic size information or the array's static size +/// information for the region \p MR. +Optional getDynamicSizeInfo(ProgramStateRef State, + const MemRegion *MR, + SValBuilder &SVB); + /// \returns The stored dynamic size expression for the region \p MR. const Expr *getDynamicSizeExpr(ProgramStateRef State, const MemRegion *MR); Index: clang/lib/StaticAnalyzer/Checkers/cert/StrChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/cert/StrChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/cert/StrChecker.cpp @@ -15,6 +15,9 @@ // - '31.c' // https://wiki.sei.cmu.edu/confluence/display/c/STR31-C.+Guarantee+that+storage+for+strings+has+sufficient+space+for+character+data+and+the+null+terminator // +// - '32.c' +// https://wiki.sei.cmu.edu/confluence/display/c/STR32-C.+Do+not+pass+a+non-null-terminated+character+sequence+to+a+library+function+that+expects+a+string +// //===----------------------------------------------------------------------===// #include "AllocationState.h" @@ -39,11 +42,14 @@ struct CallContext { CallContext(Optional DestinationPos, - Optional SourcePos = None) - : DestinationPos(DestinationPos), SourcePos(SourcePos) {} + Optional SourcePos = None, + Optional LengthPos = None) + : DestinationPos(DestinationPos), SourcePos(SourcePos), + LengthPos(LengthPos) {} Optional DestinationPos; Optional SourcePos; + Optional LengthPos; }; class StrCheckerBase : public Checker { @@ -72,9 +78,12 @@ CheckerContext &C) const; void checkStrcpy(const CallEvent &Call, const CallContext &CallC, CheckerContext &C) const; + void checkStrncpy(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) const; std::unique_ptr BT; - bool EnableStr31cChecker = false; + + bool EnableStr31cChecker = false, EnableStr32cChecker = false; private: const CallDescriptionMap> CDM = { @@ -86,13 +95,20 @@ // int fscanf(FILE *stream, const char *format, ... [char *dest]); {{"fscanf", 3, 2}, {&StrCheckerBase::checkFscanf, {2}}}, // char *strcpy(char *dest, const char *src); - {{"strcpy", 2}, {&StrCheckerBase::checkStrcpy, {0, 1}}}}; + {{"strcpy", 2}, {&StrCheckerBase::checkStrcpy, {0, 1}}}, + + // The following checks STR32-C rules. + // char *strncpy(char *dest, const char *src, size_t count) + {{"strncpy", 3}, {&StrCheckerBase::checkStrncpy, {0, 1, 2}}}}; }; } // namespace // It stores whether the string is null-terminated. REGISTER_MAP_WITH_PROGRAMSTATE(NullTerminationMap, const MemRegion *, bool) +// It stores the allocations which we emit notes on. +REGISTER_LIST_WITH_PROGRAMSTATE(AllocationList, const MemRegion *) + //===----------------------------------------------------------------------===// // Helper functions. //===----------------------------------------------------------------------===// @@ -146,14 +162,15 @@ ProgramStateRef State = C.getState(); std::string CallName = Call.getCalleeIdentifier()->getName(); - Optional ArrayName = getName(Call.getArgExpr(DestPos), C); + std::string ArrayName = "the array"; + if (Optional TmpArrayName = getName(Call.getArgExpr(DestPos), C)) + ArrayName = *TmpArrayName; const NoteTag *Tag = C.getNoteTag( [=]() -> std::string { SmallString<128> Msg; llvm::raw_svector_ostream Out(Msg); - Out << '\'' << CallName << "' could write outside of " - << (ArrayName ? *ArrayName : "the array"); + Out << '\'' << CallName << "' could write outside of " << ArrayName; return Out.str(); }, @@ -164,6 +181,46 @@ C.addTransition(State, C.getPredecessor(), Tag); } +static bool isWrongAllocation(SVal DestV, SVal SrcV, CheckerContext &C) { + ProgramStateRef State = C.getState(); + SValBuilder &SVB = C.getSValBuilder(); + + // Check the size of the allocation to prevent false alarms. + const MemRegion *SrcMR = getRegion(SrcV); + const MemRegion *DestMR = getRegion(DestV); + if (!SrcMR || !DestMR) + return false; + + DefinedOrUnknownSVal SrcSize = getDynamicSize(State, SrcMR, SVB); + DefinedOrUnknownSVal DestSize = getDynamicSize(State, DestMR, SVB); + + // 'strlen(src) + integer' is most likely fine. + // FIXME: We cannot catch every '+ integer' part at the moment so we do not + // check that property for now. + SValHasDescendant HasDescendantSrc(SrcMR); + if (HasDescendantSrc.Visit(DestSize)) + return false; + + if (const MemRegion *DestSizeR = DestSize.getAsRegion()) + if (DestSizeR == SrcMR) + return false; + + // 'StringRegion' returns the size with the null-terminator. + if (const llvm::APSInt *SrcSizeInt = SVB.getKnownValue(State, SrcSize)) + if (const llvm::APSInt *DestSizeInt = SVB.getKnownValue(State, DestSize)) + if (SrcSizeInt->getZExtValue() <= DestSizeInt->getZExtValue()) + return false; + + return true; +} + +static bool isWrongAllocation(const CallEvent &Call, const CallContext &CallC, + CheckerContext &C) { + SVal DestV = Call.getArgSVal(*CallC.DestinationPos); + SVal SrcV = Call.getArgSVal(*CallC.SourcePos); + return isWrongAllocation(DestV, SrcV, C); +} + //===----------------------------------------------------------------------===// // Evaluating problematic function calls. //===----------------------------------------------------------------------===// @@ -202,37 +259,15 @@ void StrCheckerBase::checkStrcpy(const CallEvent &Call, const CallContext &CallC, CheckerContext &C) const { - if (!EnableStr31cChecker) - return; - - ProgramStateRef State = C.getState(); - SValBuilder &SVB = C.getSValBuilder(); - SVal SrcV = Call.getArgSVal(*CallC.SourcePos); - SVal DestV = Call.getArgSVal(*CallC.DestinationPos); - - // Check the size of the allocation to prevent false alarms. - const MemRegion *SrcMR = getRegion(SrcV); - const MemRegion *DestMR = getRegion(DestV); - if (!SrcMR || !DestMR) - return; - - DefinedOrUnknownSVal SrcSize = getDynamicSize(State, SrcMR, SVB); - DefinedOrUnknownSVal DestSize = getDynamicSize(State, DestMR, SVB); - - // 'strlen(src) + integer' is most likely fine. - // FIXME: We cannot catch every '+ integer' part at the moment so we do not - // check that property for now. - SValHasDescendant HasDescendantSrc(SrcMR); - if (HasDescendantSrc.Visit(DestSize)) - return; - - // 'StringRegion' returns the size with the null-terminator. - if (const llvm::APSInt *SrcSizeInt = SVB.getKnownValue(State, SrcSize)) - if (const llvm::APSInt *DestSizeInt = SVB.getKnownValue(State, DestSize)) - if (SrcSizeInt->getZExtValue() <= DestSizeInt->getZExtValue()) - return; + if (EnableStr31cChecker && isWrongAllocation(Call, CallC, C)) + createOverflowNote(Call, CallC, C); +} - createOverflowNote(Call, CallC, C); +void StrCheckerBase::checkStrncpy(const CallEvent &Call, + const CallContext &CallC, + CheckerContext &C) const { + if (EnableStr32cChecker) + createOverflowNote(Call, CallC, C); } //===----------------------------------------------------------------------===// @@ -286,15 +321,79 @@ } } -void StrCheckerBase::checkBind(SVal L, SVal V, const Stmt *S, - CheckerContext &C) const { +static bool checkAllocation(SVal L, SVal V, const Stmt *S, CheckerContext &C, + BugType &BT) { + const MemRegion *LocMR = getRegion(L); + const MemRegion *NonLocMR = getRegion(V); + if (!LocMR || !NonLocMR) + return false; + + // A C-string is null-terminated. + if (isa(NonLocMR)) + return false; + + // Only check for character allocations. + QualType Ty; + if (const auto *LocTVR = LocMR->getAs()) { + Ty = LocTVR->getValueType(); + if (!Ty.isNull() && Ty->isPointerType()) + Ty = Ty->getPointeeType(); + } + + if (Ty.isNull() || !Ty->isAnyCharacterType()) + return false; + + // See whether we have made a report so it prevent reports on rebindings. ProgramStateRef State = C.getState(); + if (State->get().contains(NonLocMR)) + return false; + + // Check for allocations we model. + Optional SizeInfo = + getDynamicSizeInfo(State, NonLocMR, C.getSValBuilder()); + if (!SizeInfo) + return false; + + // 'strlen(something) + something' is most likely fine. + // FIXME: Use the 'SValVisitor' to catch every such constructs of the symbol. + if (const SymExpr *SizeSym = SizeInfo->getSize().getAsSymExpr()) + for (const SymExpr *Sym : SizeSym->symbols()) + if (const auto *SIE = dyn_cast(Sym)) + if (SIE->getOpcode() == BO_Add) + if (const auto *SM = dyn_cast(SIE->getLHS())) + return true; + + std::string ArrayName = "The array"; + if (const auto *VR = LocMR->getAs()) + if (const VarDecl *VD = VR->getDecl()) + ArrayName = '\'' + VD->getNameAsString() + '\''; - // Check for a hand-written null-termination, e.g: 'c_string[length] = '\0';'. - const MemRegion *MR = getRegion(L); - if (!MR) - return; + const NoteTag *Tag = C.getNoteTag( + [=]() -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + Out << ArrayName + << " is allocated without considering the length of the string it " + "will store, therefore it could overflow"; + return Out.str(); + }, + /*IsPrunable=*/false); + + // Mark the string could be not null-terminated. + State = State->set(NonLocMR, false); + State = State->add(NonLocMR); + C.addTransition(State, C.getPredecessor(), Tag); + return true; +} + +void StrCheckerBase::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + if (EnableStr32cChecker) + if (checkAllocation(L, V, S, C, *BT)) + return; + + ProgramStateRef State = C.getState(); bool IsNullTermination = false; if (const llvm::APInt *Int = C.getSValBuilder().getKnownValue(State, V)) IsNullTermination = Int->isNullValue(); @@ -303,8 +402,10 @@ return; // Mark the string null-terminated. - State = State->set(MR, true); - C.addTransition(State, C.getPredecessor()); + if (const auto *MR = getRegion(L)) { + State = State->set(MR, true); + C.addTransition(State, C.getPredecessor()); + } } void ento::registerStrCheckerBase(CheckerManager &Mgr) { @@ -326,3 +427,4 @@ bool ento::shouldRegister##Name(const LangOptions &LO) { return true; } REGISTER_CHECKER(Str31cChecker) +REGISTER_CHECKER(Str32cChecker) Index: clang/lib/StaticAnalyzer/Core/DynamicSize.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/DynamicSize.cpp +++ clang/lib/StaticAnalyzer/Core/DynamicSize.cpp @@ -40,6 +40,19 @@ return MR->getMemRegionManager().getStaticSize(MR, SVB); } +Optional getDynamicSizeInfo(ProgramStateRef State, + const MemRegion *MR, + SValBuilder &SVB) { + if (const DynamicSizeInfo *SizeInfo = State->get(MR)) + return *SizeInfo; + + if (const auto *TVR = MR->getAs()) + if (TVR->getValueType()->getAsArrayTypeUnsafe()) + return DynamicSizeInfo(MR->getMemRegionManager().getStaticSize(MR, SVB)); + + return None; +} + const Expr *getDynamicSizeExpr(ProgramStateRef State, const MemRegion *MR) { if (const DynamicSizeInfo *SizeInfo = State->get(MR)) return SizeInfo->getSizeExpr(); Index: clang/test/Analysis/Inputs/system-header-simulator.h =================================================================== --- clang/test/Analysis/Inputs/system-header-simulator.h +++ clang/test/Analysis/Inputs/system-header-simulator.h @@ -38,12 +38,29 @@ extern int errno; typedef int errno_t; -size_t strlen(const char *); +void *memcpy(void *dest, const void *src, size_t count); + +size_t strlen(const char *str); char *strcpy(char *dest, const char *src); errno_t strcpy_s(char *dest, size_t destSize, const char *src); char *strncpy(char *dest, const char *src, size_t count); -void *memcpy(void *dest, const void *src, size_t count); +// Without -fms-compatibility, wchar_t isn't a builtin type. MSVC defines +// _WCHAR_T_DEFINED if wchar_t is available. Microsoft recommends that you use +// the builtin type: "Using the typedef version can cause portability +// problems", but we're ok here because we're not actually running anything. +// Also of note is this cryptic warning: "The wchar_t type is not supported +// when you compile C code". +// +// See the docs for more: +// https://msdn.microsoft.com/en-us/library/dh8che7s.aspx +#if !defined(_WCHAR_T_DEFINED) && !__cplusplus +// "Microsoft implements wchar_t as a two-byte unsigned value" +typedef unsigned short wchar_t; +#define _WCHAR_T_DEFINED +#endif + +size_t wcslen(const wchar_t *str); typedef unsigned long __darwin_pthread_key_t; typedef __darwin_pthread_key_t pthread_key_t; Index: clang/test/Analysis/cert/str32-c-notes.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/cert/str32-c-notes.cpp @@ -0,0 +1,66 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,unix,security.cert.str.32.c \ +// RUN: -analyzer-output=text -verify %s + +// See the examples on the page of STR32-C: +// https://wiki.sei.cmu.edu/confluence/display/c/STR32-C.+Do+not+pass+a+non-null-terminated+character+sequence+to+a+library+function+that+expects+a+string + +#include "../Inputs/system-header-simulator.h" + +void *realloc(void *memblock, size_t size); + +namespace test_strncpy_bad { +enum { STR_SIZE = 32 }; + +size_t func(const char *source) { + char c_str[STR_SIZE]; + // expected-note@-1 {{'c_str' initialized here}} + size_t ret = 0; + + if (source) { + // expected-note@-1 {{Assuming 'source' is non-null}} + // expected-note@-2 {{Taking true branch}} + + c_str[sizeof(c_str) - 1] = '\0'; + strncpy(c_str, source, sizeof(c_str)); + // expected-note@-1 {{'strncpy' could write outside of 'c_str'}} + + ret = strlen(c_str); + // expected-note@-1 {{'c_str' is not null-terminated}} + // expected-warning@-2 {{'c_str' is not null-terminated}} + } + return ret; +} +} // namespace test_strncpy_bad + +namespace test_realloc_bad { +wchar_t *cur_msg = NULL; +size_t cur_msg_size = 1024; +size_t cur_msg_len = 0; + +void lessen_memory_usage(void) { + if (cur_msg == NULL) { + // expected-note@-1 {{Assuming 'cur_msg' is not equal to NULL}} + // expected-note@-2 {{Taking false branch}} + return; + } + + size_t temp_size = cur_msg_size / 2 + 1; + wchar_t *temp = (wchar_t *)realloc(cur_msg, temp_size * sizeof(wchar_t)); + // expected-note@-1 {{Memory is allocated}} + // expected-note@-2 {{'temp' is allocated without considering the length of the string it will store, therefore it could overflow}} + + /* temp and cur_msg may no longer be null-terminated */ + if (temp == NULL) { + // expected-note@-1 {{Assuming 'temp' is not equal to NULL}} + // expected-note@-2 {{Taking false branch}} + return; + } + + cur_msg = temp; // no-warning: rebinding + cur_msg_size = temp_size; + cur_msg_len = wcslen(cur_msg); + // expected-note@-1 {{'cur_msg' is not null-terminated}} + // expected-warning@-2 {{'cur_msg' is not null-terminated}} +} +} // namespace test_realloc_bad Index: clang/test/Analysis/cert/str32-c.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/cert/str32-c.cpp @@ -0,0 +1,88 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,unix,security.cert.str.32.c \ +// RUN: -verify %s + +// See the examples on the page of STR32-C: +// https://wiki.sei.cmu.edu/confluence/display/c/STR32-C.+Do+not+pass+a+non-null-terminated+character+sequence+to+a+library+function+that+expects+a+string + +#include "../Inputs/system-header-simulator.h" + +void *realloc(void *memblock, size_t size); + +namespace test_strncpy_bad { +enum { STR_SIZE = 32 }; + +size_t func(const char *source) { + char c_str[STR_SIZE]; + size_t ret = 0; + + if (source) { + c_str[sizeof(c_str) - 1] = '\0'; + strncpy(c_str, source, sizeof(c_str)); + ret = strlen(c_str); + // expected-warning@-1 {{'c_str' is not null-terminated}} + } + return ret; +} +} // namespace test_strncpy_bad + +namespace test_strncpy_good { +enum { STR_SIZE = 32 }; + +size_t func(const char *src) { + char c_str[STR_SIZE]; + size_t ret = 0; + + if (src) { + strncpy(c_str, src, sizeof(c_str) - 1); + c_str[sizeof(c_str) - 1] = '\0'; + ret = strlen(c_str); + } + return ret; +} +} // namespace test_strncpy_good + +namespace test_realloc_bad { +wchar_t *cur_msg = NULL; +size_t cur_msg_size = 1024; +size_t cur_msg_len = 0; + +void lessen_memory_usage(void) { + if (cur_msg == NULL) + return; + + size_t temp_size = cur_msg_size / 2 + 1; + wchar_t *temp = (wchar_t *)realloc(cur_msg, temp_size * sizeof(wchar_t)); + /* temp and cur_msg may no longer be null-terminated */ + if (temp == NULL) + return; + + cur_msg = temp; + cur_msg_size = temp_size; + cur_msg_len = wcslen(cur_msg); + // expected-warning@-1 {{'cur_msg' is not null-terminated}} +} +} // namespace test_realloc_bad + +namespace test_realloc_good { +wchar_t *cur_msg = NULL; +size_t cur_msg_size = 1024; +size_t cur_msg_len = 0; + +void lessen_memory_usage(void) { + if (cur_msg == NULL) + return; + + size_t temp_size = cur_msg_size / 2 + 1; + wchar_t *temp = (wchar_t *)realloc(cur_msg, temp_size * sizeof(wchar_t)); + /* temp and cur_msg may no longer be null-terminated */ + if (temp == NULL) + return; + + cur_msg = temp; + /* Properly null-terminate cur_msg */ + cur_msg[temp_size - 1] = L'\0'; + cur_msg_size = temp_size; + cur_msg_len = wcslen(cur_msg); +} +} // namespace test_realloc_good Index: clang/test/Analysis/malloc.c =================================================================== --- clang/test/Analysis/malloc.c +++ clang/test/Analysis/malloc.c @@ -9,21 +9,6 @@ void clang_analyzer_eval(int); -// Without -fms-compatibility, wchar_t isn't a builtin type. MSVC defines -// _WCHAR_T_DEFINED if wchar_t is available. Microsoft recommends that you use -// the builtin type: "Using the typedef version can cause portability -// problems", but we're ok here because we're not actually running anything. -// Also of note is this cryptic warning: "The wchar_t type is not supported -// when you compile C code". -// -// See the docs for more: -// https://msdn.microsoft.com/en-us/library/dh8che7s.aspx -#if !defined(_WCHAR_T_DEFINED) -// "Microsoft implements wchar_t as a two-byte unsigned value" -typedef unsigned short wchar_t; -#define _WCHAR_T_DEFINED -#endif // !defined(_WCHAR_T_DEFINED) - typedef __typeof(sizeof(int)) size_t; void *malloc(size_t); void *alloca(size_t);