Skip to content

Commit 730ca47

Browse files
[clang][analyzer] Model getline/getdelim preconditions and evaluation (#83027)
According to POSIX 2018. 1. lineptr, n and stream can not be NULL. 2. If *n is non-zero, *lineptr must point to a region of at least *n bytes, or be a NULL pointer. Additionally, if *lineptr is not NULL, *n must not be undefined.
1 parent a62441d commit 730ca47

File tree

7 files changed

+554
-13
lines changed

7 files changed

+554
-13
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515

1616
#include "ProgramState_Fwd.h"
1717
#include "SVals.h"
18-
1918
#include "clang/AST/OperationKinds.h"
2019
#include "clang/AST/Stmt.h"
2120
#include "clang/Basic/OperatorKinds.h"
@@ -113,8 +112,7 @@ class OperatorKind {
113112
OperatorKind operationKindFromOverloadedOperator(OverloadedOperatorKind OOK,
114113
bool IsBinary);
115114

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

119117
} // namespace ento
120118

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: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,10 +1200,25 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
12001200

12011201
// Add transition for the successful state.
12021202
NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>();
1203-
ProgramStateRef StateNotFailed =
1204-
State->BindExpr(E.CE, C.getLocationContext(), RetVal);
1203+
ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, RetVal);
12051204
StateNotFailed =
12061205
E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call));
1206+
1207+
// On success, a buffer is allocated.
1208+
auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State);
1209+
if (NewLinePtr && isa<DefinedOrUnknownSVal>(*NewLinePtr))
1210+
StateNotFailed = StateNotFailed->assume(
1211+
NewLinePtr->castAs<DefinedOrUnknownSVal>(), true);
1212+
1213+
// The buffer size `*n` must be enough to hold the whole line, and
1214+
// greater than the return value, since it has to account for '\0'.
1215+
SVal SizePtrSval = Call.getArgSVal(1);
1216+
auto NVal = getPointeeVal(SizePtrSval, State);
1217+
if (NVal && isa<NonLoc>(*NVal)) {
1218+
StateNotFailed = E.assumeBinOpNN(StateNotFailed, BO_GT,
1219+
NVal->castAs<NonLoc>(), RetVal);
1220+
StateNotFailed = E.bindReturnValue(StateNotFailed, C, RetVal);
1221+
}
12071222
if (!StateNotFailed)
12081223
return;
12091224
C.addTransition(StateNotFailed);
@@ -1217,6 +1232,10 @@ void StreamChecker::evalGetdelim(const FnDescription *Desc,
12171232
E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError;
12181233
StateFailed = E.setStreamState(
12191234
StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof()));
1235+
// On failure, the content of the buffer is undefined.
1236+
if (auto NewLinePtr = getPointeeVal(Call.getArgSVal(0), State))
1237+
StateFailed = StateFailed->bindLoc(*NewLinePtr, UndefinedVal(),
1238+
C.getLocationContext());
12201239
C.addTransition(StateFailed, E.getFailureNoteTag(this, C));
12211240
}
12221241

clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
2121
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
2222
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
23+
#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
2324
#include "llvm/ADT/STLExtras.h"
2425
#include "llvm/ADT/SmallString.h"
2526
#include "llvm/ADT/StringExtras.h"
@@ -44,10 +45,23 @@ namespace {
4445
class UnixAPIMisuseChecker
4546
: public Checker<check::PreCall, check::ASTDecl<TranslationUnitDecl>> {
4647
const BugType BT_open{this, "Improper use of 'open'", categories::UnixAPI};
48+
const BugType BT_getline{this, "Improper use of getdelim",
49+
categories::UnixAPI};
4750
const BugType BT_pthreadOnce{this, "Improper use of 'pthread_once'",
4851
categories::UnixAPI};
52+
const BugType BT_ArgumentNull{this, "NULL pointer", categories::UnixAPI};
4953
mutable std::optional<uint64_t> Val_O_CREAT;
5054

55+
ProgramStateRef
56+
EnsurePtrNotNull(SVal PtrVal, const Expr *PtrExpr, CheckerContext &C,
57+
ProgramStateRef State, const StringRef PtrDescr,
58+
std::optional<std::reference_wrapper<const BugType>> BT =
59+
std::nullopt) const;
60+
61+
ProgramStateRef EnsureGetdelimBufferAndSizeCorrect(
62+
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
63+
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const;
64+
5165
public:
5266
void checkASTDecl(const TranslationUnitDecl *TU, AnalysisManager &Mgr,
5367
BugReporter &BR) const;
@@ -56,6 +70,7 @@ class UnixAPIMisuseChecker
5670

5771
void CheckOpen(CheckerContext &C, const CallEvent &Call) const;
5872
void CheckOpenAt(CheckerContext &C, const CallEvent &Call) const;
73+
void CheckGetDelim(CheckerContext &C, const CallEvent &Call) const;
5974
void CheckPthreadOnce(CheckerContext &C, const CallEvent &Call) const;
6075

6176
void CheckOpenVariant(CheckerContext &C, const CallEvent &Call,
@@ -95,6 +110,30 @@ class UnixAPIPortabilityChecker : public Checker< check::PreStmt<CallExpr> > {
95110

96111
} // end anonymous namespace
97112

113+
ProgramStateRef UnixAPIMisuseChecker::EnsurePtrNotNull(
114+
SVal PtrVal, const Expr *PtrExpr, CheckerContext &C, ProgramStateRef State,
115+
const StringRef PtrDescr,
116+
std::optional<std::reference_wrapper<const BugType>> BT) const {
117+
const auto Ptr = PtrVal.getAs<DefinedSVal>();
118+
if (!Ptr)
119+
return State;
120+
121+
const auto [PtrNotNull, PtrNull] = State->assume(*Ptr);
122+
if (!PtrNotNull && PtrNull) {
123+
if (ExplodedNode *N = C.generateErrorNode(PtrNull)) {
124+
auto R = std::make_unique<PathSensitiveBugReport>(
125+
BT.value_or(std::cref(BT_ArgumentNull)),
126+
(PtrDescr + " pointer might be NULL.").str(), N);
127+
if (PtrExpr)
128+
bugreporter::trackExpressionValue(N, PtrExpr, *R);
129+
C.emitReport(std::move(R));
130+
}
131+
return nullptr;
132+
}
133+
134+
return PtrNotNull;
135+
}
136+
98137
void UnixAPIMisuseChecker::checkASTDecl(const TranslationUnitDecl *TU,
99138
AnalysisManager &Mgr,
100139
BugReporter &) const {
@@ -137,6 +176,9 @@ void UnixAPIMisuseChecker::checkPreCall(const CallEvent &Call,
137176

138177
else if (FName == "pthread_once")
139178
CheckPthreadOnce(C, Call);
179+
180+
else if (is_contained({"getdelim", "getline"}, FName))
181+
CheckGetDelim(C, Call);
140182
}
141183
void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C,
142184
ProgramStateRef State,
@@ -215,8 +257,7 @@ void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C,
215257
OS << "Call to '" << VariantName << "' with more than " << MaxArgCount
216258
<< " arguments";
217259

218-
ReportOpenBug(C, state,
219-
SBuf.c_str(),
260+
ReportOpenBug(C, state, SBuf.c_str(),
220261
Call.getArgExpr(MaxArgCount)->getSourceRange());
221262
return;
222263
}
@@ -266,6 +307,93 @@ void UnixAPIMisuseChecker::CheckOpenVariant(CheckerContext &C,
266307
}
267308
}
268309

310+
//===----------------------------------------------------------------------===//
311+
// getdelim and getline
312+
//===----------------------------------------------------------------------===//
313+
314+
ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
315+
SVal LinePtrPtrSVal, SVal SizePtrSVal, const Expr *LinePtrPtrExpr,
316+
const Expr *SizePtrExpr, CheckerContext &C, ProgramStateRef State) const {
317+
static constexpr llvm::StringLiteral SizeGreaterThanBufferSize =
318+
"The buffer from the first argument is smaller than the size "
319+
"specified by the second parameter";
320+
static constexpr llvm::StringLiteral SizeUndef =
321+
"The buffer from the first argument is not NULL, but the size specified "
322+
"by the second parameter is undefined.";
323+
324+
auto EmitBugReport = [this, &C, SizePtrExpr, LinePtrPtrExpr](
325+
ProgramStateRef BugState, StringRef ErrMsg) {
326+
if (ExplodedNode *N = C.generateErrorNode(BugState)) {
327+
auto R = std::make_unique<PathSensitiveBugReport>(BT_getline, ErrMsg, N);
328+
bugreporter::trackExpressionValue(N, SizePtrExpr, *R);
329+
bugreporter::trackExpressionValue(N, LinePtrPtrExpr, *R);
330+
C.emitReport(std::move(R));
331+
}
332+
};
333+
334+
// We have a pointer to a pointer to the buffer, and a pointer to the size.
335+
// We want what they point at.
336+
auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
337+
auto NSVal = getPointeeVal(SizePtrSVal, State);
338+
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
339+
return nullptr;
340+
341+
assert(LinePtrPtrExpr && SizePtrExpr);
342+
343+
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
344+
if (LinePtrNotNull && !LinePtrNull) {
345+
// If `*lineptr` is not null, but `*n` is undefined, there is UB.
346+
if (NSVal->isUndef()) {
347+
EmitBugReport(LinePtrNotNull, SizeUndef);
348+
return nullptr;
349+
}
350+
351+
// If it is defined, and known, its size must be less than or equal to
352+
// the buffer size.
353+
auto NDefSVal = NSVal->getAs<DefinedSVal>();
354+
auto &SVB = C.getSValBuilder();
355+
auto LineBufSize =
356+
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
357+
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
358+
*NDefSVal, SVB.getConditionType())
359+
.getAs<DefinedOrUnknownSVal>();
360+
if (!LineBufSizeGtN)
361+
return LinePtrNotNull;
362+
if (auto LineBufSizeOk = LinePtrNotNull->assume(*LineBufSizeGtN, true))
363+
return LineBufSizeOk;
364+
365+
EmitBugReport(LinePtrNotNull, SizeGreaterThanBufferSize);
366+
return nullptr;
367+
}
368+
return State;
369+
}
370+
371+
void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C,
372+
const CallEvent &Call) const {
373+
ProgramStateRef State = C.getState();
374+
375+
// The parameter `n` must not be NULL.
376+
SVal SizePtrSval = Call.getArgSVal(1);
377+
State = EnsurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");
378+
if (!State)
379+
return;
380+
381+
// The parameter `lineptr` must not be NULL.
382+
SVal LinePtrPtrSVal = Call.getArgSVal(0);
383+
State =
384+
EnsurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");
385+
if (!State)
386+
return;
387+
388+
State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval,
389+
Call.getArgExpr(0),
390+
Call.getArgExpr(1), C, State);
391+
if (!State)
392+
return;
393+
394+
C.addTransition(State);
395+
}
396+
269397
//===----------------------------------------------------------------------===//
270398
// pthread_once
271399
//===----------------------------------------------------------------------===//

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
}

0 commit comments

Comments
 (0)