jchuff.c: Fix uninit read w/ AArch64, WITH_SIMD=0

Because of bf01ed2fbc, the simd field in
huff_entropy_encoder (and, by extension, the simd field in
savable_state) is only initialized if WITH_SIMD is defined.  Due to an
oversight, the simd field in savable_state was queried in flush_bits()
regardless of whether WITH_SIMD was defined.  In most cases, both
branches of the query have identical code, and the optimizer removes the
branch.  However, because the legacy Neon GAS Huffman encoder uses the
older bit buffer logic from libjpeg-turbo 2.0.x and prior (refer to
087c29e07f), the branches do not have
identical code when building for AArch64 with NEON_INTRINSICS undefined
(which will be the case if WITH_SIMD is undefined.)  Thus, if
libjpeg-turbo was built for AArch64 with the SIMD extensions disabled
at build time, it was possible for the Neon GAS branch in flush_bits()
to be taken, which would have set put_bits to a value that is incorrect
for the C Huffman encoder.  Referring to #728, a user reported that this
issue sometimes caused libjpeg-turbo to generate bogus JPEG images if it
was built for AArch64 without SIMD extensions and subsequently used
through the Qt framework.  (It should be noted, however, that disabling
the SIMD extensions in AArch64 builds of libjpeg-turbo is inadvisable
for performance reasons.)

I was unable to reproduce the issue on Linux/AArch64 using libjpeg-turbo
alone, despite testing various versions of GCC and Clang and various
optimization levels.  However, the issue is reproducible using MSan with
-O0, so this commit also modifies the GitHub Actions workflow so that
compiler optimization is disabled in the linux-msan job.  That should
prevent the issue or similar issues from re-emerging.

Fixes #728
This commit is contained in:
DRC
2023-10-09 14:13:55 -04:00
parent 3d1d68cf96
commit da48edfc49
3 changed files with 25 additions and 3 deletions

View File

@@ -169,7 +169,7 @@ jobs:
run: |
mkdir build
pushd build
cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O3 -g -fsanitize=memory -fno-sanitize-recover=all -fPIE" -DWITH_SIMD=0 ..
cmake -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER=clang -DCMAKE_C_FLAGS_RELWITHDEBINFO="-O0 -g -fsanitize=memory -fno-sanitize-recover=all -fPIE" -DWITH_SIMD=0 ..
export NUMCPUS=`grep -c '^processor' /proc/cpuinfo`
make -j$NUMCPUS --load-average=$NUMCPUS
make test

View File

@@ -12,6 +12,12 @@ mathematical (but not necessarily perceptible) edge block errors when
decompressing progressive JPEG images exactly two MCU blocks in width or that
use vertical chrominance subsampling.
3. Fixed a regression introduced by 3.0 beta2[6] that, in rare cases, caused
the C Huffman encoder (which is not used by default on x86 and Arm CPUs) to
generate incorrect results if the Neon SIMD extensions were explicitly disabled
at build time (by setting the `WITH_SIMD` CMake variable to `0`) in an AArch64
build of libjpeg-turbo.
3.0.0
=====

View File

@@ -108,7 +108,9 @@ typedef bit_buf_type simd_bit_buf_type;
typedef struct {
union {
bit_buf_type c;
#ifdef WITH_SIMD
simd_bit_buf_type simd;
#endif
} put_buffer; /* current bit accumulation buffer */
int free_bits; /* # of bits available in it */
/* (Neon GAS: # of bits now in it) */
@@ -133,7 +135,9 @@ typedef struct {
long *ac_count_ptrs[NUM_HUFF_TBLS];
#endif
#ifdef WITH_SIMD
int simd;
#endif
} huff_entropy_encoder;
typedef huff_entropy_encoder *huff_entropy_ptr;
@@ -147,7 +151,9 @@ typedef struct {
size_t free_in_buffer; /* # of byte spaces remaining in buffer */
savable_state cur; /* Current bit buffer & DC state */
j_compress_ptr cinfo; /* dump_buffer needs access to this */
#ifdef WITH_SIMD
int simd;
#endif
} working_state;
@@ -511,6 +517,7 @@ flush_bits(working_state *state)
simd_bit_buf_type put_buffer; int put_bits;
int localbuf = 0;
#ifdef WITH_SIMD
if (state->simd) {
#if defined(__aarch64__) && !defined(NEON_INTRINSICS)
put_bits = state->cur.free_bits;
@@ -518,7 +525,9 @@ flush_bits(working_state *state)
put_bits = SIMD_BIT_BUF_SIZE - state->cur.free_bits;
#endif
put_buffer = state->cur.put_buffer.simd;
} else {
} else
#endif
{
put_bits = BIT_BUF_SIZE - state->cur.free_bits;
put_buffer = state->cur.put_buffer.c;
}
@@ -536,6 +545,7 @@ flush_bits(working_state *state)
EMIT_BYTE(temp)
}
#ifdef WITH_SIMD
if (state->simd) { /* and reset bit buffer to empty */
state->cur.put_buffer.simd = 0;
#if defined(__aarch64__) && !defined(NEON_INTRINSICS)
@@ -543,7 +553,9 @@ flush_bits(working_state *state)
#else
state->cur.free_bits = SIMD_BIT_BUF_SIZE;
#endif
} else {
} else
#endif
{
state->cur.put_buffer.c = 0;
state->cur.free_bits = BIT_BUF_SIZE;
}
@@ -719,7 +731,9 @@ encode_mcu_huff(j_compress_ptr cinfo, JBLOCKROW *MCU_data)
state.free_in_buffer = cinfo->dest->free_in_buffer;
state.cur = entropy->saved;
state.cinfo = cinfo;
#ifdef WITH_SIMD
state.simd = entropy->simd;
#endif
/* Emit restart marker if needed */
if (cinfo->restart_interval) {
@@ -792,7 +806,9 @@ finish_pass_huff(j_compress_ptr cinfo)
state.free_in_buffer = cinfo->dest->free_in_buffer;
state.cur = entropy->saved;
state.cinfo = cinfo;
#ifdef WITH_SIMD
state.simd = entropy->simd;
#endif
/* Flush out the last data */
if (!flush_bits(&state))