[PR] avfilter/avf_showspectrum: fix out-of-bounds read in separate mode (PR #23920)

akx via ffmpeg-devel <[email protected]> Sun, 26 Jul 2026 17:20:12 -0000
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178508645541.59.10451612104938261266@29965ddac10e>
PR #23920 opened by akx
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23920
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23920.patch

# Summary of changes

This fixes an out-of-bounds read in showspectrum, found and fixed by Claude Opus 5 during implementation of other showspectrum-related things.

The access is a read, so nothing is corrupted, and it does not fault under the system allocator. Under a guard page allocator it segfaults. No heap contents reach the picture, either.

See the commit message for full details.

A crash (SIGSEGV under GuardMalloc) is easily reproduced with e.g. on my macOS machine with

```
env DYLD_INSERT_LIBRARIES=/usr/lib/libgmalloc.dylib MALLOC_FILL_SPACE=1 ffmpeg -f lavfi -i "sine=d=8,aformat=channel_layouts=5.1" -filter_complex showspectrumpic=s=1024x512:mode=separate -frames:v 1 out.png
```




>From c8e6acacf43bb0e5283d19da80ec4dec4f7ed252 Mon Sep 17 00:00:00 2001
From: Aarni Koskela <[email protected]>
Date: Sun, 26 Jul 2026 19:32:16 +0300
Subject: [PATCH] avfilter/avf_showspectrum: fix out-of-bounds read in separate
 mode

config_output() derives the FFT size from the per channel dimension, so
fft_data[] holds about channel_height entries in a vertical layout and
about channel_width in a horizontal one. The four functions that turn
the transform into magnitudes and phases instead iterate over the full
s->h or s->w and index fft_data[] with it, reading past the end whenever
that dimension exceeds the allocated buf_size.

With 5.1 and s=1024x512, channel_height is 512 / 6 = 85, so fft_data[]
is allocated for 176 entries while the loops read 512 of them, running
2688 bytes past a 1408 byte allocation:

    ffmpeg -f lavfi -i "sine=d=8,aformat=channel_layouts=5.1" \
           -filter_complex showspectrumpic=s=1024x512:mode=separate \
           -frames:v 1 out.png

Three or more channels overflow at any size. Two channels overflow only
where halving the dimension truncates and the alignment does not round
back up, such as s=1024x513 or s=1025x512 with a 16 byte alignment.

Bound the loops by channel_height and channel_width, as the plotting
functions already are. Those functions never read past the channel
dimension, so the entries in question were computed and then discarded:
no output changes, and no heap contents reach the picture. This was
verified over channel counts, layouts and orientations against the
previous behaviour.

The access is a read, so nothing is corrupted, and it does not fault
under the system allocator. Under a guard page allocator it segfaults.

Found with AddressSanitizer. Affects showspectrum and showspectrumpic.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
 libavfilter/avf_showspectrum.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libavfilter/avf_showspectrum.c b/libavfilter/avf_showspectrum.c
index 0345b4458d..ee52136ce7 100644
--- a/libavfilter/avf_showspectrum.c
+++ b/libavfilter/avf_showspectrum.c
@@ -1310,7 +1310,7 @@ static int calc_channel_magnitudes(AVFilterContext *ctx, void *arg, int jobnr, i
 {
     ShowSpectrumContext *s = ctx->priv;
     const double w = s->win_scale * (s->scale == LOG ? s->win_scale : 1);
-    int y, h = s->orientation == VERTICAL ? s->h : s->w;
+    int y, h = s->orientation == VERTICAL ? s->channel_height : s->channel_width;
     const float f = s->gain * w;
     const int ch = jobnr;
     float *magnitudes = s->magnitudes[ch];
@@ -1324,7 +1324,7 @@ static int calc_channel_magnitudes(AVFilterContext *ctx, void *arg, int jobnr, i
 static int calc_channel_phases(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
 {
     ShowSpectrumContext *s = ctx->priv;
-    const int h = s->orientation == VERTICAL ? s->h : s->w;
+    const int h = s->orientation == VERTICAL ? s->channel_height : s->channel_width;
     const int ch = jobnr;
     float *phases = s->phases[ch];
     int y;
@@ -1359,7 +1359,7 @@ static void unwrap(float *x, int N, float tol, float *mi, float *ma)
 static int calc_channel_uphases(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
 {
     ShowSpectrumContext *s = ctx->priv;
-    const int h = s->orientation == VERTICAL ? s->h : s->w;
+    const int h = s->orientation == VERTICAL ? s->channel_height : s->channel_width;
     const int ch = jobnr;
     float *phases = s->phases[ch];
     float min, max, scale;
@@ -1378,7 +1378,7 @@ static int calc_channel_uphases(AVFilterContext *ctx, void *arg, int jobnr, int
 static void acalc_magnitudes(ShowSpectrumContext *s)
 {
     const double w = s->win_scale * (s->scale == LOG ? s->win_scale : 1);
-    int ch, y, h = s->orientation == VERTICAL ? s->h : s->w;
+    int ch, y, h = s->orientation == VERTICAL ? s->channel_height : s->channel_width;
     const float f = s->gain * w;
 
     for (ch = 0; ch < s->nb_display_channels; ch++) {
-- 
2.52.0

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