Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [FFmpeg-devel] [PATCH] avcodec/libsvtjpegxsdec: bound chunk copies and map errors (PR #21198)
@ 2025-12-14 19:32 ruikai via ffmpeg-devel
  0 siblings, 0 replies; only message in thread
From: ruikai via ffmpeg-devel @ 2025-12-14 19:32 UTC (permalink / raw)
  To: ffmpeg-devel; +Cc: ruikai

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 <ruikai@pwno.io>
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 -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org

^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2025-12-14 19:33 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-12-14 19:32 [FFmpeg-devel] [PATCH] avcodec/libsvtjpegxsdec: bound chunk copies and map errors (PR #21198) ruikai via ffmpeg-devel

Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

This inbox may be cloned and mirrored by anyone:

	git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
		ffmpegdev@gitmailbox.com
	public-inbox-index ffmpegdev

Example config snippet for mirrors.


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git