[FFmpeg-devel] tools: add a fuzzer tool for bitstream filters

Submitted by James Almer on Dec. 2, 2019, 4:15 p.m.

Details

Message ID 20191202161513.1693-1-jamrial@gmail.com
State New
Headers show

Commit Message

James Almer Dec. 2, 2019, 4:15 p.m.
Signed-off-by: James Almer <jamrial@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

Comments

Andriy Gelman Dec. 3, 2019, 7:31 p.m.
On Mon, 02. Dec 13:15, James Almer wrote:
> Signed-off-by: James Almer <jamrial@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()? 

> +            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.

> +            // 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().


> +    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? 

> +        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;
> +}
James Almer Dec. 3, 2019, 7:59 p.m.
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@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;
>> +}
>
Michael Niedermayer Dec. 3, 2019, 9:51 p.m.
On Tue, Dec 03, 2019 at 04:59:26PM -0300, James Almer wrote:
> 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@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
[...]
> > 
> > 
> >> +    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.

I dont know how or if FUZZ_TAG "connects" back into the fuzzer.

But the same issue also exist for every chunk, slice, packet header
the fuzzer has to generate valid data 

thx

[...]

Patch hide | download patch | download mbox

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);
+            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;
+            }
+            // 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);
+    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++;
+        }
+        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;
+}