Commit 8dee2827 for libheif
commit 8dee282785126cbeeb54303c638aa37ce5a5b171
Author: Anthony Hurtado <amhurtado@pm.me>
Date: Thu May 7 22:58:54 2026 -0500
fuzzing: add api_fuzzer for non-decode API surfaces
Adds a companion to file_fuzzer that targets API surfaces beyond the
basic decode-and-iterate path:
- Image sequences (heif_track_*, moov/trak/stbl)
- Region items (heif_region_*, polygon/polyline/inline mask parsing)
- Depth and auxiliary images (auxl reference following)
- Tile-by-tile grid decode (heif_image_handle_decode_image_tile)
- Item properties and transformations
- Entity groups (grpl box children)
- Color profiles (raw ICC, NCLX)
- Item-level data access (heif_item_get_item_data, decompression)
Uses only the public C API (libheif/api/libheif/heif*.h). Mirrors
file_fuzzer.cc style: LLVMFuzzerTestOneInput, 2-space indent, static
per-section helpers, LGPL-3.0-or-later header.
scripts/build-oss-fuzz.sh already does `cp fuzzing/*_fuzzer "$OUT"`,
so the new binary will be picked up by OSS-Fuzz automatically.
diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt
index 9b27f916..7169121c 100644
--- a/fuzzing/CMakeLists.txt
+++ b/fuzzing/CMakeLists.txt
@@ -14,3 +14,6 @@ configure_file(encoder_fuzzer_lsan_suppressions.txt ${CMAKE_CURRENT_BINARY_DIR}/
add_executable(file_fuzzer file_fuzzer.cc)
target_link_libraries(file_fuzzer PRIVATE heif)
+
+add_executable(api_fuzzer api_fuzzer.cc)
+target_link_libraries(api_fuzzer PRIVATE heif)
diff --git a/fuzzing/api_fuzzer.cc b/fuzzing/api_fuzzer.cc
new file mode 100644
index 00000000..a8233c52
--- /dev/null
+++ b/fuzzing/api_fuzzer.cc
@@ -0,0 +1,325 @@
+/*
+ * HEIF codec.
+ * Copyright (c) 2026 struktur AG
+ *
+ * This file is part of libheif.
+ *
+ * libheif is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * libheif is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with libheif. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Companion to file_fuzzer that exercises API surfaces beyond the basic
+ * decode-and-iterate path: image sequences, region items, depth and
+ * auxiliary images, tile-by-tile grid decode, item-level data access,
+ * entity groups, color profiles, and item properties.
+ */
+
+#include <cstdint>
+#include <cstddef>
+#include <cstdlib>
+
+#include "libheif/heif.h"
+#include "libheif/heif_sequences.h"
+#include "libheif/heif_regions.h"
+#include "libheif/heif_aux_images.h"
+#include "libheif/heif_tiling.h"
+#include "libheif/heif_properties.h"
+#include "libheif/heif_entity_groups.h"
+#include "libheif/heif_items.h"
+#include "libheif/heif_color.h"
+
+static void TestSequences(struct heif_context* ctx)
+{
+ if (!heif_context_has_sequence(ctx)) {
+ return;
+ }
+
+ int ntracks = heif_context_number_of_sequence_tracks(ctx);
+ if (ntracks <= 0 || ntracks > 16) {
+ return;
+ }
+
+ uint32_t* track_ids = static_cast<uint32_t*>(malloc(ntracks * sizeof(uint32_t)));
+ if (!track_ids) return;
+ heif_context_get_track_ids(ctx, track_ids);
+
+ for (int t = 0; t < ntracks && t < 4; t++) {
+ struct heif_track* track = heif_context_get_track(ctx, track_ids[t]);
+ if (!track) continue;
+
+ heif_track_get_track_handler_type(track);
+
+ uint16_t tw = 0, th = 0;
+ heif_track_get_image_resolution(track, &tw, &th);
+
+ for (int f = 0; f < 8; f++) {
+ struct heif_image* img = nullptr;
+ struct heif_error err = heif_track_decode_next_image(track, &img,
+ heif_colorspace_YCbCr, heif_chroma_420, nullptr);
+ if (err.code != heif_error_Ok || !img) break;
+ int stride;
+ heif_image_get_plane_readonly(img, heif_channel_Y, &stride);
+ heif_image_release(img);
+ }
+ heif_track_release(track);
+ }
+ free(track_ids);
+}
+
+static void TestRegions(struct heif_context* ctx, const struct heif_image_handle* handle)
+{
+ int nregion_items = heif_image_handle_get_number_of_region_items(handle);
+ if (nregion_items <= 0 || nregion_items > 32) {
+ return;
+ }
+
+ heif_item_id* region_ids = static_cast<heif_item_id*>(malloc(nregion_items * sizeof(heif_item_id)));
+ if (!region_ids) return;
+ heif_image_handle_get_list_of_region_item_ids(handle, region_ids, nregion_items);
+
+ for (int r = 0; r < nregion_items && r < 8; r++) {
+ struct heif_region_item* region_item = nullptr;
+ struct heif_error err = heif_context_get_region_item(ctx, region_ids[r], ®ion_item);
+ if (err.code != heif_error_Ok || !region_item) continue;
+
+ uint32_t ref_w = 0, ref_h = 0;
+ heif_region_item_get_reference_size(region_item, &ref_w, &ref_h);
+
+ int nregions = heif_region_item_get_number_of_regions(region_item);
+ if (nregions > 0 && nregions < 64) {
+ struct heif_region** regions = static_cast<struct heif_region**>(malloc(nregions * sizeof(void*)));
+ if (regions) {
+ int got = heif_region_item_get_list_of_regions(region_item, regions, nregions);
+ for (int i = 0; i < got && i < 16; i++) {
+ enum heif_region_type rtype = heif_region_get_type(regions[i]);
+ (void)rtype;
+
+ int npts = heif_region_get_polygon_num_points(regions[i]);
+ if (npts > 0 && npts < 1024) {
+ int32_t* pts = static_cast<int32_t*>(malloc(2 * npts * sizeof(int32_t)));
+ if (pts) {
+ heif_region_get_polygon_points(regions[i], pts);
+ free(pts);
+ }
+ }
+
+ size_t mask_len = heif_region_get_inline_mask_data_len(regions[i]);
+ if (mask_len > 0 && mask_len < 1024 * 1024) {
+ uint8_t* mask_data = static_cast<uint8_t*>(malloc(mask_len));
+ if (mask_data) {
+ int32_t mx, my;
+ uint32_t mw, mh;
+ heif_region_get_inline_mask_data(regions[i], &mx, &my, &mw, &mh, mask_data);
+ free(mask_data);
+ }
+ }
+ }
+ heif_region_release_many(regions, got);
+ free(regions);
+ }
+ }
+ heif_region_item_release(region_item);
+ }
+ free(region_ids);
+}
+
+static void TestDepthAux(const struct heif_image_handle* handle)
+{
+ if (heif_image_handle_has_depth_image(handle)) {
+ int ndepth = heif_image_handle_get_number_of_depth_images(handle);
+ if (ndepth > 0 && ndepth < 8) {
+ heif_item_id depth_ids[8];
+ heif_image_handle_get_list_of_depth_image_IDs(handle, depth_ids, ndepth);
+ for (int i = 0; i < ndepth && i < 2; i++) {
+ struct heif_image_handle* depth_handle = nullptr;
+ struct heif_error err = heif_image_handle_get_depth_image_handle(handle, depth_ids[i], &depth_handle);
+ if (err.code == heif_error_Ok && depth_handle) {
+ struct heif_image* img = nullptr;
+ heif_decode_image(depth_handle, &img,
+ heif_colorspace_monochrome, heif_chroma_monochrome, nullptr);
+ if (img) heif_image_release(img);
+ heif_image_handle_release(depth_handle);
+ }
+ }
+ }
+
+ const struct heif_depth_representation_info* depth_info = nullptr;
+ heif_image_handle_get_depth_image_representation_info(handle, 0, &depth_info);
+ if (depth_info) {
+ heif_depth_representation_info_free(depth_info);
+ }
+ }
+
+ int naux = heif_image_handle_get_number_of_auxiliary_images(handle, 0);
+ if (naux > 0 && naux < 16) {
+ heif_item_id aux_ids[16];
+ heif_image_handle_get_list_of_auxiliary_image_IDs(handle, 0, aux_ids, naux);
+ for (int i = 0; i < naux && i < 4; i++) {
+ struct heif_image_handle* aux_handle = nullptr;
+ struct heif_error err = heif_image_handle_get_auxiliary_image_handle(handle, aux_ids[i], &aux_handle);
+ if (err.code == heif_error_Ok && aux_handle) {
+ const char* aux_type = nullptr;
+ heif_image_handle_get_auxiliary_type(aux_handle, &aux_type);
+ if (aux_type) heif_image_handle_free_auxiliary_types(aux_handle, &aux_type);
+
+ struct heif_image* img = nullptr;
+ heif_decode_image(aux_handle, &img,
+ heif_colorspace_YCbCr, heif_chroma_420, nullptr);
+ if (img) heif_image_release(img);
+ heif_image_handle_release(aux_handle);
+ }
+ }
+ }
+}
+
+static void TestGridTiles(const struct heif_image_handle* handle)
+{
+ struct heif_image_tiling tiling = {};
+ struct heif_error err = heif_image_handle_get_image_tiling(handle, 1, &tiling);
+ if (err.code != heif_error_Ok) {
+ return;
+ }
+ if (tiling.num_columns == 0 || tiling.num_rows == 0) {
+ return;
+ }
+ if (tiling.num_columns > 8 || tiling.num_rows > 8) {
+ return;
+ }
+
+ int decoded = 0;
+ for (uint32_t y = 0; y < tiling.num_rows && decoded < 4; y++) {
+ for (uint32_t x = 0; x < tiling.num_columns && decoded < 4; x++) {
+ struct heif_image* tile_img = nullptr;
+ err = heif_image_handle_decode_image_tile(handle, &tile_img,
+ heif_colorspace_YCbCr, heif_chroma_420, nullptr, x, y);
+ if (err.code == heif_error_Ok && tile_img) {
+ int stride;
+ heif_image_get_plane_readonly(tile_img, heif_channel_Y, &stride);
+ heif_image_release(tile_img);
+ }
+ decoded++;
+ }
+ }
+}
+
+static void TestProperties(struct heif_context* ctx, const struct heif_image_handle* handle)
+{
+ heif_item_id item_id = heif_image_handle_get_item_id(handle);
+
+ heif_property_id trans_props[16];
+ int ntrans = heif_item_get_transformation_properties(ctx, item_id, trans_props, 16);
+ (void)ntrans;
+
+ size_t icc_size = heif_image_handle_get_raw_color_profile_size(handle);
+ if (icc_size > 0 && icc_size < 2 * 1024 * 1024) {
+ uint8_t* icc_data = static_cast<uint8_t*>(malloc(icc_size));
+ if (icc_data) {
+ heif_image_handle_get_raw_color_profile(handle, icc_data);
+ free(icc_data);
+ }
+ }
+
+ struct heif_color_profile_nclx* nclx = nullptr;
+ heif_image_handle_get_nclx_color_profile(handle, &nclx);
+ if (nclx) heif_nclx_color_profile_free(nclx);
+}
+
+static void TestEntityGroups(struct heif_context* ctx)
+{
+ int num_groups = 0;
+ struct heif_entity_group* groups = heif_context_get_entity_groups(ctx, 0, 0, &num_groups);
+ if (groups && num_groups > 0) {
+ heif_entity_groups_release(groups, num_groups);
+ }
+}
+
+static void TestItems(struct heif_context* ctx)
+{
+ int nItems = heif_context_get_number_of_items(ctx);
+ if (nItems <= 0 || nItems > 128) {
+ return;
+ }
+
+ heif_item_id* item_ids = static_cast<heif_item_id*>(malloc(nItems * sizeof(heif_item_id)));
+ if (!item_ids) return;
+ heif_context_get_list_of_item_IDs(ctx, item_ids, nItems);
+
+ for (int i = 0; i < nItems && i < 16; i++) {
+ uint32_t item_type = heif_item_get_item_type(ctx, item_ids[i]);
+ (void)item_type;
+ int hidden = heif_item_is_item_hidden(ctx, item_ids[i]);
+ (void)hidden;
+
+ const char* mime_type = heif_item_get_mime_item_content_type(ctx, item_ids[i]);
+ (void)mime_type;
+ const char* uri_type = heif_item_get_uri_item_uri_type(ctx, item_ids[i]);
+ (void)uri_type;
+ const char* item_name = heif_item_get_item_name(ctx, item_ids[i]);
+ (void)item_name;
+
+ enum heif_metadata_compression compression;
+ uint8_t* item_data = nullptr;
+ size_t item_data_size = 0;
+ struct heif_error err = heif_item_get_item_data(ctx, item_ids[i], &compression,
+ &item_data, &item_data_size);
+ if (err.code == heif_error_Ok && item_data) {
+ heif_release_item_data(ctx, &item_data);
+ }
+
+ char* ext_lang = nullptr;
+ heif_item_get_property_extended_language(ctx, item_ids[i], &ext_lang);
+ if (ext_lang) heif_string_release(ext_lang);
+ }
+ free(item_ids);
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ if (size < 12 || size > 512 * 1024) {
+ return 0;
+ }
+
+ struct heif_context* ctx = heif_context_alloc();
+ if (!ctx) return 0;
+
+ auto* limits = heif_context_get_security_limits(ctx);
+ limits->max_memory_block_size = 32 * 1024 * 1024;
+ limits->max_total_memory = 128 * 1024 * 1024;
+ limits->max_image_size_pixels = 512 * 512;
+
+ struct heif_error err = heif_context_read_from_memory(ctx, data, size, nullptr);
+ if (err.code != heif_error_Ok) {
+ goto quit;
+ }
+
+ TestSequences(ctx);
+ TestEntityGroups(ctx);
+ TestItems(ctx);
+
+ {
+ struct heif_image_handle* primary = nullptr;
+ err = heif_context_get_primary_image_handle(ctx, &primary);
+ if (err.code == heif_error_Ok && primary) {
+ TestRegions(ctx, primary);
+ TestDepthAux(primary);
+ TestGridTiles(primary);
+ TestProperties(ctx, primary);
+ heif_image_handle_release(primary);
+ }
+ }
+
+quit:
+ heif_context_free(ctx);
+ return 0;
+}