PR #24949 opened by michaelni URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24949 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24949.patch
Found during triage of the security reports Found-by: Joshua Rogers <[email protected]> >From 3986de0fe52840581e9f8ec61529fbe9dc9f8b27 Mon Sep 17 00:00:00 2001 From: Joshua Rogers <[email protected]> Date: Tue, 6 Oct 2026 23:03:34 +0200 Subject: [PATCH 1/6] avformat/rtpenc_mpv: Do not read a picture header past the end of the packet A packet ending in a picture start code made the temporal reference and picture type be read from after the end of the packet. This is not a security issue, the bytes read are in the padding that muxer input is required to have, see ce1f4c78d1. Fixes: read into padding Fixes: FbXPpSfFS7Zp Fixes: AISLE-2026-0111-00268 Read past the packet end Replicated through API with ASAN, using an unpadded packet Wrong picture header Replicated through UnModified FFmpeg with the sample of trac ticket 6345 Found-by: Joshua Rogers <[email protected]> --- libavformat/rtpenc_mpv.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libavformat/rtpenc_mpv.c b/libavformat/rtpenc_mpv.c index c18c75082f..c371c41ce1 100644 --- a/libavformat/rtpenc_mpv.c +++ b/libavformat/rtpenc_mpv.c @@ -59,7 +59,7 @@ void ff_rtp_send_mpegvideo(AVFormatContext *s1, const uint8_t *buf1, int size) r = avpriv_find_start_code(r1, end, &start_code); if((start_code & 0xFFFFFF00) == 0x100) { /* New start code found */ - if (start_code == 0x100) { + if (start_code == 0x100 && end - r >= 2) { frame_type = (r[1] & 0x38) >> 3; temporal_reference = (int)r[0] << 2 | r[1] >> 6; } -- 2.52.0 >From c71263bd6511b450b7ca298f5b7bdd715f033e7e Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 7 Oct 2026 00:04:51 +0200 Subject: [PATCH 2/6] avformat/rtmppkt: Write the full basic header for continuation chunks Continuation chunks were marked with the single byte 0xC0 | channel_id, which is only valid for chunk stream ids below 64. Ids from 64 need the 2 or 3 byte form of the basic header, as used for the first chunk, so the peer misparsed the rest of the stream. Repeat the basic header of the first chunk with chunk type 3. Corrupt chunk stream Replicated through UnModified FFmpeg Found during triage of the security report BvkwW4Yd19HN --- libavformat/rtmppkt.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/libavformat/rtmppkt.c b/libavformat/rtmppkt.c index e52307c5d6..6813dd78b8 100644 --- a/libavformat/rtmppkt.c +++ b/libavformat/rtmppkt.c @@ -315,6 +315,7 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt, int *nb_prev_pkt) { uint8_t pkt_hdr[16], *p = pkt_hdr; + int basic_hdr_size; int mode = RTMP_PS_TWELVEBYTES; int off = 0; int written = 0; @@ -363,6 +364,7 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt, bytestream_put_byte(&p, 1 | (mode << 6)); bytestream_put_le16(&p, pkt->channel_id - 64); } + basic_hdr_size = p - pkt_hdr; if (mode != RTMP_PS_ONEBYTE) { bytestream_put_be24(&p, pkt->ts_field); if (mode != RTMP_PS_FOURBYTES) { @@ -391,16 +393,16 @@ int ff_rtmp_packet_write(URLContext *h, RTMPPacket *pkt, if ((ret = ffurl_write(h, pkt_hdr, p - pkt_hdr)) < 0) return ret; written = p - pkt_hdr + pkt->size; + pkt_hdr[0] |= RTMP_PS_ONEBYTE << 6; while (off < pkt->size) { int towrite = FFMIN(chunk_size, pkt->size - off); if ((ret = ffurl_write(h, pkt->data + off, towrite)) < 0) return ret; off += towrite; if (off < pkt->size) { - uint8_t marker = 0xC0 | pkt->channel_id; - if ((ret = ffurl_write(h, &marker, 1)) < 0) + if ((ret = ffurl_write(h, pkt_hdr, basic_hdr_size)) < 0) return ret; - written++; + written += basic_hdr_size; if (pkt->ts_field == 0xFFFFFF) { uint8_t ts_header[4]; AV_WB32(ts_header, timestamp); -- 2.52.0 >From bba57ce8a37b5c05f0fde770618c6b7ca59c1954 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 7 Oct 2026 00:04:51 +0200 Subject: [PATCH 3/6] avformat/rtpdec_h261: Keep a single byte fragment with EBIT as end byte When a one byte payload completed the pending end byte and also had EBIT set, the merged byte was written as complete and then taken again from buf[-1] as the new pending end byte, adding a byte to the frame. Keep the merged byte pending with its 8 - EBIT valid bits instead. Wrong output Replicated through UnModified FFmpeg Found during triage of the security report TNp7g6QtpR0J --- libavformat/rtpdec_h261.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/libavformat/rtpdec_h261.c b/libavformat/rtpdec_h261.c index 1fb71f2b10..2fa5653167 100644 --- a/libavformat/rtpdec_h261.c +++ b/libavformat/rtpdec_h261.c @@ -112,10 +112,16 @@ static int h261_handle_packet(AVFormatContext *ctx, PayloadContext *rtp_h261_ctx if (rtp_h261_ctx->endbyte_bits || sbit) { if (rtp_h261_ctx->endbyte_bits == sbit) { rtp_h261_ctx->endbyte |= buf[0] & (0xff >> sbit); - rtp_h261_ctx->endbyte_bits = 0; buf++; len--; - avio_w8(rtp_h261_ctx->buf, rtp_h261_ctx->endbyte); + if (!len && ebit) { + rtp_h261_ctx->endbyte &= 0xff << ebit; + rtp_h261_ctx->endbyte_bits = 8 - ebit; + ebit = 0; + } else { + rtp_h261_ctx->endbyte_bits = 0; + avio_w8(rtp_h261_ctx->buf, rtp_h261_ctx->endbyte); + } } else { /* ebit/sbit values inconsistent, assuming packet loss */ GetBitContext gb; -- 2.52.0 >From 4084e66e67e1ef3d5aabc19d7b2018afff565385 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 7 Oct 2026 00:05:08 +0200 Subject: [PATCH 4/6] avformat/rtpdec_rfc4175: Do not leak the frame finalized on a missed marker When a packet with a new timestamp put the previous frame into pkt, an invalid packet returned an error with the frame still in pkt, and a packet with the marker set put the new frame into the same pkt. In both cases the previous frame was lost and its buffer leaked, once per such packet, so a sender can exhaust the memory of the receiver. Return the previous frame in these cases, and the new one from the following call. Fixes: memleak Fixes: iF2DzMcFQavi Memory leak Replicated through UnModified FFmpeg with ASAN Found during triage of the security report V8a3e2cGQI7w --- libavformat/rtpdec_rfc4175.c | 38 +++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/libavformat/rtpdec_rfc4175.c b/libavformat/rtpdec_rfc4175.c index b49fc55d2d..3452d57341 100644 --- a/libavformat/rtpdec_rfc4175.c +++ b/libavformat/rtpdec_rfc4175.c @@ -238,13 +238,21 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, uint16_t seq, int flags) { int length, line, offset, cont, field; - const uint8_t *headers = buf + 2; /* skip extended seqnum */ - const uint8_t *payload = buf + 2; - int payload_len = len - 2; + const uint8_t *headers, *payload; + int payload_len; int missed_last_packet = 0; uint8_t *dest; + if (!buf) { + *timestamp = data->timestamp; + return rfc4175_finalize_packet(data, pkt, st->index); + } + + headers = buf + 2; /* skip extended seqnum */ + payload = buf + 2; + payload_len = len - 2; + if (*timestamp != data->timestamp) { if (data->frame && (!data->interlaced || data->field)) { /* @@ -255,8 +263,7 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, * previous frame (or pair of fields) anyway by filling the AVPacket. */ av_log(ctx, AV_LOG_ERROR, "Missed previous RTP Marker\n"); - missed_last_packet = 1; - rfc4175_finalize_packet(data, pkt, st->index); + missed_last_packet = rfc4175_finalize_packet(data, pkt, st->index) >= 0; } if (!data->frame) @@ -266,7 +273,7 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, if (!data->frame) { av_log(ctx, AV_LOG_ERROR, "Out of memory.\n"); - return AVERROR(ENOMEM); + return missed_last_packet ? 0 : AVERROR(ENOMEM); } } @@ -276,7 +283,7 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, */ do { if (payload_len < 6) - return AVERROR_INVALIDDATA; + goto invalid; cont = payload[4] & 0x80; payload += 6; @@ -288,7 +295,7 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, int copy_offset; if (payload_len < data->pgroup) - return AVERROR_INVALIDDATA; + goto invalid; length = (headers[0] << 8) | headers[1]; field = (headers[2] & 0x80) >> 7; @@ -299,7 +306,7 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, data->field = field; if (!data->pgroup || length % data->pgroup) - return AVERROR_INVALIDDATA; + goto invalid; if (length > payload_len) length = payload_len; @@ -308,12 +315,12 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, line = 2 * line + field; if (line >= data->height) - return AVERROR_INVALIDDATA; + goto invalid; /* prevent ill-formed packets to write after buffer's end */ copy_offset = (line * data->width + offset) * data->pgroup / data->xinc; if (copy_offset + length > data->frame_size || !data->frame) - return AVERROR_INVALIDDATA; + goto invalid; dest = data->frame + copy_offset; memcpy(dest, payload, length); @@ -322,13 +329,16 @@ static int rfc4175_handle_packet(AVFormatContext *ctx, PayloadContext *data, payload_len -= length; } while (cont); - if ((flags & RTP_FLAG_MARKER)) { + if (missed_last_packet) { + return !!(flags & RTP_FLAG_MARKER); + } else if ((flags & RTP_FLAG_MARKER)) { return rfc4175_finalize_packet(data, pkt, st->index); - } else if (missed_last_packet) { - return 0; } return AVERROR(EAGAIN); + +invalid: + return missed_last_packet ? 0 : AVERROR_INVALIDDATA; } const RTPDynamicProtocolHandler ff_rfc4175_rtp_handler = { -- 2.52.0 >From 4b6c91e96591319906b9bfafce1a8148b6d300e3 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 7 Oct 2026 00:06:08 +0200 Subject: [PATCH 5/6] avformat/rdt: Allocate the RMStream of a stream only once A repeated ASMRuleBook line allocated a new RMStream for every stream it had already set up, leaking the old one. Memory leak Replicated through UnModified FFmpeg with ASAN Found during triage of the security report PXsIMPp0wPaW --- libavformat/rdt.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libavformat/rdt.c b/libavformat/rdt.c index 77048d59c8..c1a09d59a2 100644 --- a/libavformat/rdt.c +++ b/libavformat/rdt.c @@ -437,6 +437,8 @@ rdt_parse_sdp_line (AVFormatContext *s, int st_index, (count - rdt->nb_rmst) * sizeof(*rdt->rmst)); rdt->nb_rmst = count; } + if (rdt->rmst[s->streams[n]->index]) + continue; rdt->rmst[s->streams[n]->index] = ff_rm_alloc_rmstream(); if (!rdt->rmst[s->streams[n]->index]) return AVERROR(ENOMEM); -- 2.52.0 >From db57c56f72c70e13d89ebd7663e631b9360a41f8 Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Wed, 7 Oct 2026 00:54:45 +0200 Subject: [PATCH 6/6] avformat/rtpdec_rfc4175: Free the pending frame on close The handler had no close callback, so a frame that was not finalized when the stream was closed leaked. Memory leak Replicated through UnModified FFmpeg with ASAN Found during triage of the security report V8a3e2cGQI7w --- libavformat/rtpdec_rfc4175.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libavformat/rtpdec_rfc4175.c b/libavformat/rtpdec_rfc4175.c index 3452d57341..f523a13c3d 100644 --- a/libavformat/rtpdec_rfc4175.c +++ b/libavformat/rtpdec_rfc4175.c @@ -341,6 +341,11 @@ invalid: return missed_last_packet ? 0 : AVERROR_INVALIDDATA; } +static void rfc4175_close_context(PayloadContext *data) +{ + av_freep(&data->frame); +} + const RTPDynamicProtocolHandler ff_rfc4175_rtp_handler = { .enc_name = "raw", .codec_type = AVMEDIA_TYPE_VIDEO, @@ -348,4 +353,5 @@ const RTPDynamicProtocolHandler ff_rfc4175_rtp_handler = { .priv_data_size = sizeof(PayloadContext), .parse_sdp_a_line = rfc4175_parse_sdp_line, .parse_packet = rfc4175_handle_packet, + .close = rfc4175_close_context, }; -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
