diff options
author | Ravi Jotwani <rjotwani@google.com> | 2020-07-07 13:24:02 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-07 13:24:02 -0700 |
commit | 930720f048c747781ba9545c77835f41419db260 (patch) | |
tree | 1c5150fef2e2970f1f75bc4099efcaa885a2e55e | |
parent | bc2ae2ba519e7122daf0d65161809b5f2b3e5564 (diff) |
[flac] Additional fuzzer (#4073)
* added draco integration files
* wrote build file and Dockerfile for Draco
* added new fuzzer, build failing
* fuzzer_exo build working
-rw-r--r-- | projects/flac/Dockerfile | 4 | ||||
-rwxr-xr-x | projects/flac/build.sh | 2 | ||||
-rw-r--r-- | projects/flac/fuzzer_exo.cpp | 477 |
3 files changed, 482 insertions, 1 deletions
diff --git a/projects/flac/Dockerfile b/projects/flac/Dockerfile index 2111fb7b..5af9225b 100644 --- a/projects/flac/Dockerfile +++ b/projects/flac/Dockerfile @@ -15,9 +15,11 @@ ################################################################################ FROM gcr.io/oss-fuzz-base/base-builder -RUN apt-get update && apt-get install -y make autoconf automake libtool libtool-bin pkg-config gettext sudo +RUN apt-get update && apt-get install -y make autoconf automake libtool libtool-bin pkg-config gettext sudo default-jdk RUN git clone --depth 1 https://github.com/xiph/flac.git RUN git clone --depth 1 https://github.com/xiph/ogg.git +RUN git clone --depth 1 https://github.com/google/ExoPlayer.git RUN git clone --depth 1 https://github.com/guidovranken/fuzzing-headers.git RUN git clone --depth 1 https://github.com/guidovranken/flac-fuzzers.git +COPY fuzzer_exo.cpp $SRC/flac-fuzzers COPY build.sh $SRC/ diff --git a/projects/flac/build.sh b/projects/flac/build.sh index f4ef5634..c94ba22f 100755 --- a/projects/flac/build.sh +++ b/projects/flac/build.sh @@ -53,6 +53,8 @@ cd $SRC/fuzzing-headers # Build fuzzers cd $SRC/flac-fuzzers/ +$CXX $CXXFLAGS -I $SRC/flac/include/ -I $SRC/ExoPlayer/extensions/flac/src/main/jni/ -I /usr/lib/jvm/java-8-openjdk-amd64/include/ -I /usr/lib/jvm/java-8-openjdk-amd64/include/linux/ fuzzer_exo.cpp \ + $SRC/flac/src/libFLAC++/.libs/libFLAC++.a $SRC/flac/src/libFLAC/.libs/libFLAC.a $SRC/libogg-install/lib/libogg.a $LIB_FUZZING_ENGINE -o $OUT/fuzzer_exo $CXX $CXXFLAGS -I $SRC/flac/include/ fuzzer_decoder.cpp $SRC/flac/src/libFLAC++/.libs/libFLAC++.a $SRC/flac/src/libFLAC/.libs/libFLAC.a $SRC/libogg-install/lib/libogg.a $LIB_FUZZING_ENGINE -o $OUT/fuzzer_decoder $CXX $CXXFLAGS -I $SRC/flac/include/ fuzzer_encoder.cpp $SRC/flac/src/libFLAC++/.libs/libFLAC++.a $SRC/flac/src/libFLAC/.libs/libFLAC.a $SRC/libogg-install/lib/libogg.a $LIB_FUZZING_ENGINE -o $OUT/fuzzer_encoder cp fuzzer_encoder.dict $OUT/ diff --git a/projects/flac/fuzzer_exo.cpp b/projects/flac/fuzzer_exo.cpp new file mode 100644 index 00000000..9c0eac67 --- /dev/null +++ b/projects/flac/fuzzer_exo.cpp @@ -0,0 +1,477 @@ +// Copyright 2020 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. + +#include <assert.h> +#include <string> + +#include "include/flac_parser.h" + +#include <jni.h> + +// #include <android/log.h> + +#include <cassert> +#include <cstdlib> +#include <cstring> + +#define LOG_TAG "FLACParser" + +#define LITERAL_TO_STRING_INTERNAL(x) #x +#define LITERAL_TO_STRING(x) LITERAL_TO_STRING_INTERNAL(x) + +#define CHECK(x) if (!(x)) return 0; + +const int endian = 1; +#define isBigEndian() (*(reinterpret_cast<const char *>(&endian)) == 0) + +// The FLAC parser calls our C++ static callbacks using C calling conventions, +// inside FLAC__stream_decoder_process_until_end_of_metadata +// and FLAC__stream_decoder_process_single. +// We immediately then call our corresponding C++ instance methods +// with the same parameter list, but discard redundant information. + +FLAC__StreamDecoderReadStatus FLACParser::read_callback( + const FLAC__StreamDecoder * /* decoder */, FLAC__byte buffer[], + size_t *bytes, void *client_data) { + return reinterpret_cast<FLACParser *>(client_data) + ->readCallback(buffer, bytes); +} + +FLAC__StreamDecoderSeekStatus FLACParser::seek_callback( + const FLAC__StreamDecoder * /* decoder */, + FLAC__uint64 absolute_byte_offset, void *client_data) { + return reinterpret_cast<FLACParser *>(client_data) + ->seekCallback(absolute_byte_offset); +} + +FLAC__StreamDecoderTellStatus FLACParser::tell_callback( + const FLAC__StreamDecoder * /* decoder */, + FLAC__uint64 *absolute_byte_offset, void *client_data) { + return reinterpret_cast<FLACParser *>(client_data) + ->tellCallback(absolute_byte_offset); +} + +FLAC__StreamDecoderLengthStatus FLACParser::length_callback( + const FLAC__StreamDecoder * /* decoder */, FLAC__uint64 *stream_length, + void *client_data) { + return reinterpret_cast<FLACParser *>(client_data) + ->lengthCallback(stream_length); +} + +FLAC__bool FLACParser::eof_callback(const FLAC__StreamDecoder * /* decoder */, + void *client_data) { + return reinterpret_cast<FLACParser *>(client_data)->eofCallback(); +} + +FLAC__StreamDecoderWriteStatus FLACParser::write_callback( + const FLAC__StreamDecoder * /* decoder */, const FLAC__Frame *frame, + const FLAC__int32 *const buffer[], void *client_data) { + return reinterpret_cast<FLACParser *>(client_data) + ->writeCallback(frame, buffer); +} + +void FLACParser::metadata_callback(const FLAC__StreamDecoder * /* decoder */, + const FLAC__StreamMetadata *metadata, + void *client_data) { + reinterpret_cast<FLACParser *>(client_data)->metadataCallback(metadata); +} + +void FLACParser::error_callback(const FLAC__StreamDecoder * /* decoder */, + FLAC__StreamDecoderErrorStatus status, + void *client_data) { + reinterpret_cast<FLACParser *>(client_data)->errorCallback(status); +} + +// These are the corresponding callbacks with C++ calling conventions + +FLAC__StreamDecoderReadStatus FLACParser::readCallback(FLAC__byte buffer[], + size_t *bytes) { + size_t requested = *bytes; + ssize_t actual = mDataSource->readAt(mCurrentPos, buffer, requested); + if (0 > actual) { + *bytes = 0; + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; + } else if (0 == actual) { + *bytes = 0; + mEOF = true; + return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; + } else { + assert(actual <= requested); + *bytes = actual; + mCurrentPos += actual; + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; + } +} + +FLAC__StreamDecoderSeekStatus FLACParser::seekCallback( + FLAC__uint64 absolute_byte_offset) { + mCurrentPos = absolute_byte_offset; + mEOF = false; + return FLAC__STREAM_DECODER_SEEK_STATUS_OK; +} + +FLAC__StreamDecoderTellStatus FLACParser::tellCallback( + FLAC__uint64 *absolute_byte_offset) { + *absolute_byte_offset = mCurrentPos; + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +} + +FLAC__StreamDecoderLengthStatus FLACParser::lengthCallback( + FLAC__uint64 *stream_length) { + return FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED; +} + +FLAC__bool FLACParser::eofCallback() { return mEOF; } + +FLAC__StreamDecoderWriteStatus FLACParser::writeCallback( + const FLAC__Frame *frame, const FLAC__int32 *const buffer[]) { + if (mWriteRequested) { + mWriteRequested = false; + // FLAC parser doesn't free or realloc buffer until next frame or finish + mWriteHeader = frame->header; + mWriteBuffer = buffer; + mWriteCompleted = true; + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } else { + return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } +} + +void FLACParser::metadataCallback(const FLAC__StreamMetadata *metadata) { + switch (metadata->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + if (!mStreamInfoValid) { + mStreamInfo = metadata->data.stream_info; + mStreamInfoValid = true; + } else { + break; + } + break; + case FLAC__METADATA_TYPE_SEEKTABLE: + mSeekTable = &metadata->data.seek_table; + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + if (!mVorbisCommentsValid) { + FLAC__StreamMetadata_VorbisComment vorbisComment = + metadata->data.vorbis_comment; + for (FLAC__uint32 i = 0; i < vorbisComment.num_comments; ++i) { + FLAC__StreamMetadata_VorbisComment_Entry vorbisCommentEntry = + vorbisComment.comments[i]; + if (vorbisCommentEntry.entry != NULL) { + std::string comment( + reinterpret_cast<char *>(vorbisCommentEntry.entry), + vorbisCommentEntry.length); + mVorbisComments.push_back(comment); + } + } + mVorbisCommentsValid = true; + } else { + break; + } + break; + case FLAC__METADATA_TYPE_PICTURE: { + const FLAC__StreamMetadata_Picture *parsedPicture = + &metadata->data.picture; + FlacPicture picture; + picture.mimeType.assign(std::string(parsedPicture->mime_type)); + picture.description.assign( + std::string((char *)parsedPicture->description)); + picture.data.assign(parsedPicture->data, + parsedPicture->data + parsedPicture->data_length); + picture.width = parsedPicture->width; + picture.height = parsedPicture->height; + picture.depth = parsedPicture->depth; + picture.colors = parsedPicture->colors; + picture.type = parsedPicture->type; + mPictures.push_back(picture); + mPicturesValid = true; + break; + } + default: + break; + } +} + +void FLACParser::errorCallback(FLAC__StreamDecoderErrorStatus status) { + mErrorStatus = status; +} + +// Copy samples from FLAC native 32-bit non-interleaved to +// correct bit-depth (non-zero padded), interleaved. +// These are candidates for optimization if needed. +static void copyToByteArrayBigEndian(int8_t *dst, const int *const *src, + unsigned bytesPerSample, unsigned nSamples, + unsigned nChannels) { + for (unsigned i = 0; i < nSamples; ++i) { + for (unsigned c = 0; c < nChannels; ++c) { + // point to the first byte of the source address + // and then skip the first few bytes (most significant bytes) + // depending on the bit depth + const int8_t *byteSrc = + reinterpret_cast<const int8_t *>(&src[c][i]) + 4 - bytesPerSample; + memcpy(dst, byteSrc, bytesPerSample); + dst = dst + bytesPerSample; + } + } +} + +static void copyToByteArrayLittleEndian(int8_t *dst, const int *const *src, + unsigned bytesPerSample, + unsigned nSamples, unsigned nChannels) { + for (unsigned i = 0; i < nSamples; ++i) { + for (unsigned c = 0; c < nChannels; ++c) { + // with little endian, the most significant bytes will be at the end + // copy the bytes in little endian will remove the most significant byte + // so we are good here. + memcpy(dst, &(src[c][i]), bytesPerSample); + dst = dst + bytesPerSample; + } + } +} + +static void copyTrespass(int8_t * /* dst */, const int *const * /* src */, + unsigned /* bytesPerSample */, unsigned /* nSamples */, + unsigned /* nChannels */) { + ; +} + +// FLACParser + +FLACParser::FLACParser(DataSource *source) + : mDataSource(source), + mCopy(copyTrespass), + mDecoder(NULL), + mCurrentPos(0LL), + mEOF(false), + mStreamInfoValid(false), + mSeekTable(NULL), + firstFrameOffset(0LL), + mVorbisCommentsValid(false), + mPicturesValid(false), + mWriteRequested(false), + mWriteCompleted(false), + mWriteBuffer(NULL), + mErrorStatus((FLAC__StreamDecoderErrorStatus)-1) { + memset(&mStreamInfo, 0, sizeof(mStreamInfo)); + memset(&mWriteHeader, 0, sizeof(mWriteHeader)); +} + +FLACParser::~FLACParser() { + if (mDecoder != NULL) { + FLAC__stream_decoder_delete(mDecoder); + mDecoder = NULL; + } +} + +bool FLACParser::init() { + // setup libFLAC parser + mDecoder = FLAC__stream_decoder_new(); + if (mDecoder == NULL) { + // The new should succeed, since probably all it does is a malloc + // that always succeeds in Android. But to avoid dependence on the + // libFLAC internals, we check and log here. + return false; + } + FLAC__stream_decoder_set_md5_checking(mDecoder, false); + FLAC__stream_decoder_set_metadata_ignore_all(mDecoder); + FLAC__stream_decoder_set_metadata_respond(mDecoder, + FLAC__METADATA_TYPE_STREAMINFO); + FLAC__stream_decoder_set_metadata_respond(mDecoder, + FLAC__METADATA_TYPE_SEEKTABLE); + FLAC__stream_decoder_set_metadata_respond(mDecoder, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + FLAC__stream_decoder_set_metadata_respond(mDecoder, + FLAC__METADATA_TYPE_PICTURE); + FLAC__StreamDecoderInitStatus initStatus; + initStatus = FLAC__stream_decoder_init_stream( + mDecoder, read_callback, seek_callback, tell_callback, length_callback, + eof_callback, write_callback, metadata_callback, error_callback, + reinterpret_cast<void *>(this)); + if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { + // A failure here probably indicates a programming error and so is + // unlikely to happen. But we check and log here similarly to above. + return false; + } + return true; +} + +bool FLACParser::decodeMetadata() { + // parse all metadata + if (!FLAC__stream_decoder_process_until_end_of_metadata(mDecoder)) { + return false; + } + // store first frame offset + FLAC__stream_decoder_get_decode_position(mDecoder, &firstFrameOffset); + + if (mStreamInfoValid) { + // check channel count + if (getChannels() == 0 || getChannels() > 8) { + return false; + } + // check bit depth + switch (getBitsPerSample()) { + case 8: + case 16: + case 24: + case 32: + break; + default: + return false; + } + // configure the appropriate copy function based on device endianness. + if (isBigEndian()) { + mCopy = copyToByteArrayBigEndian; + } else { + mCopy = copyToByteArrayLittleEndian; + } + } else { + return false; + } + return true; +} + +size_t FLACParser::readBuffer(void *output, size_t output_size) { + mWriteRequested = true; + mWriteCompleted = false; + + if (!FLAC__stream_decoder_process_single(mDecoder)) { + return -1; + } + if (!mWriteCompleted) { + if (FLAC__stream_decoder_get_state(mDecoder) != + FLAC__STREAM_DECODER_END_OF_STREAM) { + } + return -1; + } + + // verify that block header keeps the promises made by STREAMINFO + unsigned blocksize = mWriteHeader.blocksize; + if (blocksize == 0 || blocksize > getMaxBlockSize()) { + return -1; + } + if (mWriteHeader.sample_rate != getSampleRate() || + mWriteHeader.channels != getChannels() || + mWriteHeader.bits_per_sample != getBitsPerSample()) { + return -1; + } + + unsigned bytesPerSample = getBitsPerSample() >> 3; + size_t bufferSize = blocksize * getChannels() * bytesPerSample; + if (bufferSize > output_size) { + return -1; + } + + // copy PCM from FLAC write buffer to our media buffer, with interleaving. + (*mCopy)(reinterpret_cast<int8_t *>(output), mWriteBuffer, bytesPerSample, + blocksize, getChannels()); + + // fill in buffer metadata + CHECK(mWriteHeader.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER); + + return bufferSize; +} + +bool FLACParser::getSeekPositions(int64_t timeUs, + std::array<int64_t, 4> &result) { + if (!mSeekTable) { + return false; + } + + unsigned sampleRate = getSampleRate(); + int64_t totalSamples = getTotalSamples(); + int64_t targetSampleNumber = (timeUs * sampleRate) / 1000000LL; + if (targetSampleNumber >= totalSamples) { + targetSampleNumber = totalSamples - 1; + } + + FLAC__StreamMetadata_SeekPoint* points = mSeekTable->points; + unsigned length = mSeekTable->num_points; + + for (unsigned i = length; i != 0; i--) { + int64_t sampleNumber = points[i - 1].sample_number; + if (sampleNumber == -1) { // placeholder + continue; + } + if (sampleNumber <= targetSampleNumber) { + result[0] = (sampleNumber * 1000000LL) / sampleRate; + result[1] = firstFrameOffset + points[i - 1].stream_offset; + if (sampleNumber == targetSampleNumber || i >= length || + points[i].sample_number == -1) { // placeholder + // exact seek, or no following non-placeholder seek point + result[2] = result[0]; + result[3] = result[1]; + } else { + result[2] = (points[i].sample_number * 1000000LL) / sampleRate; + result[3] = firstFrameOffset + points[i].stream_offset; + } + return true; + } + } + result[0] = 0; + result[1] = firstFrameOffset; + result[2] = 0; + result[3] = firstFrameOffset; + return true; +} + +namespace { + + class FuzzDataSource : public DataSource { + const uint8_t *data_; + size_t size_; + + public: + FuzzDataSource(const uint8_t *data, size_t size) { + data_ = data; + size_ = size; + } + + ssize_t readAt(off64_t offset, void *const data, size_t size) { + if (offset > size_) + return -1; + size_t remaining = size_ - offset; + if (remaining < size) + size = remaining; + memcpy(data, data_ + offset, size); + return size; + } + }; + +} // namespace + +// Fuzz FLAC format and instrument the result as exoplayer JNI would: +// https://github.com/google/ExoPlayer/blob/release-v2/extensions/flac/src/main/jni/ +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + FuzzDataSource source(data, size); + FLACParser parser(&source); + + // Early parsing + if (!parser.init() || !parser.decodeMetadata()) + return 0; + + auto streamInfo = parser.getStreamInfo(); + + // Similar implementation than ExoPlayer + int buffer_size = streamInfo.max_blocksize * streamInfo.channels * 2; + assert(buffer_size >= 0); // Not expected + auto buffer = new uint8_t[buffer_size]; + int runs = 0; + while (parser.readBuffer(buffer, buffer_size) >= buffer_size) { + runs++; + continue; + } + delete[] buffer; + + return 0; +} |