diff --git a/.gitignore b/.gitignore index 6a60d527..ba46d5fe 100644 --- a/.gitignore +++ b/.gitignore @@ -78,6 +78,7 @@ microbench/benchmark_residual /ogg/ oss-fuzz/fuzzer_decoder oss-fuzz/fuzzer_encoder +oss-fuzz/fuzzer_encoder_v2 /*[Bb]uild*/ /out/ diff --git a/oss-fuzz/Makefile.am b/oss-fuzz/Makefile.am index 1cbfc55f..72c0e49e 100644 --- a/oss-fuzz/Makefile.am +++ b/oss-fuzz/Makefile.am @@ -1,5 +1,5 @@ # FLAC - Free Lossless Audio Codec -# Copyright (C) 2019 Xiph.Org Foundation +# Copyright (C) 2019-2022 Xiph.Org Foundation # # This file is part the FLAC project. FLAC is comprised of several # components distributed under different licenses. The codec libraries @@ -31,7 +31,7 @@ EXTRA_DIST = \ noinst_PROGRAMS = if USE_OSSFUZZERS -noinst_PROGRAMS += fuzzer_encoder fuzzer_decoder +noinst_PROGRAMS += fuzzer_encoder fuzzer_encoder_v2 fuzzer_decoder endif fuzzer_encoder_SOURCES = fuzzer_encoder.cc @@ -39,6 +39,11 @@ fuzzer_encoder_CXXFLAGS = $(AM_CXXFLAGS) $(LIB_FUZZING_ENGINE) fuzzer_encoder_LDFLAGS = $(AM_LDFLAGS) fuzzer_encoder_LDADD = $(flac_libs) +fuzzer_encoder_v2_SOURCES = fuzzer_encoder_v2.cc +fuzzer_encoder_v2_CXXFLAGS = $(AM_CXXFLAGS) $(LIB_FUZZING_ENGINE) +fuzzer_encoder_v2_LDFLAGS = $(AM_LDFLAGS) +fuzzer_encoder_v2_LDADD = $(flac_libs) + fuzzer_decoder_SOURCES = fuzzer_decoder.cc fuzzer_decoder_CXXFLAGS = $(AM_CXXFLAGS) $(LIB_FUZZING_ENGINE) fuzzer_decoder_LDFLAGS = $(AM_LDFLAGS) diff --git a/oss-fuzz/fuzzer_encoder_v2.cc b/oss-fuzz/fuzzer_encoder_v2.cc new file mode 100644 index 00000000..2e8c9199 --- /dev/null +++ b/oss-fuzz/fuzzer_encoder_v2.cc @@ -0,0 +1,246 @@ +/* fuzzer_encoder_v2 + * Copyright (C) 2022 Xiph.Org Foundation + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Xiph.org Foundation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include /* for memcpy */ +#include "FLAC/stream_encoder.h" +#include "FLAC/metadata.h" +extern "C" { +#include "share/private.h" +} + +/* This C++ fuzzer uses the FLAC and not FLAC++ because the latter lacks a few + * hidden functions like FLAC__stream_encoder_disable_constant_subframes. It + * is still processed by a C++ compiler because that's what oss-fuzz expects */ + + +static FLAC__StreamEncoderWriteStatus write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, uint32_t samples, uint32_t current_frame, void *client_data) +{ + (void)encoder, (void)buffer, (void)bytes, (void)samples, (void)current_frame, (void)client_data; + return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + FLAC__bool encoder_valid = true; + FLAC__StreamEncoder *encoder = 0; + FLAC__StreamMetadata *metadata[16] = {NULL}; + unsigned num_metadata = 0; + FLAC__StreamMetadata_VorbisComment_Entry VorbisCommentField; + + unsigned sample_rate, channels, bps; + uint64_t samples_estimate; + unsigned compression_level, input_data_width, blocksize, max_lpc_order, qlp_coeff_precision, min_residual_partition_order, max_residual_partition_order, metadata_mask; + FLAC__bool ogg, interleaved; + + FLAC__bool data_bools[24]; + + /* allocate the encoder */ + if((encoder = FLAC__stream_encoder_new()) == NULL) { + fprintf(stderr, "ERROR: allocating encoder\n"); + return 1; + } + + /* Use first 20 byte for configuration */ + if(size < 20){ + FLAC__stream_encoder_delete(encoder); + return 0; + } + + /* First 3 byte for sample rate, 4th byte for channels, 5th byte for bps */ + sample_rate = ((unsigned)data[0] << 16) + ((unsigned)data[1] << 8) + data[2]; + channels = data[3]; + bps = data[4]; + + /* Number of samples estimate, format accepts 36-bit max */ + samples_estimate = ((uint64_t)data[5] << 32) + ((unsigned)data[6] << 24) + ((unsigned)data[7] << 16) + ((unsigned)data[8] << 8) + data[9]; + + compression_level = data[10]&0b1111; + input_data_width = 1 + (data[10]&0b1111000)%3; /* Slight bias away from 32-bit (4-byte) */ + blocksize = ((unsigned)data[11] << 8) + (unsigned)data[12]; + max_lpc_order = data[13]; + qlp_coeff_precision = data[14]; + min_residual_partition_order = data[15] & 0b1111; + max_residual_partition_order = data[15] & 0b11110000; + metadata_mask = (unsigned)data[16]; + + /* data[17] is spare */ + + /* Get array of bools from configuration */ + for(int i = 0; i < 16; i++) + data_bools[i] = data[18+i/8] & (1 << (i % 8)); + + ogg = data_bools[0]; + interleaved = data_bools[1]; + + /* Set input and process parameters */ + encoder_valid &= FLAC__stream_encoder_set_verify(encoder, data_bools[2]); + encoder_valid &= FLAC__stream_encoder_set_channels(encoder, channels); + encoder_valid &= FLAC__stream_encoder_set_bits_per_sample(encoder, bps); + encoder_valid &= FLAC__stream_encoder_set_sample_rate(encoder, sample_rate); + encoder_valid &= FLAC__stream_encoder_set_total_samples_estimate(encoder, samples_estimate); + + /* Set compression related parameters */ + encoder_valid &= FLAC__stream_encoder_set_compression_level(encoder, compression_level); + if(data_bools[3]){ + /* Bias towards regular compression levels */ + encoder_valid &= FLAC__stream_encoder_set_streamable_subset(encoder, data_bools[4]); + encoder_valid &= FLAC__stream_encoder_set_blocksize(encoder, blocksize); + encoder_valid &= FLAC__stream_encoder_set_max_lpc_order(encoder, max_lpc_order); + encoder_valid &= FLAC__stream_encoder_set_qlp_coeff_precision(encoder, qlp_coeff_precision); + encoder_valid &= FLAC__stream_encoder_set_min_residual_partition_order(encoder, min_residual_partition_order); + encoder_valid &= FLAC__stream_encoder_set_max_residual_partition_order(encoder, max_residual_partition_order); + + encoder_valid &= FLAC__stream_encoder_set_do_qlp_coeff_prec_search(encoder, data_bools[5]); + encoder_valid &= FLAC__stream_encoder_set_do_escape_coding(encoder, data_bools[6]); + encoder_valid &= FLAC__stream_encoder_set_do_exhaustive_model_search(encoder, data_bools[7]); + encoder_valid &= FLAC__stream_encoder_set_do_mid_side_stereo(encoder, data_bools[8]); + encoder_valid &= FLAC__stream_encoder_set_loose_mid_side_stereo(encoder, data_bools[9]); + + encoder_valid &= FLAC__stream_encoder_disable_constant_subframes(encoder, data_bools[10]); + encoder_valid &= FLAC__stream_encoder_disable_fixed_subframes(encoder, data_bools[11]); + encoder_valid &= FLAC__stream_encoder_disable_verbatim_subframes(encoder, data_bools[12]); + } + + /* data_bools[13..15] are spare */ + + /* add metadata */ + if(encoder_valid && (metadata_mask & 1)) { + if((metadata[num_metadata] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_STREAMINFO)) == NULL) + encoder_valid = false; + else + num_metadata++; + } + if(encoder_valid && (metadata_mask & 2) && size > 21){ + if((metadata[num_metadata] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_PADDING)) == NULL) + encoder_valid = false; + else { + metadata[num_metadata++]->length = (((unsigned)data[20]) << 8) + (unsigned)(data[21]); + } + } + if(encoder_valid && (metadata_mask & 4) && size > 20){ + FLAC__byte * application_data = (FLAC__byte *)malloc(size-20); + if(0 != application_data && ((metadata[num_metadata] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_APPLICATION)) == NULL)) + encoder_valid = false; + else { + memcpy(application_data,data+20,size-20); + FLAC__metadata_object_application_set_data(metadata[num_metadata++], application_data, size-20, 0); + } + } + if(encoder_valid && (metadata_mask & 8) > 25){ + if((metadata[num_metadata] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_SEEKTABLE)) == NULL) + encoder_valid = false; + else { + unsigned seekpoint_spacing = ((unsigned)data[22] << 8) + data[23]; + unsigned total_samples_for_seekpoints = ((unsigned)data[24] << 8) + data[25]; + FLAC__metadata_object_seektable_template_append_spaced_points_by_samples(metadata[num_metadata++], seekpoint_spacing, total_samples_for_seekpoints); + } + } + if(encoder_valid && (metadata_mask & 16)){ + if((metadata[num_metadata] = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT)) == NULL) + encoder_valid = false; + else { + /* Append a vorbis comment */ + if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&VorbisCommentField, "COMMENTARY", "Nothing to 🤔 report")) + encoder_valid = false; + else { + FLAC__metadata_object_vorbiscomment_append_comment(metadata[num_metadata], VorbisCommentField, false); + + /* Insert a vorbis comment at the first index */ + if(!FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(&VorbisCommentField, "COMMENTARY", "Still nothing to report 🤔🤣")) + encoder_valid = false; + else + FLAC__metadata_object_vorbiscomment_insert_comment(metadata[num_metadata++], 0, VorbisCommentField, false); + } + } + } + + if(num_metadata && encoder_valid) + encoder_valid = FLAC__stream_encoder_set_metadata(encoder, metadata, num_metadata); + + /* initialize encoder */ + if(encoder_valid) { + FLAC__StreamEncoderInitStatus init_status; + if(ogg) + init_status = FLAC__stream_encoder_init_ogg_stream(encoder, NULL, write_callback, NULL, NULL, NULL, NULL); + else + init_status = FLAC__stream_encoder_init_stream(encoder, write_callback, NULL, NULL, NULL, NULL); + if(init_status != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { + encoder_valid = false; + } + } + + + /* send samples to encoder */ + if(encoder_valid && size > (input_data_width*channels+26)) { + unsigned samples = (size - 26)/input_data_width/channels; + const uint8_t * pcm_data = data + 26; + int32_t * data_as_int32 = (int32_t *)malloc(4*samples*channels); + if(0 != data_as_int32){ + for(unsigned i = 0; i < samples*channels; i++) + if(input_data_width == 1) + data_as_int32[i] = (int32_t)pcm_data[i] - 0x80; + else if(input_data_width == 2) + data_as_int32[i] = (((int32_t)pcm_data[i*2] << 8) + pcm_data[i*2+1]) - 0x8000; + else if(input_data_width == 3) + data_as_int32[i] = (((int32_t)pcm_data[i*3] << 16) + ((int32_t)pcm_data[i*3+1] << 8) + pcm_data[i*3+2]) - 0x800000; + else if(input_data_width == 4) + data_as_int32[i] = (((int64_t)pcm_data[i*4] << 24) + ((int32_t)pcm_data[i*4+1] << 16) + ((int32_t)pcm_data[i*4+2] << 8) + pcm_data[i*4+3]) - 0x80000000; + + /* feed samples to encoder */ + if(interleaved) + encoder_valid = FLAC__stream_encoder_process_interleaved(encoder, data_as_int32, samples); + else { + encoder_valid = FLAC__stream_encoder_process(encoder, (const int32_t*[]){data_as_int32, + data_as_int32+samples, + data_as_int32+samples*2, + data_as_int32+samples*3, + data_as_int32+samples*4, data_as_int32+samples*5, data_as_int32+samples*6, data_as_int32+samples*7}, samples); + } + free(data_as_int32); + } + else { + encoder_valid = false; + } + } + + FLAC__stream_encoder_finish(encoder); + + /* now that encoding is finished, the metadata can be freed */ + for(unsigned i = 0; i < 16; i++) + if(0 != metadata[i]) + FLAC__metadata_object_delete(metadata[i]); + + FLAC__stream_encoder_delete(encoder); + + return 0; +} +