/src/libavif/tests/gtest/avif_fuzztest_dec_incr.cc
Line | Count | Source |
1 | | // Copyright 2022 Google LLC |
2 | | // SPDX-License-Identifier: BSD-2-Clause |
3 | | // Compare non-incremental and incremental decode results of an arbitrary byte |
4 | | // sequence. |
5 | | |
6 | | #include <algorithm> |
7 | | #include <cstdint> |
8 | | #include <string> |
9 | | |
10 | | #include "avif/avif.h" |
11 | | #include "avif_fuzztest_helpers.h" |
12 | | #include "avifincrtest_helpers.h" |
13 | | #include "fuzztest/fuzztest.h" |
14 | | #include "gtest/gtest.h" |
15 | | |
16 | | using ::fuzztest::Arbitrary; |
17 | | |
18 | | namespace avif { |
19 | | namespace testutil { |
20 | | namespace { |
21 | | |
22 | | //------------------------------------------------------------------------------ |
23 | | |
24 | | struct DecoderInput { |
25 | | const uint8_t* available_bytes; |
26 | | size_t available_size; |
27 | | size_t read_size; |
28 | | }; |
29 | | |
30 | | // A custom reader is necessary to get the number of bytes read by libavif. |
31 | | // See avifIOReadFunc() documentation. |
32 | | avifResult AvifIoRead(struct avifIO* io, uint32_t read_flags, uint64_t offset, |
33 | 86.3k | size_t size, avifROData* out) { |
34 | 86.3k | DecoderInput* data = reinterpret_cast<DecoderInput*>(io->data); |
35 | 86.3k | if (read_flags != 0 || !data || data->available_size < offset) { |
36 | 0 | return AVIF_RESULT_IO_ERROR; |
37 | 0 | } |
38 | 86.3k | out->data = data->available_bytes + offset; |
39 | 86.3k | out->size = |
40 | 86.3k | std::min(size, data->available_size - static_cast<size_t>(offset)); |
41 | 86.3k | data->read_size = |
42 | 86.3k | std::max(data->read_size, static_cast<size_t>(offset) + out->size); |
43 | 86.3k | return AVIF_RESULT_OK; |
44 | 86.3k | } |
45 | | |
46 | | //------------------------------------------------------------------------------ |
47 | | |
48 | | void DecodeIncr(const std::string& arbitrary_bytes, bool is_persistent, |
49 | 12.2k | bool give_size_hint, bool use_nth_image_api) { |
50 | 12.2k | ASSERT_FALSE(GetSeedDataDirs().empty()); // Make sure seeds are available. |
51 | | |
52 | 12.2k | ImagePtr reference(avifImageCreateEmpty()); |
53 | 12.2k | ASSERT_NE(reference.get(), nullptr); |
54 | | |
55 | 12.2k | DecoderInput data = {reinterpret_cast<const uint8_t*>(arbitrary_bytes.data()), |
56 | 12.2k | arbitrary_bytes.size(), 0}; |
57 | 12.2k | avifIO io = {.destroy = nullptr, |
58 | 12.2k | .read = AvifIoRead, |
59 | 12.2k | .write = nullptr, |
60 | 12.2k | .sizeHint = arbitrary_bytes.size(), |
61 | 12.2k | .persistent = AVIF_TRUE, |
62 | 12.2k | .data = &data}; |
63 | | |
64 | 12.2k | DecoderPtr decoder(avifDecoderCreate()); |
65 | 12.2k | ASSERT_NE(decoder.get(), nullptr); |
66 | 12.2k | avifDecoderSetIO(decoder.get(), &io); |
67 | | // OSS-Fuzz limits the allocated memory to 2560 MB. |
68 | 12.2k | constexpr uint32_t kMaxMem = 2560u * 1024 * 1024; |
69 | | // Consider at most four planes of 16-bit samples. |
70 | 12.2k | constexpr uint32_t kMaxImageSize = |
71 | 12.2k | kMaxMem / (AVIF_PLANE_COUNT_YUV + 1) / sizeof(uint16_t); |
72 | | // Reduce the limit further to include pixel buffer copies and other memory |
73 | | // allocations. |
74 | 12.2k | constexpr uint32_t kImageSizeLimit = kMaxImageSize / 4; |
75 | | // avifDecoderParse returns AVIF_RESULT_NOT_IMPLEMENTED if kImageSizeLimit is |
76 | | // bigger than AVIF_DEFAULT_IMAGE_SIZE_LIMIT. |
77 | 12.2k | static_assert(kImageSizeLimit <= AVIF_DEFAULT_IMAGE_SIZE_LIMIT, |
78 | 12.2k | "Too big an image size limit"); |
79 | 12.2k | decoder->imageSizeLimit = kImageSizeLimit; |
80 | | |
81 | 12.2k | if (avifDecoderRead(decoder.get(), reference.get()) == AVIF_RESULT_OK) { |
82 | | // Avoid timeouts by discarding big images decoded many times. |
83 | | // TODO(yguyon): Increase this arbitrary threshold but decode incrementally |
84 | | // fewer times than as many bytes. |
85 | 3.71k | if (reference->width * reference->height * data.read_size > |
86 | 3.71k | 8 * 1024 * 1024) { |
87 | 639 | return; |
88 | 639 | } |
89 | | // decodeIncrementally() will fail if there are leftover bytes. |
90 | 3.07k | const avifRWData encoded_data = {const_cast<uint8_t*>(data.available_bytes), |
91 | 3.07k | data.read_size}; |
92 | | // No clue on whether encoded_data is tiled so use a lower bound of a single |
93 | | // tile for the whole image. |
94 | | // Note that an AVIF tile is at most as high as an AV1 frame |
95 | | // (aomediacodec.github.io/av1-spec says max_frame_height_minus_1 < 65536) |
96 | | // but libavif successfully decodes AVIF files with dimensions unrelated to |
97 | | // the underlying AV1 frame (for example a 1x1000000 AVIF for a 1x1 AV1). |
98 | | // Otherwise we could use the minimum of reference->height and 65536u below. |
99 | 3.07k | const uint32_t max_cell_height = reference->height; |
100 | 3.07k | const avifResult result = DecodeIncrementally( |
101 | 3.07k | encoded_data, decoder.get(), is_persistent, give_size_hint, |
102 | 3.07k | use_nth_image_api, *reference, max_cell_height, |
103 | 3.07k | /*enable_fine_incremental_check=*/false, |
104 | 3.07k | /*expect_whole_file_read=*/true, |
105 | 3.07k | /*expect_parse_success_from_partial_file=*/false); |
106 | | // The result does not matter, as long as we do not crash. |
107 | 3.07k | (void)result; |
108 | 3.07k | } |
109 | 12.2k | } |
110 | | |
111 | | FUZZ_TEST(DecodeAvifFuzzTest, DecodeIncr) |
112 | | .WithDomains(ArbitraryImageWithSeeds({AVIF_APP_FILE_FORMAT_AVIF}), |
113 | | Arbitrary<bool>(), Arbitrary<bool>(), Arbitrary<bool>()); |
114 | | |
115 | | //------------------------------------------------------------------------------ |
116 | | |
117 | | } // namespace |
118 | | } // namespace testutil |
119 | | } // namespace avif |