[FFmpeg-devel,PR] swscale: multiple copy issues (PR #24851)

Message ID 20261002130014.BFE18333350A@ffbox0-bg.ffmpeg.org
State New
Headers
Series [FFmpeg-devel,PR] swscale: multiple copy issues (PR #24851) |

Commit Message

michaelni Oct. 2, 2026, 1 p.m. UTC
PR #24851 opened by michaelni
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24851
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24851.patch


>From e43d6b55b4543934ca53b06aee377bdc47c5ca6d Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <michael@niedermayer.cc>
Date: Fri, 2 Oct 2026 03:20:55 +0200
Subject: [PATCH 1/4] swscale/ops_dispatch: do not rely on padding in frames
 from the caller

Size the lines of caller frames from their width instead. Buffers
allocated by the graph are padded for the backend and keep using their
linesize. For widths that are not a multiple of the block size this
costs up to 2% with the x86 backend.

The interlaced case was reported, the separatefields and crop cases
were found during triage of the report.

Fixes: out of array read
Fixes: out of array write
Fixes: edlOwd4qVGBw
Regression since: db2bc11a97
Found-by: mzfr <security@mzfr.me>
Replicated through UnModified FFmpeg
---
 libswscale/ops_dispatch.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)
  

Patch

diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c
index a0d049f616..ca2e8caf0e 100644
--- a/libswscale/ops_dispatch.c
+++ b/libswscale/ops_dispatch.c
@@ -225,8 +225,9 @@  static int op_pass_setup(const SwsFrame *out, const SwsFrame *in,
     for (int i = 0; i < p->planes_in; i++) {
         const int idx = p->idx_in[i];
         size_t input_bytes = in->linesize[idx];
-        if (p->filter_size_h && float_in) {
-            /* Floating point inputs may contain NaN / Infinity in the padding */
+        if (!pass->input || (p->filter_size_h && float_in)) {
+            /* Caller frames may be unpadded, and floating point inputs
+             * may contain NaN / Infinity in the padding */
             const int plane_w = AV_CEIL_RSHIFT(in->width, exec->in_sub_x[i]);
             input_bytes = pixel_bytes(plane_w, p->pixel_bits_in, AV_ROUND_UP);
         }
@@ -256,7 +257,11 @@  static int op_pass_setup(const SwsFrame *out, const SwsFrame *in,
 
     for (int i = 0; i < p->planes_out; i++) {
         const int idx = p->idx_out[i];
-        size_t safe_bytes = safe_bytes_pad(out->linesize[idx], comp->over_write[i]);
+        size_t output_bytes = out->linesize[idx];
+        if (!pass->output->avframe)
+            output_bytes = pixel_bytes(AV_CEIL_RSHIFT(out->width, exec->out_sub_x[i]),
+                                       p->pixel_bits_out, AV_ROUND_UP);
+        size_t safe_bytes = safe_bytes_pad(output_bytes, comp->over_write[i]);
         size_t safe_blocks_out = safe_bytes / exec->block_size_out[i];
         if (safe_blocks_out < num_blocks) {
             p->memcpy_out = true;
-- 
2.52.0


>From 8042f48e893ffaf0847b3964851f0446abf397b8 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <michael@niedermayer.cc>
Date: Fri, 2 Oct 2026 03:25:33 +0200
Subject: [PATCH 2/4] swscale/graph: do not copy past the last line in
 run_copy()

Use ff_copyPlane() with the width of the line. It still copies equal
positive linesizes as one block, but ends it with the last line.

Found during triage of the security report edlOwd4qVGBw.

Fixes: out of array read
Fixes: out of array write
Fixes: negative size memcpy()
Fixes: xvstjaVdxtut
Regression since: bf738412e8
Replicated through API
---
 libswscale/graph.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index 5a5d469a51..e74b2b0227 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -292,15 +292,10 @@  static void run_copy(const SwsFrame *out, const SwsFrame *in, int y, int h,
 
         if (in_data[i] == out_data[i]) {
             av_assert0(in->linesize[i] == out->linesize[i]);
-        } else if (in->linesize[i] == out->linesize[i]) {
-            memcpy(out_data[i], in_data[i], lines * out->linesize[i]);
         } else {
-            const int linesize = FFMIN(out->linesize[i], in->linesize[i]);
-            for (int j = 0; j < lines; j++) {
-                memcpy(out_data[i], in_data[i], linesize);
-                in_data[i]  += in->linesize[i];
-                out_data[i] += out->linesize[i];
-            }
+            ff_copyPlane(in_data[i], in->linesize[i], 0, lines,
+                         av_image_get_linesize(out->format, out->width, i),
+                         out_data[i], out->linesize[i]);
         }
     }
 }
-- 
2.52.0


>From 3fba40598e8300481f53dcf1964bef4ed9dd0ce9 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <michael@niedermayer.cc>
Date: Fri, 2 Oct 2026 03:35:17 +0200
Subject: [PATCH 3/4] swscale/swscale_unscaled: do not copy past the last line
 in packedCopyWrapper()

Use ff_copyPlane()

Found during triage of the security report edlOwd4qVGBw.

Fixes: out of array read
Fixes: out of array write
Fixes: IhE83jd9Xs9n
Replicated through API
---
 libswscale/swscale_unscaled.c | 18 ++++--------------
 1 file changed, 4 insertions(+), 14 deletions(-)

diff --git a/libswscale/swscale_unscaled.c b/libswscale/swscale_unscaled.c
index 7acab95e03..84c3444bb1 100644
--- a/libswscale/swscale_unscaled.c
+++ b/libswscale/swscale_unscaled.c
@@ -32,6 +32,7 @@ 
 #include "libavutil/mathematics.h"
 #include "libavutil/mem_internal.h"
 #include "libavutil/bswap.h"
+#include "libavutil/imgutils.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/avassert.h"
 #include "libavutil/avconfig.h"
@@ -2139,20 +2140,9 @@  static int packedCopyWrapper(SwsInternal *c, const uint8_t *const src[],
                              const int srcStride[], int srcSliceY, int srcSliceH,
                              uint8_t *const dst[], const int dstStride[])
 {
-    if (dstStride[0] == srcStride[0] && srcStride[0] > 0)
-        memcpy(dst[0] + dstStride[0] * srcSliceY, src[0], srcSliceH * dstStride[0]);
-    else {
-        int i;
-        const uint8_t *srcPtr = src[0];
-        uint8_t *dstPtr = dst[0] + dstStride[0] * srcSliceY;
-
-        const int length = FFMIN(FFABS(dstStride[0]), FFABS(srcStride[0]));
-        for (i = 0; i < srcSliceH; i++) {
-            memcpy(dstPtr, srcPtr, length);
-            srcPtr += srcStride[0];
-            dstPtr += dstStride[0];
-        }
-    }
+    ff_copyPlane(src[0], srcStride[0], srcSliceY, srcSliceH,
+                 av_image_get_linesize(c->opts.src_format, c->opts.src_w, 0),
+                 dst[0], dstStride[0]);
     return srcSliceH;
 }
 
-- 
2.52.0


>From f7ae08b9704550dc7726ebd15119c1a8200130a1 Mon Sep 17 00:00:00 2001
From: Michael Niedermayer <michael@niedermayer.cc>
Date: Fri, 2 Oct 2026 06:01:09 +0200
Subject: [PATCH 4/4] swscale/ops_memcpy: do not clear or copy past the last
 line

Found during triage of the security report edlOwd4qVGBw.

Fixes: out of array read
Fixes: out of array write
Fixes: negative size memset() / memcpy()
Fixes: orU7yZg5HmsB
Regression since: a151b426f9
Replicated through API
Replicated through UnModified FFmpeg
---
 libswscale/ops_memcpy.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/libswscale/ops_memcpy.c b/libswscale/ops_memcpy.c
index 5940381ea0..67fd4cd9d4 100644
--- a/libswscale/ops_memcpy.c
+++ b/libswscale/ops_memcpy.c
@@ -49,9 +49,11 @@  static void process(const SwsOpExec *exec, const void *priv,
         uint8_t *out = exec->out[i];
         const int idx = p->index[i];
         const int bytes = x_end * exec->block_size_out[i];
-        const int use_loop = exec->out_stride[i] > bytes + SWS_MAX_PADDING;
+        const int use_loop = exec->out_stride[i] < 0 ||
+                             exec->out_stride[i] > bytes + SWS_MAX_PADDING;
+        const int span = (lines - 1) * exec->out_stride[i] + bytes;
         if (idx < 0 && !use_loop) {
-            memset(out, p->clear_value[i], exec->out_stride[i] * lines);
+            memset(out, p->clear_value[i], span);
         } else if (idx < 0) {
             for (int y = y_start; y < y_end; y++) {
                 memset(out, p->clear_value[i], bytes);
@@ -61,7 +63,7 @@  static void process(const SwsOpExec *exec, const void *priv,
             av_assert1(exec->out_stride[i] == exec->in_stride[idx]);
             continue; /* plane was already ref'd */
         } else if (exec->out_stride[i] == exec->in_stride[idx] && !use_loop) {
-            memcpy(out, exec->in[idx], exec->out_stride[i] * lines);
+            memcpy(out, exec->in[idx], span);
         } else {
             const uint8_t *in = exec->in[idx];
             for (int y = y_start; y < y_end; y++) {