BUG: Do not use sscanf's output when sscanf matched nothing - #67
Conversation
b46aaf9 to
dfec2f8
Compare
dfec2f8 to
ead5873
Compare
ead5873 to
f61f914
Compare
|
Rebased onto current No test added — NOT-DEMONSTRABLE on this host. The reasoning is below; if you would rather have a test that pins the NULL return for whitespace-only input even though it passes with and without the fix, say so and I will add it. Rebase and commit-message cleanupThe ~29 shared-base commits are already on Message cleanup: removed the Why there is no testThree separate checks, all of which have to hold for a ctest to be worth adding:
I also tried The fix is still right — |
nifti_image_from_ascii() scans its input three times with
ii = sscanf( str+spos , "%1023s%n" , lhs , &nn ) ; spos += nn ;
if( ii == 0 || strcmp(lhs,"<nifti_image") != 0 ) return NULL ;
Two things are wrong with that pair of lines. sscanf() returns EOF,
not 0, when the input ends before anything is matched, so ii == 0 does
not detect the failure; and nn is added to spos before ii is looked
at, so the position advances by an indeterminate amount.
For a string holding nothing but whitespace the first scan returns
EOF, the test passes, and strcmp() reads the uninitialized 1024 byte
stack buffer. MemorySanitizer:
WARNING: MemorySanitizer: use-of-uninitialized-value
#0 nifti_image_from_ascii nifti2_io.c:8836
All three now test for the one successful conversion and only then
advance spos, which is what the surrounding code assumed. The two
inside the loop cannot fail today, because whitespace is skipped and
end of string is checked before each, and the in-tree readers gate on
has_ascii_header(), so the first is reached only through a direct call
to this published function. The same three lines exist in
nifti1_io.c and are corrected there too.
Found by fuzzing nifti_image_from_ascii() with libFuzzer under
MemorySanitizer.
f61f914 to
599279c
Compare
70fa3a6
into
InsightSoftwareConsortium:master
nifti_image_from_ascii() scans its input three times with
Two things are wrong with that pair of lines. sscanf() returns EOF, not
0, when the input ends before anything is matched, so ii == 0 does not
detect the failure; and nn is added to spos before ii is looked at, so
the position advances by an indeterminate amount whichever value comes
back.
The first of the three is reachable from any string that holds nothing
but whitespace: sscanf() returns EOF, the test passes, and strcmp()
walks an uninitialized 1024 byte stack buffer looking for a terminator
it is not guaranteed to find. MemorySanitizer:
All three now test for the one successful conversion and only then
advance spos, which is what the surrounding code assumed. The two
inside the loop cannot fail today, because whitespace is skipped and
end of string is checked before each, but they are the same pattern and
are corrected the same way.
The same function, with the same three lines, exists in nifti1_io.c.
Found by fuzzing nifti_image_from_ascii() with clang's libFuzzer under
MemorySanitizer.
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.