High chrome OOB 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in WebRTC
DescriptionImproper input validation in WebRTC
ComponentWebRTC
Bug ClassOOB
Tracker570927027
Fix commit3efe1a3a90c1 (src) +163/-117
CISA KEVNot listed
CreditedMozilla
Disclosed2026-10-27

Background

Forward Error Correction (FEC)
WebRTC’s ULPFEC/FlexFEC machinery (modules/rtp_rtcp/source/forward_error_correction.cc) that generates redundant repair packets and reconstructs lost media packets from received FEC packets at the receiver.
Packet mask tables
kPacketMaskBurstyTbl / kPacketMaskRandomTbl: flattened byte arrays encoding the bitmasks that say which media packets each FEC packet protects. The patch pins their exact length (kPacketMaskBurstyTblSize = 741, computed 1 + 12 + 78 + 650).
`packet_masks_` / `tmp_packet_masks_`
fixed-capacity scratch buffers (kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize) that GeneratePacketMasks() fills and that InsertZerosInPacketMasks() rewrites.
`ParseSequenceNumber` / `ParseSsrc`
static helpers that read the 16-bit sequence number (bytes 2–3) and 32-bit SSRC (bytes 8–11) out of an RTP header.
Spanification
the Chromium/WebRTC program of replacing raw uint8_t* + length arithmetic with std::span / std::array, so the length travels with the pointer and bounds are enforced.

Root Cause Analysis

The patch, titled “Spanify and thus harden forward error correction code,” replaces every raw-pointer/memset/memcpy access in the FEC path with bounded std::span/std::array equivalents, which is the tell that the pre-patch code had under-checked memory accesses over attacker-influenced RTP/FEC data. The receiver-side recovery functions are the sharp edge: ParseSequenceNumber(const uint8_t*) and ParseSsrc(const uint8_t*) indexed packet[2..3] and packet[8..11] with no length guarantee, and InitRecovery/FinishPacketRecovery copied fec_packet.protection_length and the recovered-length field read from data[2] into a freshly SetSized buffer using raw memcpy/memset and ByteReader/ByteWriter on bare pointers. A received FEC packet whose header-declared lengths or offsets (fec_header_size, protection_length) exceeded the actual buffer could drive those copies and the ByteReader<uint16_t>::ReadBigEndian(&data[2]) read out of bounds.

The fix threads std::span<const uint8_t> through ParseSequenceNumber/ParseSsrc (via GetBE16/GetBE32 on packet.subspan(2)/subspan(8)), rewrites the recovery copies as data.Set(span.first(kRtpHeaderSize)) and data.Append(span.subspan(fec_header_size, protection_length)), converts the generation scratch buffers packet_masks_/tmp_packet_masks_ from C arrays to std::array, and replaces the memset/memcpy in mask generation with std::ranges::fill/std::ranges::copy over std::span(...).first(num_fec_packets * mask_size). It also converts the static mask tables to constexpr std::array<uint8_t, …> with a static_assert(tbl.back() != 0) so a wrong size is caught at compile time. Because this is a broad spanification, the single exploitable out-of-bounds site is not isolated to one line by the diff (inferred); the advisory labels it “Improper input validation,” which is consistent with an under-validated length but understates that the sink is an out-of-bounds memory access.

Key insight
FEC recovery parsed and copied attacker-supplied packet fields through raw pointers whose lengths were never tied to the backing buffer, so a crafted FEC/RTP packet could read or write past the span of the real data; the fix makes the length inseparable from the pointer via std::span/std::array.

Attack Path

  1. Peer / network attacker establishes or injects into a WebRTC session that negotiates ULPFEC/FlexFEC so the victim’s receiver runs ForwardErrorCorrection decode (inferred: requires FEC negotiated).
  2. Craft FEC packet sends a received FEC packet whose header fields (protection_length, fec_header_size, or the embedded recovered-length at byte offset 2) describe more bytes than the packet actually contains.
  3. Trigger recovery loss/recovery logic enters InitRecovery/FinishPacketRecovery, which SetSizes a buffer and copies based on those attacker lengths.
  4. Out-of-bounds access the raw memcpy/ByteReader over the undersized buffer reads adjacent heap memory into the recovered packet or writes past it.
  5. Primitive out-of-bounds read (potential info leak into recovered media) or out-of-bounds write in the renderer/network process; escalation to further corruption is (inferred).

Impact Assessment

The code runs in whichever process terminates WebRTC media (renderer or a network/utility service, depending on build). No special privilege is needed beyond being a media peer on an FEC-enabled connection, which is remotely reachable. The proven primitive from the diff is an out-of-bounds read/write of a few to a few-hundred bytes around FEC scratch and recovery buffers driven by attacker-controlled header lengths; whether this is weaponizable to RCE versus a crash is not shown by the diff and is (inferred). This is a defensive spanification rather than a fix for one pinpointed overflow, so the exact pre-patch sink is an inference from the removed raw-pointer arithmetic.

Changed Functions

FunctionChangeNotes
if
modules/rtp_rtcp/source/forward_error_correction.cc
modified
for
modules/rtp_rtcp/source/forward_error_correction.cc
modified

Files Changed

  • modules/rtp_rtcp/source/forward_error_correction.cc
  • modules/rtp_rtcp/source/fec_private_tables_bursty.h
  • modules/rtp_rtcp/source/fec_private_tables_bursty.cc
  • modules/rtp_rtcp/source/forward_error_correction_internal.cc
  • unsafe_buffers_paths.txt
  • modules/rtp_rtcp/source/fec_private_tables_random.h
  • modules/rtp_rtcp/source/forward_error_correction.h
  • modules/rtp_rtcp/source/forward_error_correction_internal.h
  • modules/rtp_rtcp/test/testFec/test_fec.cc
  • modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc
  • modules/rtp_rtcp/BUILD.gn
  • modules/rtp_rtcp/source/fec_private_tables_random.cc

Audit Directions

  • Other RTP/RTCP parsers
    audit remaining const uint8_t* header parsers in modules/rtp_rtcp (e.g. RTCP, red/ulpfec header readers) for fixed-offset reads without length checks, the same pattern spanified here.
  • FlexFEC/ULPFEC header readers
    flexfec_03_header_reader_writer and ulpfec_header_reader_writer compute fec_header_size/protection_length; verify those cannot exceed the packet before recovery copies use them.
  • Scratch-buffer fills
    search for memset(buf, 0, count * size) / memcpy over fixed C arrays elsewhere in media where count*size derives from packet-count math and could exceed the array.
  • Compile-time table sizing
    look for other flattened constant tables declared const uint8_t x[] whose consuming code assumes a hard-coded length, now guardable with the static_assert(back() != 0) idiom.
From 3efe1a3a90c14ce339e22c451d48f4f7e831c75b Mon Sep 17 00:00:00 2001
From: Danil Chapovalov <[email protected]>
Date: Tue, 13 Oct 2026 11:00:54 +0200
Subject: [PATCH] Spanify and thus harden forward error correction code

Bug: chromium:570927027
Change-Id: I36cc3771838fc6e0524a4c8a1e37528eb30d769e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/508300
Reviewed-by: Tomas Gunnarsson <[email protected]>
Commit-Queue: Danil Chapovalov <[email protected]>
Reviewed-by: Erik Språng <[email protected]>
Cr-Commit-Position: refs/heads/main@{#48878}
---
diff --git a/modules/rtp_rtcp/source/fec_private_tables_bursty.cc b/modules/rtp_rtcp/source/fec_private_tables_bursty.cc
index 84a4857..52ddbef 100644
--- a/modules/rtp_rtcp/source/fec_private_tables_bursty.cc
+++ b/modules/rtp_rtcp/source/fec_private_tables_bursty.cc
@@ -10,6 +10,7 @@
 
 #include "modules/rtp_rtcp/source/fec_private_tables_bursty.h"
 
+#include <array>
 #include <cstdint>
 
 namespace webrtc {
@@ -643,7 +644,7 @@
 
 namespace fec_private_tables {
 
-const uint8_t kPacketMaskBurstyTbl[] = {
+constexpr std::array<uint8_t, kPacketMaskBurstyTblSize> kPacketMaskBurstyTbl = {
     12,
     kPacketMaskBursty1,
     kPacketMaskBursty2,
@@ -659,5 +660,11 @@
     kPacketMaskBursty12,
 };
 
+// Check size is set correctly. If size is too small, compiler would warn there
+// are excess elements in struct initializer. If size is too large, compiler
+// would initialize unspecfied values with default value 0. Check size is not
+// too large by checking last element wasn't default initialized to 0.
+static_assert(kPacketMaskBurstyTbl.back() != 0);
+
 }  // namespace fec_private_tables
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/fec_private_tables_bursty.h b/modules/rtp_rtcp/source/fec_private_tables_bursty.h
index 217d950..99729ea 100644
--- a/modules/rtp_rtcp/source/fec_private_tables_bursty.h
+++ b/modules/rtp_rtcp/source/fec_private_tables_bursty.h
@@ -25,12 +25,23 @@
 // (i.e., more packets/symbols in the code, so larger (k,m), i.e.,  k > 4,
 // m > 3).
 
-#include <stdint.h>
+#include <array>
+#include <cstddef>
+#include <cstdint>
 
 namespace webrtc {
 namespace fec_private_tables {
 
-extern const uint8_t kPacketMaskBurstyTbl[];
+// Size of the flattened table for up to N=12 media packets.
+// The flattened table keeps 12 square tables sizes from 1 to 12.
+// Sum of all squared numbers N^2 from 1 to 12: 650.
+// Each line in each table is prepended by size.
+// Sum of all numbers N from 1 to 12: 78
+// Each 2d table is prepended by its size, adding 12 more entries.
+// Table itself is prepended by the number of subtables, adding 1 more entry.
+// Total number of entries = 1 + 12 + 78 + 650 = 741.
+inline constexpr size_t kPacketMaskBurstyTblSize = 741;
+extern const std::array<uint8_t, kPacketMaskBurstyTblSize> kPacketMaskBurstyTbl;
 
 }  // namespace fec_private_tables
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/fec_private_tables_random.cc b/modules/rtp_rtcp/source/fec_private_tables_random.cc
index b5cd8bc..88bcb93 100644
--- a/modules/rtp_rtcp/source/fec_private_tables_random.cc
+++ b/modules/rtp_rtcp/source/fec_private_tables_random.cc
@@ -10,6 +10,7 @@
 
 #include "modules/rtp_rtcp/source/fec_private_tables_random.h"
 
+#include <array>
 #include <cstdint>
 
 namespace webrtc {
@@ -643,7 +644,7 @@
 
 namespace fec_private_tables {
 
-const uint8_t kPacketMaskRandomTbl[] = {
+constexpr std::array<uint8_t, kPacketMaskRandomTblSize> kPacketMaskRandomTbl = {
     12,
     kPacketMaskRandom1,  // 2 byte entries.
     kPacketMaskRandom2,
@@ -659,5 +660,11 @@
     kPacketMaskRandom12,
 };
 
+// Check size is set correctly. If size is too small, compiler would warn there
+// are excess elements in struct initializer. If size is too large, compiler
+// would initialize unspecfied values with default value 0. Check size is not
+// too large by checking last element wasn't default initialized to 0.
+static_assert(kPacketMaskRandomTbl.back() != 0);
+
 }  // namespace fec_private_tables
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/fec_private_tables_random.h b/modules/rtp_rtcp/source/fec_private_tables_random.h
index cc7b929..674ff06 100644
--- a/modules/rtp_rtcp/source/fec_private_tables_random.h
+++ b/modules/rtp_rtcp/source/fec_private_tables_random.h
@@ -15,12 +15,23 @@
 // this table are specifically designed to favor recovery to random loss.
 // These packet masks are defined to protect up to maximum of 48 media packets.
 
-#include <stdint.h>
+#include <array>
+#include <cstddef>
+#include <cstdint>
 
 namespace webrtc {
 namespace fec_private_tables {
 
-extern const uint8_t kPacketMaskRandomTbl[];
+// Size of the flattened table for up to N=12 media packets.
+// The flattened table keeps 12 square tables sizes from 1 to 12.
+// Sum of all squared numbers N^2 from 1 to 12: 650.
+// Each line in each table is prepended by its size.
+// Sum of all numbers N from 1 to 12: 78
+// Each 2d table is prepended by its size, adding 12 more entries.
+// Table itself is prepended by the number of subtables, adding 1 more entry.
+// Total number of entries = 1 + 12 + 78 + 650 = 741.
+inline constexpr size_t kPacketMaskRandomTblSize = 741;
+extern const std::array<uint8_t, kPacketMaskRandomTblSize> kPacketMaskRandomTbl;
 
 }  // namespace fec_private_tables
 }  // namespace webrtc
diff --git a/modules/rtp_rtcp/source/forward_error_correction.cc b/modules/rtp_rtcp/source/forward_error_correction.cc
index 0bb81a8..0e31bf1 100644
--- a/modules/rtp_rtcp/source/forward_error_correction.cc
+++ b/modules/rtp_rtcp/source/forward_error_correction.cc
@@ -15,6 +15,7 @@
 #include <cstring>
 #include <list>
 #include <memory>
+#include <span>
 #include <utility>
 #include <vector>
 
@@ -26,6 +27,7 @@
 #include "modules/rtp_rtcp/source/flexfec_03_header_reader_writer.h"
 #include "modules/rtp_rtcp/source/forward_error_correction_internal.h"
 #include "modules/rtp_rtcp/source/ulpfec_header_reader_writer.h"
+#include "rtc_base/byte_order.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
 #include "rtc_base/numerics/mod_ops.h"
@@ -167,7 +169,9 @@
 
   internal::PacketMaskTable mask_table(fec_mask_type, num_media_packets);
   packet_mask_size_ = internal::PacketMaskSize(num_media_packets);
-  memset(packet_masks_, 0, num_fec_packets * packet_mask_size_);
+  std::ranges::fill(
+      std::span(packet_masks_).first(num_fec_packets * packet_mask_size_), 0);
+
   internal::GeneratePacketMasks(num_media_packets, num_fec_packets,
                                 num_important_packets, use_unequal_protection,
                                 &mask_table, packet_masks_);
@@ -186,9 +190,9 @@
   GenerateFecPayloads(media_packets, num_fec_packets);
   // TODO(brandtr): Generalize this when multistream protection support is
   // added.
-  const uint32_t media_ssrc = ParseSsrc(media_packets.front()->data.data());
+  const uint32_t media_ssrc = ParseSsrc(media_packets.front()->data);
   const uint16_t seq_num_base =
-      ParseSequenceNumber(media_packets.front()->data.data());
+      ParseSequenceNumber(media_packets.front()->data);
   FinalizeFecHeaders(num_fec_packets, media_ssrc, seq_num_base);
 
   return 0;
@@ -220,8 +224,7 @@
 
     size_t media_pkt_idx = 0;
     auto media_packets_it = media_packets.cbegin();
-    uint16_t prev_seq_num =
-        ParseSequenceNumber((*media_packets_it)->data.data());
+    uint16_t prev_seq_num = ParseSequenceNumber((*media_packets_it)->data);
     while (media_packets_it != media_packets.end()) {
       Packet* const media_packet = media_packets_it->get();
       // Should `media_packet` be protected by `fec_packet`?
@@ -233,8 +236,8 @@
         if (fec_packet_length > fec_packet->data.size()) {
           size_t old_size = fec_packet->data.size();
           fec_packet->data.SetSize(fec_packet_length);
-          memset(fec_packet->data.MutableData() + old_size, 0,
-                 fec_packet_length - old_size);
+          std::ranges::fill(fec_packet->data.AsMutableSpan().subspan(old_size),
+                            0);
         }
         XorHeaders(*media_packet, fec_packet);
         XorPayloads(*media_packet, media_payload_length, fec_header_size,
@@ -242,8 +245,7 @@
       }
       media_packets_it++;
       if (media_packets_it != media_packets.end()) {
-        uint16_t seq_num =
-            ParseSequenceNumber((*media_packets_it)->data.data());
+        uint16_t seq_num = ParseSequenceNumber((*media_packets_it)->data);
         media_pkt_idx += static_cast<uint16_t>(seq_num - prev_seq_num);
         prev_seq_num = seq_num;
       }
@@ -262,10 +264,8 @@
   if (num_media_packets <= 1) {
     return num_media_packets;
   }
-  uint16_t last_seq_num =
-      ParseSequenceNumber(media_packets.back()->data.data());
-  uint16_t first_seq_num =
-      ParseSequenceNumber(media_packets.front()->data.data());
+  uint16_t last_seq_num = ParseSequenceNumber(media_packets.back()->data);
+  uint16_t first_seq_num = ParseSequenceNumber(media_packets.front()->data);
   size_t total_missing_seq_nums =
       static_cast<uint16_t>(last_seq_num - first_seq_num) - num_media_packets +
       1;
@@ -281,7 +281,9 @@
   // Allocate the new mask.
   size_t tmp_packet_mask_size =
       internal::PacketMaskSize(total_missing_seq_nums + num_media_packets);
-  memset(tmp_packet_masks_, 0, num_fec_packets * tmp_packet_mask_size);
+  std::ranges::fill(std::span(tmp_packet_masks_)
+                        .first(num_fec_packets * tmp_packet_mask_size),
+                    0);
 
   auto media_packets_it = media_packets.cbegin();
   uint16_t prev_seq_num = first_seq_num;
@@ -298,7 +300,7 @@
       // We can only cover up to 48 packets.
       break;
     }
-    uint16_t seq_num = ParseSequenceNumber((*media_packets_it)->data.data());
+    uint16_t seq_num = ParseSequenceNumber((*media_packets_it)->data);
     const int num_zeros_to_insert =
         static_cast<uint16_t>(seq_num - prev_seq_num - 1);
     if (num_zeros_to_insert > 0) {
@@ -323,8 +325,9 @@
     }
   }
   // Replace the old mask with the new.
-  memcpy(packet_masks_, tmp_packet_masks_,
-         num_fec_packets * tmp_packet_mask_size);
+  std::ranges::copy(std::span(tmp_packet_masks_)
+                        .first(num_fec_packets * tmp_packet_mask_size),
+                    packet_masks_.begin());
   return new_bit_index;
 }
 
@@ -565,20 +568,19 @@
   }
   // Initialize recovered packet data.
   recovered_packet->pkt->data.EnsureCapacity(IP_PACKET_SIZE);
-  recovered_packet->pkt->data.SetSize(fec_packet.protection_length +
-                                      kRtpHeaderSize);
+
   recovered_packet->returned = false;
   recovered_packet->was_recovered = true;
   // Copy bytes corresponding to minimum RTP header size.
   // Note that the sequence number and SSRC fields will be overwritten
   // at the end of packet recovery.
-  memcpy(recovered_packet->pkt->data.MutableData(),
-         fec_packet.pkt->data.cdata(), kRtpHeaderSize);
+  recovered_packet->pkt->data.Set(
+      std::span(fec_packet.pkt->data).first(kRtpHeaderSize));
   // Copy remaining FEC payload.
   if (fec_packet.protection_length > 0) {
-    memcpy(recovered_packet->pkt->data.MutableData() + kRtpHeaderSize,
-           fec_packet.pkt->data.cdata() + fec_packet.fec_header_size,
-           fec_packet.protection_length);
+    recovered_packet->pkt->data.Append(
+        std::span(fec_packet.pkt->data)
+            .subspan(fec_packet.fec_header_size, fec_packet.protection_length));
   }
   return true;
 }
@@ -586,13 +588,12 @@
 bool ForwardErrorCorrection::FinishPacketRecovery(
     const ReceivedFecPacket& /* fec_packet */,
     RecoveredPacket* recovered_packet) {
-  uint8_t* data = recovered_packet->pkt->data.MutableData();
+  std::span<uint8_t> data = recovered_packet->pkt->data.AsMutableSpan();
   // Set the RTP version to 2.
   data[0] |= 0x80;  // Set the 1st bit.
   data[0] &= 0xbf;  // Clear the 2nd bit.
   // Recover the packet length, from temporary location.
-  const size_t new_size =
-      ByteReader<uint16_t>::ReadBigEndian(&data[2]) + kRtpHeaderSize;
+  const size_t new_size = GetBE16(data.subspan(2)) + kRtpHeaderSize;
   if (new_size > size_t{IP_PACKET_SIZE - kRtpHeaderSize}) {
     RTC_LOG(LS_WARNING) << "The recovered packet had a length larger than a "
                            "typical IP packet, and is thus dropped.";
@@ -600,21 +601,21 @@
   }
   size_t old_size = recovered_packet->pkt->data.size();
   recovered_packet->pkt->data.SetSize(new_size);
-  data = recovered_packet->pkt->data.MutableData();
+  data = recovered_packet->pkt->data.AsMutableSpan();
   if (new_size > old_size) {
-    memset(data + old_size, 0, new_size - old_size);
+    std::ranges::fill(data.subspan(old_size), 0);
   }
 
   // Set the SN field.
-  ByteWriter<uint16_t>::WriteBigEndian(&data[2], recovered_packet->seq_num);
+  SetBE16(data.subspan(2), recovered_packet->seq_num);
   // Set the SSRC field.
-  ByteWriter<uint32_t>::WriteBigEndian(&data[8], recovered_packet->ssrc);
+  SetBE32(data.subspan(8), recovered_packet->ssrc);
   return true;
 }
 
 void ForwardErrorCorrection::XorHeaders(const Packet& src, Packet* dst) {
-  uint8_t* dst_data = dst->data.MutableData();
-  const uint8_t* src_data = src.data.cdata();
+  std::span<uint8_t> dst_data = dst->data.AsMutableSpan();
+  std::span<const uint8_t> src_data = src.data;
   // XOR the first 2 bytes of the header: V, P, X, CC, M, PT fields.
   dst_data[0] ^= src_data[0];
   dst_data[1] ^= src_data[1];
@@ -644,12 +645,11 @@
   RTC_DCHECK_LE(dst_offset + payload_length, dst->data.capacity());
   if (dst_offset + payload_length > dst->data.size()) {
     size_t old_size = dst->data.size();
-    size_t new_size = dst_offset + payload_length;
-    dst->data.SetSize(new_size);
-    memset(dst->data.MutableData() + old_size, 0, new_size - old_size);
+    dst->data.SetSize(dst_offset + payload_length);
+    std::ranges::fill(dst->data.AsMutableSpan().subspan(old_size), 0);
   }
-  uint8_t* dst_data = dst->data.MutableData();
-  const uint8_t* src_data = src.data.cdata();
+  std::span<uint8_t> dst_data = dst->data.AsMutableSpan();
+  std::span<const uint8_t> src_data = src.data;
   for (size_t i = 0; i < payload_length; ++i) {
     dst_data[dst_offset + i] ^= src_data[kRtpHeaderSize + i];
   }
@@ -774,12 +774,13 @@
           kOldSequenceThreshold);
 }
 
-uint16_t ForwardErrorCorrection::ParseSequenceNumber(const uint8_t* packet) {
-  return (packet[2] << 8) + packet[3];
+uint16_t ForwardErrorCorrection::ParseSequenceNumber(
+    std::span<const uint8_t> packet) {
+  return GetBE16(packet.subspan(2));
 }
 
-uint32_t ForwardErrorCorrection::ParseSsrc(const uint8_t* packet) {
-  return (packet[8] << 24) + (packet[9] << 16) + (packet[10] << 8) + packet[11];
+uint32_t ForwardErrorCorrection::ParseSsrc(std::span<const uint8_t> packet) {
+  return GetBE32(packet.subspan(8));
 }
 
 ForwardErrorCorrection::DecodeFecResult ForwardErrorCorrection::DecodeFec(
diff --git a/modules/rtp_rtcp/source/forward_error_correction.h b/modules/rtp_rtcp/source/forward_error_correction.h
index fcfa641..a50064bc 100644
--- a/modules/rtp_rtcp/source/forward_error_correction.h
+++ b/modules/rtp_rtcp/source/forward_error_correction.h
@@ -14,6 +14,7 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include <array>
 #include <list>
 #include <memory>
 #include <span>
@@ -250,8 +251,8 @@
 
   // TODO(brandtr): Remove these functions when the Packet classes
   // have been refactored.
-  static uint16_t ParseSequenceNumber(const uint8_t* packet);
-  static uint32_t ParseSsrc(const uint8_t* packet);
+  static uint16_t ParseSequenceNumber(std::span<const uint8_t> packet);
+  static uint32_t ParseSsrc(std::span<const uint8_t> packet);
 
  protected:
   ForwardErrorCorrection(std::unique_ptr<FecHeaderReader> fec_header_reader,
@@ -363,8 +364,10 @@
   // Arrays used to avoid dynamically allocating memory when generating
   // the packet masks.
   // (There are never more than `kUlpfecMaxMediaPackets` FEC packets generated.)
-  uint8_t packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize];
-  uint8_t tmp_packet_masks_[kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize];
+  std::array<uint8_t, kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize>
+      packet_masks_;
+  std::array<uint8_t, kUlpfecMaxMediaPackets * kUlpfecMaxPacketMaskSize>
+      tmp_packet_masks_;
   size_t packet_mask_size_;
 };
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc b/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc
index b14e2fb..bda185c 100644
--- a/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc
+++ b/modules/rtp_rtcp/source/fec_private_tables_bursty_unittest.cc
@@ -10,6 +10,7 @@
 
 #include "modules/rtp_rtcp/source/fec_private_tables_bursty.h"
 
+#include <array>
 #include <cstddef>
 #include <cstdint>
 #include <span>
@@ -21,8 +22,8 @@
 
 namespace webrtc {
 namespace {
-constexpr uint8_t kMaskRandom15_6[] = {0x82, 0x08, 0x41, 0x04, 0x20, 0x82,
-                                       0x10, 0x40, 0x08, 0x20, 0x04, 0x10};
+constexpr std::array<uint8_t, 12> kMaskRandom15_6 = {
+    0x82, 0x08, 0x41, 0x04, 0x20, 0x82, 0x10, 0x40, 0x08, 0x20, 0x04, 0x10};
 }  // namespace
 
 namespace fec_private_tables {
@@ -31,24 +32,24 @@
 
 TEST(FecTable, TestBurstyLookup) {
   std::span<const uint8_t> result;
-  result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 0, 0);
+  result = LookUpInFecTable(kPacketMaskBurstyTbl, 0, 0);
   // Should match kMaskBursty1_1.
   EXPECT_EQ(2u, result.size());
   EXPECT_EQ(0x80u, result[0]);
 
-  result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 3, 0);
+  result = LookUpInFecTable(kPacketMaskBurstyTbl, 3, 0);
   // Should match kMaskBursty4_1.
   EXPECT_EQ(2u, result.size());
   EXPECT_EQ(0xf0u, result[0]);
   EXPECT_EQ(0x00u, result[1]);
 
-  result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 1, 1);
+  result = LookUpInFecTable(kPacketMaskBurstyTbl, 1, 1);
   // Should match kMaskBursty2_2.
   EXPECT_EQ(4u, result.size());
   EXPECT_EQ(0x80u, result[0]);
   EXPECT_EQ(0xc0u, result[2]);
 
-  result = LookUpInFecTable(&kPacketMaskBurstyTbl[0], 11, 11);
+  result = LookUpInFecTable(kPacketMaskBurstyTbl, 11, 11);
   // Should match kMaskBursty12_12.
   EXPECT_EQ(24u, result.size());
   EXPECT_EQ(0x80u, result[0]);
@@ -57,12 +58,12 @@
 
 TEST(FecTable, TestRandomLookup) {
   std::span<const uint8_t> result;
-  result = LookUpInFecTable(&kPacketMaskRandomTbl[0], 0, 0);
+  result = LookUpInFecTable(kPacketMaskRandomTbl, 0, 0);
   EXPECT_EQ(2u, result.size());
   EXPECT_EQ(0x80u, result[0]);
   EXPECT_EQ(0x00u, result[1]);
 
-  result = LookUpInFecTable(&kPacketMaskRandomTbl[0], 4, 1);
+  result = LookUpInFecTable(kPacketMaskRandomTbl, 4, 1);
   // kMaskRandom5_2.
   EXPECT_EQ(4u, result.size());
   EXPECT_EQ(0xa8u, result[0]);
diff --git a/modules/rtp_rtcp/test/testFec/test_fec.cc b/modules/rtp_rtcp/test/testFec/test_fec.cc
index 067fa0c..2f66abf 100644
--- a/modules/rtp_rtcp/test/testFec/test_fec.cc
+++ b/modules/rtp_rtcp/test/testFec/test_fec.cc
@@ -13,12 +13,14 @@
  * functions in ForwardErrorCorrection directly.
  */
 
+#include <algorithm>
 #include <cstdint>
 #include <cstdio>
 #include <cstring>
 #include <ctime>
 #include <list>
 #include <memory>
+#include <span>
 #include <string>
 #include <utility>
 #include <vector>
@@ -168,8 +170,7 @@
              mask_type_idx);
 
       const uint32_t packet_mask_max = kMaxMediaPackets[mask_type_idx];
-      std::unique_ptr<uint8_t[]> packet_mask(
-          new uint8_t[packet_mask_max * kNumMaskBytesL1]);
+      std::vector<uint8_t> packet_mask(packet_mask_max * kNumMaskBytesL1);
 
       FecMaskType fec_mask_type = kMaskTypes[mask_type_idx];
 
@@ -194,13 +195,15 @@
             const uint32_t mask_bytes_per_fec_packet =
                 (num_media_packets > 16) ? kNumMaskBytesL1 : kNumMaskBytesL0;
 
-            memset(packet_mask.get(), 0,
-                   num_media_packets * mask_bytes_per_fec_packet);
+            std::ranges::fill(
+                std::span(packet_mask)
+                    .first(num_media_packets * mask_bytes_per_fec_packet),
+                0);
 
             // Transfer packet masks from bit-mask to byte-mask.
             internal::GeneratePacketMasks(
                 num_media_packets, num_fec_packets, num_imp_packets,
-                kUseUnequalProtection, &mask_table, packet_mask.get());
+                kUseUnequalProtection, &mask_table, packet_mask);
 
 #ifdef VERBOSE_OUTPUT
             printf(
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.