Open
Description
CBMC version: 3410558
Operating system: Linux, MacOS
Test case:
int main() {
char *ptr;
ptr = 0xFF;
*ptr = '0'; // PASS: write succeeds without alloc?
__CPROVER_allocated_memory(ptr, 0xFFFFFFFFFFFFFE); // PASS: but overflow in offset space!
assert(__CPROVER_w_ok(ptr, 0xFFFFFFFFFFFFFE)); // PASS: well, consistent with above
assert(__CPROVER_w_ok(ptr, 0xFFFFFFFFFFFFFF00)); // FAIL: beyond alloc
assert(__CPROVER_w_ok(ptr, 0xFFFFFFFFFFFFFF01)); // PASS: overflow beyond 64 bits!
ptr = 0xFFFFFFFFFFFFFE; // object bits unset
*ptr = '0'; // PASS: write succeeds without alloc?
__CPROVER_allocated_memory(ptr, 0xFF); // PASS: but overflow in offset space!
assert(__CPROVER_w_ok(ptr, 0xFF)); // PASS: well, consistent with above
assert(__CPROVER_w_ok(ptr, 0xFFFF)); // PASS: but beyond alloc!
ptr = 0xFFFFFFFFFFFFFFE; // object bits set to 00001111
*ptr = '0'; // PASS: write succeeds without alloc?
__CPROVER_allocated_memory(ptr, 0xFF); // PASS: but overflow in offset space!
assert(__CPROVER_w_ok(ptr, 0xFF)); // PASS: well, consistent with above
assert(__CPROVER_w_ok(ptr, 0xFFFF)); // PASS: but beyond alloc!
assert(0);
}
Exact command line resulting in the issue:
cbmc --64 \
--pointer-check --pointer-overflow-check --pointer-primitive-check \
--signed-overflow-check --unsigned-overflow-check --conversion-check \
--bounds-check \
test.c
What behaviour did you expect:
I was expecting CBMC to flag the w_ok checks on regions beyond the allocated region.
I think there are at least 3 different issues here:
- @tautschnig pointed out on Slack that
__CPROVER_allocated_memory
is flow insensitive. I think that explains why the before alloc succeed in the test case above - The
__CPROVER_allocated_memory
addresses break the (object id, offset) model for pointers. CBMC doesn't raise overflows on large allocations (overflowing into object bits) there, which matches my intuition, but I am not sure if it's for the right reasons. It looks like arithmetic on these pointers still takes object id and offset into account. - Finally, I think there might be a separate issue with
__CPROVER_w_ok
, because it succeeds on regions beyond the allocated regions. If this somehow succeeds because of overflows into object bits, the overflows should be reported in some way.
I also tried checking each of the 3 blocks above independently, since the current flow insensitive __CPROVER_allocated_memory
implementation would lead to overlapping allocations above. I don't see much of a difference though. The 1st and 3rd blocks behave exactly as before, only the __CPROVER_w_ok
check in the 2nd block works as expected now.
1st block
int main() {
char *ptr;
ptr = 0xFF;
*ptr = '0'; // PASS: write succeeds without alloc?
__CPROVER_allocated_memory(ptr, 0xFFFFFFFFFFFFFE); // PASS: but overflow in offset space!
assert(__CPROVER_w_ok(ptr, 0xFFFFFFFFFFFFFE)); // PASS: well, consistent with above
assert(__CPROVER_w_ok(ptr, 0xFFFFFFFFFFFFFF00)); // FAIL: beyond alloc
assert(__CPROVER_w_ok(ptr, 0xFFFFFFFFFFFFFF01)); // PASS: overflow beyond 64 bits!
assert(0);
}
2nd block
int main() {
char *ptr;
ptr = 0xFFFFFFFFFFFFFE; // object bits unset
*ptr = '0'; // PASS: write succeeds without alloc?
__CPROVER_allocated_memory(ptr, 0xFF); // PASS: but overflow in offset space!
assert(__CPROVER_w_ok(ptr, 0xFF)); // PASS: well, consistent with above
assert(__CPROVER_w_ok(ptr, 0xFFFF)); // FAIL: beyond alloc
assert(0);
}
3rd block
int main() {
char *ptr;
ptr = 0xFFFFFFFFFFFFFFE; // object bits set to 00001111
*ptr = '0'; // PASS: write succeeds without alloc?
__CPROVER_allocated_memory(ptr, 0xFF); // PASS: but overflow in offset space!
assert(__CPROVER_w_ok(ptr, 0xFF)); // PASS: well, consistent with above
assert(__CPROVER_w_ok(ptr, 0xFFFF)); // PASS: but beyond alloc!
assert(0);
}