Skip to content

Commit b85d0c5

Browse files
[clang][analyzer] Model strxfrm (#156507)
Signature: ```c size_t strxfrm(char *dest, const char *src, size_t n); ``` The modeling covers: * `src` can never be null * `dest` can be null only if n is 0, and then the return value is some unspecified positive integer * `src` and `dest` must not overlap * `dest` must have at least `n` bytes of capacity * The return value can either be: - `< n`, and the contents of the buffer pointed by `dest` is invalidated - `>= n`, and the contents of the buffer pointed by `dest` is marked as undefined CPP-6854
1 parent 12631c8 commit b85d0c5

File tree

2 files changed

+207
-0
lines changed

2 files changed

+207
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ class CStringChecker
163163
{{CDM::CLibrary, {"strcasecmp"}, 2}, &CStringChecker::evalStrcasecmp},
164164
{{CDM::CLibrary, {"strncasecmp"}, 3}, &CStringChecker::evalStrncasecmp},
165165
{{CDM::CLibrary, {"strsep"}, 2}, &CStringChecker::evalStrsep},
166+
{{CDM::CLibrary, {"strxfrm"}, 3}, &CStringChecker::evalStrxfrm},
166167
{{CDM::CLibrary, {"bcopy"}, 3}, &CStringChecker::evalBcopy},
167168
{{CDM::CLibrary, {"bcmp"}, 3},
168169
std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)},
@@ -211,6 +212,8 @@ class CStringChecker
211212
bool ReturnEnd, bool IsBounded, ConcatFnKind appendK,
212213
bool returnPtr = true) const;
213214

215+
void evalStrxfrm(CheckerContext &C, const CallEvent &Call) const;
216+
214217
void evalStrcat(CheckerContext &C, const CallEvent &Call) const;
215218
void evalStrncat(CheckerContext &C, const CallEvent &Call) const;
216219
void evalStrlcat(CheckerContext &C, const CallEvent &Call) const;
@@ -2243,6 +2246,106 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call,
22432246
C.addTransition(state);
22442247
}
22452248

2249+
void CStringChecker::evalStrxfrm(CheckerContext &C,
2250+
const CallEvent &Call) const {
2251+
// size_t strxfrm(char *dest, const char *src, size_t n);
2252+
CurrentFunctionDescription = "locale transformation function";
2253+
2254+
ProgramStateRef State = C.getState();
2255+
const LocationContext *LCtx = C.getLocationContext();
2256+
SValBuilder &SVB = C.getSValBuilder();
2257+
2258+
// Get arguments
2259+
DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}};
2260+
SourceArgExpr Source = {{Call.getArgExpr(1), 1}};
2261+
SizeArgExpr Size = {{Call.getArgExpr(2), 2}};
2262+
2263+
// `src` can never be null
2264+
SVal SrcVal = State->getSVal(Source.Expression, LCtx);
2265+
State = checkNonNull(C, State, Source, SrcVal);
2266+
if (!State)
2267+
return;
2268+
2269+
// Buffer must not overlap
2270+
State = CheckOverlap(C, State, Size, Dest, Source, CK_Regular);
2271+
if (!State)
2272+
return;
2273+
2274+
// The function returns an implementation-defined length needed for
2275+
// transformation
2276+
SVal RetVal = SVB.conjureSymbolVal(Call, C.blockCount());
2277+
2278+
auto BindReturnAndTransition = [&RetVal, &Call, LCtx,
2279+
&C](ProgramStateRef State) {
2280+
if (State) {
2281+
State = State->BindExpr(Call.getOriginExpr(), LCtx, RetVal);
2282+
C.addTransition(State);
2283+
}
2284+
};
2285+
2286+
// Check if size is zero
2287+
SVal SizeVal = State->getSVal(Size.Expression, LCtx);
2288+
QualType SizeTy = Size.Expression->getType();
2289+
2290+
auto [StateZeroSize, StateSizeNonZero] =
2291+
assumeZero(C, State, SizeVal, SizeTy);
2292+
2293+
// We can't assume anything about size, just bind the return value and be done
2294+
if (!StateZeroSize && !StateSizeNonZero)
2295+
return BindReturnAndTransition(State);
2296+
2297+
// If `n` is 0, we just return the implementation defined length
2298+
if (StateZeroSize && !StateSizeNonZero)
2299+
return BindReturnAndTransition(StateZeroSize);
2300+
2301+
// If `n` is not 0, `dest` can not be null.
2302+
SVal DestVal = StateSizeNonZero->getSVal(Dest.Expression, LCtx);
2303+
StateSizeNonZero = checkNonNull(C, StateSizeNonZero, Dest, DestVal);
2304+
if (!StateSizeNonZero)
2305+
return;
2306+
2307+
// Check that we can write to the destination buffer
2308+
StateSizeNonZero = CheckBufferAccess(C, StateSizeNonZero, Dest, Size,
2309+
AccessKind::write, CK_Regular);
2310+
if (!StateSizeNonZero)
2311+
return;
2312+
2313+
// Success: return value < `n`
2314+
// Failure: return value >= `n`
2315+
auto ComparisonVal = SVB.evalBinOp(StateSizeNonZero, BO_LT, RetVal, SizeVal,
2316+
SVB.getConditionType())
2317+
.getAs<DefinedOrUnknownSVal>();
2318+
if (!ComparisonVal) {
2319+
// Fallback: invalidate the buffer.
2320+
StateSizeNonZero = invalidateDestinationBufferBySize(
2321+
C, StateSizeNonZero, Dest.Expression, Call.getCFGElementRef(), DestVal,
2322+
SizeVal, Size.Expression->getType());
2323+
return BindReturnAndTransition(StateSizeNonZero);
2324+
}
2325+
2326+
auto [StateSuccess, StateFailure] = StateSizeNonZero->assume(*ComparisonVal);
2327+
2328+
if (StateSuccess) {
2329+
// The transformation invalidated the buffer.
2330+
StateSuccess = invalidateDestinationBufferBySize(
2331+
C, StateSuccess, Dest.Expression, Call.getCFGElementRef(), DestVal,
2332+
SizeVal, Size.Expression->getType());
2333+
BindReturnAndTransition(StateSuccess);
2334+
// Fallthrough: We also want to add a transition to the failure state below.
2335+
}
2336+
2337+
if (StateFailure) {
2338+
// `dest` buffer content is undefined
2339+
if (auto DestLoc = DestVal.getAs<loc::MemRegionVal>()) {
2340+
StateFailure = StateFailure->killBinding(*DestLoc);
2341+
StateFailure =
2342+
StateFailure->bindDefaultInitial(*DestLoc, UndefinedVal{}, LCtx);
2343+
}
2344+
2345+
BindReturnAndTransition(StateFailure);
2346+
}
2347+
}
2348+
22462349
void CStringChecker::evalStrcmp(CheckerContext &C,
22472350
const CallEvent &Call) const {
22482351
//int strcmp(const char *s1, const char *s2);

clang/test/Analysis/string.c

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1789,3 +1789,107 @@ void CWE124_Buffer_Underwrite__malloc_char_memcpy() {
17891789
free(dataBuffer);
17901790
}
17911791
#endif
1792+
1793+
//===----------------------------------------------------------------------===
1794+
// strxfrm()
1795+
// It is not a built-in.
1796+
//===----------------------------------------------------------------------===
1797+
1798+
size_t strxfrm(char *dest, const char *src, size_t n);
1799+
1800+
void strxfrm_null_dest(const char *src) {
1801+
strxfrm(NULL, src, 0); // no warning
1802+
strxfrm(NULL, src, 10); // expected-warning {{Null pointer passed as 1st argument}}
1803+
}
1804+
1805+
void strxfrm_null_source(char *dest) {
1806+
strxfrm(dest, NULL, 0); // expected-warning {{Null pointer passed as 2nd argument}}
1807+
}
1808+
1809+
#ifndef SUPPRESS_OUT_OF_BOUND
1810+
void strxfrm_overflow(const char *src) {
1811+
char dest[10];
1812+
strxfrm(dest, src, 55); // expected-warning {{Locale transformation function overflows the destination buffer}}
1813+
}
1814+
#endif
1815+
1816+
void strxfrm_source_smaller() {
1817+
char dest[10];
1818+
char source[5];
1819+
strxfrm(dest, source, 10);
1820+
}
1821+
1822+
void strxfrm_overlap(char *dest) {
1823+
strxfrm(dest, dest, 10); // expected-warning {{Arguments must not be overlapping buffers}}
1824+
}
1825+
1826+
void strxfrm_regular(const char *src) {
1827+
size_t n = strxfrm(NULL, src, 0);
1828+
char *dest = (char*)malloc(n + 1);
1829+
strxfrm(dest, src, n);
1830+
free(dest);
1831+
}
1832+
1833+
void clang_analyzer_warnIfReached();
1834+
1835+
int strxfrm_dest_undef(const char *src, int oracle) {
1836+
char dest[5] = {0};
1837+
clang_analyzer_eval(dest[0] == 0); // expected-warning {{TRUE}}
1838+
1839+
size_t n = strxfrm(dest, src, sizeof(dest));
1840+
1841+
if (oracle >= sizeof(dest) || oracle < 0) {
1842+
return 0;
1843+
}
1844+
1845+
int c = 0;
1846+
if (n >= sizeof(dest)) {
1847+
// Since accessing uninitialized sinks the execution, use this trick to check all positions
1848+
switch (oracle) {
1849+
case 0:
1850+
c = dest[0]; // expected-warning {{Assigned value is uninitialized}}
1851+
break;
1852+
case 1:
1853+
c = dest[1]; // expected-warning {{Assigned value is uninitialized}}
1854+
break;
1855+
case 2:
1856+
c = dest[2]; // expected-warning {{Assigned value is uninitialized}}
1857+
break;
1858+
case 3:
1859+
c = dest[3]; // expected-warning {{Assigned value is uninitialized}}
1860+
break;
1861+
case 4:
1862+
c = dest[4]; // expected-warning {{Assigned value is uninitialized}}
1863+
break;
1864+
default:
1865+
clang_analyzer_warnIfReached();
1866+
}
1867+
} else {
1868+
clang_analyzer_eval(n >= 0); // expected-warning {{TRUE}}
1869+
clang_analyzer_eval(dest[0] == 0); // expected-warning {{UNKNOWN}}
1870+
clang_analyzer_eval(dest[1] == 0); // expected-warning {{UNKNOWN}}
1871+
clang_analyzer_eval(dest[2] == 0); // expected-warning {{UNKNOWN}}
1872+
clang_analyzer_eval(dest[3] == 0); // expected-warning {{UNKNOWN}}
1873+
clang_analyzer_eval(dest[4] == 0); // expected-warning {{UNKNOWN}}
1874+
}
1875+
return c;
1876+
}
1877+
1878+
int strxfrm_unknown_dest_undef(const char *src, int oracle) {
1879+
size_t n = strlen(src);
1880+
1881+
char *dest = (char*)malloc(n * 2);
1882+
1883+
size_t n2 = strxfrm(dest, src, n);
1884+
1885+
int c = 0;
1886+
1887+
if (n2 < n) {
1888+
c += dest[50];
1889+
} else {
1890+
c += dest[50]; // expected-warning {{Assigned value is uninitialized}}
1891+
}
1892+
1893+
free(dest);
1894+
return c;
1895+
}

0 commit comments

Comments
 (0)