stack-buffer-overflow in decNumber#9019
Conversation
|
@AlexPeshkoff Good day. Should I write a unit test? |
If you wish - please do. On the one hand result appears to be obvious, but additional test makes no harm. |
Follow-up to the earlier stack-buffer-overflow fix. Adds 8 fixes: - decBasic.c: unsigned DPD shift in decFloatAdd (UB); break out of outer loop in decQuadCompareTotal (OOB read); bounds-check before UBTOUI in strip-leading-zeros loops (decQuadFMA and 3 others, OOB read) - decCommon.c: unsigned exponent accumulation in decFloatFromString (overflow) - decNumber.c: unsigned X10 in decNumberFromString (overflow/shift); unsigned n<<1 in decNumberPower and decNumberLn (UB)
|
Question: How do I run the decNumber regression tests so that they actually catch bugs? I added regression test cases for the decNumber defect batch in src/common/tests/DecNumberTest.cpp (on POSIX, these are automatically picked up by the common_test target; on MSVC, you need to manually add them to common_test.vcxproj). The problem is that most of these tests only catch regressions when built with sanitizers, and I don’t see any options for such a build in the project tree. The defects fall into two categories:
So, with a regular There are two options, but in my opinion, they are unsatisfactory.
|
decNumber — follow-up fuzzing fixes (OOB reads + UB), part 1: patch
Background
Follow-up fuzz testing of the decimal floating-point routines in the decNumber
library — this time with ASan and UBSan together — surfaced a further batch of
defects beyond the three
stack-buffer-overflowcases fixed earlier. They fall intwo classes:
stack-buffer-overflow, READ of size 4) in theNaN-payload comparison and in the FMA leading-zero strip.
parsing over-long exponents and in the power/ln bit-walk loops.
Eight distinct defects are addressed here; a ninth reproduced scenario
(
decQuadAdd,decBasic.c:1401) is already fixed by the previous PR and is keptonly as a regression case.
Potentially on versions 3, 4, 5, and master.
Affected files
lib/decBasic.cdecFloatAdd/decQuadAddlib/decBasic.cdecQuadCompareTotallib/decBasic.cdecQuadFMA/ strip-leading-zeroslib/decCommon.cdecFloatFromString/decQuadFromStringlib/decNumber.cdecNumberFromStringlib/decNumber.cdecNumberPowerlib/decNumber.cdecNumberLnHow to reproduce
The reproducer
new_poc.cis a deterministic dispatcher:seed_bugN[0]selects therounding mode,
seed_bugN[1] % 16selects the operation, and the remaining bytes aresplit into two strings fed to the
decQuad*/decNumber*functions.GCC
gcc -g -O0 -fsanitize=address,undefined -fno-sanitize-recover=undefined -I./ \ new_poc.c decContext.c decNumber.c decDouble.c decQuad.c decPacked.c -o poc_runner ./poc_runner 1 # 1..9Clang
clang -g -O0 -fsanitize=address,undefined -fno-sanitize-recover=undefined -I./ \ new_poc.c decContext.c decNumber.c decDouble.c decQuad.c decPacked.c -o poc_runner ./poc_runner 1 # 1..9All cases fire consistently on the vulnerable code under ASan/UBSan with both compilers.
Previous reproducer (
poc.c, regression check)Since this is an update, the earlier three cases (
decQuadAdd,decDoubleReduce,decQuadReduce) should still pass. Build and run the original PoC the same way:gcc -g -O0 -fsanitize=address,undefined -fno-sanitize-recover=undefined -I./ \ decContext.c decNumber.c decDouble.c decQuad.c \ decimal32.c decimal64.c decimal128.c decPacked.c poc.c -o poc_runner ./poc_runner 1 # 1..3 # clang: replace gcc with clang, same flagsnew_poc.c
poc.c
Sanitizer output
Bug #1 — decQuadAdd (UBSan)
Bug #3 — decQuadCompareTotal (ASan)
Bug #4 — decQuadFMA (ASan)
Bug #5 — decQuadFromString (UBSan)
Bug #6 / #7 — decNumberFromString (UBSan)
Bug #8 — decNumberPower (UBSan)
Bug #9 — decNumberLn (UBSan)
Changes
lib/decBasic.c— bug #1 —decFloatAddBIN2DPD[]isuShort, promoted to signedint; a DPD value (up to 12 bits) shiftedby 20/26/28/30 places reaches the sign bit — UB.
encodeisuIntand the high-bittruncation is intended, so shift in unsigned arithmetic:
lib/decBasic.c— bug #3 —decQuadCompareTotalWhen the byte-wise inner loop found a differing NaN-payload byte it broke only out of
the inner loop; the outer 4-byte loop then continued from a misaligned pointer and read
past
bufl. It was also a logic bug (the winner must be the first/most-significantdifference). Break out of the outer loop once a winner is found:
lib/decBasic.c— bug #4 — strip-leading-zeros loops (lines 1536, 2080, 2082, 2245)&&evaluates the 4-byteUBTOUI()read before the bounds check, reading past thecoefficient near its end. Test the bound first (matching
decCommon.c:267):lib/decCommon.c— bug #5 —decFloatFromStringThe "exponent too long" clip runs after the accumulation loop, so
exp*10overflowsintinside the loop. Accumulate unsigned (the over-large value is still clipped):lib/decNumber.c— bugs #6, #7 —decNumberFromStringX10(exponent)on a long exponent both left-shifts into the sign bit and overflows theadd. Legitimate 10-digit exponents (up to 1999999999) must be preserved, so compute
X10unsigned; genuinely oversized exponents are still cut by the existing length/first-digit clip:
lib/decNumber.c— bugs #8, #9 —decNumberPower(2202) anddecNumberLn(5469)The exponent bit-walk shifts a signed
Intleft while testing its sign — UB when a bitreaches the sign position. Shift in unsigned and reinterpret (the
n<0sign test ispreserved):
Verification
Rebuilt
poc_runnerwith the same command and ran cases 1–9 under ASan+UBSan(
-fno-sanitize-recover=undefined):All nine exit cleanly with no ASan/UBSan diagnostic, on both clang and gcc. The previous
reproducer
poc.c(cases 1–3) also stays clean, confirming no regression of the earlierfix:
Functional sanity checks pass unchanged (
decQuadAdd,decQuadCompareTotal,decQuadFMA,decNumberFromString,decNumberPower,decNumberLn), confirming thefixes preserve arithmetic behaviour.