ArnaudBienner created this revision.
Herald added subscribers: steakhal, martong.
Herald added a reviewer: NoQ.
Herald added a project: All.
ArnaudBienner edited the summary of this revision.
ArnaudBienner added a subscriber: dergachev.a.
ArnaudBienner published this revision for review.
Herald added a project: clang.
Herald added a subscriber: cfe-commits.
I discussed about this with you @dergachev.a a long time ago on the mailing
list ("static or dynamic code analysis for undefined behavior in sprintf") and
finally took the time to implement something :)
Let me know what you think about this.
Repository:
rG LLVM Github Monorepo
https://reviews.llvm.org/D150430
Files:
clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
clang/test/Analysis/buffer-overlap.c
Index: clang/test/Analysis/buffer-overlap.c
===================================================================
--- /dev/null
+++ clang/test/Analysis/buffer-overlap.c
@@ -0,0 +1,56 @@
+// 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_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}}
+}
Index: clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
===================================================================
--- clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp
+++ 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<ProgramStateRef , ProgramStateRef >
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 index 3:
+ // sprintf(char *buffer, const char* format, ... /* format arguments */);
+ unsigned int format_arguments_start_idx = 3;
+ // 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.
//===----------------------------------------------------------------------===//
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits