diff mbox series

[FFmpeg-devel,1/3] tools: Add target_sws_fuzzer.c

Message ID 20240220024922.22779-1-michael@niedermayer.cc
State New
Headers show
Series [FFmpeg-devel,1/3] tools: Add target_sws_fuzzer.c | expand

Checks

Context Check Description
yinshiyou/make_loongarch64 success Make finished
yinshiyou/make_fate_loongarch64 success Make fate finished
andriy/make_x86 success Make finished
andriy/make_fate_x86 success Make fate finished

Commit Message

Michael Niedermayer Feb. 20, 2024, 2:49 a.m. UTC
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
---
 Makefile                  |   3 +
 tools/Makefile            |   3 +
 tools/target_sws_fuzzer.c | 193 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 199 insertions(+)
 create mode 100644 tools/target_sws_fuzzer.c

Comments

James Almer Feb. 20, 2024, 3:41 a.m. UTC | #1
On 2/19/2024 11:49 PM, Michael Niedermayer wrote:
> +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
> +    int srcW= 48, srcH = 48;
> +    int dstW= 48, dstH = 48;
> +    int srcHShift, srcVShift;
> +    int dstHShift, dstVShift;
> +    unsigned flags = 1;
> +    int srcStride[AV_VIDEO_MAX_PLANES] = {0};
> +    int dstStride[AV_VIDEO_MAX_PLANES] = {0};
> +    int ret;
> +    const uint8_t *end = data + size;
> +    enum AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
> +    enum AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P;
> +    uint8_t *src[AV_VIDEO_MAX_PLANES] = { 0 };
> +    uint8_t *dst[AV_VIDEO_MAX_PLANES] = { 0 };
> +    struct SwsContext *sws = NULL;
> +    const AVPixFmtDescriptor *desc_src, *desc_dst;
> +
> +    if (size > 128) {
> +        GetByteContext gbc;
> +        int64_t flags64;
> +
> +        size -= 128;
> +        bytestream2_init(&gbc, data + size, 128);
> +        srcW = bytestream2_get_le32(&gbc) % 16384;
> +        srcH = bytestream2_get_le32(&gbc) % 16384;
> +        dstW = bytestream2_get_le32(&gbc) % 16384;
> +        dstH = bytestream2_get_le32(&gbc) % 16384;

Might as well use bytestream2_get_le16 to save bytes from the input buffer.

> +
> +        if (srcW * (uint64_t)srcH > 16384 || dstW * (uint64_t)dstH > 16384)
> +            return 0; // we avoid high res as its very slow

This will abort in a lot of cases. Would reading only xW then setting xH 
to 16384 / xW make sense? You can remove these checks if so.
Michael Niedermayer Feb. 21, 2024, 1:17 a.m. UTC | #2
On Tue, Feb 20, 2024 at 12:41:57AM -0300, James Almer wrote:
> On 2/19/2024 11:49 PM, Michael Niedermayer wrote:
> > +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
> > +    int srcW= 48, srcH = 48;
> > +    int dstW= 48, dstH = 48;
> > +    int srcHShift, srcVShift;
> > +    int dstHShift, dstVShift;
> > +    unsigned flags = 1;
> > +    int srcStride[AV_VIDEO_MAX_PLANES] = {0};
> > +    int dstStride[AV_VIDEO_MAX_PLANES] = {0};
> > +    int ret;
> > +    const uint8_t *end = data + size;
> > +    enum AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
> > +    enum AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P;
> > +    uint8_t *src[AV_VIDEO_MAX_PLANES] = { 0 };
> > +    uint8_t *dst[AV_VIDEO_MAX_PLANES] = { 0 };
> > +    struct SwsContext *sws = NULL;
> > +    const AVPixFmtDescriptor *desc_src, *desc_dst;
> > +
> > +    if (size > 128) {
> > +        GetByteContext gbc;
> > +        int64_t flags64;
> > +
> > +        size -= 128;
> > +        bytestream2_init(&gbc, data + size, 128);
> > +        srcW = bytestream2_get_le32(&gbc) % 16384;
> > +        srcH = bytestream2_get_le32(&gbc) % 16384;
> > +        dstW = bytestream2_get_le32(&gbc) % 16384;
> > +        dstH = bytestream2_get_le32(&gbc) % 16384;
> 
> Might as well use bytestream2_get_le16 to save bytes from the input buffer.
> 
> > +
> > +        if (srcW * (uint64_t)srcH > 16384 || dstW * (uint64_t)dstH > 16384)
> > +            return 0; // we avoid high res as its very slow
> 
> This will abort in a lot of cases. Would reading only xW then setting xH to
> 16384 / xW make sense? You can remove these checks if so.

no that would reduce the number of tested resolutions to one for each width

What we are trying to do here, is to map some flat random data into
a resolution constraint to
X > 0
Y > 0
X*Y <= 16484
and at the same time have each pair occur approximately as frequent as any other

that can be achieved in a few ways, ill use the following

static void mapres(unsigned *r0, unsigned *r1) {
    double d = (double)(*r0*10ll - 9ll*UINT32_MAX) / UINT32_MAX;
    double a = exp(d) * 16384 / exp(1) ;
    int ai = (int)round(a);
    uint64_t maxb = 16384 / ai;
    *r0 = ai;
    *r1 = 1 + (*r1 * maxb) / UINT32_MAX;
}

this avoids all the aborts and is flat enough statically for this purpose

will apply with that, but if you prefer the previous simpler code, dont hesitate
to replace it. Iam not sure this helps the fuzzer at all

thx

[...]
James Almer Feb. 21, 2024, 2:33 a.m. UTC | #3
On 2/20/2024 10:17 PM, Michael Niedermayer wrote:
> On Tue, Feb 20, 2024 at 12:41:57AM -0300, James Almer wrote:
>> On 2/19/2024 11:49 PM, Michael Niedermayer wrote:
>>> +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
>>> +    int srcW= 48, srcH = 48;
>>> +    int dstW= 48, dstH = 48;
>>> +    int srcHShift, srcVShift;
>>> +    int dstHShift, dstVShift;
>>> +    unsigned flags = 1;
>>> +    int srcStride[AV_VIDEO_MAX_PLANES] = {0};
>>> +    int dstStride[AV_VIDEO_MAX_PLANES] = {0};
>>> +    int ret;
>>> +    const uint8_t *end = data + size;
>>> +    enum AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
>>> +    enum AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P;
>>> +    uint8_t *src[AV_VIDEO_MAX_PLANES] = { 0 };
>>> +    uint8_t *dst[AV_VIDEO_MAX_PLANES] = { 0 };
>>> +    struct SwsContext *sws = NULL;
>>> +    const AVPixFmtDescriptor *desc_src, *desc_dst;
>>> +
>>> +    if (size > 128) {
>>> +        GetByteContext gbc;
>>> +        int64_t flags64;
>>> +
>>> +        size -= 128;
>>> +        bytestream2_init(&gbc, data + size, 128);
>>> +        srcW = bytestream2_get_le32(&gbc) % 16384;
>>> +        srcH = bytestream2_get_le32(&gbc) % 16384;
>>> +        dstW = bytestream2_get_le32(&gbc) % 16384;
>>> +        dstH = bytestream2_get_le32(&gbc) % 16384;
>>
>> Might as well use bytestream2_get_le16 to save bytes from the input buffer.
>>
>>> +
>>> +        if (srcW * (uint64_t)srcH > 16384 || dstW * (uint64_t)dstH > 16384)
>>> +            return 0; // we avoid high res as its very slow
>>
>> This will abort in a lot of cases. Would reading only xW then setting xH to
>> 16384 / xW make sense? You can remove these checks if so.
> 
> no that would reduce the number of tested resolutions to one for each width
> 
> What we are trying to do here, is to map some flat random data into
> a resolution constraint to
> X > 0
> Y > 0
> X*Y <= 16484
> and at the same time have each pair occur approximately as frequent as any other
> 
> that can be achieved in a few ways, ill use the following
> 
> static void mapres(unsigned *r0, unsigned *r1) {
>      double d = (double)(*r0*10ll - 9ll*UINT32_MAX) / UINT32_MAX;
>      double a = exp(d) * 16384 / exp(1) ;
>      int ai = (int)round(a);
>      uint64_t maxb = 16384 / ai;
>      *r0 = ai;
>      *r1 = 1 + (*r1 * maxb) / UINT32_MAX;
> }
> 
> this avoids all the aborts and is flat enough statically for this purpose
> 
> will apply with that, but if you prefer the previous simpler code, dont hesitate
> to replace it. Iam not sure this helps the fuzzer at all

No, you know this better than i do.
diff mbox series

Patch

diff --git a/Makefile b/Makefile
index dbc930270b..b309dbc4db 100644
--- a/Makefile
+++ b/Makefile
@@ -64,6 +64,9 @@  tools/target_dem_fuzzer$(EXESUF): tools/target_dem_fuzzer.o $(FF_DEP_LIBS)
 tools/target_io_dem_fuzzer$(EXESUF): tools/target_io_dem_fuzzer.o $(FF_DEP_LIBS)
 	$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)
 
+tools/target_sws_fuzzer$(EXESUF): tools/target_sws_fuzzer.o $(FF_DEP_LIBS)
+	$(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)
+
 
 tools/enum_options$(EXESUF): ELIBS = $(FF_EXTRALIBS)
 tools/enum_options$(EXESUF): $(FF_DEP_LIBS)
diff --git a/tools/Makefile b/tools/Makefile
index dee6a41668..72e8e709a8 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -17,6 +17,9 @@  tools/target_dem_fuzzer.o: tools/target_dem_fuzzer.c
 tools/target_io_dem_fuzzer.o: tools/target_dem_fuzzer.c
 	$(COMPILE_C) -DIO_FLAT=0
 
+tools/target_sws_fuzzer.o: tools/target_sws_fuzzer.c
+	$(COMPILE_C)
+
 tools/enc_recon_frame_test$(EXESUF): tools/decode_simple.o
 tools/venc_data_dump$(EXESUF): tools/decode_simple.o
 tools/scale_slice_test$(EXESUF): tools/decode_simple.o
diff --git a/tools/target_sws_fuzzer.c b/tools/target_sws_fuzzer.c
new file mode 100644
index 0000000000..a118368ce0
--- /dev/null
+++ b/tools/target_sws_fuzzer.c
@@ -0,0 +1,193 @@ 
+/*
+ * Copyright (c) 2024 Michael Niedermayer <michael-ffmpeg@niedermayer.cc>
+ *
+ * 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/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/cpu.h"
+#include "libavutil/imgutils.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/opt.h"
+
+#include "libavcodec/bytestream.h"
+
+#include "libswscale/swscale.h"
+
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+static void error(const char *err)
+{
+    fprintf(stderr, "%s", err);
+    exit(1);
+}
+
+static int alloc_plane(uint8_t *data[AV_VIDEO_MAX_PLANES], int stride[AV_VIDEO_MAX_PLANES], int w, int h, int format, int *hshift, int *vshift)
+{
+    size_t size[AV_VIDEO_MAX_PLANES];
+    ptrdiff_t ptrdiff_stride[AV_VIDEO_MAX_PLANES];
+    int ret = av_image_fill_linesizes(stride, format, w);
+    if (ret < 0)
+        return -1;
+
+    av_assert0(AV_VIDEO_MAX_PLANES == 4); // Some of the libavutil API has 4 hardcoded so this has undefined behaviour if its not 4
+
+    av_pix_fmt_get_chroma_sub_sample(format, hshift, vshift);
+
+    for(int p=0; p<AV_VIDEO_MAX_PLANES; p++) {
+        stride[p] =
+        ptrdiff_stride[p] = FFALIGN(stride[p], 32);
+    }
+    ret = av_image_fill_plane_sizes(size, format, h, ptrdiff_stride);
+    if (ret < 0)
+        return ret;
+
+    for(int p=0; p<AV_VIDEO_MAX_PLANES; p++) {
+        if (size[p]) {
+            data[p] = av_mallocz(size[p] + 32);
+            if (!data[p])
+                return -1;
+        } else
+            data[p] = NULL;
+    }
+    return 0;
+}
+
+static void free_plane(uint8_t *data[AV_VIDEO_MAX_PLANES])
+{
+    for(int p=0; p<AV_VIDEO_MAX_PLANES; p++)
+        av_freep(&data[p]);
+}
+
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+    int srcW= 48, srcH = 48;
+    int dstW= 48, dstH = 48;
+    int srcHShift, srcVShift;
+    int dstHShift, dstVShift;
+    unsigned flags = 1;
+    int srcStride[AV_VIDEO_MAX_PLANES] = {0};
+    int dstStride[AV_VIDEO_MAX_PLANES] = {0};
+    int ret;
+    const uint8_t *end = data + size;
+    enum AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
+    enum AVPixelFormat dstFormat = AV_PIX_FMT_YUV420P;
+    uint8_t *src[AV_VIDEO_MAX_PLANES] = { 0 };
+    uint8_t *dst[AV_VIDEO_MAX_PLANES] = { 0 };
+    struct SwsContext *sws = NULL;
+    const AVPixFmtDescriptor *desc_src, *desc_dst;
+
+    if (size > 128) {
+        GetByteContext gbc;
+        int64_t flags64;
+
+        size -= 128;
+        bytestream2_init(&gbc, data + size, 128);
+        srcW = bytestream2_get_le32(&gbc) % 16384;
+        srcH = bytestream2_get_le32(&gbc) % 16384;
+        dstW = bytestream2_get_le32(&gbc) % 16384;
+        dstH = bytestream2_get_le32(&gbc) % 16384;
+
+        if (srcW * (uint64_t)srcH > 16384 || dstW * (uint64_t)dstH > 16384)
+            return 0; // we avoid high res as its very slow
+
+        flags = bytestream2_get_le32(&gbc);
+
+        unsigned mask = flags & (SWS_POINT         |
+                                SWS_AREA          |
+                                SWS_BILINEAR      |
+                                SWS_FAST_BILINEAR |
+                                SWS_BICUBIC       |
+                                SWS_X             |
+                                SWS_GAUSS         |
+                                SWS_LANCZOS       |
+                                SWS_SINC          |
+                                SWS_SPLINE        |
+                                SWS_BICUBLIN);
+        mask &= flags;
+        if (mask && (mask & (mask -1)))
+            return 0; // multiple scalers are set, not possible
+
+        srcFormat = bytestream2_get_le32(&gbc) % AV_PIX_FMT_NB;
+        dstFormat = bytestream2_get_le32(&gbc) % AV_PIX_FMT_NB;
+
+        flags64 = bytestream2_get_le64(&gbc);
+        if (flags64 & 0x10)
+            av_force_cpu_flags(0);
+
+        if (av_image_check_size(srcW, srcH, srcFormat, NULL) < 0)
+            srcW = srcH = 23;
+        if (av_image_check_size(dstW, dstH, dstFormat, NULL) < 0)
+            dstW = dstH = 23;
+        //TODO alphablend
+    }
+
+    desc_src = av_pix_fmt_desc_get(srcFormat);
+    desc_dst = av_pix_fmt_desc_get(dstFormat);
+
+    fprintf(stderr, "%d x %d %s -> %d x %d %s\n", srcW, srcH, desc_src->name, dstW, dstH, desc_dst->name);
+
+    ret = alloc_plane(src, srcStride, srcW, srcH, srcFormat, &srcHShift, &srcVShift);
+    if (ret < 0)
+        goto end;
+
+    ret = alloc_plane(dst, dstStride, dstW, dstH, dstFormat, &dstHShift, &dstVShift);
+    if (ret < 0)
+        goto end;
+
+
+    for(int p=0; p<AV_VIDEO_MAX_PLANES; p++) {
+        int psize = srcStride[p] * AV_CEIL_RSHIFT(srcH, (p == 1 || p == 2) ? srcVShift : 0);
+        if (psize > size)
+            psize = size;
+        if (psize) {
+            memcpy(src[p], data, psize);
+            data += psize;
+            size -= psize;
+        }
+    }
+
+    sws = sws_alloc_context();
+    if (!sws)
+        error("Failed sws allocation");
+
+    av_opt_set_int(sws, "sws_flags",  flags, 0);
+    av_opt_set_int(sws, "srcw",       srcW, 0);
+    av_opt_set_int(sws, "srch",       srcH, 0);
+    av_opt_set_int(sws, "dstw",       dstW, 0);
+    av_opt_set_int(sws, "dsth",       dstH, 0);
+    av_opt_set_int(sws, "src_format", srcFormat, 0);
+    av_opt_set_int(sws, "dst_format", dstFormat, 0);
+    av_opt_set(sws, "alphablend", "none", 0);
+
+    ret = sws_init_context(sws, NULL, NULL);
+    if (ret < 0)
+        goto end;
+
+    //TODO Slices
+    sws_scale(sws, (const uint8_t * const*)src, srcStride, 0, srcH, dst, dstStride);
+
+end:
+    sws_freeContext(sws);
+
+    free_plane(src);
+    free_plane(dst);
+
+    return 0;
+}