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]