PR #24048 opened by Jake (jakefineman)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24048
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24048.patch
The `AV_PIX_FMT_RGB24`/`AV_PIX_FMT_BGR24` output case in
`ff_proc_from_dnn_to_frame()` (`libavfilter/dnn/dnn_io_proc.c`) hardcodes the
`sws_getContext`/`sws_scale` width arguments and strides to `frame->width * 3`,
unconditional on the model's real output channel count. For any model whose
output has fewer than 3 channels — e.g. a single-channel alpha-matte output
from an RGB-in background-removal model such as MODNet — that mismatch is
memory-unsafe on both layouts:
- The first `sws_scale` runs for **both** `DL_NCHW` and `DL_NHWC` output,
before the planar-to-packed branch, and reads `frame->width * 3 *
src_datatype_size * frame->height` bytes from `output->data`; a 1-channel
tensor holds a third of that: a ~3x source-side over-read.
- On `DL_NCHW`, `middle_data` is correctly sized for the real channel count
(`plane_size * output->dims[1]`), so the same hardcoded width also makes
`sws_scale` write `frame->width * 3 * frame->height` bytes into a buffer sized
for only `frame->width * frame->height * output->dims[1]` bytes: a heap write
3x past the allocation for `dims[1] == 1`. glibc's allocator catches this on a
subsequent `free()`/malloc consistency check, producing `SIGABRT`, not a clean
FFmpeg error return.
## Fix
Reject channel-mismatched model output at the top of the RGB24/BGR24 case,
layout-agnostically: read the channel dim via
`dnn_get_channel_idx_by_layout(output->layout)` (the same helper this file
already uses on the input side, `dnn_io_proc.c:370`), and if
`output->dims[channel_idx] != 3`, log a clear error and return
`AVERROR(ENOSYS)` before any of the mismatched-stride arithmetic runs.
One check covers all three hazards: the source-side over-read (both layouts),
the `DL_NCHW` destination over-write, and the `DL_NCHW` planar-to-packed GBRP
repack a few lines further down, which likewise unconditionally assumes 3
planes and is never reached once channel count is checked.
## Repro
Build: FFmpeg `n9.0` (pinned commit `d32b387`), debian trixie, ONNX Runtime
1.28.0, `--enable-libonnxruntime`, native linux/arm64. Model:
`onnx-community/modnet-webnn` `onnx/model.onnx`, sha256
`07c308cf0fc7e6e8b2065a12ed7fc07e1de8febb7dc7839d7b7f15dd66584df9` (output
`[1,1,H,W]`, i.e. `DL_NCHW` with `dims[1] == 1`).
**Before** — identical build with this patch omitted:
```
$ ffmpeg -f lavfi -i testsrc2=size=320x320:rate=1:duration=1 \
-vf
'format=rgb24,dnn_processing=dnn_backend=onnx:model=modnet.onnx:input=input:output=output'
\
-frames:v 1 -f null -
[dnn_base] Using CPU execution provider
free(): invalid pointer
$ echo $?
134
```
**After** — same command, same model, patched build:
```
[dnn_base] dnn_processing to a rgb24 frame requires a 3-channel model output,
got 1 channels;
channel-reducing/expanding models (e.g. single-channel matte output) are not
supported by this filter
$ echo $?
0
```
Scope note on the receipt: the runtime repro above exercises the `DL_NCHW`
destination over-write, which is the path that aborts. The `DL_NHWC`
source-side over-read is identified from source rather than separately
reproduced — I did not have a channel-reducing NHWC model to hand — but it runs
through the same hardcoded-stride `sws_scale` call above the layout branch,
which is why the guard is keyed on the layout-resolved channel index rather
than on `dims[1]`.
## Deliberate scope
This patch rejects channel-reducing model output cleanly; it does **not** add
support for actually rendering a 1-channel (or 2-channel) model output through
this filter. Real support for that would need a new pixel-format/plane-count
target on the output side (e.g. gray/ya8 for a matte, or a caller-supplied
channel mapping) — a bigger design decision than a minimal heap-safety fix
should make unilaterally, and out of scope here.
I'm sending the minimal safety fix first because the crash is the more urgent
problem: a SIGABRT driven by model-controlled output shape is a hardening bug
independent of whether anyone wants channel-reducing rendering. Happy to follow
up with a rendering-support patch, or take direction on it, if a maintainer
prefers that be done in the same series instead of split.
One further caveat, so the receipt does not overclaim: `vf_dnn_processing.c`'s
own caller does not hard-abort the filter pipeline on this specific
`AVERROR(ENOSYS)` return (pre-existing behaviour of that file, untouched here)
— for a real (non-`-f null`) pipeline the practical effect is an
unfiltered/passthrough frame rather than a full pipeline failure.
## Checks
Applies clean with `git am` on both `n9.0` and current `master` (`master`'s
`dnn_io_proc.c` still hardcodes `frame->width * 3` unconditionally as of this
writing — not yet fixed upstream). Builds with `--enable-libonnxruntime`.
`tools/patcheck` reports only the expected "missing changelog entry" advisory
for a one-function bugfix.
No existing trac ticket or patchwork series found for this bug in a bounded
search (`dnn_processing sws_scale`, `dnn_io_proc heap`, `background removal
matte crash dnn`).
From b3f3be7585bd7ac299c044316843acfd047c8626 Mon Sep 17 00:00:00 2001
From: Jake Fineman <[email protected]>
Date: Fri, 7 Aug 2026 17:57:03 -0400
Subject: [PATCH] avfilter/dnn: reject channel-mismatched model output in
dnn_processing
ff_proc_from_dnn_to_frame()'s AV_PIX_FMT_RGB24/BGR24 case assumes the
model's output has exactly 3 channels, but never checks it.
The first sws_scale() in that case reads from output->data with a
hardcoded source stride of frame->width * 3 * src_datatype_size over
frame->height rows, i.e. frame->width * 3 * frame->height *
src_datatype_size bytes. For a model whose output has fewer channels
than that -- e.g. the single-channel alpha matte an RGB-in
background-removal model produces -- the real tensor only holds
frame->width * frame->height * channels * src_datatype_size bytes, so
the call reads roughly 3x past the end of it. This affects both
DL_NCHW and DL_NHWC output, since it happens before the layout-specific
planar-to-packed branch.
For DL_NCHW there is a second, independent overflow on the write side:
middle_data is allocated plane_size * output->dims[1] bytes, but
linesize[0] is set to frame->width * 3 unconditionally, so the same
sws_scale() also writes past that allocation. This reproduces as a
SIGABRT (glibc "free(): invalid pointer", or a malloc.c assertion,
depending on allocator state) inside dnn_processing, after inference
has already completed successfully in the backend.
The input side is guarded -- check_modelinput_inlink() in
vf_dnn_processing.c -- but the output side is not. dnn_processing only
supports channel-preserving models for these pixel formats by design,
so reject a mismatched channel count with a clear error instead of
reading and writing out of bounds. The channel dimension is resolved
via dnn_get_channel_idx_by_layout() so the check covers both layouts.
This does not add support for rendering a 1- or 2-channel model output
through this filter; that would need a different pixel-format target
and is left as future work.
Signed-off-by: Jake Fineman <[email protected]>
---
libavfilter/dnn/dnn_io_proc.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/libavfilter/dnn/dnn_io_proc.c b/libavfilter/dnn/dnn_io_proc.c
index 826110dab0..55981c64da 100644
--- a/libavfilter/dnn/dnn_io_proc.c
+++ b/libavfilter/dnn/dnn_io_proc.c
@@ -50,6 +50,7 @@ int ff_proc_from_dnn_to_frame(AVFrame *frame, DNNData
*output, void *log_ctx)
int plane_size = frame->width * frame->height * sizeof(uint8_t);
enum AVPixelFormat src_fmt = AV_PIX_FMT_NONE;
int src_datatype_size = get_datatype_size(output->dt);
+ int channel_idx;
int bytewidth = av_image_get_linesize(frame->format, frame->width, 0);
if (bytewidth < 0) {
@@ -83,6 +84,17 @@ int ff_proc_from_dnn_to_frame(AVFrame *frame, DNNData
*output, void *log_ctx)
switch (frame->format) {
case AV_PIX_FMT_RGB24:
case AV_PIX_FMT_BGR24:
+ channel_idx = dnn_get_channel_idx_by_layout(output->layout);
+ if (output->dims[channel_idx] != 3) {
+ av_log(log_ctx, AV_LOG_ERROR,
+ "dnn_processing to a %s frame requires a 3-channel model "
+ "output, got %d channels; channel-reducing/expanding "
+ "models (e.g. single-channel matte output) are not "
+ "supported by this filter\n",
+ av_get_pix_fmt_name(frame->format),
output->dims[channel_idx]);
+ ret = AVERROR(ENOSYS);
+ goto err;
+ }
sws_ctx = sws_getContext(frame->width * 3,
frame->height,
src_fmt,
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]