You've already forked libjpeg-turbo
mirror of
https://github.com/libjpeg-turbo/libjpeg-turbo.git
synced 2026-10-23 11:48:53 +03:00
TJ: Only track dst buf size if TJPARAM_NOREALLOC=0
The destination manager always tracked the destination buffer size from previous compression or transform function calls, even if TJPARAM_NOREALLOC was 1 during those previous calls. That was incorrect behavior per the documentation, because the destination buffer is only supposed to be API-managed if TJPARAM_NOREALLOC is 0. Under rare circumstances, the incorrect behavior exposed an issue with our own compress fuzzer (https://issues.oss-fuzz.com/issues/511253454) whereby, on the fourth test, malloc() reused the memory block from the third test (the only test with TJPARAM_NOREALLOC=1), causing the destination manager to think that the destination buffer was allocated to the worst-case size instead of 100 bytes. This led to a fully-contained buffer overrun. In practice, the issue was thwarted by ASan and valgrind, so OSS-Fuzz caught it only when testing with UBSan. In my lab, the issue occurred only with an Ubuntu 26.04/AArch64 virtual machine and only if the libjpeg-turbo SIMD extensions were disabled. Note that bufReuseTest() in tjunittest previously worked around the aforementioned incorrect behavior. It now tests for it. This commit also modifies the compress fuzzer so that it doesn't reallocate the destination buffer while it is API-managed. Additional issues stemming from that were extremely unlikely, however, since the destination buffer was reallocated to 100 bytes every time. (If the buffer had automatically been reallocated during a previous test, then it is extremely unlikely that malloc() would have reused the memory block of the reallocated buffer.)
This commit is contained in:
@@ -122,15 +122,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
} else
|
||||
srcBuf = imgBuf;
|
||||
|
||||
dstSize = maxBufSize = tj3JPEGBufSize(width, height, tests[ti].subsamp);
|
||||
maxBufSize = tj3JPEGBufSize(width, height, tests[ti].subsamp);
|
||||
if (tj3Get(handle, TJPARAM_NOREALLOC)) {
|
||||
tj3Free(dstBuf);
|
||||
dstSize = maxBufSize;
|
||||
if ((dstBuf = (unsigned char *)tj3Alloc(dstSize)) == NULL)
|
||||
goto bailout;
|
||||
} else if (dstBuf == NULL) {
|
||||
if ((dstBuf = (unsigned char *)tj3Alloc(100)) == NULL)
|
||||
goto bailout;
|
||||
dstSize = 100;
|
||||
}
|
||||
} else
|
||||
dstSize = 0;
|
||||
|
||||
if (size >= 34)
|
||||
tj3SetICCProfile(handle, (unsigned char *)&data[2], 32);
|
||||
@@ -145,8 +148,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
|
||||
sum += dstBuf[i];
|
||||
}
|
||||
|
||||
tj3Free(dstBuf);
|
||||
dstBuf = NULL;
|
||||
if (tj3Get(handle, TJPARAM_NOREALLOC)) {
|
||||
tj3Free(dstBuf);
|
||||
dstBuf = NULL;
|
||||
}
|
||||
tj3Free(imgBuf);
|
||||
imgBuf = NULL;
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ jpeg_mem_dest_tj(j_compress_ptr cinfo, unsigned char **outbuffer,
|
||||
dest->pub.init_destination = init_mem_destination;
|
||||
dest->pub.empty_output_buffer = empty_mem_output_buffer;
|
||||
dest->pub.term_destination = term_mem_destination;
|
||||
if (dest->buffer == *outbuffer && *outbuffer != NULL && alloc)
|
||||
if (dest->buffer == *outbuffer && *outbuffer != NULL && alloc && dest->alloc)
|
||||
reused = TRUE;
|
||||
dest->outbuffer = outbuffer;
|
||||
dest->outsize = outsize;
|
||||
|
||||
@@ -1091,10 +1091,6 @@ static void bufReuseTest(void)
|
||||
strcmp(tj3GetErrorStr(handle),
|
||||
"Buffer passed to JPEG library is too small"))
|
||||
THROW("tj3Compress8() succeeded unexpectedly");
|
||||
/* Re-specify the destination buffer size. */
|
||||
jpegSizes[0] = worstCaseSize;
|
||||
TRY_TJ(handle, tj3Compress8(handle, srcBuf, w, 0, h, TJPF_BGRX, &jpegBufs[0],
|
||||
&jpegSizes[0]));
|
||||
|
||||
/* Automatic destination buffer (re)allocation enabled */
|
||||
TRY_TJ(handle, tj3Set(handle, TJPARAM_NOREALLOC, 0));
|
||||
@@ -1103,6 +1099,11 @@ static void bufReuseTest(void)
|
||||
pre-allocated to the worst-case size and that the buffer size is ignored
|
||||
when the destination buffer is reused. */
|
||||
oldJPEGBufs[0] = jpegBufs[0];
|
||||
jpegSizes[0] = worstCaseSize;
|
||||
TRY_TJ(handle, tj3Compress8(handle, srcBuf, w, 0, h, TJPF_BGRX, &jpegBufs[0],
|
||||
&jpegSizes[0]));
|
||||
if (jpegBufs[0] != oldJPEGBufs[0])
|
||||
THROW("JPEG buffer unexpectedly reallocated")
|
||||
jpegSizes[0] = 0;
|
||||
TRY_TJ(handle, tj3Compress8(handle, srcBuf, w, 0, h, TJPF_BGRX, &jpegBufs[0],
|
||||
&jpegSizes[0]));
|
||||
|
||||
Reference in New Issue
Block a user