MDEV-40454: aria_pack UBSAN 'shift exponent 64' in flush_bits() - #5525
MDEV-40454: aria_pack UBSAN 'shift exponent 64' in flush_bits()#5525prathamesh04 wants to merge 1 commit into
Conversation
|
@prathamesh04 . I'm happy to review it. As a 10.11 fix can this be rebased back to there? |
56cd653 to
f7a6fdc
Compare
|
Done as requested — rebased the single commit onto 10.11 and switched the PR base branch to 10.11. The diff is unchanged (just the 2-line |
gkodinov
left a comment
There was a problem hiding this comment.
Thank you for your contribution! This is a preliminary review.
LGTM, but I also have a question below.
Please stand by for the final review.
| if (file_buffer.pos >= file_buffer.end) | ||
| flush_buffer(~ (ulong) 0); | ||
| file_buffer.bits= BITS_SAVED; | ||
| file_buffer.bitbucket= 0; |
There was a problem hiding this comment.
I believe this needs to be set to 0 on the return branch. I'd style the whole thing a bit differently:
static void flush_bits(void)
{
int bits;
ulonglong bit_buffer;
bits= file_buffer.bits & ~7;
if (bits != BITS_SAVED) {
bit_buffer= file_buffer.bitbucket >> bits;
bits= BITS_SAVED - bits;
while (bits > 0)
{
bits-= 8;
*file_buffer.pos++= (uchar) (bit_buffer >> bits);
}
if (file_buffer.pos >= file_buffer.end)
flush_buffer(~ (ulong) 0);
file_buffer.bits= BITS_SAVED;
}
file_buffer.bitbucket= 0;
There was a problem hiding this comment.
Good point, thanks @gkodinov!. I've restructured flush_bits() as you suggested: the flush is now wrapped in if (bits != BITS_SAVED) and file_buffer.bitbucket= 0; runs unconditionally, so it's reset on the no-op branch too. file_buffer.bits stays reset inside the flush path (it is already BITS_SAVED when there is nothing to flush).
Re-verified under a UBSAN build: no runtime error, exit 0, packed data round-trips and CHECK TABLE is OK; maria.aria_pack_mdev14183 passes.
flush_bits() shifts a 64-bit value by BITS_SAVED (=64) when no whole bytes are pending (file_buffer.bits is a multiple of 8), which is undefined behavior. On x86 the shift count is masked, silently writing garbage bits into the compressed output; on other platforms and under UBSAN with halt_on_error it aborts aria_pack. Return early from flush_bits() when there is nothing to flush. The existing test maria.aria_pack_mdev14183 reproduces the problem under a UBSAN build (runtime error: shift exponent 64 is too large for 64-bit type 'ulonglong').
f7a6fdc to
e1655a3
Compare
|
FYI: According to our development cycle we work on bugs In the following periods 15.03 - 30.04, 15.06 - 30.07, 15.12 - 31.01 and 15.09 - 30.10. So, please, expect to get a review somewhere between these two dates and the goal is to have your PR merged before the second date |
MDEV-40454: UBSAN
shift exponent 64 is too large for 64-bit type 'ulonglong'inaria_packProblem
The UBSAN buildbot reports on 10.11:
Root cause
flush_bits()does:file_buffer.bitsstarts atBITS_SAVED(=64) and counts down as bits arewritten.
flush_bits()is called at the end of every Huffman-tree block inwrite_huff_tree(), even when that block wrote nothing (itstree_buffwasempty / already flushed by the previous block). In that case
file_buffer.bits & ~7 == BITS_SAVED, andbitbucket >> 64is undefinedbehavior.
On x86 the shift count is masked to
& 63, so the garbage is silentlyfolded into the compressed output; under UBSAN with
halt_on_error(as onbuildbot)
aria_packaborts.maria.aria_pack_mdev14183is the existingtest that trips it.
Fix
Return early when there are no whole bytes to flush (
bits == BITS_SAVED).flush_bits()is a no-op in that case, so the change is behavior-preservingand simply removes the undefined shift.
Verification (11.4 debug + UBSAN)
-DWITH_UBSAN=ON:UBSAN_OPTIONS=halt_on_error=1 aria_pack -t taborts with the runtime error.CHECK TABLEOK.mariadb-test-run --suite=maria aria_pack_mdev14183passes; fullmariasuite is 60/60 green.
The same
flush_bits()exists unchanged in 10.6/10.11/11.4, so the fixapplies to all maintained branches (merge-up from 10.6 if that's your flow).
@vuvova could you take a look?