PR #23053 opened by t-ivan-gr
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23053
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23053.patch

Fixes a 2-byte heap buffer overrun in libswresample's SSE2 int16 resampler.

The offending function is ff_resample_common_int16_sse2 in
libswresample/x86/resample.asm. Its outer per-sample loop emits

    movd  [dstq], m0     ; write 4 bytes
    add    dstq, %2      ; advance by bps (=2 for int16)

so each iteration writes 4 bytes but advances the destination by only 2.
Successive writes overlap, but the last iteration writes 2 bytes past the
end of the destination buffer.

The destination buffer is s->preout.ch[i] (or s->midbuf.ch[i] when
resample_first), allocated by swri_realloc_audio() to exactly count*bps
bytes. The tail write spills into whatever follows in the heap.

The bug stays hidden in most call sites because the doubling allocator
(count *= 2 in swri_realloc_audio) usually leaves enough trailing chunk
space to absorb the 2-byte spill. It surfaces deterministically when a
caller invokes swr_convert(N) followed by swr_convert(2N): the first
call grows the internal buffer to exactly 2N*bps bytes, the second call
asks for 2N and reuses the buffer with zero slack, and the SIMD store
overruns it.

Fix: in swr_convert_internal(), pad the resample destination by one
frame when HAVE_X86ASM and internal format is S16P.

Reproducer (run under valgrind, without the fix):

    Invalid write of size 4
       at 0x4877975: ??? (in libswresample.so.6)
     Address 0x4d868fe is 638 bytes inside a block of size 640 alloc'd
       at posix_memalign
       by av_mallocz                  mem.c:258
       by swri_realloc_audio          swresample.c:426
       by swr_convert_internal        swresample.c:617
       by swr_convert                 swresample.c:778

Signed-off-by: Ivan Grigorev <[email protected]>


>From adb5e1847eccac16a7e9e19cdd4763ee5134c44c Mon Sep 17 00:00:00 2001
From: Ivan Grigorev <[email protected]>
Date: Wed, 6 May 2026 03:44:17 -0700
Subject: [PATCH] swresample: fix heap buffer overrun in x86 int16 SIMD
 resampler

Fixes a 2-byte heap buffer overrun in libswresample's SSE2 int16 resampler.

The offending function is ff_resample_common_int16_sse2 in
libswresample/x86/resample.asm. Its outer per-sample loop emits

    movd  [dstq], m0     ; write 4 bytes
    add    dstq, %2      ; advance by bps (=2 for int16)

so each iteration writes 4 bytes but advances the destination by only 2.
Successive writes overlap, but the last iteration writes 2 bytes past the
end of the destination buffer.

The destination buffer is s->preout.ch[i] (or s->midbuf.ch[i] when
resample_first), allocated by swri_realloc_audio() to exactly count*bps
bytes. The tail write spills into whatever follows in the heap.

The bug stays hidden in most call sites because the doubling allocator
(count *= 2 in swri_realloc_audio) usually leaves enough trailing chunk
space to absorb the 2-byte spill. It surfaces deterministically when a
caller invokes swr_convert(N) followed by swr_convert(2N): the first
call grows the internal buffer to exactly 2N*bps bytes, the second call
asks for 2N and reuses the buffer with zero slack, and the SIMD store
overruns it.

Fix: in swr_convert_internal(), pad the resample destination by one
frame when HAVE_X86ASM and internal format is S16P.

Reproducer (run under valgrind, without the fix):

    Invalid write of size 4
       at 0x4877975: ??? (in libswresample.so.6)
     Address 0x4d868fe is 638 bytes inside a block of size 640 alloc'd
       at posix_memalign
       by av_mallocz                  mem.c:258
       by swri_realloc_audio          swresample.c:426
       by swr_convert_internal        swresample.c:617
       by swr_convert                 swresample.c:778

Signed-off-by: Ivan Grigorev <[email protected]>
---
 libswresample/Makefile                        |   4 +-
 libswresample/swresample.c                    |  13 +-
 .../tests/swresample_resample_realloc.c       | 111 ++++++++++++++++++
 tests/fate/libswresample.mak                  |   4 +
 tests/ref/fate/swr-resample-realloc           |   0
 5 files changed, 129 insertions(+), 3 deletions(-)
 create mode 100644 libswresample/tests/swresample_resample_realloc.c
 create mode 100644 tests/ref/fate/swr-resample-realloc

diff --git a/libswresample/Makefile b/libswresample/Makefile
index 8b9a0fe6f5..ceac63a310 100644
--- a/libswresample/Makefile
+++ b/libswresample/Makefile
@@ -24,4 +24,6 @@ SHLIBOBJS              += log2_tab.o
 # Windows resource file
 SHLIBOBJS-$(HAVE_GNU_WINDRES) += swresampleres.o
 
-TESTPROGS = swresample
+TESTPROGS = swresample \
+            swresample_resample_realloc \
+
diff --git a/libswresample/swresample.c b/libswresample/swresample.c
index d777efd802..fbb74eac94 100644
--- a/libswresample/swresample.c
+++ b/libswresample/swresample.c
@@ -605,16 +605,25 @@ static int swr_convert_internal(struct SwrContext *s, 
AudioData *out, int out_co
 
     if((ret=swri_realloc_audio(&s->postin, in_count))<0)
         return ret;
+    /* Pad the destination of resample() (midbuf if resample_first, else
+     * preout) by one frame on x86 with int16 internal format:
+     * ff_resample_common_int16_sse2 overruns its destination by 2 bytes
+     * on the last iteration. */
+    int resample_dst_slack = 0;
+#if HAVE_X86ASM
+    if (s->int_sample_fmt == AV_SAMPLE_FMT_S16P)
+        resample_dst_slack = 1;
+#endif
     if(s->resample_first){
         av_assert0(s->midbuf.ch_count == s->used_ch_layout.nb_channels);
-        if((ret=swri_realloc_audio(&s->midbuf, out_count))<0)
+        if((ret=swri_realloc_audio(&s->midbuf, out_count + 
resample_dst_slack))<0)
             return ret;
     }else{
         av_assert0(s->midbuf.ch_count ==  s->out.ch_count);
         if((ret=swri_realloc_audio(&s->midbuf,  in_count))<0)
             return ret;
     }
-    if((ret=swri_realloc_audio(&s->preout, out_count))<0)
+    if((ret=swri_realloc_audio(&s->preout, out_count + resample_dst_slack))<0)
         return ret;
 
     postin= &s->postin;
diff --git a/libswresample/tests/swresample_resample_realloc.c 
b/libswresample/tests/swresample_resample_realloc.c
new file mode 100644
index 0000000000..e8a0e224d1
--- /dev/null
+++ b/libswresample/tests/swresample_resample_realloc.c
@@ -0,0 +1,111 @@
+/*
+ * Exercise the swr_convert(N) -> swr_convert(2N) edge case where the
+ * second call reuses the internal preout buffer at full capacity, with
+ * no trailing slack from swri_realloc_audio()'s amortized doubling.
+ * Forces internal_sample_fmt=S16P to reach the int16 SIMD resample path.
+ *
+ * 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.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "libavutil/channel_layout.h"
+#include "libavutil/mem.h"
+#include "libavutil/opt.h"
+#include "libavutil/samplefmt.h"
+#include "libswresample/swresample.h"
+
+int main(void)
+{
+    const int IN_RATE  = 48000;
+    const int OUT_RATE = 16000;
+    /* First call asks for N out frames, second call asks for 2N. */
+    const int N1_OUT = 160;
+    const int N2_OUT = 320;
+    const int N1_IN  = N1_OUT * IN_RATE / OUT_RATE; /* 480 */
+    const int N2_IN  = N2_OUT * IN_RATE / OUT_RATE; /* 960 */
+
+    SwrContext *swr = swr_alloc();
+    AVChannelLayout mono = AV_CHANNEL_LAYOUT_MONO;
+    int ret = 0;
+
+    if (!swr) {
+        fprintf(stderr, "swr_alloc failed\n");
+        return 1;
+    }
+
+    av_opt_set_chlayout   (swr, "in_chlayout",          &mono,                 
0);
+    av_opt_set_chlayout   (swr, "out_chlayout",         &mono,                 
0);
+    av_opt_set_int        (swr, "in_sample_rate",       IN_RATE,               
0);
+    av_opt_set_int        (swr, "out_sample_rate",      OUT_RATE,              
0);
+    av_opt_set_sample_fmt (swr, "in_sample_fmt",        AV_SAMPLE_FMT_S16,     
0);
+    av_opt_set_sample_fmt (swr, "out_sample_fmt",       AV_SAMPLE_FMT_S16,     
0);
+    /* Force the int16 SIMD resample path. */
+    av_opt_set_sample_fmt (swr, "internal_sample_fmt",  AV_SAMPLE_FMT_S16P,    
0);
+
+    if ((ret = swr_init(swr)) < 0) {
+        fprintf(stderr, "swr_init failed: %d\n", ret);
+        ret = 1;
+        goto end;
+    }
+
+    {
+        int16_t *input = av_calloc(N2_IN,  sizeof(int16_t));
+        int16_t *out   = av_calloc(N2_OUT, sizeof(int16_t));
+        const uint8_t *in_planes[1];
+        uint8_t       *out_planes[1];
+        int i, n;
+
+        if (!input || !out) {
+            fprintf(stderr, "alloc failed\n");
+            av_free(input);
+            av_free(out);
+            ret = 1;
+            goto end;
+        }
+
+        /* Non-zero samples so the SIMD inner loop produces real data. */
+        for (i = 0; i < N2_IN; ++i)
+            input[i] = (int16_t)((i * 7) & 0x3fff);
+
+        /* Call #1: out_count = N. swri_realloc_audio() doubles count and
+         * grows s->preout to capacity 2N (e.g. 640 bytes for N=160). */
+        in_planes[0]  = (const uint8_t *)input;
+        out_planes[0] = (uint8_t *)out;
+        n = swr_convert(swr, out_planes, N1_OUT, in_planes, N1_IN);
+        if (n < 0) {
+            fprintf(stderr, "swr_convert call#1 failed: %d\n", n);
+            av_free(input);
+            av_free(out);
+            ret = 1;
+            goto end;
+        }
+
+        /* Call #2: out_count = 2N. a->count == 2N, so swri_realloc_audio()
+         * skips realloc and reuses the existing buffer at full capacity. */
+        in_planes[0]  = (const uint8_t *)input;
+        out_planes[0] = (uint8_t *)out;
+        n = swr_convert(swr, out_planes, N2_OUT, in_planes, N2_IN);
+        if (n < 0) {
+            fprintf(stderr, "swr_convert call#2 failed: %d\n", n);
+            av_free(input);
+            av_free(out);
+            ret = 1;
+            goto end;
+        }
+
+        av_free(input);
+        av_free(out);
+    }
+
+end:
+    swr_free(&swr);
+    return ret;
+}
diff --git a/tests/fate/libswresample.mak b/tests/fate/libswresample.mak
index 5317d4148d..fbf9cf064c 100644
--- a/tests/fate/libswresample.mak
+++ b/tests/fate/libswresample.mak
@@ -1107,3 +1107,7 @@ fate-swr-custom-rematrix: REF = 
2a14a44deb4ae26e3b474ddbfbc048f8
 FATE_SWR += $(FATE_SWR_CUSTOM_REMATRIX-yes)
 FATE_FFMPEG += $(FATE_SWR)
 fate-swr: $(FATE_SWR)
+
+FATE_SWR += fate-swr-resample-realloc
+fate-swr-resample-realloc: 
libswresample/tests/swresample_resample_realloc$(EXESUF)
+fate-swr-resample-realloc: CMD = run 
libswresample/tests/swresample_resample_realloc$(EXESUF)
diff --git a/tests/ref/fate/swr-resample-realloc 
b/tests/ref/fate/swr-resample-realloc
new file mode 100644
index 0000000000..e69de29bb2
-- 
2.52.0

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

Reply via email to