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

Since the multi-symbol VLC reader was added to utvideodec (da888b790a), 
READ_PLANE stops reading a row when the slice bitstream runs out, and the 
"Slice decoding ran out of bits" check that used to follow each row was 
dropped. A slice whose data ends early is now decoded as a success: 
decode_plane() and decode_plane10() only warn when bits are left over, never 
when they are missing, so the rest of the slice is never written and keeps 
whatever the frame buffer held before. With the default pool that is an earlier 
decoded frame. With left prediction the rows are rebuilt from the residuals 
still in the temporary row buffer, so they are wrong but do not come from the 
frame buffer. This is the same problem as in magicyuv (#25825); utvideo does 
propagate the other slice errors, so only the missing check is needed here.

To reproduce: encode a few frames with `-c:v utvideo -pred none -slices 1` into 
AVI and lower the end offset of the Y slice in the last packet (moving the U/V 
planes and frame information up and padding the packet so the AVI index stays 
valid). Decoding with `-threads 1` gives the last frame with no error or 
warning, and 63 of its 64 luma rows are an earlier frame of the stream. With 
median prediction, undoing the prediction on the output gives the earlier frame 
the same way. The same happens across streams for an application that reuses 
the context after avcodec_flush_buffers(). A hand-built UQY2 (10-bit) stream 
shows the same behaviour in decode_plane10().

The patch stops at a row that ends before its width or reads past the end of 
the slice, and fails the frame. With AV_CODEC_FLAG_OUTPUT_CORRUPT the frame is 
output with AV_FRAME_FLAG_CORRUPT instead, as apv_decode does.

Tested on an ASan build: fate-utvideo (22 sample tests), fate-utvideoenc (15 
tests) and fate-filter-curves pass. Decoding FFmpeg utvideo encodes of the 
vsynth1 images (gbrp, gbrap, yuv420p, yuv422p, yuv444p; pred none, left and 
median; 1, 4 and 11 slices) gives the same framemd5 before and after the patch, 
as does a valid hand-built UQY2 stream. The truncated test files now give 
"Slice decoding ran out of bits" and no frame; with -flags output_corrupt they 
are output and marked corrupt. ASan reports nothing either before or after the 
patch.

This issue was found while following up an LLM-Enhanced Static Analysis Engine 
finding (the magicyuv one in #25825) and verified manually.



>From df680523a1b9dbaa81547a34ea18afe3d6e26d6b Mon Sep 17 00:00:00 2001
From: Martin Mirchev <[email protected]>
Date: Sat, 7 Nov 2026 11:49:20 +0800
Subject: [PATCH] avcodec/utvideodec: do not output frames with incomplete
 slices

Since the multi-symbol VLC reader was added (da888b790a), READ_PLANE
stops reading a row when the slice bitstream runs out, and the "ran out
of bits" check that used to follow each row was dropped. A slice whose
data ends early is therefore decoded as a success: the rest of the slice
is never written and keeps whatever the frame buffer held before, which
with the default pool is an earlier decoded frame (with left prediction
the rows are built from the residuals left in the temporary row buffer).

Stop at a row that ends before its width or reads past the end of the
slice, and fail the frame, or with AV_CODEC_FLAG_OUTPUT_CORRUPT output it
marked AV_FRAME_FLAG_CORRUPT.

Signed-off-by: Martin Mirchev <[email protected]>
---
 libavcodec/utvideodec.c | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/libavcodec/utvideodec.c b/libavcodec/utvideodec.c
index ddfaa200050..ac464a9609e 100644
--- a/libavcodec/utvideodec.c
+++ b/libavcodec/utvideodec.c
@@ -58,6 +58,7 @@ typedef struct UtvideoContext {
     int      frame_pred;
     int      pro;
     int      pack;
+    int      corrupt;
 
     uint8_t *slice_bits;
     int      slice_bits_size;
@@ -129,6 +130,8 @@ static int build_huff(UtvideoContext *c, const uint8_t 
*src, VLC *vlc,
     } \
     for (; i < width && get_bits_left(&gb) > 0; i++) \
         buf[i] = get_vlc2(&gb, vlc.table, VLC_BITS, 3); \
+    if (i < width || get_bits_left(&gb) < 0) /* leave the row loop */ \
+        break; \
     if (use_pred) { \
         if (b == 2) \
             c->llviddsp.add_left_pred_int16((uint16_t *)dest, (const uint16_t 
*)buf, 0x3ff, width, prev); \
@@ -211,7 +214,13 @@ static int decode_plane10(UtvideoContext *c, int plane_no,
         prev = 0x200;
         for (j = sstart; j < send; j++)
             READ_PLANE(2, 3)
-        if (get_bits_left(&gb) > 32)
+        if (j < send) {
+            av_log(c->avctx, AV_LOG_ERROR,
+                   "Slice decoding ran out of bits\n");
+            if (!(c->avctx->flags & AV_CODEC_FLAG_OUTPUT_CORRUPT))
+                goto fail;
+            c->corrupt = 1;
+        } else if (get_bits_left(&gb) > 32)
             av_log(c->avctx, AV_LOG_WARNING,
                    "%d bits left after decoding slice\n", get_bits_left(&gb));
     }
@@ -356,7 +365,13 @@ static int decode_plane(UtvideoContext *c, int plane_no,
         prev = 0x80;
         for (j = sstart; j < send; j++)
             READ_PLANE(1, 5)
-        if (get_bits_left(&gb) > 32)
+        if (j < send) {
+            av_log(c->avctx, AV_LOG_ERROR,
+                   "Slice decoding ran out of bits\n");
+            if (!(c->avctx->flags & AV_CODEC_FLAG_OUTPUT_CORRUPT))
+                goto fail;
+            c->corrupt = 1;
+        } else if (get_bits_left(&gb) > 32)
             av_log(c->avctx, AV_LOG_WARNING,
                    "%d bits left after decoding slice\n", get_bits_left(&gb));
     }
@@ -593,6 +608,8 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
     if ((ret = ff_thread_get_buffer(avctx, frame, 0)) < 0)
         return ret;
 
+    c->corrupt = 0;
+
     /* parse plane structure to get frame flags and validate slice offsets */
     bytestream2_init(&gb, buf, buf_size);
 
@@ -892,6 +909,8 @@ static int decode_frame(AVCodecContext *avctx, AVFrame 
*frame,
 
     if (c->interlaced)
         frame->flags |= AV_FRAME_FLAG_INTERLACED;
+    if (c->corrupt)
+        frame->flags |= AV_FRAME_FLAG_CORRUPT;
 
     *got_frame = 1;
 
-- 
2.54.0

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

Reply via email to