From e47326293df7aa634d49760ebd0ad9871c9996a0 Mon Sep 17 00:00:00 2001 From: Kostya Serebryany Date: Fri, 21 Dec 2018 15:10:13 -0800 Subject: [PATCH] Adding libpng-proto, an example of proto-based fuzzer (#2048) * Adding libpng-proto, an example of proto-based fuzzer * fix year * remove redundant line * address comments * simplify names * small update in build.sh --- projects/libpng-proto/Dockerfile | 25 +++++ projects/libpng-proto/build.sh | 28 ++++++ projects/libpng-proto/png_fuzz_proto.proto | 41 ++++++++ .../libpng-proto/png_proto_fuzzer_example.cc | 98 +++++++++++++++++++ projects/libpng-proto/project.yaml | 7 ++ 5 files changed, 199 insertions(+) create mode 100644 projects/libpng-proto/Dockerfile create mode 100755 projects/libpng-proto/build.sh create mode 100644 projects/libpng-proto/png_fuzz_proto.proto create mode 100644 projects/libpng-proto/png_proto_fuzzer_example.cc create mode 100644 projects/libpng-proto/project.yaml diff --git a/projects/libpng-proto/Dockerfile b/projects/libpng-proto/Dockerfile new file mode 100644 index 000000000..4438b0b3f --- /dev/null +++ b/projects/libpng-proto/Dockerfile @@ -0,0 +1,25 @@ +# Copyright 2018 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +FROM gcr.io/oss-fuzz-base/base-builder +MAINTAINER kcc@google.com +RUN apt-get update && \ + apt-get install -y make autoconf automake libtool zlib1g-dev ninja-build cmake + +RUN git clone --depth 1 https://github.com/glennrp/libpng.git +RUN git clone --depth 1 https://github.com/google/libprotobuf-mutator.git +RUN (mkdir LPM && cd LPM && cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF && ninja) +COPY build.sh png_fuzz_proto.proto png_proto_fuzzer_example.cc $SRC/ diff --git a/projects/libpng-proto/build.sh b/projects/libpng-proto/build.sh new file mode 100755 index 000000000..690056a1f --- /dev/null +++ b/projects/libpng-proto/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# build libpng using the upstream-provided build.sh. +# it will also build the vanilla (non-proto) fuzz target, +# but we discard it. +(cd libpng/ && contrib/oss-fuzz/build.sh && rm $OUT/*) + +# Compile png_fuzz_proto.proto; should produce two files in genfiles/: +# png_fuzz_proto.pb.cc png_fuzz_proto.pb.h +rm -rf genfiles && mkdir genfiles && LPM/external.protobuf/bin/protoc png_fuzz_proto.proto --cpp_out=genfiles + +# compile the upstream-provided vanilla fuzz target +# but replace LLVMFuzzerTestOneInput with FuzzPNG so that +# png_proto_fuzzer_example.cc can call FuzzPNG from its own +# LLVMFuzzerTestOneInput. +$CXX $CXXFLAGS -c -DLLVMFuzzerTestOneInput=FuzzPNG libpng/contrib/oss-fuzz/libpng_read_fuzzer.cc -I libpng + +# compile & link the rest +$CXX $CXXFLAGS png_proto_fuzzer_example.cc libpng_read_fuzzer.o genfiles/png_fuzz_proto.pb.cc \ + -I genfiles -I. -I libprotobuf-mutator/ -I LPM/external.protobuf/include \ + -lz \ + LPM/src/libfuzzer/libprotobuf-mutator-libfuzzer.a \ + LPM/src/libprotobuf-mutator.a \ + LPM/external.protobuf/lib/libprotobuf.a \ + libpng/.libs/libpng16.a \ + $LIB_FUZZING_ENGINE \ + -o $OUT/png_proto_fuzzer_example + diff --git a/projects/libpng-proto/png_fuzz_proto.proto b/projects/libpng-proto/png_fuzz_proto.proto new file mode 100644 index 000000000..d978978f7 --- /dev/null +++ b/projects/libpng-proto/png_fuzz_proto.proto @@ -0,0 +1,41 @@ +syntax = "proto2"; +// Very simple proto description of the PNG format, +// described at https://en.wikipedia.org/wiki/Portable_Network_Graphics + +message IHDR { + required uint32 width = 1; + required uint32 height = 2; + required uint32 other1 = 3; + required uint32 other2 = 4; // Only 1 byte used. +} + +message PLTE { + required bytes data = 1; +} + +message IDAT { + required bytes data = 1; +} + +message OtherChunk { + oneof type { + uint32 known_type = 1; + uint32 unknown_type = 2; + } + required bytes data = 3; +} + +message PngChunk { + oneof chunk { + PLTE plte = 1; + IDAT idat = 2; + OtherChunk other_chunk = 10000; + } +} + +message PngProto { + required IHDR ihdr = 1; + repeated PngChunk chunks = 2; +} + +// package fuzzer_examples; diff --git a/projects/libpng-proto/png_proto_fuzzer_example.cc b/projects/libpng-proto/png_proto_fuzzer_example.cc new file mode 100644 index 000000000..346f12f33 --- /dev/null +++ b/projects/libpng-proto/png_proto_fuzzer_example.cc @@ -0,0 +1,98 @@ +// Example fuzzer for PNG using protos. +#include +#include +#include +#include +#include // for crc32 + +#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h" +#include "png_fuzz_proto.pb.h" + +static void WriteInt(std::stringstream &out, uint32_t x) { + x = __builtin_bswap32(x); + out.write((char *)&x, sizeof(x)); +} + +static void WriteByte(std::stringstream &out, uint8_t x) { + out.write((char *)&x, sizeof(x)); +} + +// Chunk is written as: +// * 4-byte length +// * 4-byte type +// * the data itself +// * 4-byte crc (of type and data) +static void WriteChunk(std::stringstream &out, const char *type, + const std::string &chunk) { + uint32_t len = chunk.size(); + uint32_t crc = crc32(crc32(0, (const unsigned char *)type, 4), + (const unsigned char *)chunk.data(), chunk.size()); + WriteInt(out, len); + out.write(type, 4); + // TODO: IDAT chunks are compressed, so we better compress them here. + out.write(chunk.data(), chunk.size()); + WriteInt(out, crc); +} + +std::string ProtoToPng(const PngProto &png_proto) { + std::stringstream all; + const unsigned char header[] = {0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}; + all.write((const char*)header, sizeof(header)); + std::stringstream ihdr_str; + auto &ihdr = png_proto.ihdr(); + // Avoid large images. + // They may have interesting bugs, but OOMs are going to kill fuzzing. + uint32_t w = std::min(ihdr.width(), 4096U); + uint32_t h = std::min(ihdr.height(), 4096U); + WriteInt(ihdr_str, w); + WriteInt(ihdr_str, h); + WriteInt(ihdr_str, ihdr.other1()); + WriteByte(ihdr_str, ihdr.other2()); + WriteChunk(all, "IHDR", ihdr_str.str()); + + for (size_t i = 0, n = png_proto.chunks_size(); i < n; i++) { + auto &chunk = png_proto.chunks(i); + if (chunk.has_plte()) { + WriteChunk(all, "PLTE", chunk.plte().data()); + } else if (chunk.has_idat()) { + WriteChunk(all, "IDAT", chunk.idat().data()); + } else if (chunk.has_other_chunk()) { + auto &other_chunk = chunk.other_chunk(); + char type[5] = {0}; + if (other_chunk.has_known_type()) { + static const std::vector known_chunks = { + "bKGD", "cHRM", "dSIG", "eXIf", "gAMA", "hIST", "iCCP", + "iTXt", "pHYs", "sBIT", "sPLT", "sRGB", "sTER", "tEXt", + "tIME", "tRNS", "zTXt", "sCAL", "pCAL", "oFFs", + }; + size_t chunk_idx = other_chunk.known_type() % known_chunks.size(); + memcpy(type, known_chunks[chunk_idx], 4); + } else if (other_chunk.has_unknown_type()) { + uint32_t unknown_type_int = other_chunk.unknown_type(); + memcpy(type, &unknown_type_int, 4); + } else { + continue; + } + type[4] = 0; + WriteChunk(all, type, other_chunk.data()); + } + } + WriteChunk(all, "IEND", ""); + + std::string res = all.str(); + if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { + // With libFuzzer binary run this to generate a PNG file x.png: + // PROTO_FUZZER_DUMP_PATH=x.png ./a.out proto-input + std::ofstream of(dump_path); + of.write(res.data(), res.size()); + } + return res; +} + +// The actual fuzz target that consumes the PNG data. +extern "C" int FuzzPNG(const uint8_t* data, size_t size); + +DEFINE_PROTO_FUZZER(const PngProto &png_proto) { + auto s = ProtoToPng(png_proto); + FuzzPNG((const uint8_t*)s.data(), s.size()); +} diff --git a/projects/libpng-proto/project.yaml b/projects/libpng-proto/project.yaml new file mode 100644 index 000000000..f8a305fa8 --- /dev/null +++ b/projects/libpng-proto/project.yaml @@ -0,0 +1,7 @@ +homepage: "http://www.libpng.org/pub/png/libpng.html" +primary_contact: "kcc@google.com" +sanitizers: + - address + - undefined +fuzzing_engines: + - libfuzzer