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);
+}