[FFmpeg-devel] [PATCH] tools: add a fuzzer tool for bitstream filters
James Almer
jamrial at gmail.com
Tue Dec 3 21:59:26 EET 2019
On 12/3/2019 4:31 PM, Andriy Gelman wrote:
> On Mon, 02. Dec 13:15, James Almer wrote:
>> Signed-off-by: James Almer <jamrial at gmail.com>
>> ---
>> Untested.
>>
>> The BSF can be set the same way a decoder can in target_dec_fuzzer. The
>> codec_id will be randomly chosen from the supported list, if any.
>>
>> tools/Makefile | 3 +
>> tools/target_bsf_fuzzer.c | 166 ++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 169 insertions(+)
>> create mode 100644 tools/target_bsf_fuzzer.c
>>
>> diff --git a/tools/Makefile b/tools/Makefile
>> index 370ee35416..001093105b 100644
>> --- a/tools/Makefile
>> +++ b/tools/Makefile
>> @@ -5,6 +5,9 @@ TOOLS-$(CONFIG_ZLIB) += cws2fws
>> tools/target_dec_%_fuzzer.o: tools/target_dec_fuzzer.c
>> $(COMPILE_C) -DFFMPEG_DECODER=$*
>>
>> +tools/target_bsf_%_fuzzer.o: tools/target_bsf_fuzzer.c
>> + $(COMPILE_C) -DFFMPEG_BSF=$*
>> +
>> tools/target_dem_fuzzer.o: tools/target_dem_fuzzer.c
>> $(COMPILE_C)
>>
>> diff --git a/tools/target_bsf_fuzzer.c b/tools/target_bsf_fuzzer.c
>> new file mode 100644
>> index 0000000000..6849aaed0d
>> --- /dev/null
>> +++ b/tools/target_bsf_fuzzer.c
>> @@ -0,0 +1,166 @@
>> +/*
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.1 of the License, or (at your option) any later version.
>> + *
>> + * FFmpeg is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with FFmpeg; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>> + */
>> +
>> +#include "config.h"
>> +#include "libavutil/imgutils.h"
>> +
>> +#include "libavcodec/avcodec.h"
>> +#include "libavcodec/bsf.h"
>> +#include "libavcodec/bytestream.h"
>> +#include "libavcodec/internal.h"
>> +
>> +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
>> +
>> +static void error(const char *err)
>> +{
>> + fprintf(stderr, "%s", err);
>> + exit(1);
>> +}
>> +
>> +static AVBitStreamFilter *f = NULL;
>> +
>> +static const uint64_t FUZZ_TAG = 0x4741542D5A5A5546ULL;
>> +
>> +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
>> + const uint64_t fuzz_tag = FUZZ_TAG;
>> + const uint8_t *last = data;
>> + const uint8_t *end = data + size;
>> + AVBSFContext *bsf = NULL;
>> + AVPacket in, out;
>> + uint64_t keyframes = 0;
>> + int res;
>> +
>> + if (!f) {
>> +#ifdef FFMPEG_BSF
>> +#define BSF_SYMBOL0(BSF) ff_##BSF##_bsf
>> +#define BSF_SYMBOL(BSF) BSF_SYMBOL0(BSF)
>> + extern AVBitStreamFilter BSF_SYMBOL(FFMPEG_BSF);
>> + f = &BSF_SYMBOL(FFMPEG_BSF);
>> +#else
>> + extern AVBitStreamFilter ff_null_bsf;
>> + f = &ff_null_bsf;
>> +#endif
>> + av_log_set_level(AV_LOG_PANIC);
>> + }
>> +
>> + res = av_bsf_alloc(f, &bsf);
>> + if (res < 0)
>> + error("Failed memory allocation");
>> +
>> + if (size > 1024) {
>> + GetByteContext gbc;
>> + int extradata_size;
>> + size -= 1024;
>> + bytestream2_init(&gbc, data + size, 1024);
>> + bsf->par_in->width = bytestream2_get_le32(&gbc);
>> + bsf->par_in->height = bytestream2_get_le32(&gbc);
>> + bsf->par_in->bit_rate = bytestream2_get_le64(&gbc);
>> + bsf->par_in->bits_per_coded_sample = bytestream2_get_le32(&gbc);
>> +
>
>> + if (f->codec_ids) {
>
>> + int i, j, idx = bytestream2_get_byte(&gbc);
>
> Can you just read a bigger number instead of checking ++j == 8 below?
> Maybe bytestream2_get_be24()?
The idea was read bytes until one bit was a 0 to signal to stop the
loop. It would in theory work even with a bsf that supports INT_MAX
amount of codecs, and eventually stop anyway since the GetByteContext
would run out of data and start returning zeroes.
>
>> + int id = AV_CODEC_ID_NONE;
>> + for (i = 0, j = 0; f->codec_ids[i] != AV_CODEC_ID_NONE; i++) {
>> + // Iterate through all supported codec ids and get a random one
>> + if (idx & (1 << j)) {
>> + // There's at least one bsf that reports supporting more than eight codecs
>
>> + if (++j == 8) {
>> + idx = bytestream2_get_byte(&gbc);
>> + j = 0;
>> + }
>> + continue;
>> + }
>> + id = f->codec_ids[i];
>> + break;
>> + }
>
> The selection of the codecs doesn't seem uniform.
> The probability of each codec is more like (1/2)^n, where n is codec index. I'm
> not sure if the fuzzer will eventually learn this.
>
> It may be better to use: id = idx % num_supported_codecs. But of course
> num_supported_codecs would have to be evaluated first.
Sure. With this even more reasons to just read one byte of random data.
>
>> + // Force using a codec if all were skipped
>> + if (id == AV_CODEC_ID_NONE)
>> + id = f->codec_ids[0];
>> + bsf->par_in->codec_id = id;
>> + bsf->par_in->codec_tag = bytestream2_get_le32(&gbc);
>> + }
>> +
>> + extradata_size = bytestream2_get_le32(&gbc);
>> +
>> + bsf->par_in->sample_rate = bytestream2_get_le32(&gbc);
>> + bsf->par_in->channels = (unsigned)bytestream2_get_le32(&gbc) % FF_SANE_NB_CHANNELS;
>> + bsf->par_in->block_align = bytestream2_get_le32(&gbc);
>> + keyframes = bytestream2_get_le64(&gbc);
>> +
>> + if (extradata_size < size) {
>> + bsf->par_in->extradata = av_mallocz(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE);
>> + if (bsf->par_in->extradata) {
>> + bsf->par_in->extradata_size = extradata_size;
>> + size -= bsf->par_in->extradata_size;
>> + memcpy(bsf->par_in->extradata, data + size, bsf->par_in->extradata_size);
>> + }
>> + }
>> + if (av_image_check_size(bsf->par_in->width, bsf->par_in->height, 0, bsf))
>> + bsf->par_in->width = bsf->par_in->height = 0;
>> + }
>> +
>> + res = av_bsf_init(bsf);
>> + if (res < 0) {
>> + av_bsf_free(&bsf);
>> + return 0; // Failure of av_bsf_init() does not imply that a issue was found
>> + }
>> +
>> + av_init_packet(&in);
>> + av_init_packet(&out);
>
> I think you also need to add:
>
> out.data = NULL;
> out.size = 0;
>
> Otherwise a random packet size is used in av_bsf_receive_packet().
Ah, for the data >= end case. Sure, will add an av_packet_unref() call
before the flush code below for good measure instead.
>
>
>> + while (data < end) {
>
>> + // Search for the TAG
>> + while (data + sizeof(fuzz_tag) < end) {
>> + if (data[0] == (fuzz_tag & 0xFF) && AV_RN64(data) == fuzz_tag)
>> + break;
>> + data++;
>> + }
>
> Is the idea here to add "FUZZ_TAG" via the -dict option when running the
> fuzzer?
I don't know, but Michael might.
>
>> + if (data + sizeof(fuzz_tag) > end)
>> + data = end;
>> +
>> + res = av_new_packet(&in, data - last);
>> + if (res < 0)
>> + error("Failed memory allocation");
>> + memcpy(in.data, last, data - last);
>> + in.flags = (keyframes & 1) * AV_PKT_FLAG_DISCARD + (!!(keyframes & 2)) * AV_PKT_FLAG_KEY;
>> + keyframes = (keyframes >> 2) + (keyframes<<62);
>> + data += sizeof(fuzz_tag);
>> + last = data;
>> +
>> + while (in.size) {
>> + res = av_bsf_send_packet(bsf, &in);
>> + if (res < 0 && res != AVERROR(EAGAIN))
>> + break;
>> + res = av_bsf_receive_packet(bsf, &out);
>> + if (res < 0)
>> + break;
>> + av_packet_unref(&out);
>> + }
>> + av_packet_unref(&in);
>> + }
>> +
>> + res = av_bsf_send_packet(bsf, NULL);
>> + while (!res) {
>> + res = av_bsf_receive_packet(bsf, &out);
>> + if (res < 0)
>> + break;
>> + av_packet_unref(&out);
>> + }
>> +
>> + av_bsf_free(&bsf);
>> + return 0;
>> +}
>
More information about the ffmpeg-devel
mailing list