Commit b294f091 for libheif

commit b294f091ed822599ae782722eda2b8cb4aa7db42
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sun Oct 25 02:39:25 2026 +0100

    Fix thumbnail handle on no-op encoding (GHSA-9rpf-2x22-78m9)

diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc
index 08c6ab18..602aa424 100644
--- a/examples/heif_enc.cc
+++ b/examples/heif_enc.cc
@@ -2532,7 +2532,7 @@ int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding
     if (thumbnail_bbox_size > 0) {
       // encode thumbnail

-      struct heif_image_handle* thumbnail_handle;
+      struct heif_image_handle* thumbnail_handle = nullptr;

       options->save_alpha_channel = master_alpha && thumb_alpha;

diff --git a/libheif/api/libheif/heif_aux_images.cc b/libheif/api/libheif/heif_aux_images.cc
index 214ebfb5..3e8e8c0e 100644
--- a/libheif/api/libheif/heif_aux_images.cc
+++ b/libheif/api/libheif/heif_aux_images.cc
@@ -200,6 +200,10 @@ heif_error heif_context_encode_thumbnail(heif_context* ctx,
                                          int bbox_size,
                                          heif_image_handle** out_image_handle)
 {
+  if (out_image_handle) {
+    *out_image_handle = nullptr;
+  }
+
   heif_encoding_options* options = heif_encoding_options_alloc();
   heif_encoding_options_copy(options, input_options);

@@ -216,10 +220,8 @@ heif_error heif_context_encode_thumbnail(heif_context* ctx,
   std::shared_ptr<ImageItem> thumbnail_image = *encodingResult;

   if (!thumbnail_image) {
-    Error err(heif_error_Usage_error,
-              heif_suberror_Invalid_parameter_value,
-              "Thumbnail images must be smaller than the original image.");
-    return err.error_struct(ctx->context.get());
+    // The image already fits the bounding box; no thumbnail was encoded.
+    return heif_error_success;
   }

   Error error = ctx->context->assign_thumbnail(image_handle->image, thumbnail_image);
diff --git a/libheif/api/libheif/heif_cxx.h b/libheif/api/libheif/heif_cxx.h
index 6ce9091d..66aff52f 100644
--- a/libheif/api/libheif/heif_cxx.h
+++ b/libheif/api/libheif/heif_cxx.h
@@ -159,7 +159,7 @@ namespace heif {
     // throws Error
     void set_primary_image(ImageHandle& new_primary_image_handle);

-    // throws Error
+    // Throws Error, or returns an empty ImageHandle if no thumbnail is needed.
     ImageHandle encode_thumbnail(const Image& image,
                                  const ImageHandle& master_image,
                                  Encoder& encoder,
@@ -1457,7 +1457,7 @@ namespace heif {
                                                const EncodingOptions& options,
                                                int bbox_size)
   {
-    struct heif_image_handle* thumb_image_handle;
+    struct heif_image_handle* thumb_image_handle = nullptr;

     Error err = Error(heif_context_encode_thumbnail(m_context.get(),
                                                     image.m_image.get(),
diff --git a/libheif/context.cc b/libheif/context.cc
index 5150f3c7..f3cad89f 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -1858,9 +1858,8 @@ Result<std::shared_ptr<ImageItem>> HeifContext::encode_thumbnail(const std::shar
   int thumb_width, thumb_height;

   if (orig_width <= bbox_size && orig_height <= bbox_size) {
-    // original image is smaller than thumbnail size -> do not encode any thumbnail
-
-    return Error::Ok;
+    // No thumbnail is needed. Return an empty value, not an Error alternative.
+    return std::shared_ptr<ImageItem>{};
   }
   else if (orig_width > orig_height) {
     thumb_height = orig_height * bbox_size / orig_width;
diff --git a/tests/encode.cc b/tests/encode.cc
index 8136a604..d17907a7 100644
--- a/tests/encode.cc
+++ b/tests/encode.cc
@@ -254,3 +254,38 @@ TEST_CASE("kvazaar encodes monochrome images", "[heif_encoder]") {
   heif_encoder_release(enc);
   heif_context_free(ctx);
 }
+
+
+TEST_CASE("thumbnail of an image fitting the bounding box returns a null handle", "[heif_encoder]") {
+  heif_encoder* enc = get_encoder_or_skip_test(heif_compression_HEVC);
+  heif_context* ctx = heif_context_alloc();
+
+  constexpr int size = 64;
+  heif_image* img = nullptr;
+  heif_error err = heif_image_create(size, size, heif_colorspace_monochrome,
+                                     heif_chroma_monochrome, &img);
+  REQUIRE(err.code == heif_error_Ok);
+  fill_new_plane(img, heif_channel_Y, size, size);
+
+  heif_image_handle* master = nullptr;
+  err = heif_context_encode_image(ctx, img, enc, nullptr, &master);
+  REQUIRE(err.code == heif_error_Ok);
+
+  // An untouched output would alias the master handle and could be released twice.
+  heif_image_handle* thumbnail = master;
+  err = heif_context_encode_thumbnail(ctx, img, master, enc, nullptr, size, &thumbnail);
+  REQUIRE(err.code == heif_error_Ok);
+  REQUIRE(thumbnail == nullptr);
+
+  err = heif_context_encode_thumbnail(ctx, img, master, enc, nullptr, size / 2, &thumbnail);
+  REQUIRE(err.code == heif_error_Ok);
+  REQUIRE(thumbnail != nullptr);
+  CHECK(heif_image_handle_get_width(thumbnail) == size / 2);
+  CHECK(heif_image_handle_get_height(thumbnail) == size / 2);
+
+  heif_image_handle_release(thumbnail);
+  heif_image_handle_release(master);
+  heif_image_release(img);
+  heif_encoder_release(enc);
+  heif_context_free(ctx);
+}