BUG: Stop loc_strnlen reading one byte past the buffer it is given - #66
Conversation
loc_strnlen measures a string that need not be terminated, but it dereferences before testing the bound, so when no NUL appears in the first maxlen bytes the last iteration reads str[maxlen]. Both callers pass an unterminated buffer: axml_read_buf takes the caller's buffer and its length, and axml_read_file measures what fread returned, which fills the buffer for any larger file. AddressSanitizer on a buffer sized to its content reports a heap-buffer-overflow read 0 bytes after a 324-byte region. The returned length is unchanged wherever the old order was in bounds.
cccf828 to
fb14ede
Compare
|
Rebased onto current This is the first time the change has been built by CI at all — Commit message trimmed to the prose budgetThe body was 30 lines and carried What was kept is the part a future reader cannot get from the diff: the mechanism (the loop dereferences before testing the bound, so it reads The test suite cannot catch this, and that is worth recordingAs the original commit message observed, the existing tests use XML smaller than the buffer, so the byte after the data still falls inside the allocation and the over-read is invisible. That means merging this does not make the path covered — it makes it correct. A regression test needs a buffer sized exactly to its contents, which is the same class of fixture the malformed-input corpus would provide. Recorded against the testing-gap notes rather than claimed as tested here. |
85fae84
into
InsightSoftwareConsortium:master
|
The commit messages in this range were rewritten to remove trailers that do not belong in permanent history: This PR's commit on the rewritten
The SHA recorded above by GitHub is from the pre-rewrite history and no longer resolves. |
loc_strnlen() exists to measure a string that need not be terminated,
but it dereferences before it checks the bound:
When no NUL appears in the first maxlen bytes, the last iteration reads
str[maxlen], one byte past the end.
Both callers pass a buffer with no terminator:
XML buffer that is exactly its own length -- an in-memory document,
or the CIFTI extension of a NIfTI file -- reads past the end.
fills the whole buffer for every XML file larger than the buffer,
so a heap over-read happens on each full block, once per
axml_get_buf_size() bytes of file.
The existing tests do not catch it because their XML is smaller than
the buffer, which leaves the byte after the data inside the allocation.
AddressSanitizer on a buffer sized to its content:
Swapping the two conditions fixes it; the returned length is unchanged
in every case that was already in bounds.
Found by fuzzing axml_read_buf() with clang's libFuzzer under
AddressSanitizer.
How to reproduce, and the sanitizer builds used, are in the commit message. Found with clang libFuzzer harnesses over
nifti_image_read(), the two header converters,nifti_image_from_ascii()andaxml_read_buf(), run under AddressSanitizer, UndefinedBehaviorSanitizer and MemorySanitizer.One commit on master, independent of my other open PRs.
ctestpasses on master with this branch alone (343/343, excluding thenifti_c22_copy_imagefailure that #31 fixes), and on the union of all my branches under ASan+UBSan, TSan and MSan.