Skip to content

Commit ce97312

Browse files
committed
Implement BufferOverlap check for sprint/snprintf
Differential Revision: https://reviews.llvm.org/D150430
1 parent 0b42ee4 commit ce97312

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
#include "InterCheckerAPI.h"
15+
#include "clang/Basic/Builtins.h"
1516
#include "clang/Basic/CharInfo.h"
1617
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
1718
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
@@ -175,6 +176,8 @@ class CStringChecker : public Checker< eval::Call,
175176
std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)},
176177
{{CDF_MaybeBuiltin, {"bzero"}, 2}, &CStringChecker::evalBzero},
177178
{{CDF_MaybeBuiltin, {"explicit_bzero"}, 2}, &CStringChecker::evalBzero},
179+
{{CDF_MaybeBuiltin, {"sprintf"}, 2}, &CStringChecker::evalSprintf},
180+
{{CDF_MaybeBuiltin, {"snprintf"}, 2}, &CStringChecker::evalSnprintf},
178181
};
179182

180183
// These require a bit of special handling.
@@ -228,6 +231,11 @@ class CStringChecker : public Checker< eval::Call,
228231
void evalMemset(CheckerContext &C, const CallExpr *CE) const;
229232
void evalBzero(CheckerContext &C, const CallExpr *CE) const;
230233

234+
void evalSprintf(CheckerContext &C, const CallExpr *CE) const;
235+
void evalSnprintf(CheckerContext &C, const CallExpr *CE) const;
236+
void evalSprintfCommon(CheckerContext &C, const CallExpr *CE, bool IsBounded,
237+
bool IsBuiltin) const;
238+
231239
// Utility methods
232240
std::pair<ProgramStateRef , ProgramStateRef >
233241
static assumeZero(CheckerContext &C,
@@ -2352,6 +2360,51 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const {
23522360
C.addTransition(State);
23532361
}
23542362

2363+
void CStringChecker::evalSprintf(CheckerContext &C, const CallExpr *CE) const {
2364+
CurrentFunctionDescription = "'sprintf'";
2365+
bool IsBI = CE->getBuiltinCallee() == Builtin::BI__builtin___sprintf_chk;
2366+
evalSprintfCommon(C, CE, /* IsBounded */ false, IsBI);
2367+
}
2368+
2369+
void CStringChecker::evalSnprintf(CheckerContext &C, const CallExpr *CE) const {
2370+
CurrentFunctionDescription = "'snprintf'";
2371+
bool IsBI = CE->getBuiltinCallee() == Builtin::BI__builtin___snprintf_chk;
2372+
evalSprintfCommon(C, CE, /* IsBounded */ true, IsBI);
2373+
}
2374+
2375+
void CStringChecker::evalSprintfCommon(CheckerContext &C, const CallExpr *CE,
2376+
bool IsBounded, bool IsBuiltin) const {
2377+
ProgramStateRef State = C.getState();
2378+
DestinationArgExpr Dest = {CE->getArg(0), 0};
2379+
2380+
const auto NumParams = CE->getCalleeDecl()->getAsFunction()->getNumParams();
2381+
assert(CE->getNumArgs() >= NumParams);
2382+
2383+
const auto AllArguments =
2384+
llvm::make_range(CE->getArgs(), CE->getArgs() + CE->getNumArgs());
2385+
const auto VariadicArguments = drop_begin(enumerate(AllArguments), NumParams);
2386+
2387+
for (const auto &[ArgIdx, ArgExpr] : VariadicArguments) {
2388+
// We consider only string buffers
2389+
if (const QualType type = ArgExpr->getType();
2390+
!type->isAnyPointerType() ||
2391+
!type->getPointeeType()->isAnyCharacterType())
2392+
continue;
2393+
SourceArgExpr Source = {ArgExpr, unsigned(ArgIdx)};
2394+
2395+
// Ensure the buffers do not overlap.
2396+
SizeArgExpr SrcExprAsSizeDummy = {Source.Expression, Source.ArgumentIndex};
2397+
State = CheckOverlap(
2398+
C, State,
2399+
(IsBounded ? SizeArgExpr{CE->getArg(1), 1} : SrcExprAsSizeDummy), Dest,
2400+
Source);
2401+
if (!State)
2402+
return;
2403+
}
2404+
2405+
C.addTransition(State);
2406+
}
2407+
23552408
//===----------------------------------------------------------------------===//
23562409
// The driver method, and other Checker callbacks.
23572410
//===----------------------------------------------------------------------===//

clang/test/Analysis/buffer-overlap.c

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// RUN: %clang_analyze_cc1 -verify %s \
2+
// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap
3+
//
4+
// RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \
5+
// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap
6+
//
7+
// RUN: %clang_analyze_cc1 -verify %s -DVARIANT \
8+
// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap
9+
//
10+
// RUN: %clang_analyze_cc1 -verify %s -DVARIANT -DUSE_BUILTINS \
11+
// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap
12+
13+
// This provides us with four possible sprintf() definitions.
14+
15+
#ifdef USE_BUILTINS
16+
#define BUILTIN(f) __builtin_##f
17+
#else /* USE_BUILTINS */
18+
#define BUILTIN(f) f
19+
#endif /* USE_BUILTINS */
20+
21+
typedef typeof(sizeof(int)) size_t;
22+
23+
#ifdef VARIANT
24+
25+
#define __sprintf_chk BUILTIN(__sprintf_chk)
26+
#define __snprintf_chk BUILTIN(__snprintf_chk)
27+
int __sprintf_chk (char * __restrict str, int flag, size_t os,
28+
const char * __restrict fmt, ...);
29+
int __snprintf_chk (char * __restrict str, size_t len, int flag, size_t os,
30+
const char * __restrict fmt, ...);
31+
32+
#define sprintf(str, ...) __sprintf_chk(str, 0, __builtin_object_size(str, 0), __VA_ARGS__)
33+
#define snprintf(str, len, ...) __snprintf_chk(str, len, 0, __builtin_object_size(str, 0), __VA_ARGS__)
34+
35+
#else /* VARIANT */
36+
37+
#define sprintf BUILTIN(sprintf)
38+
int sprintf(char *restrict buffer, const char *restrict format, ... );
39+
int snprintf(char *restrict buffer, size_t bufsz,
40+
const char *restrict format, ... );
41+
#endif /* VARIANT */
42+
43+
void test_sprintf1() {
44+
char a[4] = {0};
45+
sprintf(a, "%d/%s", 1, a); // expected-warning{{Arguments must not be overlapping buffers}}
46+
}
47+
48+
void test_sprintf2() {
49+
char a[4] = {0};
50+
sprintf(a, "%s", a); // expected-warning{{Arguments must not be overlapping buffers}}
51+
}
52+
53+
void test_sprintf3() {
54+
char a[4] = {0};
55+
sprintf(a, "%s/%s", a, a); // expected-warning{{Arguments must not be overlapping buffers}}
56+
}
57+
58+
void test_sprintf4() {
59+
char a[4] = {0};
60+
sprintf(a, "%d", 42); // no-warning
61+
}
62+
63+
void test_sprintf5() {
64+
char a[4] = {0};
65+
char b[4] = {0};
66+
sprintf(a, "%s", b); // no-warning
67+
}
68+
69+
void test_snprintf1() {
70+
char a[4] = {0};
71+
snprintf(a, sizeof(a), "%d/%s", 1, a); // expected-warning{{Arguments must not be overlapping buffers}}
72+
}
73+
74+
void test_snprintf2() {
75+
char a[4] = {0};
76+
snprintf(a+1, sizeof(a)-1, "%d/%s", 1, a); // expected-warning{{Arguments must not be overlapping buffers}}
77+
}
78+
79+
void test_snprintf3() {
80+
char a[4] = {0};
81+
snprintf(a, sizeof(a), "%s", a); // expected-warning{{Arguments must not be overlapping buffers}}
82+
}
83+
84+
void test_snprintf4() {
85+
char a[4] = {0};
86+
snprintf(a, sizeof(a), "%s/%s", a, a); // expected-warning{{Arguments must not be overlapping buffers}}
87+
}
88+
89+
void test_snprintf5() {
90+
char a[4] = {0};
91+
snprintf(a, sizeof(a), "%d", 42); // no-warning
92+
}
93+
94+
void test_snprintf6() {
95+
char a[4] = {0};
96+
char b[4] = {0};
97+
snprintf(a, sizeof(a), "%s", b); // no-warning
98+
}

0 commit comments

Comments
 (0)