diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "InterCheckerAPI.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/CharInfo.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -175,6 +176,8 @@ std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)}, {{CDF_MaybeBuiltin, {"bzero"}, 2}, &CStringChecker::evalBzero}, {{CDF_MaybeBuiltin, {"explicit_bzero"}, 2}, &CStringChecker::evalBzero}, + {{CDF_MaybeBuiltin, {"sprintf"}}, &CStringChecker::evalSprintf}, + {{CDF_MaybeBuiltin, {"snprintf"}}, &CStringChecker::evalSnprintf}, }; // These require a bit of special handling. @@ -228,6 +231,11 @@ void evalMemset(CheckerContext &C, const CallExpr *CE) const; void evalBzero(CheckerContext &C, const CallExpr *CE) const; + void evalSprintf(CheckerContext &C, const CallExpr *CE) const; + void evalSnprintf(CheckerContext &C, const CallExpr *CE) const; + void evalSprintfCommon(CheckerContext &C, const CallExpr *CE, bool IsBounded, + bool IsBuiltin) const; + // Utility methods std::pair static assumeZero(CheckerContext &C, @@ -2352,6 +2360,61 @@ C.addTransition(State); } +void CStringChecker::evalSprintf(CheckerContext &C, const CallExpr *CE) const { + CurrentFunctionDescription = "'sprintf'"; + bool IsBI = CE->getBuiltinCallee() == Builtin::BI__builtin___sprintf_chk; + evalSprintfCommon(C, CE, /* IsBounded */ false, IsBI); +} + +void CStringChecker::evalSnprintf(CheckerContext &C, const CallExpr *CE) const { + CurrentFunctionDescription = "'snprintf'"; + bool IsBI = CE->getBuiltinCallee() == Builtin::BI__builtin___snprintf_chk; + evalSprintfCommon(C, CE, /* IsBounded */ true, IsBI); +} + +void CStringChecker::evalSprintfCommon(CheckerContext &C, const CallExpr *CE, + bool IsBounded, bool IsBuiltin) const { + ProgramStateRef state = C.getState(); + DestinationArgExpr Dest = {CE->getArg(0), 0}; + + // Check that the source and destination do not overlap. + // Iterate over CE->getNumArgs(), skipping all parameters which are not format + // arguments + // For sprintf case, it starts at position 3/index 2: + // sprintf(char *buffer, const char* format, ... /* format arguments */); + unsigned int format_arguments_start_idx = 2; + // snprintf case: one extra extra arguments for size + // int snprintf(char *buffer, size_t bufsz, const char *format, + // ... /* format arguments */); + if (IsBounded) + format_arguments_start_idx++; + // built in functions have two extra arguments: flag and os, before format + // arguments + // int __builtin___sprintf_chk (char *buffer, int flag, size_t os, + // const char *format, + // ... /* format arguments */); + if (IsBuiltin) + format_arguments_start_idx += 2; + + for (unsigned int i = format_arguments_start_idx; i < CE->getNumArgs(); ++i) { + const Expr *expr = CE->getArg(i); + // We consider only string buffers + if (!expr->getType()->isAnyPointerType() || + !expr->getType()->getPointeeType()->isAnyCharacterType()) + continue; + SourceArgExpr Source = {expr, i}; + + // Ensure the buffers do not overlap. + SizeArgExpr SrcExprAsSizeDummy = {Source.Expression, Source.ArgumentIndex}; + state = CheckOverlap( + C, state, + (IsBounded ? SizeArgExpr{CE->getArg(1), 1} : SrcExprAsSizeDummy), Dest, + Source); + if (!state) + return; + } +} + //===----------------------------------------------------------------------===// // The driver method, and other Checker callbacks. //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/buffer-overlap.c b/clang/test/Analysis/buffer-overlap.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/buffer-overlap.c @@ -0,0 +1,76 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap +// +// RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap +// +// RUN: %clang_analyze_cc1 -verify %s -DVARIANT \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap +// +// RUN: %clang_analyze_cc1 -verify %s -DVARIANT -DUSE_BUILTINS \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap + +// This provides us with four possible sprintf() definitions. + +#ifdef USE_BUILTINS +#define BUILTIN(f) __builtin_##f +#else /* USE_BUILTINS */ +#define BUILTIN(f) f +#endif /* USE_BUILTINS */ + +typedef typeof(sizeof(int)) size_t; + +#ifdef VARIANT + +#define __sprintf_chk BUILTIN(__sprintf_chk) +#define __snprintf_chk BUILTIN(__snprintf_chk) +int __sprintf_chk (char * __restrict str, int flag, size_t os, + const char * __restrict fmt, ...); +int __snprintf_chk (char * __restrict str, size_t len, int flag, size_t os, + const char * __restrict fmt, ...); + +#define sprintf(str, ...) __sprintf_chk(str, 0, __builtin_object_size(str, 0), __VA_ARGS__) +#define snprintf(str, len, ...) __snprintf_chk(str, len, 0, __builtin_object_size(str, 0), __VA_ARGS__) + +#else /* VARIANT */ + +#define sprintf BUILTIN(sprintf) +int sprintf(char *restrict buffer, const char *restrict format, ... ); +int snprintf(char *restrict buffer, size_t bufsz, + const char *restrict format, ... ); +#endif /* VARIANT */ + +void test_sprintf1() { + char a[4] = {0}; + sprintf(a, "%d/%s", 1, a); // expected-warning{{overlapping}} +} + +void test_sprintf2() { + char a[4] = {0}; + sprintf(a, "%s", a); // expected-warning{{overlapping}} +} + +void test_sprintf3() { + char a[4] = {0}; + sprintf(a, "%s/%s", a, a); // expected-warning{{overlapping}} +} + +void test_snprintf1() { + char a[4] = {0}; + snprintf(a, sizeof(a), "%d/%s", 1, a); // expected-warning{{overlapping}} +} + +void test_snprintf2() { + char a[4] = {0}; + snprintf(a+1, sizeof(a)-1, "%d/%s", 1, a); // expected-warning{{overlapping}} +} + +void test_snprintf3() { + char a[4] = {0}; + snprintf(a, sizeof(a), "%s", a); // expected-warning{{overlapping}} +} + +void test_snprintf4() { + char a[4] = {0}; + snprintf(a, sizeof(a), "%s/%s", a, a); // expected-warning{{overlapping}} +}