PR #21198 opened by ruikai
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21198
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21198.patch

Regression since: 08db850159

Chunked JPEG-XS decode returned positive libsvtjpegxs errors directly,
so FFmpeg treated failures as “bytes consumed” and kept calling the
decoder with a stale buffer_filled_len. If a libsvtjpegxs error occurred
after the chunk buffer reached frame_size, the next call underflowed
frame_size - buffer_filled_len and memcpy wrote out of bounds.

Fix by translating libsvtjpegxs errors to AVERROR codes, guarding the
chunk memcpy with strict bounds, and resetting buffer_filled_len on
decoder errors so fragmented JPEG-XS input remains safe instead of
crashing.

Repro (ASan):
1) Build with ASan + --enable-libsvtjpegxs.
2) Generate a valid JPEG-XS stream and split into chunks:
   - 64x64 yuv420p 8-bit -> sample.jxs
   - chunk1 = first 400 bytes; chunk2 = 2500 zero bytes; chunk3 = 1 byte 0xff
   - chunks.ffconcat:
     ffconcat version 1.0
     file chunk1.jxs
     duration 0.04
     file chunk2.jxs
     duration 0.04
     file chunk3.jxs
     duration 0.04
3) Run:
   LD_LIBRARY_PATH=/usr/local/lib ASAN_OPTIONS=detect_leaks=0    ./ffmpeg -v 
debug -safe 0 -protocol_whitelist file      -f concat -i chunks.ffconcat      
-c:v libsvtjpegxs -f null -

ASan: AddressSanitizer: negative-size-param in memcpy
  at libavcodec/libsvtjpegxsdec.c:161
  libsvtjpegxs returns err=-2147471359 (positive), FFmpeg continues, next
  packet underflows bytes_to_copy and triggers the OOB write.

Found-by: Pwno


From 1e53dadf8c629f7530e465d90c7e71a029370fe8 Mon Sep 17 00:00:00 2001
From: Ruikai Peng <[email protected]>
Date: Sun, 14 Dec 2025 14:29:05 -0500
Subject: [PATCH] avcodec/libsvtjpegxsdec: bound chunk copies and map errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Regression since: 08db850159

Chunked JPEG-XS decode returned positive libsvtjpegxs errors directly,
so FFmpeg treated failures as “bytes consumed” and kept calling the
decoder with a stale buffer_filled_len. If a libsvtjpegxs error occurred
after the chunk buffer reached frame_size, the next call underflowed
frame_size - buffer_filled_len and memcpy wrote out of bounds.

Fix by translating libsvtjpegxs errors to AVERROR codes, guarding the
chunk memcpy with strict bounds, and resetting buffer_filled_len on
decoder errors so fragmented JPEG-XS input remains safe instead of
crashing.

Repro (ASan):
1) Build with ASan + --enable-libsvtjpegxs.
2) Generate a valid JPEG-XS stream and split into chunks:
   - 64x64 yuv420p 8-bit -> sample.jxs
   - chunk1 = first 400 bytes; chunk2 = 2500 zero bytes; chunk3 = 1 byte 0xff
   - chunks.ffconcat:
     ffconcat version 1.0
     file chunk1.jxs
     duration 0.04
     file chunk2.jxs
     duration 0.04
     file chunk3.jxs
     duration 0.04
3) Run:
   LD_LIBRARY_PATH=/usr/local/lib ASAN_OPTIONS=detect_leaks=0    ./ffmpeg -v 
debug -safe 0 -protocol_whitelist file      -f concat -i chunks.ffconcat      
-c:v libsvtjpegxs -f null -

ASan: AddressSanitizer: negative-size-param in memcpy
  at libavcodec/libsvtjpegxsdec.c:161
  libsvtjpegxs returns err=-2147471359 (positive), FFmpeg continues, next
  packet underflows bytes_to_copy and triggers the OOB write.

Found-by: Pwno
---
 libavcodec/libsvtjpegxsdec.c | 33 ++++++++++++++++++++++++---------
 1 file changed, 24 insertions(+), 9 deletions(-)

diff --git a/libavcodec/libsvtjpegxsdec.c b/libavcodec/libsvtjpegxsdec.c
index 45d9134cd4..bb2625eca5 100644
--- a/libavcodec/libsvtjpegxsdec.c
+++ b/libavcodec/libsvtjpegxsdec.c
@@ -51,6 +51,15 @@ typedef struct SvtJpegXsDecodeContext {
     int proxy_mode;
 } SvtJpegXsDecodeContext;
 
+static av_always_inline int map_svt_err(SvtJxsErrorType_t err)
+{
+    if (err == SvtJxsErrorDecoderConfigChange)
+        return AVERROR_INPUT_CHANGED;
+    if (err != SvtJxsErrorNone)
+        return AVERROR_EXTERNAL;
+    return 0;
+}
+
 static int set_pix_fmt(AVCodecContext* avctx, const svt_jpeg_xs_image_config_t 
*config)
 {
     int ret = 0;
@@ -109,7 +118,7 @@ static int svt_jpegxs_dec_decode(AVCodecContext* avctx, 
AVFrame* picture, int* g
             avpkt->data, avpkt->size, NULL, &svt_dec->frame_size, 1 /*quick 
search*/, svt_dec->decoder.proxy_mode);
         if (err) {
             av_log(avctx, AV_LOG_ERROR, 
"svt_jpeg_xs_decoder_get_single_frame_size_with_proxy failed, err=%d\n", err);
-            return err;
+            return map_svt_err(err);
         }
         if (avpkt->size < svt_dec->frame_size) {
             svt_dec->chunk_decoding = 1;
@@ -129,7 +138,7 @@ static int svt_jpegxs_dec_decode(AVCodecContext* avctx, 
AVFrame* picture, int* g
                                        &svt_dec->decoder, avpkt->data, 
avpkt->size, &svt_dec->config);
         if (err) {
             av_log(avctx, AV_LOG_ERROR, "svt_jpeg_xs_decoder_init failed, 
err=%d\n", err);
-            return err;
+            return map_svt_err(err);
         }
 
         ret = set_pix_fmt(avctx, &svt_dec->config);
@@ -151,12 +160,14 @@ static int svt_jpegxs_dec_decode(AVCodecContext* avctx, 
AVFrame* picture, int* g
         return 0;
 
     if (svt_dec->chunk_decoding) {
+        if (svt_dec->buffer_filled_len >= svt_dec->frame_size)
+            return AVERROR_INVALIDDATA;
+
+        if (avpkt->size > svt_dec->frame_size - svt_dec->buffer_filled_len)
+            return AVERROR_INVALIDDATA;
+
         uint8_t* bitstrream_addr = svt_dec->bitstream_buffer + 
svt_dec->buffer_filled_len;
-        int bytes_to_copy = avpkt->size;
-        //Do not copy more data than allocation
-        if ((bytes_to_copy + svt_dec->buffer_filled_len) > 
svt_dec->frame_size) {
-            bytes_to_copy = svt_dec->frame_size - svt_dec->buffer_filled_len;
-        }
+        const int bytes_to_copy = avpkt->size;
 
         memcpy(bitstrream_addr, avpkt->data, bytes_to_copy);
         svt_dec->buffer_filled_len += avpkt->size;
@@ -190,7 +201,9 @@ static int svt_jpegxs_dec_decode(AVCodecContext* avctx, 
AVFrame* picture, int* g
     err = svt_jpeg_xs_decoder_send_frame(&svt_dec->decoder, &dec_input, 1 
/*blocking*/);
     if (err) {
         av_log(avctx, AV_LOG_ERROR, "svt_jpeg_xs_decoder_send_frame failed, 
err=%d\n", err);
-        return err;
+        if (svt_dec->chunk_decoding)
+            svt_dec->buffer_filled_len = 0;
+        return map_svt_err(err);
     }
 
     err = svt_jpeg_xs_decoder_get_frame(&svt_dec->decoder, &dec_output, 1 
/*blocking*/);
@@ -200,7 +213,9 @@ static int svt_jpegxs_dec_decode(AVCodecContext* avctx, 
AVFrame* picture, int* g
     }
     if (err) {
         av_log(avctx, AV_LOG_ERROR, "svt_jpeg_xs_decoder_get_frame failed, 
err=%d\n", err);
-        return err;
+        if (svt_dec->chunk_decoding)
+            svt_dec->buffer_filled_len = 0;
+        return map_svt_err(err);
     }
 
     if (dec_output.user_prv_ctx_ptr != avpkt) {
-- 
2.49.1

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

Reply via email to