Skip to content

Commit 175ad66

Browse files
NagyDonatsteakhal
andauthored
[analyzer] Mention possibility of underflow in array overflow errors (llvm#84201)
The checker alpha.security.ArrayBoundV2 performs bounds checking in two steps: first it checks for underflow, and if it isn't guaranteed then it assumes that there is no underflow. After this, it checks for overflow, and if that's guaranteed or the index is tainted then it reports it. This meant that in situations where overflow and underflow are both possible (but the index is either tainted or guaranteed to be invalid), the checker was reporting just an overflow error. This commit modifies the messages printed in these cases to mention the possibility of an underflow. --------- Co-authored-by: Balazs Benics <[email protected]>
1 parent 335f365 commit 175ad66

File tree

3 files changed

+86
-15
lines changed

3 files changed

+86
-15
lines changed

clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ class StateUpdateReporter {
8383
AssumedUpperBound = UpperBoundVal;
8484
}
8585

86+
bool assumedNonNegative() { return AssumedNonNegative; }
87+
8688
const NoteTag *createNoteTag(CheckerContext &C) const;
8789

8890
private:
@@ -402,7 +404,8 @@ static bool tryDividePair(std::optional<int64_t> &Val1,
402404
}
403405

404406
static Messages getExceedsMsgs(ASTContext &ACtx, const SubRegion *Region,
405-
NonLoc Offset, NonLoc Extent, SVal Location) {
407+
NonLoc Offset, NonLoc Extent, SVal Location,
408+
bool AlsoMentionUnderflow) {
406409
std::string RegName = getRegionName(Region);
407410
const auto *EReg = Location.getAsRegion()->getAs<ElementRegion>();
408411
assert(EReg && "this checker only handles element access");
@@ -414,17 +417,20 @@ static Messages getExceedsMsgs(ASTContext &ACtx, const SubRegion *Region,
414417
int64_t ElemSize = ACtx.getTypeSizeInChars(ElemType).getQuantity();
415418

416419
bool UseByteOffsets = !tryDividePair(OffsetN, ExtentN, ElemSize);
420+
const char *OffsetOrIndex = UseByteOffsets ? "byte offset" : "index";
417421

418422
SmallString<256> Buf;
419423
llvm::raw_svector_ostream Out(Buf);
420424
Out << "Access of ";
421425
if (!ExtentN && !UseByteOffsets)
422426
Out << "'" << ElemType.getAsString() << "' element in ";
423427
Out << RegName << " at ";
424-
if (OffsetN) {
425-
Out << (UseByteOffsets ? "byte offset " : "index ") << *OffsetN;
428+
if (AlsoMentionUnderflow) {
429+
Out << "a negative or overflowing " << OffsetOrIndex;
430+
} else if (OffsetN) {
431+
Out << OffsetOrIndex << " " << *OffsetN;
426432
} else {
427-
Out << "an overflowing " << (UseByteOffsets ? "byte offset" : "index");
433+
Out << "an overflowing " << OffsetOrIndex;
428434
}
429435
if (ExtentN) {
430436
Out << ", while it holds only ";
@@ -441,17 +447,20 @@ static Messages getExceedsMsgs(ASTContext &ACtx, const SubRegion *Region,
441447
Out << "s";
442448
}
443449

444-
return {
445-
formatv("Out of bound access to memory after the end of {0}", RegName),
446-
std::string(Buf)};
450+
return {formatv("Out of bound access to memory {0} {1}",
451+
AlsoMentionUnderflow ? "around" : "after the end of",
452+
RegName),
453+
std::string(Buf)};
447454
}
448455

449-
static Messages getTaintMsgs(const SubRegion *Region, const char *OffsetName) {
456+
static Messages getTaintMsgs(const SubRegion *Region, const char *OffsetName,
457+
bool AlsoMentionUnderflow) {
450458
std::string RegName = getRegionName(Region);
451459
return {formatv("Potential out of bound access to {0} with tainted {1}",
452460
RegName, OffsetName),
453-
formatv("Access of {0} with a tainted {1} that may be too large",
454-
RegName, OffsetName)};
461+
formatv("Access of {0} with a tainted {1} that may be {2}too large",
462+
RegName, OffsetName,
463+
AlsoMentionUnderflow ? "negative or " : "")};
455464
}
456465

457466
const NoteTag *StateUpdateReporter::createNoteTag(CheckerContext &C) const {
@@ -600,6 +609,13 @@ void ArrayBoundCheckerV2::performCheck(const Expr *E, CheckerContext &C) const {
600609
// CHECK UPPER BOUND
601610
DefinedOrUnknownSVal Size = getDynamicExtent(State, Reg, SVB);
602611
if (auto KnownSize = Size.getAs<NonLoc>()) {
612+
// In a situation where both overflow and overflow are possible (but the
613+
// index is either tainted or known to be invalid), the logic of this
614+
// checker will first assume that the offset is non-negative, and then
615+
// (with this additional assumption) it will detect an overflow error.
616+
// In this situation the warning message should mention both possibilities.
617+
bool AlsoMentionUnderflow = SUR.assumedNonNegative();
618+
603619
auto [WithinUpperBound, ExceedsUpperBound] =
604620
compareValueToThreshold(State, ByteOffset, *KnownSize, SVB);
605621

@@ -615,8 +631,9 @@ void ArrayBoundCheckerV2::performCheck(const Expr *E, CheckerContext &C) const {
615631
return;
616632
}
617633

618-
Messages Msgs = getExceedsMsgs(C.getASTContext(), Reg, ByteOffset,
619-
*KnownSize, Location);
634+
Messages Msgs =
635+
getExceedsMsgs(C.getASTContext(), Reg, ByteOffset, *KnownSize,
636+
Location, AlsoMentionUnderflow);
620637
reportOOB(C, ExceedsUpperBound, Msgs, ByteOffset, KnownSize);
621638
return;
622639
}
@@ -632,7 +649,7 @@ void ArrayBoundCheckerV2::performCheck(const Expr *E, CheckerContext &C) const {
632649
if (isTainted(State, ASE->getIdx(), C.getLocationContext()))
633650
OffsetName = "index";
634651

635-
Messages Msgs = getTaintMsgs(Reg, OffsetName);
652+
Messages Msgs = getTaintMsgs(Reg, OffsetName, AlsoMentionUnderflow);
636653
reportOOB(C, ExceedsUpperBound, Msgs, ByteOffset, KnownSize,
637654
/*IsTaintBug=*/true);
638655
return;

clang/test/Analysis/out-of-bounds-diagnostics.c

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,33 @@ void taintedIndex(void) {
2424
scanf("%d", &index);
2525
// expected-note@-1 {{Taint originated here}}
2626
// expected-note@-2 {{Taint propagated to the 2nd argument}}
27+
TenElements[index] = 5;
28+
// expected-warning@-1 {{Potential out of bound access to 'TenElements' with tainted index}}
29+
// expected-note@-2 {{Access of 'TenElements' with a tainted index that may be negative or too large}}
30+
}
31+
32+
void taintedIndexNonneg(void) {
33+
int index;
34+
scanf("%d", &index);
35+
// expected-note@-1 {{Taint originated here}}
36+
// expected-note@-2 {{Taint propagated to the 2nd argument}}
37+
38+
// expected-note@+2 {{Assuming 'index' is >= 0}}
39+
// expected-note@+1 {{Taking false branch}}
40+
if (index < 0)
41+
return;
42+
43+
TenElements[index] = 5;
44+
// expected-warning@-1 {{Potential out of bound access to 'TenElements' with tainted index}}
45+
// expected-note@-2 {{Access of 'TenElements' with a tainted index that may be too large}}
46+
}
47+
48+
void taintedIndexUnsigned(void) {
49+
unsigned index;
50+
scanf("%u", &index);
51+
// expected-note@-1 {{Taint originated here}}
52+
// expected-note@-2 {{Taint propagated to the 2nd argument}}
53+
2754
TenElements[index] = 5;
2855
// expected-warning@-1 {{Potential out of bound access to 'TenElements' with tainted index}}
2956
// expected-note@-2 {{Access of 'TenElements' with a tainted index that may be too large}}
@@ -59,7 +86,7 @@ void taintedOffset(void) {
5986
int *p = TenElements + index;
6087
p[0] = 5;
6188
// expected-warning@-1 {{Potential out of bound access to 'TenElements' with tainted offset}}
62-
// expected-note@-2 {{Access of 'TenElements' with a tainted offset that may be too large}}
89+
// expected-note@-2 {{Access of 'TenElements' with a tainted offset that may be negative or too large}}
6390
}
6491

6592
void arrayOverflow(void) {
@@ -109,6 +136,33 @@ int *potentialAfterTheEndPtr(int idx) {
109136
// &TenElements[idx].
110137
}
111138

139+
int overflowOrUnderflow(int arg) {
140+
// expected-note@+2 {{Assuming 'arg' is < 0}}
141+
// expected-note@+1 {{Taking false branch}}
142+
if (arg >= 0)
143+
return 0;
144+
145+
return TenElements[arg - 1];
146+
// expected-warning@-1 {{Out of bound access to memory around 'TenElements'}}
147+
// expected-note@-2 {{Access of 'TenElements' at a negative or overflowing index, while it holds only 10 'int' elements}}
148+
}
149+
150+
char TwoElements[2] = {11, 22};
151+
char overflowOrUnderflowConcrete(int arg) {
152+
// expected-note@#cond {{Assuming 'arg' is < 3}}
153+
// expected-note@#cond {{Left side of '||' is false}}
154+
// expected-note@#cond {{Assuming 'arg' is not equal to 0}}
155+
// expected-note@#cond {{Left side of '||' is false}}
156+
// expected-note@#cond {{Assuming 'arg' is not equal to 1}}
157+
// expected-note@#cond {{Taking false branch}}
158+
if (arg >= 3 || arg == 0 || arg == 1) // #cond
159+
return 0;
160+
161+
return TwoElements[arg];
162+
// expected-warning@-1 {{Out of bound access to memory around 'TwoElements'}}
163+
// expected-note@-2 {{Access of 'TwoElements' at a negative or overflowing index, while it holds only 2 'char' elements}}
164+
}
165+
112166
int scalar;
113167
int scalarOverflow(void) {
114168
return (&scalar)[1];

clang/test/Analysis/taint-diagnostic-visitor.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ int taintDiagnosticOutOfBound(void) {
3030
scanf("%d", &index); // expected-note {{Taint originated here}}
3131
// expected-note@-1 {{Taint propagated to the 2nd argument}}
3232
return Array[index]; // expected-warning {{Potential out of bound access to 'Array' with tainted index}}
33-
// expected-note@-1 {{Access of 'Array' with a tainted index that may be too large}}
33+
// expected-note@-1 {{Access of 'Array' with a tainted index}}
3434
}
3535

3636
int taintDiagnosticDivZero(int operand) {

0 commit comments

Comments
 (0)