
A memory access is an out of bounds error if the offset is < the extent of the memory region. Notice that here "<" is a _mathematical_ comparison between two numbers and NOT a C/C++ operator that compares two typed C++ values: for example -1 < 1000 is true in mathematics, but if the `-1` is an `int` and the `1000` is a `size_t` value, then evaluating the C/C++ operator `<` will return false because the `-1` will be converted to `SIZE_MAX` by the automatic type conversions. This means that it's incorrect to perform a bounds check with `evalBinOpNN(State, BO_LT, ...)` which performs automatic conversions and can produce wildly incorrect results. ArrayBoundsCheckerV2 already had a special case where it avoided calling `evalBinOpNN` in a situation where it would have performed an automatic conversion; this commit replaces that code with a more general one that covers more situations. (It's still not perfect, but it's better than the previous version and I think it will cover practically all real-world code.) Note that this is not a limitation/bug of the simplification algorithm defined in `getSimplifedOffsets()`: the simplification is not applied in the test case `test_comparison_with_extent_symbol` (because the `Extent` is not a concrete int), but without the new code it would still run into a `-1 < UNSIGNED` comparison that evaluates to false because `evalBinOpNN` performs an automatic type conversion.
197 lines
5.1 KiB
C
197 lines
5.1 KiB
C
// RUN: %clang_analyze_cc1 -Wno-array-bounds -analyzer-checker=core,alpha.security.ArrayBoundV2,debug.ExprInspection -verify %s
|
|
|
|
void clang_analyzer_eval(int);
|
|
|
|
// Tests doing an out-of-bounds access after the end of an array using:
|
|
// - constant integer index
|
|
// - constant integer size for buffer
|
|
void test1(int x) {
|
|
int buf[100];
|
|
buf[100] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test1_ok(int x) {
|
|
int buf[100];
|
|
buf[99] = 1; // no-warning
|
|
}
|
|
|
|
const char test1_strings_underrun(int x) {
|
|
const char *mystr = "mary had a little lamb";
|
|
return mystr[-1]; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
const char test1_strings_overrun(int x) {
|
|
const char *mystr = "mary had a little lamb";
|
|
return mystr[1000]; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
const char test1_strings_ok(int x) {
|
|
const char *mystr = "mary had a little lamb";
|
|
return mystr[5]; // no-warning
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access after the end of an array using:
|
|
// - indirect pointer to buffer
|
|
// - constant integer index
|
|
// - constant integer size for buffer
|
|
void test1_ptr(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p[101] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test1_ptr_ok(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p[99] = 1; // no-warning
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access before the start of an array using:
|
|
// - indirect pointer to buffer, manipulated using simple pointer arithmetic
|
|
// - constant integer index
|
|
// - constant integer size for buffer
|
|
void test1_ptr_arith(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p = p + 100;
|
|
p[0] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test1_ptr_arith_ok(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p = p + 99;
|
|
p[0] = 1; // no-warning
|
|
}
|
|
|
|
void test1_ptr_arith_bad(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p = p + 99;
|
|
p[1] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test1_ptr_arith_ok2(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p = p + 99;
|
|
p[-1] = 1; // no-warning
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access before the start of an array using:
|
|
// - constant integer index
|
|
// - constant integer size for buffer
|
|
void test2(int x) {
|
|
int buf[100];
|
|
buf[-1] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access before the start of an array using:
|
|
// - indirect pointer to buffer
|
|
// - constant integer index
|
|
// - constant integer size for buffer
|
|
void test2_ptr(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
p[-1] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access before the start of an array using:
|
|
// - indirect pointer to buffer, manipulated using simple pointer arithmetic
|
|
// - constant integer index
|
|
// - constant integer size for buffer
|
|
void test2_ptr_arith(int x) {
|
|
int buf[100];
|
|
int *p = buf;
|
|
--p;
|
|
p[0] = 1; // expected-warning {{Out of bound access to memory preceding}}
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access before the start of a multi-dimensional
|
|
// array using:
|
|
// - constant integer indices
|
|
// - constant integer sizes for the array
|
|
void test2_multi(int x) {
|
|
int buf[100][100];
|
|
buf[0][-1] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
// Tests doing an out-of-bounds access before the start of a multi-dimensional
|
|
// array using:
|
|
// - constant integer indices
|
|
// - constant integer sizes for the array
|
|
void test2_multi_b(int x) {
|
|
int buf[100][100];
|
|
buf[-1][0] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test2_multi_ok(int x) {
|
|
int buf[100][100];
|
|
buf[0][0] = 1; // no-warning
|
|
}
|
|
|
|
void test3(int x) {
|
|
int buf[100];
|
|
if (x < 0)
|
|
buf[x] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test4(int x) {
|
|
int buf[100];
|
|
if (x > 99)
|
|
buf[x] = 1; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test_assume_after_access(unsigned long x) {
|
|
int buf[100];
|
|
buf[x] = 1;
|
|
clang_analyzer_eval(x <= 99); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
// Don't warn when indexing below the start of a symbolic region's whose
|
|
// base extent we don't know.
|
|
int *get_symbolic(void);
|
|
void test_underflow_symbolic(void) {
|
|
int *buf = get_symbolic();
|
|
buf[-1] = 0; // no-warning;
|
|
}
|
|
|
|
// But warn if we understand the internal memory layout of a symbolic region.
|
|
typedef struct {
|
|
int id;
|
|
char name[256];
|
|
} user_t;
|
|
|
|
user_t *get_symbolic_user(void);
|
|
char test_underflow_symbolic_2() {
|
|
user_t *user = get_symbolic_user();
|
|
return user->name[-1]; // expected-warning{{Out of bound access to memory}}
|
|
}
|
|
|
|
void test_incomplete_struct(void) {
|
|
extern struct incomplete incomplete;
|
|
int *p = (int *)&incomplete;
|
|
p[1] = 42; // no-warning
|
|
}
|
|
|
|
void test_extern_void(void) {
|
|
extern void v;
|
|
int *p = (int *)&v;
|
|
p[1] = 42; // no-warning
|
|
}
|
|
|
|
void test_assume_after_access2(unsigned long x) {
|
|
char buf[100];
|
|
buf[x] = 1;
|
|
clang_analyzer_eval(x <= 99); // expected-warning{{TRUE}}
|
|
}
|
|
|
|
struct incomplete;
|
|
char test_comparison_with_extent_symbol(struct incomplete *p) {
|
|
// Previously this was reported as a (false positive) overflow error because
|
|
// the extent symbol of the area pointed by `p` was an unsigned and the '-1'
|
|
// was converted to its type by `evalBinOpNN`.
|
|
return ((char *)p)[-1]; // no-warning
|
|
}
|
|
|