From 20c65fecd96c01444bb0fee1f5029ad8b0e6aa2e Mon Sep 17 00:00:00 2001 From: Max Dymond Date: Sat, 27 Jul 2019 20:44:18 +0100 Subject: [PATCH] Add ossfuzz support for libsndfile Fixes #476 Add a fuzzer (`sndfile_fuzzer`) which can be compiled and run by the OSS-Fuzz infrastructure. --- .github/workflows/action.yml | 19 ++++- Makefile.am | 35 +++++++++ configure.ac | 10 +++ ossfuzz/.gitignore | 1 + ossfuzz/ci_oss.sh | 41 +++++++++++ ossfuzz/ossfuzz.sh | 28 ++++++++ ossfuzz/sndfile_fuzzer.cc | 134 +++++++++++++++++++++++++++++++++++ ossfuzz/standaloneengine.cc | 86 ++++++++++++++++++++++ ossfuzz/testinput.h | 3 + 9 files changed, 355 insertions(+), 2 deletions(-) create mode 100644 ossfuzz/.gitignore create mode 100755 ossfuzz/ci_oss.sh create mode 100755 ossfuzz/ossfuzz.sh create mode 100644 ossfuzz/sndfile_fuzzer.cc create mode 100644 ossfuzz/standaloneengine.cc create mode 100644 ossfuzz/testinput.h diff --git a/.github/workflows/action.yml b/.github/workflows/action.yml index 663a73d2..2bfaced8 100644 --- a/.github/workflows/action.yml +++ b/.github/workflows/action.yml @@ -10,6 +10,7 @@ jobs: name: [ ubuntu-gcc-autotools, ubuntu-clang-autotools, + ubuntu-gcc-ossfuzz, macos-autotools, ubuntu-gcc-cmake, ubuntu-gcc-cmake-shared, @@ -35,6 +36,12 @@ jobs: cxx: clang++ build-system: autotools + - name: ubuntu-gcc-ossfuzz + os: ubuntu-latest + cc: gcc + cxx: g++ + build-system: ossfuzz + - name: macos-autotools os: macos-latest cc: clang @@ -166,7 +173,7 @@ jobs: -DVCPKG_TARGET_TRIPLET=x86-windows-static -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v2 @@ -197,7 +204,7 @@ jobs: else Scripts/asan-configure.sh --enable-werror && make clean all check && make distcheck fi - + - name: Configure, build and test with CMake env: CC: ${{ matrix.cc }} @@ -209,3 +216,11 @@ jobs: cmake .. -G "${{matrix.cmake-generator}}" ${{matrix.cmake-options}} cmake --build . --config Release ctest + + - name: Configure, build and test with OSSFuzz + env: + CC: ${{ matrix.cc }} + CXX: ${{ matrix.cxx }} + if: startsWith(matrix.build-system,'ossfuzz') + run: | + ./ossfuzz/ci_oss.sh diff --git a/Makefile.am b/Makefile.am index 335cc20c..d8c71411 100644 --- a/Makefile.am +++ b/Makefile.am @@ -26,6 +26,8 @@ cmake_files = cmake/ClipMode.cmake cmake/FindFLAC.cmake \ pkgconfig_DATA = sndfile.pc +noinst_PROGRAMS = + #=============================================================================== test: check @@ -435,6 +437,39 @@ man/sndfile-deinterleave.1: man/sndfile-interleave.1 -rm -f $@ cd $(top_srcdir)/man && $(LN_S) sndfile-interleave.1 sndfile-deinterleave.1 +############ +# ossfuzz/ # +############ + +if USE_OSSFUZZ_FLAG +FUZZ_FLAG = $(LIB_FUZZING_ENGINE) +FUZZ_LDADD = +else +if USE_OSSFUZZ_STATIC +FUZZ_LDADD = $(LIB_FUZZING_ENGINE) +FUZZ_FLAG = +else +FUZZ_LDADD = libstandaloneengine.la +FUZZ_FLAG = +endif +endif + +if USE_OSSFUZZERS +noinst_PROGRAMS += \ + ossfuzz/sndfile_fuzzer + +noinst_LTLIBRARIES += \ + ossfuzz/libstandaloneengine.la +endif + +ossfuzz_sndfile_fuzzer_SOURCES = ossfuzz/sndfile_fuzzer.cc +ossfuzz_sndfile_fuzzer_CXXFLAGS = $(AM_CXXFLAGS) $(FUZZ_FLAG) +ossfuzz_sndfile_fuzzer_LDFLAGS = $(AM_LDFLAGS) -static +ossfuzz_sndfile_fuzzer_LDADD = src/libsndfile.la $(FUZZ_LDADD) + +ossfuzz_libstandaloneengine_la_SOURCES = ossfuzz/standaloneengine.cc ossfuzz/testinput.h +ossfuzz_libstandaloneengine_la_CXXFLAGS = $(AM_CXXFLAGS) + ############# # programs/ # ############# diff --git a/configure.ac b/configure.ac index c3221862..9b069918 100644 --- a/configure.ac +++ b/configure.ac @@ -163,6 +163,16 @@ AC_ARG_ENABLE([test-coverage], [AS_HELP_STRING([--enable-test-coverage], [enable test coverage])]) AM_CONDITIONAL([ENABLE_TEST_COVERAGE], [test "x$enable_test_coverage" = "xyes"]) +AC_ARG_ENABLE([ossfuzzers], + [AS_HELP_STRING([--enable-ossfuzzers], + [Whether to generate the fuzzers for OSS-Fuzz])], + [have_ossfuzzers=yes], [have_ossfuzzers=no]) +AM_CONDITIONAL([USE_OSSFUZZERS], [test "x$have_ossfuzzers" = "xyes"]) + +AC_SUBST([LIB_FUZZING_ENGINE]) +AM_CONDITIONAL([USE_OSSFUZZ_FLAG], [test "x$LIB_FUZZING_ENGINE" = "x-fsanitize=fuzzer"]) +AM_CONDITIONAL([USE_OSSFUZZ_STATIC], [test -f "$LIB_FUZZING_ENGINE"]) + dnl ==================================================================================== dnl Check types and their sizes. diff --git a/ossfuzz/.gitignore b/ossfuzz/.gitignore new file mode 100644 index 00000000..6e9ed16a --- /dev/null +++ b/ossfuzz/.gitignore @@ -0,0 +1 @@ +sndfile_fuzzer diff --git a/ossfuzz/ci_oss.sh b/ossfuzz/ci_oss.sh new file mode 100755 index 00000000..91e479df --- /dev/null +++ b/ossfuzz/ci_oss.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +set -ex + +PROJECT_NAME=libsndfile + +# Clone the oss-fuzz repository +git clone https://github.com/google/oss-fuzz.git /tmp/ossfuzz + +# TODO: Verify that the GITHUB variables below are correct for a PR +env | grep "GITHUB_" + +if [[ ! -d /tmp/ossfuzz/projects/${PROJECT_NAME} ]] +then + echo "Could not find the ${PROJECT_NAME} project in ossfuzz" + + # Exit with a success code while the libsndfile project is not expected to exist + # on oss-fuzz. + exit 0 +fi + +# Work out which repo to clone from, inside Docker +if [[ -n ${GITHUB_BASE_REF} ]] +then + # Pull-request branch + REPO=${GITHUB_REPOSITORY} + BRANCH=${GITHUB_BASE_REF} +else + # Push build. + REPO=${GITHUB_REPOSITORY} + BRANCH=${GITHUB_REF} +fi + +# Modify the oss-fuzz Dockerfile so that we're checking out the current branch on travis. +sed -i "s@https://github.com/erikd/libsndfile.git@-b ${BRANCH} https://github.com/${REPO}.git@" /tmp/ossfuzz/projects/${PROJECT_NAME}/Dockerfile + +# Try and build the fuzzers +pushd /tmp/ossfuzz +python infra/helper.py build_image --pull ${PROJECT_NAME} +python infra/helper.py build_fuzzers ${PROJECT_NAME} +popd diff --git a/ossfuzz/ossfuzz.sh b/ossfuzz/ossfuzz.sh new file mode 100755 index 00000000..5da8270d --- /dev/null +++ b/ossfuzz/ossfuzz.sh @@ -0,0 +1,28 @@ +#!/bin/bash -eu + +# This script is called by the oss-fuzz main project when compiling the fuzz +# targets. This script is regression tested by ci_oss.sh. + +# Save off the current folder as the build root. +export BUILD_ROOT=$PWD + +echo "CC: ${CC:-}" +echo "CXX: ${CXX:-}" +echo "LIB_FUZZING_ENGINE: ${LIB_FUZZING_ENGINE:-}" +echo "CFLAGS: ${CFLAGS:-}" +echo "CXXFLAGS: ${CXXFLAGS:-}" +echo "OUT: ${OUT:-}" + +export MAKEFLAGS+="-j$(nproc)" + +# Install dependencies +apt-get -y install autoconf autogen automake libasound2-dev \ + libflac-dev libogg-dev libopus-dev libtool libvorbis-dev pkg-config python + +# Compile the fuzzer. +./autogen.sh +./configure --enable-werror --enable-ossfuzzers +make V=1 + +# Copy the fuzzer to the output directory. +cp -v ossfuzz/sndfile_fuzzer $OUT/ diff --git a/ossfuzz/sndfile_fuzzer.cc b/ossfuzz/sndfile_fuzzer.cc new file mode 100644 index 00000000..6c663b1b --- /dev/null +++ b/ossfuzz/sndfile_fuzzer.cc @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +typedef struct +{ + sf_count_t offset; + sf_count_t length; + const unsigned char *data; +} VIO_DATA; + +static sf_count_t vfget_filelen (void *user_data) +{ + VIO_DATA *vf = (VIO_DATA *)user_data; + return vf->length; +} + +static sf_count_t vfseek (sf_count_t offset, int whence, void *user_data) +{ + VIO_DATA *vf = (VIO_DATA *)user_data; + + switch (whence) + { + case SEEK_SET: + vf->offset = offset; + break ; + + case SEEK_CUR: + vf->offset = vf->offset + offset; + break ; + + case SEEK_END: + vf->offset = vf->length + offset; + break; + + default: + break; + } + + return vf->offset; +} + +static sf_count_t vfread (void *ptr, sf_count_t count, void *user_data) +{ + VIO_DATA *vf = (VIO_DATA *)user_data; + + if (vf->offset + count > vf->length) + { + count = vf->length - vf->offset; + } + + memcpy(ptr, vf->data + vf->offset, count); + vf->offset += count; + + return count; +} + +static sf_count_t vfwrite (const void *ptr, sf_count_t count, void *user_data) +{ + (void)ptr; + (void)count; + (void)user_data; + + // Cannot write to this virtual file. + return 0; +} + +static sf_count_t vftell (void *user_data) +{ VIO_DATA *vf = (VIO_DATA *)user_data; + + return vf->offset; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + VIO_DATA vio_data; + SF_VIRTUAL_IO vio; + SF_INFO sndfile_info; + SNDFILE *sndfile = NULL; + float* read_buffer = NULL; + + // Initialize the virtual IO structure. + vio.get_filelen = vfget_filelen; + vio.seek = vfseek; + vio.read = vfread; + vio.write = vfwrite; + vio.tell = vftell; + + // Initialize the VIO user data. + vio_data.data = data; + vio_data.length = size; + vio_data.offset = 0; + + memset(&sndfile_info, 0, sizeof(SF_INFO)); + + // Try and open the virtual file. + sndfile = sf_open_virtual(&vio, SFM_READ, &sndfile_info, &vio_data); + + if (sndfile_info.channels == 0) + { + // No sound channels in file. + goto EXIT_LABEL; + } + else if (sndfile_info.channels > 1024 * 1024) + { + // Too many channels to handle. + goto EXIT_LABEL; + } + + // Just the right number of channels. Create some buffer space for reading. + read_buffer = (float*)malloc(sizeof(float) * sndfile_info.channels); + if (read_buffer == NULL) + { + abort(); + } + + while (sf_readf_float(sndfile, read_buffer, 1)) + { + // Do nothing with the data. + } + +EXIT_LABEL: + + if (sndfile != NULL) + { + sf_close(sndfile); + } + + free(read_buffer); + + return 0; +} diff --git a/ossfuzz/standaloneengine.cc b/ossfuzz/standaloneengine.cc new file mode 100644 index 00000000..ab6408d9 --- /dev/null +++ b/ossfuzz/standaloneengine.cc @@ -0,0 +1,86 @@ +#include +#include +#include + +#include "testinput.h" + +/** + * Main procedure for standalone fuzzing engine. + * + * Reads filenames from the argument array. For each filename, read the file + * into memory and then call the fuzzing interface with the data. + */ +int main(int argc, char **argv) +{ + int ii; + for(ii = 1; ii < argc; ii++) + { + FILE *infile; + printf("[%s] ", argv[ii]); + + /* Try and open the file. */ + infile = fopen(argv[ii], "rb"); + if(infile) + { + uint8_t *buffer = NULL; + size_t buffer_len; + + printf("Opened.. "); + + /* Get the length of the file. */ + fseek(infile, 0L, SEEK_END); + buffer_len = ftell(infile); + + /* Reset the file indicator to the beginning of the file. */ + fseek(infile, 0L, SEEK_SET); + + /* Allocate a buffer for the file contents. */ + buffer = (uint8_t *)calloc(buffer_len, sizeof(uint8_t)); + if(buffer) + { + size_t result; + + /* Read all the text from the file into the buffer. */ + result = fread(buffer, sizeof(uint8_t), buffer_len, infile); + + if (result == buffer_len) + { + printf("Read %zu bytes, fuzzing.. ", buffer_len); + /* Call the fuzzer with the data. */ + LLVMFuzzerTestOneInput(buffer, buffer_len); + + printf("complete !!"); + } + else + { + fprintf(stderr, + "Failed to read %zu bytes (result %zu)\n", + buffer_len, + result); + } + + /* Free the buffer as it's no longer needed. */ + free(buffer); + buffer = NULL; + } + else + { + fprintf(stderr, + "[%s] Failed to allocate %zu bytes \n", + argv[ii], + buffer_len); + } + + /* Close the file as it's no longer needed. */ + fclose(infile); + infile = NULL; + } + else + { + /* Failed to open the file. Maybe wrong name or wrong permissions? */ + fprintf(stderr, "[%s] Open failed. \n", argv[ii]); + } + + printf("\n"); + } +} diff --git a/ossfuzz/testinput.h b/ossfuzz/testinput.h new file mode 100644 index 00000000..6ab9b515 --- /dev/null +++ b/ossfuzz/testinput.h @@ -0,0 +1,3 @@ +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);