Commit cd9d4d9b for libheif
commit cd9d4d9b02f274b10e3d4bf803ad50e2e42e3e20
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Mon Aug 24 20:54:04 2026 +0200
reject unci encoding of images that have no pixel planes
heif_image_create() only records the colorspace and chroma format; the pixel
planes are allocated separately with heif_image_add_plane(). An image where
that second step never happened reached the uncompressed encoders, which access
the planes implied by the chroma format. For the interleaved formats this ended
in get_component_ids_interleaved(), where find_storage_for_channel() returns
null and only an assert() stood between that and a null pointer dereference in
release builds.
Check for the expected planes in unc_encoder_factory::get_unc_encoder(), which
both heif_context_add_empty_unci_image() and heif_context_encode_image() pass
through. A planar image without any plane did not crash but produced an item
with zero components, so it is rejected as well.
diff --git a/libheif/codecs/uncompressed/unc_encoder.cc b/libheif/codecs/uncompressed/unc_encoder.cc
index 5421066d..4ba36eb0 100644
--- a/libheif/codecs/uncompressed/unc_encoder.cc
+++ b/libheif/codecs/uncompressed/unc_encoder.cc
@@ -165,6 +165,24 @@ unc_encoder::unc_encoder(const std::shared_ptr<const HeifPixelImage>& image)
Result<std::unique_ptr<const unc_encoder> > unc_encoder_factory::get_unc_encoder(const std::shared_ptr<const HeifPixelImage>& prototype_image,
const heif_encoding_options& options)
{
+ // The encoders access the pixel planes implied by the image's colorspace and chroma format.
+ // An image that never received those planes (heif_image_create() without a matching
+ // heif_image_add_plane()) would make them dereference a plane that does not exist, so reject
+ // it before we pick an encoder for it.
+
+ if (num_interleaved_components_per_plane(prototype_image->get_chroma_format()) > 1) {
+ if (!prototype_image->has_channel(heif_channel_interleaved)) {
+ return Error{heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Image has an interleaved chroma format, but no interleaved pixel plane."};
+ }
+ }
+ else if (prototype_image->get_used_planar_component_ids().empty()) {
+ return Error{heif_error_Invalid_input,
+ heif_suberror_Unspecified,
+ "Image has no pixel planes."};
+ }
+
static unc_encoder_factory_rgb_pixel_interleave enc_rgb_pixel_interleave;
static unc_encoder_factory_rgb_block_pixel_interleave enc_rgb_block_pixel_interleave;
static unc_encoder_factory_rgb_bytealign_pixel_interleave enc_rgb_bytealign_pixel_interleave;
diff --git a/tests/uncompressed_encode.cc b/tests/uncompressed_encode.cc
index 4161e0fc..ecc6994c 100644
--- a/tests/uncompressed_encode.cc
+++ b/tests/uncompressed_encode.cc
@@ -1235,3 +1235,74 @@ TEST_CASE("Add tile rejects images that do not match the unci configuration")
heif_image_release(prototype);
}
}
+
+
+// An image can be created without ever adding a pixel plane. The encoders access the planes
+// implied by the colorspace and chroma format, so such an image used to make them dereference
+// a plane that does not exist.
+TEST_CASE("Encoder rejects images without pixel planes")
+{
+ auto try_encode = [](heif_image *image) -> heif_error {
+ heif_unci_image_parameters params{};
+ params.version = 1;
+ params.image_width = 32;
+ params.image_height = 32;
+ params.tile_width = 16;
+ params.tile_height = 16;
+ params.compression = heif_unci_compression_off;
+
+ heif_context *ctx = heif_context_alloc();
+
+ heif_encoding_options *options = heif_encoding_options_alloc();
+ options->macOS_compatibility_workaround_no_nclx_profile = true;
+
+ heif_image_handle *handle = nullptr;
+ heif_error err = heif_context_add_empty_unci_image(ctx, ¶ms, options, image, &handle);
+
+ if (handle) {
+ heif_image_handle_release(handle);
+ }
+ heif_encoding_options_free(options);
+ heif_context_free(ctx);
+
+ return err;
+ };
+
+ const heif_chroma interleaved_formats[] = {heif_chroma_interleaved_RGB,
+ heif_chroma_interleaved_RGBA,
+ heif_chroma_interleaved_RRGGBB_LE,
+ heif_chroma_interleaved_RRGGBBAA_BE};
+
+ for (heif_chroma chroma : interleaved_formats) {
+ heif_image *image;
+ heif_error err = heif_image_create(16, 16, heif_colorspace_RGB, chroma, &image);
+ REQUIRE(err.code == heif_error_Ok);
+ // No heif_image_add_plane() call.
+
+ REQUIRE(try_encode(image).code != heif_error_Ok);
+
+ heif_image_release(image);
+ }
+
+ SECTION("planar image without planes") {
+ heif_image *image;
+ heif_error err = heif_image_create(16, 16, heif_colorspace_YCbCr, heif_chroma_420, &image);
+ REQUIRE(err.code == heif_error_Ok);
+
+ REQUIRE(try_encode(image).code != heif_error_Ok);
+
+ heif_image_release(image);
+ }
+
+ SECTION("a properly allocated image is still accepted") {
+ heif_image *image;
+ heif_error err = heif_image_create(16, 16, heif_colorspace_RGB, heif_chroma_interleaved_RGB, &image);
+ REQUIRE(err.code == heif_error_Ok);
+ err = heif_image_add_plane(image, heif_channel_interleaved, 16, 16, 8);
+ REQUIRE(err.code == heif_error_Ok);
+
+ REQUIRE(try_encode(image).code == heif_error_Ok);
+
+ heif_image_release(image);
+ }
+}