Fix memOutOfBounds handling of negative pointer offsets#2017
Conversation
| let check_ptr_offset_bounds ptr_size offs = | ||
| let ptr_size_lt_offs = | ||
| try ID.lt ptr_size offs | ||
| with IntDomain.ArithmeticOnIntegerBot _ -> None | ||
| in | ||
| offs_lt_zero offs, ptr_size_lt_offs |
There was a problem hiding this comment.
Could you elaborate on why this needs to be handled differently from the things above?
There was a problem hiding this comment.
First I didn't give it much thought, I just refactored out the parts that were in the original code, but with the help of Codex, I now know the difference and we also added regtests that would catch a change in these in both directions:
This follows directly from the C standard distinction between pointer values and dereferenceable positions. Per C11 §6.5.6, pointer arithmetic may produce a pointer one past the end of an object (this is well-defined), but such a pointer must not be dereferenced. So:
For dereferencing, the valid range is [0, size), hence ID.le ptr_size offs (reject offs == size).
For pointer values, the valid range is [0, size], hence ID.lt ptr_size offs (allow offs == size, reject only offs > size).and changing either side breaks soundness/precision, I added two regression tests to make this concrete:
74/36: valid one-past pointer
(buf + 4)→ fails if we switch tolein the pointer case (false positive)
74/37: dereference at end → missed if we switch toltin the deref case (unsound)So the difference encodes the standard’s [0, size] vs [0, size) distinction and is intentional.
The unsoundness by changing the deref case to lt is also caught by an existing test 74/13, but the other way around was not detectable by the current tests.
There was a problem hiding this comment.
But this is still strange. The memOutOfBounds analysis should only consider dereferencing. If a pointer points one or more past the end should make no difference if it's never dereferenced. It would only matter for the valid-memtrack subproperty which is handled by memLeak (which has many other problems).
There was a problem hiding this comment.
I'll merge this so we can see if it makes any difference in the knightly.
This strangeness already existed before and could be fixed separately.
Co-authored-by: Simmo Saan <simmo.saan@gmail.com>
michael-schwarz
left a comment
There was a problem hiding this comment.
Other than the suggestion, LGTM!
| char *buf = malloc(4); | ||
| char *end; | ||
| end = buf + 4; //NOWARN | ||
| printf("%p", (void *) end); //NOWARN |
There was a problem hiding this comment.
| printf("%p", (void *) end); //NOWARN | |
| printf("%p", (void *) end); //NOWARN | |
| printf("%c", *end); //WARN |
So we also see that we do indeed warn if this pointer is dereferenced.
There was a problem hiding this comment.
ha! it seems you just uncovered an unrelated unsoundness with this ;) I suggest we merge this as is and I will add this suggestion on another PR with an appropriate fix.
This PR fixes a bug in
memOutOfBoundswhere before-start accesses were classified as safe when the offset was negative and precise enough underana.int.interval, i.e., the bug only manifested when interval domain was turned on. Withana.int.interval, Goblint can compute offsets precisely enough that negative-offset accesses no longer stay at top. This issue was found using the dashboard comparison between Goblint and Mopsa and was exposed by the SV-COMP tasklist-ext-properties/960521-1_1-1.i.memOutOfBoundscases intests/regression/74-invalid_deref.memOutOfBoundsto warn when the computed byte offset is negative, in addition to the existing past-the-end check.ptr_size_lt_offscomparison sites handleIntDomain.ArithmeticOnIntegerBotconsistently by falling back to the existing conservative “could not compare” behavior.(size - 1) < offsetsize < offset