[PR] avfilter/af_pan: check named input channel ids before use (PR #24004)

iSoldLeo via ffmpeg-devel <[email protected]> Tue, 04 Aug 2026 12:18:02 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178584588339.59.13335218697373102776@29965ddac10e>
PR #24004 opened by iSoldLeo
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24004
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24004.patch

Named input channels are parsed to sparse AVChannel values and then used
directly as indices into 64-element arrays. Values such as UNSD, UNK and
AMBI can therefore cause out-of-bounds accesses.

Reject unsupported ids before the first array access. Keep the check in the
input path because high channel ids on the output side are valid after
conversion to layout indices.

Add a FATE test that checks for the specific diagnostic, so a crash cannot
be mistaken for the expected rejection.

Fixes #22963.

Tested on an Apple M4 Mac:
- native and sanitizer builds
- all fate-filter-pan-* tests
- valid and invalid named and numbered channel cases


>From 9cb1c0fe734ec3386c2a3f24f50465adca6f3e44 Mon Sep 17 00:00:00 2001
From: iSold Leo <[email protected]>
Date: Tue, 4 Aug 2026 19:50:11 +0800
Subject: [PATCH 1/2] avfilter/af_pan: check the id of named input channels
 before use

parse_channel_name() only rejects negative values on the named channel
path, while the numbered "c%d" path also checks against MAX_CHANNELS.
av_channel_from_string() accepts UNSD (512), UNK (768) and AMBI (1024),
and the id was then used directly to index used_in_ch[MAX_CHANNELS] on
the stack and pan->gain[out_ch_id][in_ch_id].

Before this change:

  ffmpeg -f lavfi -i "anullsrc=cl=stereo" -af "pan=stereo|FL=AMBI" -f null -
  af_pan.c:214:17: runtime error: index 1024 out of bounds for type 'int[64]'

The first out of bounds access is the read at the "reference twice"
check, so the symptom depends on what happens to be on the stack:
FL=UNK is silently accepted and produces silence, FL=UNSD reports
"Can not reference in channel 512 twice", and FL=AMBI aborts.

The check has to be at the caller and not in parse_channel_name(), which
is shared with the out channel path: there the named id is converted by
av_channel_layout_index_from_channel() into a layout index, so high ids
are legitimately supported and "pan=AMBI|AMBI=FL" works.

AVERROR_PATCHWELCOME is used because these are valid AVChannel values
that af_pan cannot represent: for named input channels the gain matrix is
indexed by the raw id, and the renumbering loop in config_props() is
bounded by MAX_CHANNELS as well, so such a mapping never worked.

The in_ch_id < 0 half is currently unreachable and only kept for symmetry
with the out channel check above.

Regression since 1f96db959c1235bb7079d354e09914a0a2608f62, which dropped
the "channel_id >= MAX_CHANNELS" part of the check when converting to the
new channel layout API.

Fixes: #22963
---
 libavfilter/af_pan.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/libavfilter/af_pan.c b/libavfilter/af_pan.c
index 32bd28fe0a..abb0e5804d 100644
--- a/libavfilter/af_pan.c
+++ b/libavfilter/af_pan.c
@@ -211,6 +211,12 @@ static av_cold int init(AVFilterContext *ctx)
                 ret = AVERROR(EINVAL);
                 goto fail;
             }
+            if (in_ch_id < 0 || in_ch_id >= MAX_CHANNELS) {
+                av_log(ctx, AV_LOG_ERROR,
+                       "Input channel id %d is not supported\n", in_ch_id);
+                ret = AVERROR_PATCHWELCOME;
+                goto fail;
+            }
             if (used_in_ch[in_ch_id]) {
                 av_log(ctx, AV_LOG_ERROR,
                        "Can not reference in channel %d twice\n", in_ch_id);
-- 
2.52.0


>From 00b019289c53f939b28a8e466a77066f9c24c69d Mon Sep 17 00:00:00 2001
From: iSold Leo <[email protected]>
Date: Tue, 4 Aug 2026 19:50:11 +0800
Subject: [PATCH 2/2] fate/filter-audio: add test for pan named input channel
 id rejection

"pan=stereo|FL=UNK" resolves to AVChannel id 768, which was used as an
index into a 64 element array before the previous commit.

UNK is used because it was the only one of the three reachable high ids
that exited with 0 before the fix; AMBI (1024) aborted and UNSD (512)
failed with an unrelated message, so a test that only checked for a
non-zero exit status would have passed before the fix as well.

For the same reason the test greps the error message on stderr instead of
only looking at the exit status: a crash also exits non-zero.
---
 tests/fate/filter-audio.mak | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak
index 7817548037..6593750805 100644
--- a/tests/fate/filter-audio.mak
+++ b/tests/fate/filter-audio.mak
@@ -191,6 +191,11 @@ fate-filter-pan-downmix2: tests/data/asynth-44100-11.wav
 fate-filter-pan-downmix2: SRC = $(TARGET_PATH)/tests/data/asynth-44100-11.wav
 fate-filter-pan-downmix2: CMD = framecrc -ss 3.14 -i $(SRC) -frames:a 20 -filter:a "pan=5C|c0=0.7*c0+0.7*c10|c1=c9|c2=c8|c3=c7|c4=c6"
 
+FATE_AFILTER-$(call ALLYES, LAVFI_INDEV ANULLSRC_FILTER PAN_FILTER PCM_S16LE_ENCODER NULL_MUXER) += fate-filter-pan-channel-id-limit
+fate-filter-pan-channel-id-limit: CMD = run $(FFMPEG) -nostdin -hide_banner -f lavfi -i "anullsrc=cl=stereo:r=44100:d=0.1" -af "pan=stereo|FL=UNK" -f null - ; true
+fate-filter-pan-channel-id-limit: CMP = grep
+fate-filter-pan-channel-id-limit: REF = Input channel id 768
+
 FATE_AFILTER-$(call ALLYES, LAVFI_INDEV AEVALSRC_FILTER SILENCEREMOVE_FILTER ARESAMPLE_FILTER) += fate-filter-silenceremove
 fate-filter-silenceremove: CMD = framecrc -auto_conversion_filters -f lavfi -i "aevalsrc=between(t\,1\,2)+between(t\,4\,5)+between(t\,7\,9):d=10:n=8192,silenceremove=start_periods=0:start_duration=0:start_threshold=0:stop_periods=-1:stop_duration=0:stop_threshold=-90dB:window=0:detection=avg"
 
-- 
2.52.0

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