Update fork to upstream libjpeg-turbo 3.2.0#1
Conversation
(NOTE: I can do this only because I solely authored and modified those files.) This is mainly to avoid confusion, since jcstest and strtest are meant only for the libjpeg API library, and LICENSE.md says that the IJG License applies to "the libjpeg API library and associated programs." Previous branches of libjpeg-turbo will continue to provide those files under the 3-clause BSD license.
Following up on ced602a, - Rename simd/jsimd.h to simd/jsimdint.h to avoid confusion and to be somewhat consistent with the header naming conventions of the libjpeg API library. - Eliminate unnecessary #include directives in the SIMD modules. - Move src/jsimd.h and src/jsimddct.h to simd/. This makes the code easier to understand, since jsimd.h and jsimddct.h define the external interfaces for libsimd. This also ensures that all SIMD-related code and zlib-licensed code lives in the same directory. - In jsimd.h and jsimddct.h, remove references to libjpeg/SIMD and jsimdext.inc and replace them with the complete zlib License header. jsimdext.inc only exists in the x86 SIMD extensions, and jsimd.h and jsimddct.h never contained any code derived from libjpeg/SIMD. - Guard all inclusions of jsimd.h and jsimddct.h in the libjpeg API library source code with #ifdef WITH_SIMD. This eliminates the need for those headers to have knowledge of the macros defined in jconfig*.h. - Fix the generation of jsimdcfg.inc (broken by ced602a.)
Due to an oversight when the progressive Huffman encoding SIMD modules were developed, JSIMD_NOHUFFENC was not extended to those modules.
(+ document the feature)
The libjpeg in-memory destination manager has always re-allocated the JPEG destination buffer if the specified buffer pointer is NULL or the specified buffer size is 0. TurboJPEG's destination manager inherited that behavior. Because of fe80ec2, TurboJPEG's destination manager tries to reuse the most recent destination buffer if the same buffer pointer is specified. (The purpose of that is to enable repeated invocations of tj*Compress*() or tj*Transform() to automatically grow the destination buffer, as needed, with no intervention from the calling program.) However, because of the inherited code, TurboJPEG's destination manager also reallocated the destination buffer if the specified buffer size was 0. Thus, passing a previously-used JPEG destination buffer pointer to tj*Compress*() or tj*Transform() while specifying a destination buffer size of 0 confused the destination manager. It reallocated the destination buffer to 4096 bytes but reported the old destination buffer size to the libjpeg API. This caused a buffer overrun if the old destination buffer size was larger than 4096 bytes. The documentation for tj*Compress*() is contradictory on this matter. It states that the JPEG destination buffer size must be specified if the destination buffer pointer is non-NULL. However, it also states that, if the destination buffer is reused, the specified destination buffer size is ignored. The documentation for tj*Transform() does not specify the function's behavior if the destination buffer is reused. Thus, the behavior of the API is at best undefined if a calling application attempts to reuse a destination buffer while specifying a destination buffer size of 0. If that ever worked, it only worked in libjpeg-turbo 1.3.x and prior. This issue was exposed only through API abuse, and calling applications that abused the API in that manner would not have worked for the last 11 years. Thus, the issue did not represent a security threat. This commit merely hardens the API against such abuse, by modifying TurboJPEG's destination manager so that it refuses to re-allocate the JPEG destination buffer if the buffer pointer is reused and the specified buffer size is 0. That is consistent with the most permissive interpretation of the TurboJPEG API documentation. (The API already ignored the destination buffer size if the destination buffer pointer was reused and the specified buffer size was non-zero. It makes sense for it to do likewise if the specified buffer size is 0.) This commit also modifies TJUnitTest so that it verifies whether the API is hardened against the aforementioned abuse.
- If TJPARAM_LOSSLESS was set, then tj3EncodeYUV*8() called
jpeg_enable_lossless() (via setCompDefaults()), which caused the
underlying libjpeg API to silently disable subsampling and color
conversion. This led to three issues:
1. Attempting to encode RGB pixels produced incorrect YCbCr or
grayscale components, since color conversion did not occur. The
same issue occurred if TJPARAM_COLORSPACE was explicitly set to
TJCS_RGB.
2. Attempting to encode RGB pixels into a grayscale plane caused
tj3EncodeYUVPlanes8() to overflow the caller's destination pointer
array if the array was not big enough to accommodate three
pointers. If called from tj3EncodeYUV8(), tj3EncodeYUVPlanes8()
did not overflow the caller's destination pointer array, but a
segfault occurred when it attempted to copy to the Cb and Cr
pointers, which were NULL. The same issue occurred if
TJPARAM_COLORSPACE was explicitly set to anything other than
TJCS_GRAY.
3. Attempting to encode RGB pixels into subsampled YUV planes caused
tj3EncodeYUV*8() to overflow the caller's buffer(s) if the
buffer(s) were not big enough to accommodate 4:4:4 (non-subsampled)
YUV planes. That would have been the case if the caller allocated
its buffer(s) based on the return value of tj3YUVBufSize() or
tj3YUVPlaneSize(). The same issue occurs if TJPARAM_SUBSAMP is
explicitly set to TJSAMP_444.
tj3EncodeYUV*8() now ignores TJPARAM_LOSSLESS and TJPARAM_COLORSPACE.
- If TJPARAM_LOSSLESS was set, then attempting to compress a grayscale
plane into a JPEG image caused tj3CompressFromYUVPlanes8() to overflow
the caller's source pointer array if the array was not big enough to
accommodate three pointers. If called from tj3CompressFromYUV8(),
tj3CompressFromYUVPlanes8() did not overflow the caller's source
pointer array, but a segfault occurred when it attempted to copy from
the Cb and Cr pointers, which were NULL. This was similar to Issue 2
above. The same issue occurred if TJPARAM_COLORSPACE was explicitly
set to anything other than TJCS_GRAY.
tj3CompressFromYUV*8() now throws an error if TJPARAM_LOSSLESS is set,
and it now ignores TJPARAM_COLORSPACE.
These issues did not pose a security risk, since security exploits
involve supported workflows that function normally except when supplied
with malformed input data. It is documented that colorspace conversion,
chrominance subsampling, and compression from planar YUV images are
unavailable when TJPARAM_LOSSLESS is set. When TJPARAM_LOSSLESS is set,
the library effectively sets TJPARAM_SUBSAMP to TJSAMP_444 and
TJPARAM_COLORSPACE to TJCS_RGB, TJCS_GRAY, or TJCS_CMYK, depending on
the pixel format of the source image. That behavior is strongly implied
by the documentation of TJPARAM_LOSSLESS, although the documentation
isn't specific about whether TJPARAM_LOSSLESS applies to
tj3EncodeYUV*8(). In any case, setting TJPARAM_LOSSLESS before calling
tj3CompressFromYUV*8() was never a supported or functional workflow, and
setting TJPARAM_LOSSLESS before calling tj3EncodeYUV*8() was never a
functional workflow. Thus, there should be no applications "in the
wild" that use either workflow. Such applications would crash every
time they attempted to encode to or compress from a YUV image. In other
words, setting TJPARAM_LOSSLESS or TJPARAM_COLORSPACE required the
caller to understand the ramifications of the loss of color conversion
and/or subsampling, and failing to do so was essentially API abuse
(albeit subtle API abuse, hence the desire to make the behavior more
intuitive.)
This commit also removes no-op code introduced by
6da0515. Since setCompDefaults()
returns after calling jpeg_enable_lossless(), modifying the subsampling
level locally had no effect. The libjpeg API already silently disables
subsampling in jinit_c_master_control() if lossless compression is
enabled, so it was not necessary for setCompDefaults() to handle that.
Fixes libjpeg-turbo#839
- Clarify that YUV encoding performs downsampling as well as color conversion. - Clarify that TJPARAM_LOSSLESS is ignored by tj3CompressFromYUV*8(). - Clarify that tj3CompressFromYUV*8() generates only YCbCr or grayscale JPEG images, i.e. that TJPARAM_COLORSPACE has no effect.
(macos-13 is deprecated.)
This is not a security vulnerability, since applications that pass such values to the Java API would fail regardless, and such a bug would never make it into the wild. By contrast, a security vulnerability arises from applications that work correctly with most input data sets but trigger a library failure, such as a buffer overrun, with one or more specific input data sets. As with most imaging APIs, the libjpeg-turbo APIs rely upon the calling application to pass appropriately-sized buffers and appropriate size/dimension arguments. The failure to do so is no more the fault of libjpeg-turbo than calling 'buf = malloc(1); buf[2] = 0;' is the fault of the C library. Buffer size checking is a bonus feature of the Java API that isn't (and can't be) provided by the C API, so this commit merely hardens the bonus feature against API abuse, in keeping with the Java paradigm of throwing an exception rather than crashing due to a caller-imposed buffer overrun.
Use stronger language in hopes that people will actually read it before spamming the security advisory system. If not, then I may be forced to disable private vulnerability reporting entirely.
If loading a 2-to-8-bit image, unset srcBuf12 and srcBuf16. If loading a 9-to-12-bit image, unset srcBuf8 and srcBuf16. If loading a 13-to-16-bit image, unset srcBuf8 and srcBuf12. Otherwise, TJCompressor.getSourceBuf() will return srcBuf8 or srcBuf12, in order, if any previous invocation of TJCompressor.loadSourceImage() set them, irrespective of the buffer that was set by the most recent invocation. This probably helps with garbage collection as well, since it signals to the GC that the unused buffers are really unused.
If the data precision of the PBMPLUS file does not match the target data precision, then the grayscale or RGB samples are rescaled to the target data precision. Thus, we need to pass (1 << cinfo->data_precision) - 1 to rgb_to_cmyk() instead of maxval. This commit also modifies TJUnitTest so that it validates the correctness of upconversion and downconversion in the PPM reader. Fixes libjpeg-turbo#841
This fully reverts c96e93b. Specifying a full path with install(TARGETS ...) causes the paths in the CMake package config files to be hard-coded, which effectively makes the package non-relocatable. Specifying a full path with other install() commands breaks the --prefix option to cmake --install. There is in fact no sane way to support blank install directories. Integrators who wish to install certain files into the main libjpeg-turbo install directory can set a particular CMAKE_INSTALL_*DIR variable to "<CMAKE_INSTALL_PREFIX>", with the understanding that doing so will break relocatability in the same way that c96e93b did. This commit also tweaks the RPM spec so that RPMs built from a source RPM will contain relocatable CMake package config files.
With the advent of AI code analysis tools, I keep getting false positive reports (three in the past two weeks) about potential integer overflow/wraparound in the buffer size doubling code in empty_mem_output_buffer(). Overflow could never actually occur there, because: - On 64-bit systems, the maximum theoretical JPEG size is 65500 * 65500 * cinfo->num_components * sizeof(DCTELEM) bytes, which is of course much less than 8 exabytes (SIZE_MAX / 2). - On 32-bit systems, malloc() will never return a buffer >= 2 GB, so the malloc() call will fail before 32-bit integer overflow can occur. However, because I'm sick of fielding multiple duplicate reports about this, here's a "fix" for the non-existent problem.
(to prepare for new commits)
This extends 3e8911a to fix similar issues that occur when attempting to use an Arm64 build of CMake to generate Visual Studio project files targetting x64 or x86 ("win32" in CMake parlance) or attempting to use an x86[-64] build of CMake to generate Visual Studio project files targetting 32-bit Arm. In a nutshell, CMAKE_SYSTEM_PROCESSOR is set based on the architecture of the CMake executable, so CMAKE_GENERATOR_PLATFORM should take precedence over that when generating Visual Studio project files. Fixes libjpeg-turbo#880
Because of a66398f (libjpeg-turbo 3.1 beta1), md5cmp is now under ${CMAKE_BINARY_DIR}/test rather than ${CMAKE_BINARY_DIR}/md5. Apparently the indexedcolortest script only worked because I had leftover cruft from a 3.0.x build in my build directory.
Range-check all samples before using them as indices for the rescale[] array. This guards against a potential buffer overrun if the caller attempts to write out-of-range samples to the PNG image. Referring to the definition of a security vulnerability in SECURITY.md, this bug was in an official, supported release (although it affects only 3.2 beta1, and we do not claim that beta-quality code is production-ready.) However, the caller was not well-behaved, i.e. a caller that abused the API in this manner could never work properly. (The best it could hope for is a bogus output image or an error.) Furthermore, the only exposure to the issue in a public API function was in tj3SaveImage*(), which (unlike tj3LoadImage*()) is not exposed to arbitrary external input data. In other words, because tj3SaveImage*() does not interact with images loaded from files or the network, a caller could only trigger the issue by abusing the API.
In master_selection(), which is called in the body of jpeg_start_decompress(), ensure that the calling application has not erroneously changed the value of cinfo->data_precision from its initial value set in jpeg_read_header(), unless the application is decompressing an 8-bit-per-sample lossy JPEG image to a 12-bit-per-sample output image. This hardens the libjpeg API against applications that may, when decompressing a lossless JPEG image, erroneously set the output data precision to a different value than the JPEG data precision after calling jpeg_read_header() and before calling jpeg_start_decompress(). Only the lossy code paths deal with differing JPEG and output data precisions. Thus, in the context of djpeg, forcing 12-bit output (enabled by 4d0df37) while attempting to decompress a lossless JPEG image with more than 12 bits of data precision per sample could have exposed the buffer overrun in the PNG writer that was fixed by the previous commit. In other cases, a bogus output image was generated. As with the PNG writer issue, this represented an opportunity for API hardening rather than a security vulnerability. Setting the output data precision to a different value than the JPEG data precision only produces defined behavior when decompressing an 8-bit-per-sample lossy JPEG image. An application that sets the output data precision in any other case is abusing the API, and the best it could hope for is a bogus output image or an error. Regardless, this issue was ancillary to the PNG writer issue, and once that issue was fixed, the worst case for this issue was a bogus output image. Note that the TurboJPEG API does not allow forcing 12-bit-per-sample output when decompressing lossless JPEG images. This further supports the assertion that the PNG writer bug fixed by the previous commit was not exploitable, since a calling application could not have triggered it by naively passing the output of a TurboJPEG decompression function into tj3SaveImage*().
(broken by previous commit) cinfo->master->jpeg_data_precision will only be set if jpeg_read_header() reads the SOF marker. That may not happen if the caller uses a custom marker processor, as the tj*DecodeYUV*() functions do.
We don't actually support gamma correction when loading PNG files, so the in-tree implementation of libspng will never call pow(). + Add comments to indicate where and why we have disabled code.
jpeg_crop_scanline() doesn't actually work with raw data output. If buffered-image mode is disabled and raw data output is enabled, the function throws JERR_BAD_STATE because the global state is DSTATE_RAW_OK rather than DSTATE_SCANNING. However, due to an oversight in 8162edd (libjpeg-turbo 2.1.4), enabling both buffered-image mode and raw data output allowed the caller to sneak through the gates. Thus, executing the following sequence with the same libjpeg API instance caused a use-after-free issue: - jpeg_start_decompress(&cinfo) * calls j*init_upsampler() to allocate the upsampler (cinfo.upsample) - jpeg_abort_decompress(&cinfo) * frees the upsampler - cinfo.raw_data_out = TRUE - cinfo.buffered_image = TRUE - jpeg_start_decompress(&cinfo) * does not call j*init_upsampler(), because cinfo.raw_data_out == TRUE - jpeg_crop_scanline(&cinfo, &xoffset, &width) where xoffset and width would cause any of the component planes to be cropped to a width of 1 sample * fails to catch that raw data output is enabled, because the global state is DSTATE_BUFIMAGE rather than DSTATE_RAW_OK * fails to catch that the previous decompression operation was aborted, because cinfo->output_scanline is 0 (since jpeg*_read_scanlines() was never called) * calls j*init_upsampler() with cinfo->master->jinit_upsampler_no_alloc == TRUE, which tells j*init_upsampler() to reuse the upsampler * j*init_upsampler() tries to reuse the upsampler that has already been freed. Any application that did this could never work properly. (The best it could hope for is a bogus output image or an error.) Thus, this represented an opportunity for API hardening rather than a security vulnerability. This commit also modifies the jpeg_crop_scanline() documentation to clarify that the function doesn't work with raw data output. (Previously, the documentation was subtle on that point. It says that jpeg_crop_scanline() must be called between jpeg_start_decompress() and jpeg*_read_scanlines(), which implies that it can't be called between jpeg_start_decompress() and jpeg*_read_raw_data().)
The TurboJPEG API was originally a project-private wrapper for VirtualGL and TurboVNC that abstracted various high-speed JPEG libraries on various platforms. The earliest prototype version in VirtualGL 0.9.x (so early that it wasn't called "TurboJPEG" yet) didn't allow the pitch to be specified. (Instead, a flag specified whether each row should be padded to the nearest multiple of 4 bytes.) The TurboJPEG 1.0 API, which was first implemented in VirtualGL and TurboVNC 1.0.x, introduced the pitch parameter, documented that it must be > 0, and returned an error if it was < 0. TurboJPEG 1.0 also set the pitch to the default value (width * pixel size) if it was 0, but that behavior was undocumented until TurboJPEG 1.1. I vaguely recall that I had originally intended to allow calling applications to set the pitch to a smaller value than the default. I can't remember why, but perhaps it was to enable a perverse form of cropping years before the partial image decompression feature existed. Setting the pitch to a smaller value than the default works, but you have to allocate the buffer to (pitch * (height - 1) + width * pixel size) samples. Thus, if we wanted to support that use case, then the documentation would have to be modified accordingly. I am inclined not to support it, because: - the use case would rarely if ever be tested, - the documentation implies that it isn't supported, and - partial image decompression is a much more reasonable way to trim the image width. Thus, this commit hardens the TurboJPEG API to disallow pitch values greater than 0 but less than the default value. Fixes libjpeg-turbo#882
(introduced by previous commit)
The flatten and reflect cropping extensions fill the expanded region with, respectively, the DC coefficient(s) of the nearest block(s) in the input image and repeated reflections of the input image. If the input image is narrower than 1 iMCU and partial iMCUs are trimmed, then none of the input image blocks will exist in the output image (i.e. there will be nothing to flatten or reflect.) In that case, comp_width will be 0 in do_crop_ext_flat() and do_crop_ext_reflect(), so we need to throw an error to avoid (respectively) a buffer overrun and an infinite loop. Fixes libjpeg-turbo#883
The intermediate PAD(width, mcuw) * PAD(height, mcuh) computation was
performed using signed integer arithmetic and could thus overflow if
PAD(width, mcuw) * PAD(height, mcuh) > INT_MAX. This was innocuous,
however, because the API requires that the allocated buffer size be
reported to tj3Compress*() or tj3Transform(). The calling program would
presumably pass the (incorrect) return value from tj3JPEGBufSize() to
tj3Alloc() and report the same size to tj3Compress*() or tj3Transform().
Depending on the value of TJPARAM_NOREALLOC,
tj3Compress*()/tj3Transform() would either reallocate the buffer as
needed or fail if the reported buffer size was exceeded. As long as the
reported buffer size was the same as the allocated buffer size, no
buffer overrun could occur. Thus, the only issue was a usability issue:
passing large dimensions to tj3JPEGBufSize(), then passing the return
value of tj3JPEGBufSize() to tj3Alloc() and
tj3Compress*()/tj3Transform(), could have caused an error ("Buffer
passed to JPEG library is too small") if TJPARAM_NOREALLOC was set.
- "Linux" = "Un*x" (we see you, *BSD) - Grammar tweaks - Clarify our project's AI and pull request policies.
This is a similar fix to abf0f59, except applied to the bytes per row (stride) of each plane in a planar YUV image rather than the bytes per row (pitch) of a packed-pixel image. Specifying a stride less than the plane width has never been a useful or supported use case. Fixes libjpeg-turbo#894
This is the same fix from 94d5ff4 applied to the legacy TJBUFSIZE() function in the TurboJPEG 1.0 API.
The PAD(pw, align) computation was performed using signed integer arithmetic and could thus overflow if pw + align > INT_MAX. This was innocuous, however, because: - The intermediate value always overflowed to a negative integer. - When the PAD() macro's bitwise AND operator was applied, the negative integer changed to the correct positive integer. In other words, because PAD() is ultimately a bitwise operation rather than a mathematical operation, this was purely a UBSan warning rather than an actual issue. Fixes libjpeg-turbo#896
The PAD(pw0, align) and PAD(pw1, align) computations were performed
using signed integer arithmetic and could thus overflow if pw0 + align >
INT_MAX or pw1 + align > INT_MAX. This was innocuous, however, because:
- The intermediate values always overflowed to negative integers.
- When the PAD() macro's bitwise AND operator was applied, the negative
integers changed to the correct positive integers.
- Values large enough to trigger an overflow would have subsequently
caused a TurboJPEG API error ("Image or row alignment is too large")
or a libjpeg API error ("Maximum supported image dimension is 65500
pixels") to be thrown.
In other words, because PAD() is ultimately a bitwise operation rather
than a mathematical operation, these were purely UBSan warnings rather
than actual issues.
As of June 2026, the windows-2025 runner image uses Visual Studio 2026.
|
Important Review skippedToo many files! This PR contains 698 files, which is 548 over the limit of 150. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (58)
📒 Files selected for processing (698)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Advances the cornerstonejs libjpeg-turbo fork from
dc4a93f(2.1.4-era, Dec 2022) to upstream 3.2.0 (latest stable, 2026-06-30).No custom patches to carry — the fork's pin is a plain upstream commit with zero divergence (merge-base with upstream == HEAD) and a direct ancestor of 3.2.0. Clean version advance.
Note: this is a major version jump (2.x -> 3.x), so the codecs-side glue (JPEGDecoder/JPEGEncoder.hpp for both the 8-bit and 12-bit packages) may need adaptation for API changes — validated on the codecs submodule-bump PR. Not built locally.