Skip to content

Commit 82f9579

Browse files
committed
[clang][analyzer] fix crash when modelling 'getline'
1 parent 7468718 commit 82f9579

File tree

4 files changed

+184
-12
lines changed

4 files changed

+184
-12
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,6 +1060,9 @@ impact the linker behaviour like the other `-static-*` flags.
10601060
Crash and bug fixes
10611061
^^^^^^^^^^^^^^^^^^^
10621062

1063+
- Fixed a crash in ``UnixAPIMisuseChecker`` and ``MallocChecker`` when analyzing
1064+
code with non-standard ``getline`` or ``getdelim`` function signatures.
1065+
10631066
Improvements
10641067
^^^^^^^^^^^^
10651068

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,13 @@ void MallocChecker::preGetdelim(ProgramStateRef State, const CallEvent &Call,
14891489
if (isFromStdNamespace(Call))
14901490
return;
14911491

1492+
if (Call.getNumArgs() < 1)
1493+
return;
1494+
1495+
const Expr *Arg0 = Call.getArgExpr(0);
1496+
if (!Arg0->getType()->isPointerType())
1497+
return; // Skip checks if argument is not a pointer
1498+
14921499
const auto LinePtr = getPointeeVal(Call.getArgSVal(0), State);
14931500
if (!LinePtr)
14941501
return;
@@ -1518,10 +1525,23 @@ void MallocChecker::checkGetdelim(ProgramStateRef State, const CallEvent &Call,
15181525
if (!CE)
15191526
return;
15201527

1521-
const auto LinePtr =
1522-
getPointeeVal(Call.getArgSVal(0), State)->getAs<DefinedSVal>();
1523-
const auto Size =
1524-
getPointeeVal(Call.getArgSVal(1), State)->getAs<DefinedSVal>();
1528+
if (Call.getNumArgs() < 2)
1529+
return;
1530+
1531+
const Expr *Arg0 = Call.getArgExpr(0);
1532+
const Expr *Arg1 = Call.getArgExpr(1);
1533+
if (!Arg0->getType()->isPointerType() || !Arg1->getType()->isPointerType())
1534+
return; // Skip checks if arguments are not pointers
1535+
1536+
auto LinePtrOpt = getPointeeVal(Call.getArgSVal(0), State);
1537+
if (!LinePtrOpt)
1538+
return;
1539+
const auto LinePtr = LinePtrOpt->getAs<DefinedSVal>();
1540+
1541+
auto SizeOpt = getPointeeVal(Call.getArgSVal(1), State);
1542+
if (!SizeOpt)
1543+
return;
1544+
const auto Size = SizeOpt->getAs<DefinedSVal>();
15251545
if (!LinePtr || !Size || !LinePtr->getAsRegion())
15261546
return;
15271547

clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,21 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
332332

333333
// We have a pointer to a pointer to the buffer, and a pointer to the size.
334334
// We want what they point at.
335-
auto LinePtrSVal = getPointeeVal(LinePtrPtrSVal, State)->getAs<DefinedSVal>();
335+
auto LinePtrValOpt = getPointeeVal(LinePtrPtrSVal, State);
336+
if (!LinePtrValOpt)
337+
return nullptr;
338+
339+
auto LinePtrSVal = LinePtrValOpt->getAs<DefinedSVal>();
336340
auto NSVal = getPointeeVal(SizePtrSVal, State);
337341
if (!LinePtrSVal || !NSVal || NSVal->isUnknown())
338342
return nullptr;
339343

340344
assert(LinePtrPtrExpr && SizePtrExpr);
341345

346+
// Add defensive check to ensure we can handle the assume operation
347+
if (!LinePtrSVal->getAs<DefinedSVal>())
348+
return nullptr;
349+
342350
const auto [LinePtrNotNull, LinePtrNull] = State->assume(*LinePtrSVal);
343351
if (LinePtrNotNull && !LinePtrNull) {
344352
// If `*lineptr` is not null, but `*n` is undefined, there is UB.
@@ -350,9 +358,17 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
350358
// If it is defined, and known, its size must be less than or equal to
351359
// the buffer size.
352360
auto NDefSVal = NSVal->getAs<DefinedSVal>();
361+
if (!NDefSVal)
362+
return LinePtrNotNull;
363+
353364
auto &SVB = C.getSValBuilder();
354-
auto LineBufSize =
355-
getDynamicExtent(LinePtrNotNull, LinePtrSVal->getAsRegion(), SVB);
365+
366+
367+
const MemRegion *LinePtrRegion = LinePtrSVal->getAsRegion();
368+
if (!LinePtrRegion)
369+
return LinePtrNotNull;
370+
371+
auto LineBufSize = getDynamicExtent(LinePtrNotNull, LinePtrRegion, SVB);
356372
auto LineBufSizeGtN = SVB.evalBinOp(LinePtrNotNull, BO_GE, LineBufSize,
357373
*NDefSVal, SVB.getConditionType())
358374
.getAs<DefinedOrUnknownSVal>();
@@ -370,23 +386,29 @@ ProgramStateRef UnixAPIMisuseChecker::EnsureGetdelimBufferAndSizeCorrect(
370386
void UnixAPIMisuseChecker::CheckGetDelim(CheckerContext &C,
371387
const CallEvent &Call) const {
372388
ProgramStateRef State = C.getState();
389+
if (Call.getNumArgs() < 2)
390+
return;
373391

392+
const Expr *Arg0 = Call.getArgExpr(0);
393+
const Expr *Arg1 = Call.getArgExpr(1);
394+
395+
if (!Arg0->getType()->isPointerType() || !Arg1->getType()->isPointerType())
396+
return; // Skip checks if arguments are not pointers
397+
374398
// The parameter `n` must not be NULL.
375399
SVal SizePtrSval = Call.getArgSVal(1);
376-
State = EnsurePtrNotNull(SizePtrSval, Call.getArgExpr(1), C, State, "Size");
400+
State = EnsurePtrNotNull(SizePtrSval, Arg1, C, State, "Size");
377401
if (!State)
378402
return;
379403

380404
// The parameter `lineptr` must not be NULL.
381405
SVal LinePtrPtrSVal = Call.getArgSVal(0);
382-
State =
383-
EnsurePtrNotNull(LinePtrPtrSVal, Call.getArgExpr(0), C, State, "Line");
406+
State = EnsurePtrNotNull(LinePtrPtrSVal, Arg0, C, State, "Line");
384407
if (!State)
385408
return;
386409

387410
State = EnsureGetdelimBufferAndSizeCorrect(LinePtrPtrSVal, SizePtrSval,
388-
Call.getArgExpr(0),
389-
Call.getArgExpr(1), C, State);
411+
Arg0, Arg1, C, State);
390412
if (!State)
391413
return;
392414

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_CORRECT
2+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_1
3+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_2
4+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_3
5+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_4
6+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_5
7+
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix -verify %s -DTEST_GETLINE_GH144884
8+
9+
// emulator of "system-header-simulator.h" because of redefinition of 'getline' function
10+
typedef struct _FILE FILE;
11+
typedef __typeof(sizeof(int)) size_t;
12+
typedef long ssize_t;
13+
#define NULL 0
14+
15+
int fclose(FILE *fp);
16+
FILE *tmpfile(void);
17+
18+
#ifdef TEST_CORRECT
19+
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
20+
ssize_t getdelim(char **lineptr, size_t *n, int delimiter, FILE *stream);
21+
22+
void test_correct() {
23+
FILE *F1 = tmpfile();
24+
if (!F1)
25+
return;
26+
char *buffer = NULL;
27+
getline(&buffer, NULL, F1); // expected-warning {{Size pointer might be NULL}}
28+
fclose(F1);
29+
}
30+
31+
void test_delim_correct() {
32+
FILE *F1 = tmpfile();
33+
if (!F1)
34+
return;
35+
char *buffer = NULL;
36+
getdelim(&buffer, NULL, ',', F1); // expected-warning {{Size pointer might be NULL}}
37+
fclose(F1);
38+
}
39+
#endif
40+
41+
#ifdef TEST_GETLINE_1
42+
// expected-no-diagnostics
43+
ssize_t getline(int lineptr);
44+
45+
void test() {
46+
FILE *F1 = tmpfile();
47+
if (!F1)
48+
return;
49+
int buffer = 0;
50+
getline(buffer);
51+
fclose(F1);
52+
}
53+
#endif
54+
55+
#ifdef TEST_GETLINE_2
56+
ssize_t getline(char **lineptr, size_t *n);
57+
58+
void test() {
59+
FILE *F1 = tmpfile();
60+
if (!F1)
61+
return;
62+
char *buffer = NULL;
63+
getline(&buffer, NULL); // expected-warning {{Size pointer might be NULL}}
64+
fclose(F1);
65+
}
66+
#endif
67+
68+
#ifdef TEST_GETLINE_3
69+
// expected-no-diagnostics
70+
ssize_t getline(char **lineptr, size_t n, FILE *stream);
71+
72+
void test() {
73+
FILE *F1 = tmpfile();
74+
if (!F1)
75+
return;
76+
char *buffer = NULL;
77+
getline(&buffer, 0, F1);
78+
fclose(F1);
79+
}
80+
#endif
81+
82+
#ifdef TEST_GETLINE_4
83+
ssize_t getline(char **lineptr, size_t *n, int stream);
84+
ssize_t getdelim(char **lineptr, size_t *n, int delimiter, int stream);
85+
86+
void test() {
87+
FILE *F1 = tmpfile();
88+
if (!F1)
89+
return;
90+
char *buffer = NULL;
91+
getline(&buffer, NULL, 1); // expected-warning {{Size pointer might be NULL}}
92+
fclose(F1);
93+
}
94+
95+
void test_delim() {
96+
FILE *F1 = tmpfile();
97+
if (!F1)
98+
return;
99+
char *buffer = NULL;
100+
getdelim(&buffer, NULL, ',', 1); // expected-warning {{Size pointer might be NULL}}
101+
fclose(F1);
102+
}
103+
#endif
104+
105+
#ifdef TEST_GETLINE_5
106+
ssize_t getdelim(char **lineptr, size_t *n, const char* delimiter, FILE *stream);
107+
108+
void test_delim() {
109+
FILE *F1 = tmpfile();
110+
if (!F1)
111+
return;
112+
char *buffer = NULL;
113+
getdelim(&buffer, NULL, ",", F1); // expected-warning {{Size pointer might be NULL}}
114+
fclose(F1);
115+
}
116+
#endif
117+
118+
#ifdef TEST_GETLINE_GH144884
119+
// expected-no-diagnostics
120+
struct AW_string {};
121+
void getline(int *, struct AW_string);
122+
void top() {
123+
struct AW_string line;
124+
int getline_file_info;
125+
getline(&getline_file_info, line);
126+
}
127+
#endif

0 commit comments

Comments
 (0)