/src/libheif/fuzzing/tile_fuzzer.cc
Line | Count | Source |
1 | | /* |
2 | | * HEIF codec. |
3 | | * Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com> |
4 | | * |
5 | | * This file is part of libheif. |
6 | | * |
7 | | * libheif is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as |
9 | | * published by the Free Software Foundation, either version 3 of |
10 | | * the License, or (at your option) any later version. |
11 | | * |
12 | | * libheif is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libheif. If not, see <http://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | // Fuzz harness for the per-tile read API (heif_image_handle_get_image_tiling, |
22 | | // heif_image_handle_get_grid_image_tile_id, heif_image_handle_decode_image_tile). |
23 | | // file_fuzzer.cc only calls heif_decode_image, which routes grid images through |
24 | | // decode_full_grid_image and never exercises decode_grid_tile or the surrounding |
25 | | // tiling-API surface. This harness fills that gap. |
26 | | |
27 | | #include <assert.h> |
28 | | #include <limits.h> |
29 | | #include <stdint.h> |
30 | | #include <stdlib.h> |
31 | | |
32 | | #include "libheif/heif.h" |
33 | | #include "libheif/heif_tiling.h" |
34 | | |
35 | | static const enum heif_colorspace kFuzzColorSpace = heif_colorspace_YCbCr; |
36 | | static const enum heif_chroma kFuzzChroma = heif_chroma_420; |
37 | | |
38 | | // Cap how many tiles we try to decode per image. Synthetic grids can claim |
39 | | // millions of tiles; bounding here keeps the per-input time budget sane |
40 | | // without losing meaningful coverage (crashes show up on the first few tiles). |
41 | | static const uint32_t kMaxTilesPerImage = 32; |
42 | | |
43 | | static void TestTileAPI(const struct heif_image_handle* handle, |
44 | | int process_image_transformations) |
45 | 39.7k | { |
46 | 39.7k | struct heif_image_tiling tiling = {}; |
47 | 39.7k | struct heif_error err = heif_image_handle_get_image_tiling( |
48 | 39.7k | handle, process_image_transformations, &tiling); |
49 | 39.7k | if (err.code != heif_error_Ok) { |
50 | 0 | return; |
51 | 0 | } |
52 | | |
53 | | // Probe per-tile id lookup and decode within the declared grid, capped. |
54 | 39.7k | uint32_t cols = tiling.num_columns; |
55 | 39.7k | uint32_t rows = tiling.num_rows; |
56 | 39.7k | uint32_t total = 0; |
57 | | |
58 | 79.4k | for (uint32_t ty = 0; ty < rows && total < kMaxTilesPerImage; ty++) { |
59 | 79.4k | for (uint32_t tx = 0; tx < cols && total < kMaxTilesPerImage; tx++) { |
60 | 39.7k | heif_item_id tile_id = 0; |
61 | | // Return value intentionally ignored; we only care that the call does |
62 | | // not crash on malformed input. |
63 | 39.7k | heif_image_handle_get_grid_image_tile_id( |
64 | 39.7k | handle, process_image_transformations, tx, ty, &tile_id); |
65 | | |
66 | 39.7k | struct heif_image* tile_img = nullptr; |
67 | 39.7k | err = heif_image_handle_decode_image_tile( |
68 | 39.7k | handle, &tile_img, kFuzzColorSpace, kFuzzChroma, nullptr, tx, ty); |
69 | 39.7k | if (err.code == heif_error_Ok && tile_img != nullptr) { |
70 | 1.43k | heif_image_release(tile_img); |
71 | 38.3k | } else if (tile_img != nullptr) { |
72 | | // Defensive: some error paths may still produce an image we own. |
73 | 0 | heif_image_release(tile_img); |
74 | 0 | } |
75 | | |
76 | 39.7k | total++; |
77 | 39.7k | } |
78 | 39.7k | } |
79 | 39.7k | } |
80 | | |
81 | | static int clip_int(size_t size) |
82 | 18.1k | { |
83 | 18.1k | return size > INT_MAX ? INT_MAX : static_cast<int>(size); |
84 | 18.1k | } |
85 | | |
86 | | extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) |
87 | 18.1k | { |
88 | 18.1k | struct heif_context* ctx; |
89 | 18.1k | struct heif_error err; |
90 | 18.1k | struct heif_image_handle* primary_handle = nullptr; |
91 | 18.1k | int images_count; |
92 | 18.1k | heif_item_id* image_IDs = nullptr; |
93 | 18.1k | bool explicit_init = size == 0 || data[size - 1] & 1; |
94 | | |
95 | 18.1k | if (explicit_init) { |
96 | 9.79k | heif_init(nullptr); |
97 | 9.79k | } |
98 | | |
99 | 18.1k | heif_check_filetype(data, clip_int(size)); |
100 | | |
101 | 18.1k | ctx = heif_context_alloc(); |
102 | 18.1k | assert(ctx); |
103 | | |
104 | 18.1k | auto* limits = heif_context_get_security_limits(ctx); |
105 | 18.1k | limits->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024; |
106 | 18.1k | limits->max_memory_block_size = 128 * 1024 * 1024; |
107 | | |
108 | 18.1k | err = heif_context_read_from_memory(ctx, data, size, nullptr); |
109 | 18.1k | if (err.code != heif_error_Ok) { |
110 | 4.91k | goto quit; |
111 | 4.91k | } |
112 | | |
113 | 13.2k | err = heif_context_get_primary_image_handle(ctx, &primary_handle); |
114 | 13.2k | if (err.code == heif_error_Ok) { |
115 | 13.2k | TestTileAPI(primary_handle, /*process_image_transformations=*/1); |
116 | 13.2k | TestTileAPI(primary_handle, /*process_image_transformations=*/0); |
117 | 13.2k | heif_image_handle_release(primary_handle); |
118 | 13.2k | primary_handle = nullptr; |
119 | 13.2k | } |
120 | | |
121 | 13.2k | images_count = heif_context_get_number_of_top_level_images(ctx); |
122 | 13.2k | if (!images_count) { |
123 | 0 | goto quit; |
124 | 0 | } |
125 | | |
126 | 13.2k | image_IDs = static_cast<heif_item_id*>(malloc(images_count * sizeof(heif_item_id))); |
127 | 13.2k | assert(image_IDs); |
128 | 13.2k | images_count = heif_context_get_list_of_top_level_image_IDs(ctx, image_IDs, images_count); |
129 | 13.2k | if (!images_count) { |
130 | 0 | goto quit; |
131 | 0 | } |
132 | | |
133 | 26.5k | for (int i = 0; i < images_count; ++i) { |
134 | 13.2k | struct heif_image_handle* image_handle = nullptr; |
135 | 13.2k | err = heif_context_get_image_handle(ctx, image_IDs[i], &image_handle); |
136 | 13.2k | if (err.code != heif_error_Ok) { |
137 | 29 | heif_image_handle_release(image_handle); |
138 | 29 | continue; |
139 | 29 | } |
140 | | |
141 | 13.2k | TestTileAPI(image_handle, /*process_image_transformations=*/1); |
142 | | |
143 | | // Also iterate thumbnails — these can themselves be grid/overlay items |
144 | | // and have separate decoder paths. |
145 | 13.2k | int num_thumbnails = heif_image_handle_get_number_of_thumbnails(image_handle); |
146 | 13.2k | for (int t = 0; t < num_thumbnails; ++t) { |
147 | 0 | struct heif_image_handle* thumbnail_handle = nullptr; |
148 | 0 | heif_image_handle_get_thumbnail(image_handle, t, &thumbnail_handle); |
149 | 0 | if (thumbnail_handle) { |
150 | 0 | TestTileAPI(thumbnail_handle, /*process_image_transformations=*/1); |
151 | 0 | heif_image_handle_release(thumbnail_handle); |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | 13.2k | heif_image_handle_release(image_handle); |
156 | 13.2k | } |
157 | | |
158 | 18.1k | quit: |
159 | 18.1k | heif_image_handle_release(primary_handle); |
160 | 18.1k | heif_context_free(ctx); |
161 | 18.1k | free(image_IDs); |
162 | | |
163 | 18.1k | if (explicit_init) { |
164 | 9.79k | heif_deinit(); |
165 | 9.79k | } |
166 | | |
167 | 18.1k | return 0; |
168 | 13.2k | } |