Medium chrome Integer Overflow 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInteger overflow in WebRTC
DescriptionInteger overflow in WebRTC
ComponentWebRTC
Bug ClassInteger Overflow
Tracker502783118
Fix commit424a6bd0b7f9 (src) +46/-14
CISA KEVNot listed
Creditedngrunbaum
Disclosed2026-09-08

Background

`RtpPacketizer`
WebRTC helper that splits an encoded video frame payload into MTU-sized RTP packets for a given codec format.
`RtpPacketizer::Create`
Factory that dispatches to a codec-specific packetizer (kRaw, kH264, kVp8, kVp9, kAV1, kGeneric) based on the PacketizationFormat.
`SplitAboutEqually`
Utility that divides a payload_size into near-equal packet lengths and casts it to int payload_len for the arithmetic.
`SafeGt`
WebRTC safe-comparison primitive that compares two integers without triggering an overflow or signedness bug.

Root Cause Analysis

Before the fix, the oversized-frame guard (payload_size == 0 || SafeGt(payload_size, limits.max_payload_len * 0x7000)) lived inside SplitAboutEqually, so RtpPacketizer::Create first selected and constructed a codec-specific packetizer and only later reached the size check. This let an unrealistically large payload flow into codec-specific packetization logic that performs its own size arithmetic, where the static_cast<int>(payload_size) and per-packet length math could overflow a signed 32-bit int, violating the invariant that a frame never occupies close to half the RTP sequence-number space.

The fix hoists the identical check to the top of RtpPacketizer::Create, rejecting empty or oversized payloads (>~34MB at ~1.2KB MTU) before any codec path runs. Because callers require a non-null result, an oversized input now returns a benign EmptyRtpPacketizer (via the absl_nonnull return type) that produces zero packets. This protects all packetizer implementations, not just SplitAboutEqually, from the overflow-prone arithmetic.

Key insight
The single mistake was placing the oversized-payload bound check deep inside one helper (SplitAboutEqually) instead of at the RtpPacketizer::Create entry point, so other codec paths reached unchecked integer arithmetic; the fix moves the check earlier so no packetizer ever sees a frame large enough to overflow its length math.

Attack Path

  1. Establish a media session A peer negotiates a WebRTC connection where the local endpoint will encode and packetize outgoing video.
  2. Produce an oversized frame A malformed or adversarial encoder/source yields an encoded video frame whose payload approaches or exceeds the max_payload_len * 0x7000 bound.
  3. Reach the vulnerable factory The frame is handed to RtpPacketizer::Create, which (pre-fix) constructs a codec-specific packetizer before the size guard runs.
  4. Trigger the overflow Codec-specific length arithmetic and the static_cast<int>(payload_size) overflow signed 32-bit int, yielding corrupt packet-length computations.

Impact Assessment

An attacker who can drive an oversized video payload into the packetizer can cause an integer overflow in the RTP packetization arithmetic, leading to incorrect length computations and potential memory-safety consequences within the process handling WebRTC media (the renderer or a media/network utility process). Exploitation requires an active WebRTC session and the ability to supply a frame near the ~34MB bound, which the commit notes is impractical for legitimate use. Severity is rated medium, consistent with a hard-to-reach integer-overflow condition rather than a direct remote code-execution primitive.

Changed Functions

FunctionChangeNotes
EmptyRtpPacketizer
modules/rtp_rtcp/source/rtp_format.cc
modified
switch
modules/rtp_rtcp/source/rtp_format.cc
modified
TEST
modules/rtp_rtcp/source/rtp_format_unittest.cc
modified

Files Changed

  • modules/rtp_rtcp/BUILD.gn
  • modules/rtp_rtcp/source/rtp_format.cc
  • modules/rtp_rtcp/source/rtp_format.h
  • modules/rtp_rtcp/source/rtp_format_unittest.cc

Audit Directions

  • Validate-before-dispatch
    Audit other factory/Create functions that construct codec- or format-specific objects before validating input size, and move bound checks to the earliest entry point.
  • Narrowing casts on attacker-influenced sizes
    Flag static_cast<int> (and similar narrowing) applied to size_t payload lengths in media pipelines, ensuring an upstream SafeGt/range check precedes them.
  • Nullability contracts
    Review functions changed to absl_nonnull for callers that previously assumed a nullable result, and confirm rejection paths return a safe sentinel (like EmptyRtpPacketizer) rather than null.
From 424a6bd0b7f93659204ecccff1d61d63c25937e2 Mon Sep 17 00:00:00 2001
From: Danil Chapovalov <[email protected]>
Date: Wed, 05 Aug 2026 18:47:37 +0200
Subject: [PATCH] Move oversized payload size check earlier in rtp packetizer

To protect more code from unrealistic video frames

Bug: chromium:502783118
Change-Id: I19cbbf84237e93346e515eb3670d561e1bdcefa9
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/494100
Reviewed-by: Åsa Persson <[email protected]>
Commit-Queue: Danil Chapovalov <[email protected]>
Cr-Commit-Position: refs/heads/main@{#48300}
---

diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn
index d5e1bb9..365cb37 100644
--- a/modules/rtp_rtcp/BUILD.gn
+++ b/modules/rtp_rtcp/BUILD.gn
@@ -355,6 +355,7 @@
     "../video_coding:codec_globals_headers",
     "//third_party/abseil-cpp/absl/algorithm:container",
     "//third_party/abseil-cpp/absl/base:core_headers",
+    "//third_party/abseil-cpp/absl/base:nullability",
     "//third_party/abseil-cpp/absl/container:inlined_vector",
     "//third_party/abseil-cpp/absl/functional:any_invocable",
     "//third_party/abseil-cpp/absl/memory",
@@ -1248,6 +1249,8 @@
     deps = [
       ":rtp_format_vp8_test_helper",
       ":rtp_rtcp",
+      ":rtp_video_header",
+      "../../api/video:video_frame_type",
       "../../test:test_support",
       "//third_party/abseil-cpp/absl/algorithm:container",
     ]
diff --git a/modules/rtp_rtcp/source/rtp_format.cc b/modules/rtp_rtcp/source/rtp_format.cc
index 75d00b1..326a1b7 100644
--- a/modules/rtp_rtcp/source/rtp_format.cc
+++ b/modules/rtp_rtcp/source/rtp_format.cc
@@ -16,6 +16,7 @@
 #include <span>
 #include <vector>
 
+#include "absl/base/nullability.h"
 #include "modules/rtp_rtcp/source/rtp_format_h264.h"
 #include "modules/rtp_rtcp/source/rtp_format_video_generic.h"
 #include "modules/rtp_rtcp/source/rtp_format_vp8.h"
@@ -32,13 +33,28 @@
 #endif
 
 namespace webrtc {
+namespace {
+class EmptyRtpPacketizer : public RtpPacketizer {
+ public:
+  size_t NumPackets() const override { return 0; }
+  bool NextPacket(RtpPacketToSend* packet) override { return false; }
+};
+}  // namespace
 
-std::unique_ptr<RtpPacketizer> RtpPacketizer::Create(
+absl_nonnull std::unique_ptr<RtpPacketizer> RtpPacketizer::Create(
     PacketizationFormat format,
     std::span<const uint8_t> payload,
     PayloadSizeLimits limits,
     // Codec-specific details.
     const RTPVideoHeader& rtp_video_header) {
+  if (payload.empty() ||
+      SafeGt(payload.size(), limits.max_payload_len * 0x7000)) {
+    // Do not support frames that are so large they need almost half of the RTP
+    // sequence number space. With MTU ~= 1.2KB that puts a limit of ~34MB on a
+    // single frame. Sending such large frames over RTP is likely impractical.
+    return std::make_unique<EmptyRtpPacketizer>();
+  }
+
   using enum PacketizationFormat;
   switch (format) {
     case kRaw: {
@@ -87,13 +103,6 @@
   RTC_DCHECK_GE(limits.last_packet_reduction_len, 0);
 
   std::vector<int> result;
-  if (payload_size == 0 ||
-      SafeGt(payload_size, limits.max_payload_len * 0x7000)) {
-    // Do not support frames that are so large they need almost half of the RTP
-    // sequence number space. With MTU ~= 1.2KB that puts a limit of ~34MB on a
-    // single frame. Sending such large frames over RTP is likely impractical.
-    return result;
-  }
   int payload_len = static_cast<int>(payload_size);
 
   if (limits.max_payload_len >=
diff --git a/modules/rtp_rtcp/source/rtp_format.h b/modules/rtp_rtcp/source/rtp_format.h
index 214594b..193387d 100644
--- a/modules/rtp_rtcp/source/rtp_format.h
+++ b/modules/rtp_rtcp/source/rtp_format.h
@@ -18,6 +18,7 @@
 #include <span>
 #include <vector>
 
+#include "absl/base/nullability.h"
 #include "modules/rtp_rtcp/source/rtp_video_header.h"
 
 namespace webrtc {
@@ -43,7 +44,7 @@
     kVP9,
     kAV1,
   };
-  static std::unique_ptr<RtpPacketizer> Create(
+  static absl_nonnull std::unique_ptr<RtpPacketizer> Create(
       PacketizationFormat format,
       std::span<const uint8_t> payload,
       PayloadSizeLimits limits,
diff --git a/modules/rtp_rtcp/source/rtp_format_unittest.cc b/modules/rtp_rtcp/source/rtp_format_unittest.cc
index 4cb5153..e32ec1a 100644
--- a/modules/rtp_rtcp/source/rtp_format_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_format_unittest.cc
@@ -10,9 +10,13 @@
 
 #include "modules/rtp_rtcp/source/rtp_format.h"
 
+#include <cstdint>
+#include <memory>
 #include <vector>
 
 #include "absl/algorithm/container.h"
+#include "api/video/video_frame_type.h"
+#include "modules/rtp_rtcp/source/rtp_video_header.h"
 #include "test/gmock.h"
 #include "test/gtest.h"
 
@@ -25,6 +29,7 @@
 using ::testing::IsEmpty;
 using ::testing::Le;
 using ::testing::Not;
+using ::testing::NotNull;
 using ::testing::SizeIs;
 
 // Calculate difference between largest and smallest packets respecting sizes
@@ -231,19 +236,33 @@
   EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), ElementsAre(9, 11));
 }
 
-TEST(RtpPacketizerSplitAboutEqually, RejectsZeroSize) {
+TEST(RtpPacketizerTest, RejectsZeroSize) {
   RtpPacketizer::PayloadSizeLimits limits;
   limits.max_payload_len = 1200;
+  RTPVideoHeader video_header;
+  video_header.frame_type = VideoFrameType::kVideoFrameKey;
 
-  EXPECT_THAT(RtpPacketizer::SplitAboutEqually(0, limits), IsEmpty());
+  std::unique_ptr<RtpPacketizer> packetizer =
+      RtpPacketizer::Create(RtpPacketizer::PacketizationFormat::kGeneric,
+                            /*payload=*/{}, limits, video_header);
+
+  ASSERT_THAT(packetizer, NotNull());
+  EXPECT_EQ(packetizer->NumPackets(), 0u);
 }
 
-TEST(RtpPacketizerSplitAboutEqually, RejectsHugeSize) {
+TEST(RtpPacketizerTest, RejectsHugeSize) {
   RtpPacketizer::PayloadSizeLimits limits;
   limits.max_payload_len = 1200;
+  RTPVideoHeader video_header;
+  video_header.frame_type = VideoFrameType::kVideoFrameKey;
+  const uint8_t kPayload[40'000'000] = {};
 
-  EXPECT_THAT(RtpPacketizer::SplitAboutEqually(0xFFFF'FFFF, limits), IsEmpty());
-  EXPECT_THAT(RtpPacketizer::SplitAboutEqually(40'000'000, limits), IsEmpty());
+  std::unique_ptr<RtpPacketizer> packetizer =
+      RtpPacketizer::Create(RtpPacketizer::PacketizationFormat::kGeneric,
+                            kPayload, limits, video_header);
+
+  ASSERT_THAT(packetizer, NotNull());
+  EXPECT_EQ(packetizer->NumPackets(), 0u);
 }
 
 TEST(RtpPacketizerSplitAboutEqually, RejectsZeroMaxPayloadLen) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/modules/rtp_rtcp/source/rtp_format_unittest.cc b/modules/rtp_rtcp/source/rtp_format_unittest.cc
index 4cb5153..e32ec1a 100644
--- a/modules/rtp_rtcp/source/rtp_format_unittest.cc
+++ b/modules/rtp_rtcp/source/rtp_format_unittest.cc
@@ -10,9 +10,13 @@
 
 #include "modules/rtp_rtcp/source/rtp_format.h"
 
+#include <cstdint>
+#include <memory>
 #include <vector>
 
 #include "absl/algorithm/container.h"
+#include "api/video/video_frame_type.h"
+#include "modules/rtp_rtcp/source/rtp_video_header.h"
 #include "test/gmock.h"
 #include "test/gtest.h"
 
@@ -25,6 +29,7 @@
 using ::testing::IsEmpty;
 using ::testing::Le;
 using ::testing::Not;
+using ::testing::NotNull;
 using ::testing::SizeIs;
 
 // Calculate difference between largest and smallest packets respecting sizes
@@ -231,19 +236,33 @@
   EXPECT_THAT(RtpPacketizer::SplitAboutEqually(20, limits), ElementsAre(9, 11));
 }
 
-TEST(RtpPacketizerSplitAboutEqually, RejectsZeroSize) {
+TEST(RtpPacketizerTest, RejectsZeroSize) {
   RtpPacketizer::PayloadSizeLimits limits;
   limits.max_payload_len = 1200;
+  RTPVideoHeader video_header;
+  video_header.frame_type = VideoFrameType::kVideoFrameKey;
 
-  EXPECT_THAT(RtpPacketizer::SplitAboutEqually(0, limits), IsEmpty());
+  std::unique_ptr<RtpPacketizer> packetizer =
+      RtpPacketizer::Create(RtpPacketizer::PacketizationFormat::kGeneric,
+                            /*payload=*/{}, limits, video_header);
+
+  ASSERT_THAT(packetizer, NotNull());
+  EXPECT_EQ(packetizer->NumPackets(), 0u);
 }
 
-TEST(RtpPacketizerSplitAboutEqually, RejectsHugeSize) {
+TEST(RtpPacketizerTest, RejectsHugeSize) {
   RtpPacketizer::PayloadSizeLimits limits;
   limits.max_payload_len = 1200;
+  RTPVideoHeader video_header;
+  video_header.frame_type = VideoFrameType::kVideoFrameKey;
+  const uint8_t kPayload[40'000'000] = {};
 
-  EXPECT_THAT(RtpPacketizer::SplitAboutEqually(0xFFFF'FFFF, limits), IsEmpty());
-  EXPECT_THAT(RtpPacketizer::SplitAboutEqually(40'000'000, limits), IsEmpty());
+  std::unique_ptr<RtpPacketizer> packetizer =
+      RtpPacketizer::Create(RtpPacketizer::PacketizationFormat::kGeneric,
+                            kPayload, limits, video_header);
+
+  ASSERT_THAT(packetizer, NotNull());
+  EXPECT_EQ(packetizer->NumPackets(), 0u);
 }
 
 TEST(RtpPacketizerSplitAboutEqually, RejectsZeroMaxPayloadLen) {
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.