diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index cfc6d34a75ca2..36f316df0c3ff 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -163,6 +163,7 @@ class CStringChecker {{CDM::CLibrary, {"strcasecmp"}, 2}, &CStringChecker::evalStrcasecmp}, {{CDM::CLibrary, {"strncasecmp"}, 3}, &CStringChecker::evalStrncasecmp}, {{CDM::CLibrary, {"strsep"}, 2}, &CStringChecker::evalStrsep}, + {{CDM::CLibrary, {"strxfrm"}, 3}, &CStringChecker::evalStrxfrm}, {{CDM::CLibrary, {"bcopy"}, 3}, &CStringChecker::evalBcopy}, {{CDM::CLibrary, {"bcmp"}, 3}, std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)}, @@ -211,6 +212,8 @@ class CStringChecker bool ReturnEnd, bool IsBounded, ConcatFnKind appendK, bool returnPtr = true) const; + void evalStrxfrm(CheckerContext &C, const CallEvent &Call) const; + void evalStrcat(CheckerContext &C, const CallEvent &Call) const; void evalStrncat(CheckerContext &C, const CallEvent &Call) const; void evalStrlcat(CheckerContext &C, const CallEvent &Call) const; @@ -2243,6 +2246,106 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, C.addTransition(state); } +void CStringChecker::evalStrxfrm(CheckerContext &C, + const CallEvent &Call) const { + // size_t strxfrm(char *dest, const char *src, size_t n); + CurrentFunctionDescription = "locale transformation function"; + + ProgramStateRef State = C.getState(); + const LocationContext *LCtx = C.getLocationContext(); + SValBuilder &SVB = C.getSValBuilder(); + + // Get arguments + DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}}; + SourceArgExpr Source = {{Call.getArgExpr(1), 1}}; + SizeArgExpr Size = {{Call.getArgExpr(2), 2}}; + + // `src` can never be null + SVal SrcVal = State->getSVal(Source.Expression, LCtx); + State = checkNonNull(C, State, Source, SrcVal); + if (!State) + return; + + // Buffer must not overlap + State = CheckOverlap(C, State, Size, Dest, Source, CK_Regular); + if (!State) + return; + + // The function returns an implementation-defined length needed for + // transformation + SVal RetVal = SVB.conjureSymbolVal(Call, C.blockCount()); + + auto BindReturnAndTransition = [&RetVal, &Call, LCtx, + &C](ProgramStateRef State) { + if (State) { + State = State->BindExpr(Call.getOriginExpr(), LCtx, RetVal); + C.addTransition(State); + } + }; + + // Check if size is zero + SVal SizeVal = State->getSVal(Size.Expression, LCtx); + QualType SizeTy = Size.Expression->getType(); + + auto [StateZeroSize, StateSizeNonZero] = + assumeZero(C, State, SizeVal, SizeTy); + + // We can't assume anything about size, just bind the return value and be done + if (!StateZeroSize && !StateSizeNonZero) + return BindReturnAndTransition(State); + + // If `n` is 0, we just return the implementation defined length + if (StateZeroSize && !StateSizeNonZero) + return BindReturnAndTransition(StateZeroSize); + + // If `n` is not 0, `dest` can not be null. + SVal DestVal = StateSizeNonZero->getSVal(Dest.Expression, LCtx); + StateSizeNonZero = checkNonNull(C, StateSizeNonZero, Dest, DestVal); + if (!StateSizeNonZero) + return; + + // Check that we can write to the destination buffer + StateSizeNonZero = CheckBufferAccess(C, StateSizeNonZero, Dest, Size, + AccessKind::write, CK_Regular); + if (!StateSizeNonZero) + return; + + // Success: return value < `n` + // Failure: return value >= `n` + auto ComparisonVal = SVB.evalBinOp(StateSizeNonZero, BO_LT, RetVal, SizeVal, + SVB.getConditionType()) + .getAs(); + if (!ComparisonVal) { + // Fallback: invalidate the buffer. + StateSizeNonZero = invalidateDestinationBufferBySize( + C, StateSizeNonZero, Dest.Expression, Call.getCFGElementRef(), DestVal, + SizeVal, Size.Expression->getType()); + return BindReturnAndTransition(StateSizeNonZero); + } + + auto [StateSuccess, StateFailure] = StateSizeNonZero->assume(*ComparisonVal); + + if (StateSuccess) { + // The transformation invalidated the buffer. + StateSuccess = invalidateDestinationBufferBySize( + C, StateSuccess, Dest.Expression, Call.getCFGElementRef(), DestVal, + SizeVal, Size.Expression->getType()); + BindReturnAndTransition(StateSuccess); + // Fallthrough: We also want to add a transition to the failure state below. + } + + if (StateFailure) { + // `dest` buffer content is undefined + if (auto DestLoc = DestVal.getAs()) { + StateFailure = StateFailure->killBinding(*DestLoc); + StateFailure = + StateFailure->bindDefaultInitial(*DestLoc, UndefinedVal{}, LCtx); + } + + BindReturnAndTransition(StateFailure); + } +} + void CStringChecker::evalStrcmp(CheckerContext &C, const CallEvent &Call) const { //int strcmp(const char *s1, const char *s2); diff --git a/clang/test/Analysis/string.c b/clang/test/Analysis/string.c index e017aff3b4a1d..cdd36275568e3 100644 --- a/clang/test/Analysis/string.c +++ b/clang/test/Analysis/string.c @@ -1789,3 +1789,107 @@ void CWE124_Buffer_Underwrite__malloc_char_memcpy() { free(dataBuffer); } #endif + +//===----------------------------------------------------------------------=== +// strxfrm() +// It is not a built-in. +//===----------------------------------------------------------------------=== + +size_t strxfrm(char *dest, const char *src, size_t n); + +void strxfrm_null_dest(const char *src) { + strxfrm(NULL, src, 0); // no warning + strxfrm(NULL, src, 10); // expected-warning {{Null pointer passed as 1st argument}} +} + +void strxfrm_null_source(char *dest) { + strxfrm(dest, NULL, 0); // expected-warning {{Null pointer passed as 2nd argument}} +} + +#ifndef SUPPRESS_OUT_OF_BOUND +void strxfrm_overflow(const char *src) { + char dest[10]; + strxfrm(dest, src, 55); // expected-warning {{Locale transformation function overflows the destination buffer}} +} +#endif + +void strxfrm_source_smaller() { + char dest[10]; + char source[5]; + strxfrm(dest, source, 10); +} + +void strxfrm_overlap(char *dest) { + strxfrm(dest, dest, 10); // expected-warning {{Arguments must not be overlapping buffers}} +} + +void strxfrm_regular(const char *src) { + size_t n = strxfrm(NULL, src, 0); + char *dest = (char*)malloc(n + 1); + strxfrm(dest, src, n); + free(dest); +} + +void clang_analyzer_warnIfReached(); + +int strxfrm_dest_undef(const char *src, int oracle) { + char dest[5] = {0}; + clang_analyzer_eval(dest[0] == 0); // expected-warning {{TRUE}} + + size_t n = strxfrm(dest, src, sizeof(dest)); + + if (oracle >= sizeof(dest) || oracle < 0) { + return 0; + } + + int c = 0; + if (n >= sizeof(dest)) { + // Since accessing uninitialized sinks the execution, use this trick to check all positions + switch (oracle) { + case 0: + c = dest[0]; // expected-warning {{Assigned value is uninitialized}} + break; + case 1: + c = dest[1]; // expected-warning {{Assigned value is uninitialized}} + break; + case 2: + c = dest[2]; // expected-warning {{Assigned value is uninitialized}} + break; + case 3: + c = dest[3]; // expected-warning {{Assigned value is uninitialized}} + break; + case 4: + c = dest[4]; // expected-warning {{Assigned value is uninitialized}} + break; + default: + clang_analyzer_warnIfReached(); + } + } else { + clang_analyzer_eval(n >= 0); // expected-warning {{TRUE}} + clang_analyzer_eval(dest[0] == 0); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(dest[1] == 0); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(dest[2] == 0); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(dest[3] == 0); // expected-warning {{UNKNOWN}} + clang_analyzer_eval(dest[4] == 0); // expected-warning {{UNKNOWN}} + } + return c; +} + +int strxfrm_unknown_dest_undef(const char *src, int oracle) { + size_t n = strlen(src); + + char *dest = (char*)malloc(n * 2); + + size_t n2 = strxfrm(dest, src, n); + + int c = 0; + + if (n2 < n) { + c += dest[50]; + } else { + c += dest[50]; // expected-warning {{Assigned value is uninitialized}} + } + + free(dest); + return c; +}