/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 | | using ::fuzztest::BitFlagCombinationOf; |
18 | | |
19 | | namespace avif { |
20 | | namespace testutil { |
21 | | namespace { |
22 | | |
23 | | //------------------------------------------------------------------------------ |
24 | | |
25 | | struct DecoderInput { |
26 | | const uint8_t* available_bytes; |
27 | | size_t available_size; |
28 | | size_t read_size; |
29 | | }; |
30 | | |
31 | | // A custom reader is necessary to get the number of bytes read by libavif. |
32 | | // See avifIOReadFunc() documentation. |
33 | | avifResult AvifIoRead(struct avifIO* io, uint32_t read_flags, uint64_t offset, |
34 | 92.1k | size_t size, avifROData* out) { |
35 | 92.1k | DecoderInput* data = reinterpret_cast<DecoderInput*>(io->data); |
36 | 92.1k | if (read_flags != 0 || !data || data->available_size < offset) { |
37 | 0 | return AVIF_RESULT_IO_ERROR; |
38 | 0 | } |
39 | 92.1k | out->data = data->available_bytes + offset; |
40 | 92.1k | out->size = |
41 | 92.1k | std::min(size, data->available_size - static_cast<size_t>(offset)); |
42 | 92.1k | data->read_size = |
43 | 92.1k | std::max(data->read_size, static_cast<size_t>(offset) + out->size); |
44 | 92.1k | return AVIF_RESULT_OK; |
45 | 92.1k | } |
46 | | |
47 | | //------------------------------------------------------------------------------ |
48 | | |
49 | | void DecodeIncr(const std::string& arbitrary_bytes, bool is_persistent, |
50 | | bool give_size_hint, |
51 | | avifImageContentTypeFlags content_to_decode, |
52 | 13.3k | bool use_nth_image_api) { |
53 | 13.3k | ASSERT_FALSE(GetSeedDataDirs().empty()); // Make sure seeds are available. |
54 | | |
55 | 13.3k | ImagePtr reference(avifImageCreateEmpty()); |
56 | 13.3k | ASSERT_NE(reference.get(), nullptr); |
57 | | |
58 | 13.3k | DecoderInput data = {reinterpret_cast<const uint8_t*>(arbitrary_bytes.data()), |
59 | 13.3k | arbitrary_bytes.size(), 0}; |
60 | 13.3k | avifIO io = {.destroy = nullptr, |
61 | 13.3k | .read = AvifIoRead, |
62 | 13.3k | .write = nullptr, |
63 | 13.3k | .sizeHint = arbitrary_bytes.size(), |
64 | 13.3k | .persistent = AVIF_TRUE, |
65 | 13.3k | .data = &data}; |
66 | | |
67 | 13.3k | DecoderPtr decoder(avifDecoderCreate()); |
68 | 13.3k | ASSERT_NE(decoder.get(), nullptr); |
69 | 13.3k | avifDecoderSetIO(decoder.get(), &io); |
70 | | // OSS-Fuzz limits the allocated memory to 2560 MB. |
71 | 13.3k | constexpr uint32_t kMaxMem = 2560u * 1024 * 1024; |
72 | | // Consider at most four planes of 16-bit samples. |
73 | 13.3k | constexpr uint32_t kMaxImageSize = |
74 | 13.3k | kMaxMem / (AVIF_PLANE_COUNT_YUV + 1) / sizeof(uint16_t); |
75 | | // Reduce the limit further to include pixel buffer copies and other memory |
76 | | // allocations. |
77 | 13.3k | constexpr uint32_t kImageSizeLimit = kMaxImageSize / 4; |
78 | 13.3k | decoder->imageSizeLimit = kImageSizeLimit; |
79 | | // This can lead to AVIF_RESULT_NO_CONTENT. |
80 | 13.3k | decoder->imageContentToDecode = content_to_decode; |
81 | | |
82 | 13.3k | avifResult result = avifDecoderRead(decoder.get(), reference.get()); |
83 | | // AVIF_RESULT_INTERNAL_ERROR means a broken invariant and should not happen. |
84 | 13.3k | ASSERT_NE(result, AVIF_RESULT_INTERNAL_ERROR); |
85 | 13.3k | if (result != AVIF_RESULT_OK) return; |
86 | | |
87 | | // Avoid timeouts by discarding big images decoded many times. |
88 | 3.90k | if (reference->width * reference->height * data.read_size > 8 * 1024 * 1024) { |
89 | 715 | return; |
90 | 715 | } |
91 | | // decodeIncrementally() will fail if there are leftover bytes. |
92 | 3.18k | const avifRWData encoded_data = {const_cast<uint8_t*>(data.available_bytes), |
93 | 3.18k | data.read_size}; |
94 | | // No clue on whether encoded_data is tiled so use a lower bound of a single |
95 | | // tile for the whole image. |
96 | | // Note that an AVIF tile is at most as high as an AV1 frame |
97 | | // (aomediacodec.github.io/av1-spec says max_frame_height_minus_1 < 65536) |
98 | | // but libavif successfully decodes AVIF files with dimensions unrelated to |
99 | | // the underlying AV1 frame (for example a 1x1000000 AVIF for a 1x1 AV1). |
100 | | // Otherwise we could use the minimum of reference->height and 65536u below. |
101 | 3.18k | const uint32_t max_cell_height = reference->height; |
102 | 3.18k | result = DecodeIncrementally( |
103 | 3.18k | encoded_data, decoder.get(), is_persistent, give_size_hint, |
104 | 3.18k | use_nth_image_api, *reference, max_cell_height, |
105 | 3.18k | /*enable_fine_incremental_check=*/false, /*expect_whole_file_read=*/true, |
106 | 3.18k | /*expect_parse_success_from_partial_file=*/false); |
107 | | ASSERT_NE(result, AVIF_RESULT_INTERNAL_ERROR); |
108 | 3.18k | } |
109 | | |
110 | | FUZZ_TEST(DecodeAvifFuzzTest, DecodeIncr) |
111 | | .WithDomains(ArbitraryImageWithSeeds({AVIF_APP_FILE_FORMAT_AVIF}), |
112 | | /*is_persistent=*/Arbitrary<bool>(), |
113 | | /*give_size_hint=*/Arbitrary<bool>(), |
114 | | BitFlagCombinationOf<avifImageContentTypeFlags>( |
115 | | {AVIF_IMAGE_CONTENT_COLOR_AND_ALPHA, |
116 | | AVIF_IMAGE_CONTENT_GAIN_MAP, |
117 | | AVIF_IMAGE_CONTENT_SAMPLE_TRANSFORMS}), |
118 | | /*use_nth_image_api=*/Arbitrary<bool>()); |
119 | | |
120 | | //------------------------------------------------------------------------------ |
121 | | |
122 | | } // namespace |
123 | | } // namespace testutil |
124 | | } // namespace avif |