PR #24798 opened by Forgejo_Fairy
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24798
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24798.patch

A reference-counted video frame with an unset or out-of-range pixel format is 
currently accepted by buffersrc. In a buffer → scale → buffersink graph, scale 
copies that format into its input link during dynamic reconfiguration and 
dereferences a NULL pixel-format descriptor.

Validate the video format in av_buffersrc_add_frame_flags() before updating 
source timing or transferring frame ownership. Return AVERROR(EINVAL) and log 
the invalid value. Apply this validation even with 
AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT, which skips format-change checks; valid 
dynamic format changes remain accepted. This addresses malformed frames 
submitted through buffersrc, rather than corruption arising later inside a 
graph.

Addresses #23991. The cause of the invalid metadata in the reported 
KPipeWire/KRDP application remains undetermined.

Validation on AArch64:

- Reproduced the original crash on master 09f879b07428 and release/8.1 
29e619e767cd; also reproduced it on release/4.4 241fe6494061.
- Added fate-api-buffersrc, covering three invalid format values and all 
combinations of NO_CHECK_FORMAT, KEEP_REF, and PUSH. It prints rejection 
results and retained frame state for comparison with 
tests/ref/fate/api-buffersrc.
- The test also processes valid frames before and after rejection, including a 
YUV420P-to-YUV444P input change.
- The FATE test segfaults with the buffersrc fix removed and passes with it 
applied.

Minimal test configuration: `./configure --disable-everything 
--disable-autodetect --disable-programs --disable-doc --enable-avfilter 
--enable-swscale --enable-filter=scale`, then `make -j8 fate-api-buffersrc`.


From 41d1f7a7ed9a9274d37707a000b2695e90777fcb Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <[email protected]>
Date: Tue, 29 Sep 2026 20:55:51 +0000
Subject: [PATCH] avfilter/buffersrc: reject invalid video pixel formats

Reject frames with no pixel format descriptor before taking ownership or 
updating source properties. Otherwise a reference-counted frame with an unset 
or out-of-range format can reach scale and crash during dynamic reconfiguration.

Check validity even with AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT, which skips 
format-change checks but should not permit invalid formats.

Add a FATE API test covering invalid formats, all combinations of the buffer 
source flags, input ownership on rejection, and subsequent valid frames 
including a dynamic format change.

Addresses #23991.

Assisted-by: Fairy
---
 libavfilter/buffersrc.c        |   6 ++
 tests/api/Makefile             |   1 +
 tests/api/api-buffersrc-test.c | 127 +++++++++++++++++++++++++++++++++
 tests/fate/api.mak             |   4 ++
 tests/ref/fate/api-buffersrc   |  27 +++++++
 5 files changed, 165 insertions(+)
 create mode 100644 tests/api/api-buffersrc-test.c
 create mode 100644 tests/ref/fate/api-buffersrc

diff --git a/libavfilter/buffersrc.c b/libavfilter/buffersrc.c
index 16efa6e6c8..ad02080e31 100644
--- a/libavfilter/buffersrc.c
+++ b/libavfilter/buffersrc.c
@@ -220,6 +220,12 @@ int attribute_align_arg 
av_buffersrc_add_frame_flags(AVFilterContext *ctx, AVFra
     if (s->eof)
         return AVERROR_EOF;
 
+    if (ctx->outputs[0]->type == AVMEDIA_TYPE_VIDEO &&
+        !av_pix_fmt_desc_get(frame->format)) {
+        av_log(ctx, AV_LOG_ERROR, "Invalid video pixel format: %d\n", 
frame->format);
+        return AVERROR(EINVAL);
+    }
+
     s->last_pts = frame->pts + frame->duration;
 
     refcounted = !!frame->buf[0];
diff --git a/tests/api/Makefile b/tests/api/Makefile
index f2738d4cba..fbd2b38958 100644
--- a/tests/api/Makefile
+++ b/tests/api/Makefile
@@ -1,3 +1,4 @@
+APITESTPROGS-$(call ALLYES, AVFILTER SCALE_FILTER) += api-buffersrc
 APITESTPROGS-$(call ALLYES, LIBASTCENC_ENCODER LIBASTCENC_DECODER) += 
api-astc-profile
 APITESTPROGS-$(call ENCDEC, FLAC, FLAC) += api-flac
 APITESTPROGS-$(call DEMDEC, H264, H264) += api-h264
diff --git a/tests/api/api-buffersrc-test.c b/tests/api/api-buffersrc-test.c
new file mode 100644
index 0000000000..699ff5ee88
--- /dev/null
+++ b/tests/api/api-buffersrc-test.c
@@ -0,0 +1,127 @@
+/*
+ * Buffer source pixel format validation test
+ *
+ * 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 <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "libavfilter/avfilter.h"
+#include "libavfilter/buffersink.h"
+#include "libavfilter/buffersrc.h"
+#include "libavutil/error.h"
+#include "libavutil/frame.h"
+#include "libavutil/log.h"
+#include "libavutil/pixdesc.h"
+
+static int send_valid(AVFilterContext *src, AVFilterContext *sink,
+                      AVFrame *frame, AVFrame *out, enum AVPixelFormat format,
+                      int64_t pts)
+{
+    int ret;
+
+    frame->format = format;
+    frame->width = frame->height = 32;
+    frame->sample_aspect_ratio = (AVRational){ 1, 1 };
+    frame->pts = pts;
+    ret = av_frame_get_buffer(frame, 32);
+    if (ret < 0)
+        return ret;
+    for (int p = 0; p < 3; p++)
+        memset(frame->data[p], 0, frame->linesize[p] *
+               (p && format == AV_PIX_FMT_YUV420P ? 16 : 32));
+    ret = av_buffersrc_add_frame(src, frame);
+    if (ret < 0)
+        return ret;
+    ret = av_buffersink_get_frame(sink, out);
+    if (ret < 0)
+        return ret;
+    printf("valid %s: %dx%d %s pts=%" PRId64 "\n",
+           av_get_pix_fmt_name(format), out->width, out->height,
+           av_get_pix_fmt_name(out->format), out->pts);
+    av_frame_unref(out);
+    return 0;
+}
+
+int main(void)
+{
+    static const int formats[] = { AV_PIX_FMT_NONE, -2, INT32_MAX };
+    AVFilterGraph *graph = avfilter_graph_alloc();
+    AVFilterContext *src, *scale, *sink;
+    AVFrame *frame = av_frame_alloc(), *out = av_frame_alloc();
+    int ret = AVERROR(ENOMEM);
+
+    av_log_set_level(AV_LOG_QUIET);
+    if (!graph || !frame || !out)
+        goto end;
+    graph->nb_threads = 1;
+    ret = avfilter_graph_create_filter(&src, avfilter_get_by_name("buffer"),
+                                      "src", 
"video_size=32x32:pix_fmt=yuv420p:"
+                                      "time_base=1/25:pixel_aspect=1/1", NULL, 
graph);
+    if (ret < 0)
+        goto end;
+    ret = avfilter_graph_create_filter(&scale, avfilter_get_by_name("scale"),
+                                      "scale", "16:16", NULL, graph);
+    if (ret < 0)
+        goto end;
+    ret = avfilter_graph_create_filter(&sink, 
avfilter_get_by_name("buffersink"),
+                                      "sink", NULL, NULL, graph);
+    if (ret < 0)
+        goto end;
+    if ((ret = avfilter_link(src, 0, scale, 0)) < 0 ||
+        (ret = avfilter_link(scale, 0, sink, 0)) < 0 ||
+        (ret = avfilter_graph_config(graph, NULL)) < 0)
+        goto end;
+    ret = send_valid(src, sink, frame, out, AV_PIX_FMT_YUV420P, 0);
+    if (ret < 0)
+        goto end;
+
+    for (int f = 0; f < FF_ARRAY_ELEMS(formats); f++) {
+        for (int mode = 0; mode < 8; mode++) {
+            int flags = (mode & 1 ? AV_BUFFERSRC_FLAG_NO_CHECK_FORMAT : 0) |
+                        (mode & 2 ? AV_BUFFERSRC_FLAG_KEEP_REF : 0) |
+                        (mode & 4 ? AV_BUFFERSRC_FLAG_PUSH : 0);
+
+            frame->format = AV_PIX_FMT_YUV420P;
+            frame->width = frame->height = 32;
+            frame->pts = 1;
+            ret = av_frame_get_buffer(frame, 32);
+            if (ret < 0)
+                goto end;
+            frame->format = formats[f];
+            ret = av_buffersrc_add_frame_flags(src, frame, flags);
+            printf("format=%d mode=%d: %s, retained=%d format=%d pts=%" PRId64 
"\n",
+                   formats[f], mode, ret < 0 ? av_err2str(ret) : "accepted",
+                   !!frame->buf[0], frame->format, frame->pts);
+            av_frame_unref(frame);
+        }
+    }
+    ret = send_valid(src, sink, frame, out, AV_PIX_FMT_YUV420P, 1);
+    if (ret < 0)
+        goto end;
+    ret = send_valid(src, sink, frame, out, AV_PIX_FMT_YUV444P, 2);
+end:
+    if (ret < 0)
+        fprintf(stderr, "%s\n", av_err2str(ret));
+    av_frame_free(&frame);
+    av_frame_free(&out);
+    avfilter_graph_free(&graph);
+    return ret < 0;
+}
diff --git a/tests/fate/api.mak b/tests/fate/api.mak
index a10edfe4da..63c1490696 100644
--- a/tests/fate/api.mak
+++ b/tests/fate/api.mak
@@ -1,3 +1,7 @@
+FATE_API-$(call ALLYES, AVFILTER SCALE_FILTER) += fate-api-buffersrc
+fate-api-buffersrc: $(APITESTSDIR)/api-buffersrc-test$(EXESUF)
+fate-api-buffersrc: CMD = run $(APITESTSDIR)/api-buffersrc-test$(EXESUF)
+
 FATE_API_LIBAVCODEC-$(call ALLYES, LIBASTCENC_ENCODER LIBASTCENC_DECODER) += 
fate-api-astc-profile
 fate-api-astc-profile: $(APITESTSDIR)/api-astc-profile-test$(EXESUF)
 fate-api-astc-profile: CMD = run $(APITESTSDIR)/api-astc-profile-test$(EXESUF)
diff --git a/tests/ref/fate/api-buffersrc b/tests/ref/fate/api-buffersrc
new file mode 100644
index 0000000000..7df69ec760
--- /dev/null
+++ b/tests/ref/fate/api-buffersrc
@@ -0,0 +1,27 @@
+valid yuv420p: 16x16 yuv420p pts=0
+format=-1 mode=0: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=1: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=2: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=3: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=4: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=5: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=6: Invalid argument, retained=1 format=-1 pts=1
+format=-1 mode=7: Invalid argument, retained=1 format=-1 pts=1
+format=-2 mode=0: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=1: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=2: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=3: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=4: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=5: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=6: Invalid argument, retained=1 format=-2 pts=1
+format=-2 mode=7: Invalid argument, retained=1 format=-2 pts=1
+format=2147483647 mode=0: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=1: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=2: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=3: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=4: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=5: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=6: Invalid argument, retained=1 format=2147483647 pts=1
+format=2147483647 mode=7: Invalid argument, retained=1 format=2147483647 pts=1
+valid yuv420p: 16x16 yuv420p pts=1
+valid yuv444p: 16x16 yuv420p pts=2
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to