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]
