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]

Reply via email to