Commit f99d5f58 for libheif
commit f99d5f58fdb9369861a24b0c24648a8c3b6a2ee2
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sat Oct 24 22:24:34 2026 +0200
Reject JPEG streams with unknown coded size
diff --git a/libheif/codecs/jpeg_dec.cc b/libheif/codecs/jpeg_dec.cc
index f8f25d6a..19441510 100644
--- a/libheif/codecs/jpeg_dec.cc
+++ b/libheif/codecs/jpeg_dec.cc
@@ -66,6 +66,17 @@ Error Decoder_JPEG::parse_SOF(const std::vector<uint8_t>& data)
return Error::Ok;
}
+ ConfigInfo info;
+ Error err = parse_SOF(data, &info);
+ if (!err) {
+ m_config = info;
+ }
+ return err;
+}
+
+
+Error Decoder_JPEG::parse_SOF(const std::vector<uint8_t>& data, ConfigInfo* out_config)
+{
Error error_invalidSOF{heif_error_Invalid_input,
heif_suberror_Unspecified,
"Invalid JPEG SOF header"};
@@ -123,7 +134,7 @@ Error Decoder_JPEG::parse_SOF(const std::vector<uint8_t>& data)
}
}
- m_config = info;
+ *out_config = info;
return Error::Ok;
}
@@ -157,17 +168,18 @@ Result<std::optional<ImageSize>> Decoder_JPEG::get_max_coded_image_size(const st
// separate configuration record that carries it). `compressed_data` is the
// buffer about to be handed to the decoder, so an oversized SOF is caught here
// before libjpeg allocates, regardless of the (possibly tiny) 'ispe'.
- Error err = const_cast<Decoder_JPEG*>(this)->parse_SOF(compressed_data);
+ // Parse this exact buffer rather than reusing m_config. A sequence decoder is
+ // reused across samples, and every sample can carry a different SOF.
+ ConfigInfo config;
+ Error err = parse_SOF(compressed_data, &config);
if (err) {
- // No parseable SOF marker: skip the gate and let the decoder handle it.
- return std::optional<ImageSize>{};
- }
-
- if (m_config->coded_width == 0 || m_config->coded_height == 0) {
- return std::optional<ImageSize>{};
+ // A JPEG decoder must find the same SOF before allocating its image. If we
+ // cannot parse it, a different or future backend must not receive unchecked
+ // dimensions.
+ return err;
}
- return std::optional<ImageSize>{ImageSize{m_config->coded_width, m_config->coded_height}};
+ return std::optional<ImageSize>{ImageSize{config.coded_width, config.coded_height}};
}
diff --git a/libheif/codecs/jpeg_dec.h b/libheif/codecs/jpeg_dec.h
index f8752ebe..2e2fd99e 100644
--- a/libheif/codecs/jpeg_dec.h
+++ b/libheif/codecs/jpeg_dec.h
@@ -70,6 +70,7 @@ private:
Error parse_SOF();
Error parse_SOF(const std::vector<uint8_t>& data);
+ static Error parse_SOF(const std::vector<uint8_t>& data, ConfigInfo* out_config);
};
#endif
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3675689a..80275385 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -70,6 +70,7 @@ add_libheif_test(sdtp_sample_limit)
add_libheif_test(overlay_spec_parse)
add_libheif_test(add_channel_checks)
add_libheif_test(jpeg2000)
+ add_libheif_test(jpeg_coded_size)
add_libheif_test(avc_box)
add_libheif_test(vvc_sps)
add_libheif_test(av1_coded_size)
diff --git a/tests/jpeg_coded_size.cc b/tests/jpeg_coded_size.cc
new file mode 100644
index 00000000..f6f8b40b
--- /dev/null
+++ b/tests/jpeg_coded_size.cc
@@ -0,0 +1,79 @@
+/*
+ libheif JPEG coded-size scan tests
+
+ MIT License
+
+ Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+*/
+
+#include "catch_amalgamated.hpp"
+#include "codecs/jpeg_dec.h"
+#include "libheif/heif.h"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+
+static std::vector<uint8_t> make_jpeg_header(uint16_t width, uint16_t height)
+{
+ return {
+ 0xff, 0xd8, // SOI
+ 0xff, 0xc0, 0x00, 0x11, // baseline SOF, segment length 17
+ 0x08, // sample precision
+ static_cast<uint8_t>(height >> 8), static_cast<uint8_t>(height),
+ static_cast<uint8_t>(width >> 8), static_cast<uint8_t>(width),
+ 0x03, // components
+ 0x01, 0x22, 0x00, // Y: 2x2
+ 0x02, 0x11, 0x01, // Cb: 1x1
+ 0x03, 0x11, 0x01, // Cr: 1x1
+ 0xff, 0xd9 // EOI
+ };
+}
+
+
+TEST_CASE("JPEG coded-size scan rejects an unparseable SOF")
+{
+ Decoder_JPEG decoder(nullptr);
+
+ const std::vector<uint8_t> malformed{0xff, 0xd8, 0xff, 0xd9};
+ auto result = decoder.get_max_coded_image_size(malformed);
+ REQUIRE(result.is_error());
+ CHECK(result.error().error_code == heif_error_Invalid_input);
+}
+
+
+TEST_CASE("JPEG coded-size scan checks every sequence sample")
+{
+ Decoder_JPEG decoder(nullptr);
+
+ auto first = decoder.get_max_coded_image_size(make_jpeg_header(64, 48));
+ REQUIRE(!first.is_error());
+ REQUIRE(first->has_value());
+ CHECK((*first)->width == 64);
+ CHECK((*first)->height == 48);
+
+ auto second = decoder.get_max_coded_image_size(make_jpeg_header(320, 240));
+ REQUIRE(!second.is_error());
+ REQUIRE(second->has_value());
+ CHECK((*second)->width == 320);
+ CHECK((*second)->height == 240);
+}