jpegli_dec_fuzzer.cc (6528B)
1 // Copyright (c) the JPEG XL Project Authors. All rights reserved. 2 // 3 // Use of this source code is governed by a BSD-style 4 // license that can be found in the LICENSE file. 5 6 #include <setjmp.h> 7 #include <stdint.h> 8 #include <stdlib.h> 9 #include <string.h> 10 11 #include <hwy/targets.h> 12 #include <vector> 13 14 #include "lib/jpegli/decode.h" 15 16 namespace { 17 18 // Externally visible value to ensure pixels are used in the fuzzer. 19 int external_code = 0; 20 21 template <typename It> 22 void Consume(const It& begin, const It& end) { 23 for (auto it = begin; it < end; ++it) { 24 if (*it == 0) { 25 external_code ^= ~0; 26 } else { 27 external_code ^= *it; 28 } 29 } 30 } 31 32 // Options for the fuzzing 33 struct FuzzSpec { 34 size_t chunk_size; 35 JpegliDataType output_type; 36 JpegliEndianness output_endianness; 37 int crop_output; 38 }; 39 40 constexpr uint8_t kFakeEoiMarker[2] = {0xff, 0xd9}; 41 constexpr size_t kNumSourceBuffers = 4; 42 43 class SourceManager { 44 public: 45 SourceManager(const uint8_t* data, size_t len, size_t max_chunk_size) 46 : data_(data), len_(len), max_chunk_size_(max_chunk_size) { 47 pub_.skip_input_data = skip_input_data; 48 pub_.resync_to_restart = jpegli_resync_to_restart; 49 pub_.term_source = term_source; 50 pub_.init_source = init_source; 51 pub_.fill_input_buffer = fill_input_buffer; 52 if (max_chunk_size_ == 0) max_chunk_size_ = len; 53 buffers_.resize(kNumSourceBuffers, std::vector<uint8_t>(max_chunk_size_)); 54 Reset(); 55 } 56 57 void Reset() { 58 pub_.next_input_byte = nullptr; 59 pub_.bytes_in_buffer = 0; 60 pos_ = 0; 61 chunk_idx_ = 0; 62 } 63 64 private: 65 jpeg_source_mgr pub_; 66 const uint8_t* data_; 67 size_t len_; 68 size_t chunk_idx_; 69 size_t pos_; 70 size_t max_chunk_size_; 71 std::vector<std::vector<uint8_t>> buffers_; 72 73 static void init_source(j_decompress_ptr cinfo) {} 74 75 static boolean fill_input_buffer(j_decompress_ptr cinfo) { 76 auto* src = reinterpret_cast<SourceManager*>(cinfo->src); 77 if (src->pos_ < src->len_) { 78 size_t remaining = src->len_ - src->pos_; 79 size_t chunk_size = std::min(remaining, src->max_chunk_size_); 80 size_t next_idx = ++src->chunk_idx_ % kNumSourceBuffers; 81 // Larger number of chunks causes fuzzer timuout. 82 if (src->chunk_idx_ >= (1u << 15)) { 83 chunk_size = remaining; 84 next_idx = src->buffers_.size(); 85 src->buffers_.emplace_back(chunk_size); 86 } 87 uint8_t* next_buffer = src->buffers_[next_idx].data(); 88 memcpy(next_buffer, src->data_ + src->pos_, chunk_size); 89 src->pub_.next_input_byte = next_buffer; 90 src->pub_.bytes_in_buffer = chunk_size; 91 } else { 92 src->pub_.next_input_byte = kFakeEoiMarker; 93 src->pub_.bytes_in_buffer = 2; 94 src->len_ += 2; 95 } 96 src->pos_ += src->pub_.bytes_in_buffer; 97 return TRUE; 98 } 99 100 static void skip_input_data(j_decompress_ptr cinfo, long num_bytes) { 101 auto* src = reinterpret_cast<SourceManager*>(cinfo->src); 102 if (num_bytes <= 0) { 103 return; 104 } 105 if (src->pub_.bytes_in_buffer >= static_cast<size_t>(num_bytes)) { 106 src->pub_.bytes_in_buffer -= num_bytes; 107 src->pub_.next_input_byte += num_bytes; 108 } else { 109 src->pos_ += num_bytes - src->pub_.bytes_in_buffer; 110 src->pub_.bytes_in_buffer = 0; 111 } 112 } 113 114 static void term_source(j_decompress_ptr cinfo) {} 115 }; 116 117 bool DecodeJpeg(const uint8_t* data, size_t size, size_t max_pixels, 118 const FuzzSpec& spec, std::vector<uint8_t>* pixels, 119 size_t* xsize, size_t* ysize) { 120 SourceManager src(data, size, spec.chunk_size); 121 jpeg_decompress_struct cinfo; 122 const auto try_catch_block = [&]() -> bool { 123 jpeg_error_mgr jerr; 124 jmp_buf env; 125 cinfo.err = jpegli_std_error(&jerr); 126 if (setjmp(env)) { 127 return false; 128 } 129 cinfo.client_data = reinterpret_cast<void*>(&env); 130 cinfo.err->error_exit = [](j_common_ptr cinfo) { 131 jmp_buf* env = reinterpret_cast<jmp_buf*>(cinfo->client_data); 132 jpegli_destroy(cinfo); 133 longjmp(*env, 1); 134 }; 135 cinfo.err->emit_message = [](j_common_ptr cinfo, int msg_level) {}; 136 jpegli_create_decompress(&cinfo); 137 cinfo.src = reinterpret_cast<jpeg_source_mgr*>(&src); 138 jpegli_read_header(&cinfo, TRUE); 139 *xsize = cinfo.image_width; 140 *ysize = cinfo.image_height; 141 size_t num_pixels = *xsize * *ysize; 142 if (num_pixels > max_pixels) return false; 143 jpegli_set_output_format(&cinfo, spec.output_type, spec.output_endianness); 144 jpegli_start_decompress(&cinfo); 145 if (spec.crop_output) { 146 JDIMENSION xoffset = cinfo.output_width / 3; 147 JDIMENSION xsize_cropped = cinfo.output_width / 3; 148 jpegli_crop_scanline(&cinfo, &xoffset, &xsize_cropped); 149 } 150 151 size_t bytes_per_sample = jpegli_bytes_per_sample(spec.output_type); 152 size_t stride = 153 bytes_per_sample * cinfo.output_components * cinfo.output_width; 154 size_t buffer_size = *ysize * stride; 155 pixels->resize(buffer_size); 156 for (size_t y = 0; y < *ysize; ++y) { 157 JSAMPROW rows[] = {pixels->data() + y * stride}; 158 jpegli_read_scanlines(&cinfo, rows, 1); 159 } 160 Consume(pixels->cbegin(), pixels->cend()); 161 jpegli_finish_decompress(&cinfo); 162 return true; 163 }; 164 bool success = try_catch_block(); 165 jpegli_destroy_decompress(&cinfo); 166 return success; 167 } 168 169 int TestOneInput(const uint8_t* data, size_t size) { 170 if (size < 4) return 0; 171 uint32_t flags = 0; 172 size_t used_flag_bits = 0; 173 memcpy(&flags, data + size - 4, 4); 174 size -= 4; 175 176 const auto getFlag = [&flags, &used_flag_bits](size_t max_value) { 177 size_t limit = 1; 178 while (limit <= max_value) { 179 limit <<= 1; 180 used_flag_bits++; 181 if (used_flag_bits > 32) abort(); 182 } 183 uint32_t result = flags % limit; 184 flags /= limit; 185 return result % (max_value + 1); 186 }; 187 188 FuzzSpec spec; 189 spec.output_type = static_cast<JpegliDataType>(getFlag(JPEGLI_TYPE_UINT16)); 190 spec.output_endianness = 191 static_cast<JpegliEndianness>(getFlag(JPEGLI_BIG_ENDIAN)); 192 uint32_t chunks = getFlag(15); 193 spec.chunk_size = chunks ? 1u << (chunks - 1) : 0; 194 spec.crop_output = getFlag(1); 195 196 std::vector<uint8_t> pixels; 197 size_t xsize; 198 size_t ysize; 199 size_t max_pixels = 1 << 21; 200 201 const auto targets = hwy::SupportedAndGeneratedTargets(); 202 hwy::SetSupportedTargetsForTest(targets[getFlag(targets.size() - 1)]); 203 DecodeJpeg(data, size, max_pixels, spec, &pixels, &xsize, &ysize); 204 hwy::SetSupportedTargetsForTest(0); 205 206 return 0; 207 } 208 209 } // namespace 210 211 extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { 212 return TestOneInput(data, size); 213 }