diff --git a/projects/libpng-proto/Dockerfile b/projects/libpng-proto/Dockerfile index e10689255..f68cca559 100644 --- a/projects/libpng-proto/Dockerfile +++ b/projects/libpng-proto/Dockerfile @@ -23,4 +23,4 @@ RUN git clone --depth 1 https://github.com/glennrp/libpng.git RUN git clone --depth 1 https://github.com/google/libprotobuf-mutator.git RUN git clone --depth 1 https://github.com/google/fuzzer-test-suite RUN (mkdir LPM && cd LPM && cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release && ninja) -COPY build.sh png_fuzz_proto.proto png_proto_fuzzer_example.cc $SRC/ +COPY build.sh png_fuzz_proto.proto png_proto_fuzzer_example.cc libpng_transforms_fuzzer.cc $SRC/ diff --git a/projects/libpng-proto/build.sh b/projects/libpng-proto/build.sh index e22b4fb63..28b8f5568 100755 --- a/projects/libpng-proto/build.sh +++ b/projects/libpng-proto/build.sh @@ -29,7 +29,7 @@ $CXX $CXXFLAGS png_proto_fuzzer_example.cc libpng_read_fuzzer.o genfiles/png_fuz echo > dummy.cc -# Also compile another target, w/o protos but with a specialized custom mutator. +# A target, w/o protos but with a specialized custom mutator. $CXX $CXXFLAGS -c libpng/contrib/oss-fuzz/libpng_read_fuzzer.cc -I libpng $CXX $CXXFLAGS dummy.cc \ -include fuzzer-test-suite/libpng-1.2.56/png_mutator.h \ @@ -40,3 +40,14 @@ $CXX $CXXFLAGS dummy.cc \ $LIB_FUZZING_ENGINE \ -o $OUT/png_custom_mutator_fuzzer_example +# An experimental out-of-tree target, with a specialized custom mutator. +$CXX $CXXFLAGS libpng_transforms_fuzzer.cc \ + -include fuzzer-test-suite/libpng-1.2.56/png_mutator.h \ + -D PNG_MUTATOR_DEFINE_LIBFUZZER_CUSTOM_MUTATOR \ + -I libpng \ + -lz \ + libpng/.libs/libpng16.a \ + $LIB_FUZZING_ENGINE \ + -o $OUT/png_transforms_fuzzer + + diff --git a/projects/libpng-proto/libpng_transforms_fuzzer.cc b/projects/libpng-proto/libpng_transforms_fuzzer.cc new file mode 100644 index 000000000..470545d3f --- /dev/null +++ b/projects/libpng-proto/libpng_transforms_fuzzer.cc @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include + +#include "png.h" + +namespace { + +struct PngReader { + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + png_infop end_info = nullptr; +}; + +struct PngArrayStream { + const uint8_t *data; + size_t size; + size_t pos; +}; + +void PngArrayStreamCallback(png_structp png_ptr, png_bytep data, + png_size_t size) { + PngArrayStream *stream = + static_cast(png_get_io_ptr(png_ptr)); + if (stream->pos + size > stream->size) { + memset(data, 0, size); + stream->pos = size; + } else { + memcpy(data, &stream->data[stream->pos], size); + stream->pos += size; + } +} + +static bool PngVerboseWarnings = getenv("PNG_VERBOSE_WARNINGS") != nullptr; + +void PngErrorHandler(png_structp png_ptr, png_const_charp error_message) { + if (PngVerboseWarnings) fprintf(stderr, "%s\n", error_message); + longjmp(png_jmpbuf(png_ptr), 1); +} + +void PngWarningHandler(png_structp png_ptr, png_const_charp warning_message) { + if (PngVerboseWarnings) fprintf(stderr, "%s\n", warning_message); + longjmp(png_jmpbuf(png_ptr), 1); +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const size_t kPngSignatureSize = 8; + const size_t kIHDRSize = 4 + 4 + 13 + 4; + const size_t kMaxImageSize = 1 << 20; + + auto Read32 = [&](const uint8_t *p) { + uint32_t res; + assert(p >= data); + assert(p + sizeof(res) < data + size); + memcpy(&res, p, sizeof(res)); + return res; + }; + + if (size < kPngSignatureSize + kIHDRSize) return 0; + if (png_sig_cmp(data, 0, kPngSignatureSize)) return 0; + uint32_t width = __builtin_bswap32(Read32(data + kPngSignatureSize + 8)); + uint32_t height = __builtin_bswap32(Read32(data + kPngSignatureSize + 12)); + if ((uint64_t)width * height > kMaxImageSize) return 0; + + // Find the fUZz chunk and it's contents. + const size_t fUZz_chunk_size = 16; + const uint8_t fUZz_signature[8] = {0, 0, 0, fUZz_chunk_size, + 'f', 'U', 'Z', 'z'}; + const uint8_t *fUZz_beg = + std::search(data, data + size, fUZz_signature, + fUZz_signature + sizeof(fUZz_signature)); + if (fUZz_beg + sizeof(fUZz_signature) + fUZz_chunk_size < data + size) + fUZz_beg += sizeof(fUZz_signature); + else + fUZz_beg = nullptr; + + PngReader reader; + reader.png_ptr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + assert(reader.png_ptr); + reader.info_ptr = png_create_info_struct(reader.png_ptr); + assert(reader.info_ptr); + reader.end_info = png_create_info_struct(reader.png_ptr); + assert(reader.end_info); + + png_set_error_fn(reader.png_ptr, png_get_error_ptr(reader.png_ptr), + PngErrorHandler, PngWarningHandler); + + PngArrayStream stream{data, size, 0}; + + if (setjmp(png_jmpbuf(reader.png_ptr)) == 0) { + png_set_read_fn(reader.png_ptr, &stream, PngArrayStreamCallback); + + // Take transforms from the fUZz chunk. By default, enable all. + int transforms = fUZz_beg ? Read32(fUZz_beg) : ~0; + png_read_png(reader.png_ptr, reader.info_ptr, transforms, nullptr); + } + png_destroy_read_struct(&reader.png_ptr, &reader.info_ptr, &reader.end_info); + return 0; +}