Skip to content

Commit f048c55

Browse files
Report on undefined size with non-null buffer
1 parent 9db5a4a commit f048c55

File tree

5 files changed

+54
-26
lines changed

5 files changed

+54
-26
lines changed

clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,7 @@ class OperatorKind {
112112
OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK,
113113
bool IsBinary);
114114

115-
std::optional<DefinedSVal> getPointeeDefVal(SVal PtrSVal,
116-
ProgramStateRef State);
115+
std::optional<SVal> getPointeeVal(SVal PtrSVal, ProgramStateRef State);
117116

118117
} // namespace ento
119118

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ void MallocChecker::preGetdelim(const CallEvent &Call,
14411441
return;
14421442

14431443
ProgramStateRef State = C.getState();
1444-
const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State);
1444+
const auto LinePtr = getPointeeVal(Call.getArgSVal(0), State);
14451445
if (!LinePtr)
14461446
return;
14471447

@@ -1470,8 +1470,10 @@ void MallocChecker::checkGetdelim(const CallEvent &Call,
14701470

14711471
SValBuilder &SVB = C.getSValBuilder();
14721472

1473-
const auto LinePtr = getPointeeDefVal(Call.getArgSVal(0), State);
1474-
const auto Size = getPointeeDefVal(Call.getArgSVal(1), State);
1473+
const auto LinePtr =
1474+
getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>();
1475+
const auto Size =
1476+
getPointeeVal(Call.getArgSVal(1), State)->getAs<DefinedSVal>();
14751477
if (!LinePtr || !Size || !LinePtr->getAsRegion())
14761478
return;
14771479

clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -236,9 +236,7 @@ class StreamChecker : public Checker<check::PreCall, eval::Call,
236236
BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error",
237237
/*SuppressOnSink =*/true};
238238
BugType BT_SizeNull{this, "NULL size pointer", "Stream handling error"};
239-
BugType BT_SizeGreaterThanBufferSize{
240-
this, "Size greater than the allocated buffer size",
241-
categories::MemoryError};
239+
BugType BT_IllegalSize{this, "Invalid buffer size", categories::MemoryError};
242240

243241
public:
244242
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
@@ -1221,28 +1219,50 @@ StreamChecker::ensurePtrNotNull(SVal PtrVal, const Expr *PtrExpr,
12211219
ProgramStateRef StreamChecker::ensureGetdelimBufferAndSizeCorrect(
12221220
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
12231221
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
1224-
// If the argument `*n` is non-zero, `*lineptr` must point to an object of
1225-
// size at least `*n` bytes, or a `NULL` pointer.
12261222
static constexpr char SizeGreaterThanBufferSize[] =
12271223
"The buffer from the first argument is smaller than the size "
12281224
"specified by the second parameter";
1225+
static constexpr char SizeUndef[] =
1226+
"The buffer from the first argument is not NULL, but the size specified "
1227+
"by the second parameter is undefined.";
1228+
1229+
auto EmitBugReport = [this, &C, SizePtrExpr,
1230+
LinePtrPtrExpr](const ProgramStateRef &BugState,
1231+
const char *ErrMsg) {
1232+
if (ExplodedNode *N = C.generateErrorNode(BugState)) {
1233+
auto R =
1234+
std::make_unique<PathSensitiveBugReport>(BT_IllegalSize, ErrMsg, N);
1235+
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1236+
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1237+
C.emitReport(std::move(R));
1238+
}
1239+
};
12291240

12301241
// We have a pointer to a pointer to the buffer, and a pointer to the size.
12311242
// We want what they point at.
1232-
auto LinePtrSVal = getPointeeDefVal(LinePtrPtrSVal, State);
1233-
auto NSVal = getPointeeDefVal(SizePtrSVal, State);
1234-
if (!LinePtrSVal || !NSVal)
1243+
auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
1244+
auto NSVal = getPointeeVal(SizePtrSVal, State);
1245+
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
12351246
return nullptr;
12361247

12371248
assert(LinePtrPtrExpr && SizePtrExpr);
12381249

12391250
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
12401251
if (LinePtrNotNull && !LinePtrNull) {
1252+
// If `*lineptr` is not null, but `*n` is undefined, there is UB.
1253+
if (NSVal->isUndef()) {
1254+
EmitBugReport(LinePtrNotNull, SizeUndef);
1255+
return nullptr;
1256+
}
1257+
1258+
// If it is defined, and known, its size must be less than or equal to
1259+
// the buffer size.
1260+
auto NDefSVal = NSVal->getAs<DefinedSVal>();
12411261
auto &SVB = C.getSValBuilder();
12421262
auto LineBufSize =
12431263
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
12441264
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
1245-
*NSVal, SVB.getConditionType())
1265+
*NDefSVal, SVB.getConditionType())
12461266
.getAs<DefinedOrUnknownSVal>();
12471267
if (!LineBufSizeGtN) {
12481268
return LinePtrNotNull;
@@ -1251,13 +1271,7 @@ ProgramStateRef StreamChecker::ensureGetdelimBufferAndSizeCorrect(
12511271
return LineBufSizeOk;
12521272
}
12531273

1254-
if (ExplodedNode *N = C.generateErrorNode(LinePtrNotNull)) {
1255-
auto R = std::make_unique<PathSensitiveBugReport>(
1256-
BT_SizeGreaterThanBufferSize, SizeGreaterThanBufferSize, N);
1257-
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
1258-
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
1259-
C.emitReport(std::move(R));
1260-
}
1274+
EmitBugReport(LinePtrNotNull, SizeGreaterThanBufferSize);
12611275
return nullptr;
12621276
}
12631277
return State;
@@ -1337,7 +1351,7 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
13371351
// The buffer size `*n` must be enough to hold the whole line, and
13381352
// greater than the return value, since it has to account for '\0'.
13391353
auto SizePtrSval = Call.getArgSVal(1);
1340-
auto NVal = getPointeeDefVal(SizePtrSval, State);
1354+
auto NVal = getPointeeVal(SizePtrSval, State);
13411355
if (NVal) {
13421356
StateNotFailed = StateNotFailed->assume(
13431357
E.SVB
@@ -1362,7 +1376,7 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
13621376
StateFailed = E.setStreamState(
13631377
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
13641378
// On failure, the content of the buffer is undefined.
1365-
if (auto NewLinePtr = getPointeeDefVal(Call.getArgSVal(0), State)) {
1379+
if (auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State)) {
13661380
StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),
13671381
C.getLocationContext());
13681382
}

clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,9 @@ OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK,
183183
}
184184
}
185185

186-
std::optional<DefinedSVal> getPointeeDefVal(SVal PtrSVal,
187-
ProgramStateRef State) {
186+
std::optional<SVal> getPointeeVal(SVal PtrSVal, ProgramStateRef State) {
188187
if (const auto *Ptr = PtrSVal.getAsRegion()) {
189-
return State->getSVal(Ptr).getAs<DefinedSVal>();
188+
return State->getSVal(Ptr);
190189
}
191190
return std::nullopt;
192191
}

clang/test/Analysis/getline-stream.c

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ void test_getline_buffer_smaller_size() {
121121
free(buffer);
122122
}
123123

124+
void test_getline_buffer_undef_size() {
125+
FILE *F1 = tmpfile();
126+
if (!F1)
127+
return;
128+
129+
char *buffer = malloc(100);
130+
size_t n;
131+
if (buffer != NULL)
132+
getline(&buffer, &n, F1); // expected-warning {{The buffer from the first argument is not NULL, but the size specified by the second parameter is undefined}}
133+
fclose(F1);
134+
free(buffer);
135+
}
136+
137+
124138
void test_getline_null_buffer() {
125139
FILE *F1 = tmpfile();
126140
if (!F1)

0 commit comments

Comments
 (0)