CVE-2026-78950
Overview
Background
- `RTCRtpEncodingParameters.maxBitrate`
- a per-encoding upper bound, in bits per second, that a page sets for an outgoing RTP stream, either at creation through
addTransceiver(kind, {sendEncodings})or later throughRTCRtpSender.setParameters(). - `RTCRtpTransceiverInit`
- the dictionary passed to
RTCPeerConnection.addTransceiver(). Blink converts it withToRtpTransceiverInitinto a nativewebrtcinit structure whosesend_encodingsgo to the WebRTC engine. - `ExceptionState::ThrowRangeError`
- the Blink mechanism for raising a JavaScript
RangeError. The patched functions return right after calling it (std::nullopt/EmptyPromise()), so the invalid parameters are never passed on. - WebRTC bitrate configuration
- the native WebRTC code (running in the renderer process) that uses each encoding’s max bitrate to configure encoders and bandwidth allocation.
Root Cause Analysis
Blink’s WebRTC bindings accepted maxBitrate: 0 in sendEncodings (for addTransceiver) and in encodings (for RTCRtpSender::setParameters). They passed it unchecked into the native WebRTC parameter structures. The advisory classifies the result as an integer overflow in WebRTC. The diff does not show the downstream arithmetic, but a zero maximum bitrate breaks the invariant that a configured bitrate cap is a positive value. Code that subtracts from, scales or divides by it can then wrap or produce out-of-range values (inferred).
The fix adds an up-front loop over the supplied encodings in both entry points: if any encoding hasMaxBitrate() with a value of 0, it throws a RangeError and returns early. In the addTransceiver path this check runs before ToRtpTransceiverInit and the existing send_encodings validation. In setParameters it runs before the promise resolver is created and before parameters are handed to the sender. The zero value therefore never reaches native WebRTC code.
maxBitrate of zero and passed it to native bitrate code that does not handle it safely. The patch tightens input validation at both JavaScript entry points so zero is rejected with a RangeError before any conversion or native call.Attack Path
- Page creates a peer connection
ordinary web content constructs an
RTCPeerConnection. No special permission is required for this (the regression tests use a plainnew RTCPeerConnection()). - Supply a zero bitrate cap
the page calls
addTransceiver('audio' or 'video', {sendEncodings: [{maxBitrate: 0}]}), or editssender.getParameters().encodings[0].maxBitrate = 0and callssetParameters(). - Value reaches native WebRTC before the fix, the encoding was converted and passed to the WebRTC sender configuration with a max bitrate of zero.
- Integer overflow in bitrate handling native rate-control or allocation arithmetic that operates on the zero cap overflows or wraps, corrupting derived values in the renderer (inferred; the specific sink is not in the diff).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
forthird_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc |
modified |
Files Changed
third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.htmlthird_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.htmlthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.ccthird_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
Audit Directions
- Zero/degenerate numeric parameters crossing into native media codereview other
RTCRtpEncodingParametersand codec fields (scaleResolutionDownBy,maxFramerate,scalabilityMode-derived values,minBitrateinterplay) that Blink forwards to WebRTC. Make sure zero, negative and extreme values are rejected at the IDL boundary or handled safely natively. - Validation parity across entry pointswhen a parameter can be set both at creation (
addTransceiver,addTrack) and later (setParameters), confirm every path runs the same validation. Also check the native WebRTCRtpSender::SetParameterschecks, so non-Blink callers are covered too. - Arithmetic on bitrate/rate valuesaudit WebRTC bitrate allocators and rate controllers for subtraction, scaling or division on configured caps that assume a positive value. Prefer checked arithmetic or clamping in those places.
Patch
From 5207e12e48669e9f2a924cc77d51bcaf57efbedf Mon Sep 17 00:00:00 2001
From: Tommi <tommi@chromium.org>
Date: Mon, 27 Jul 2026 05:33:07 -0700
Subject: [PATCH] Fix rtp sender max bitrate
Reject maxBitrate = 0 in setParameters and addTransceiver.
Bug: 501881082, 501879826
Change-Id: Ia2f70e2c561deff3ad00595a31089078f8417250
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8148539
Reviewed-by: Guido Urdaneta <guidou@chromium.org>
Commit-Queue: Tomas Gunnarsson <tommi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1668537}
---
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
index c31760f..2256945 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc
@@ -2031,6 +2031,15 @@
ExceptionState& exception_state,
const RTCRtpTransceiverInit* init,
const String kind) {
+ if (init->hasSendEncodings()) {
+ for (const auto& encoding : init->sendEncodings()) {
+ if (encoding->hasMaxBitrate() && encoding->maxBitrate() == 0) {
+ exception_state.ThrowRangeError("maxBitrate must be greater than 0.");
+ return std::nullopt;
+ }
+ }
+ }
+
auto webrtc_init = ToRtpTransceiverInit(execution_context, init, kind);
// Validate sendEncodings.
for (auto& encoding : webrtc_init.send_encodings) {
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
index 8d89499..e76fb187 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
@@ -822,6 +822,16 @@
const RTCSetParameterOptions* options,
ExceptionState& exception_state) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ if (parameters->hasEncodings()) {
+ for (const auto& encoding : parameters->encodings()) {
+ if (encoding->hasMaxBitrate() && encoding->maxBitrate() == 0) {
+ exception_state.ThrowRangeError("maxBitrate must be greater than 0.");
+ return EmptyPromise();
+ }
+ }
+ }
+
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<IDLUndefined>>(
script_state, exception_state.GetContext());
auto promise = resolver->Promise();
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html
index 1a81319c..ed04478 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html
@@ -88,6 +88,14 @@
assert_throws_js(TypeError, () => pc.addTransceiver('invalid'));
}, 'addTransceiver() with string argument as invalid kind should throw TypeError');
+ test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ assert_throws_js(RangeError, () => pc.addTransceiver('audio', {
+ sendEncodings: [{ maxBitrate: 0 }]
+ }));
+ }, 'addTransceiver() with maxBitrate set to 0 should throw RangeError');
+
/*
5.1. addTransceiver
The initial value of mid is null.
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
index 3c03184..4c26c84 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
@@ -56,4 +56,13 @@
}, `setParameters() with already used parameters should reject with InvalidStateError if the event loop has been relinquished`);
}
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const { sender } = pc.addTransceiver('audio');
+ const param = sender.getParameters();
+ param.encodings[0].maxBitrate = 0;
+ await promise_rejects_js(t, RangeError, sender.setParameters(param));
+ }, `setParameters() with maxBitrate set to 0 should reject with RangeError`);
+
</script>
Regression Test / PoC
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html
index 1a81319c..ed04478 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.html
@@ -88,6 +88,14 @@
assert_throws_js(TypeError, () => pc.addTransceiver('invalid'));
}, 'addTransceiver() with string argument as invalid kind should throw TypeError');
+ test(t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ assert_throws_js(RangeError, () => pc.addTransceiver('audio', {
+ sendEncodings: [{ maxBitrate: 0 }]
+ }));
+ }, 'addTransceiver() with maxBitrate set to 0 should throw RangeError');
+
/*
5.1. addTransceiver
The initial value of mid is null.
diff --git a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
index 3c03184..4c26c84 100644
--- a/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
+++ b/third_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
@@ -56,4 +56,13 @@
}, `setParameters() with already used parameters should reject with InvalidStateError if the event loop has been relinquished`);
}
+ promise_test(async t => {
+ const pc = new RTCPeerConnection();
+ t.add_cleanup(() => pc.close());
+ const { sender } = pc.addTransceiver('audio');
+ const param = sender.getParameters();
+ param.encodings[0].maxBitrate = 0;
+ await promise_rejects_js(t, RangeError, sender.setParameters(param));
+ }, `setParameters() with maxBitrate set to 0 should reject with RangeError`);
+
</script>
Original Bug Report
RTCRtpSender.setParameters accepts maxBitrate > UINT32_MAX (MAX_SAFE_INTEGER, -1) without TypeError. WebIDL unsigned long spec violation. uint32→int32 cast is undefined behavior in C++.
Report description
RTCRtpSender.setParameters accepts maxBitrate > UINT32_MAX (MAX_SAFE_INTEGER, -1) without TypeError. WebIDL unsigned long spec violation. uint32→int32 cast is undefined behavior in C++.
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
The problem
Please describe the technical details of the vulnerability
Summary
RTCRtpSender.setParameters() accepts maxBitrate values exceeding the uint32 range without throwing TypeError, violating the WebIDL spec.
The WebRTC spec defines maxBitrate as WebIDL unsigned long, which maps
to uint32_t (0 to 4,294,967,295). Per WebIDL §3.2.5, converting values
outside this range should produce specific results:
- Values > 2^32 should be modulo-reduced (ToUint32)
- NaN should become 0
- Infinity should become 0 (via ToUint32)
However, the actual behavior shows these values are silently accepted and
the truncated/converted values are passed to the internal BitrateAllocator
where max_bitrate_bps is stored as std::optional<int> (signed 32-bit).
Tested Values
| Input | WebIDL ToUint32 | Stored as int32 | Expected | Actual |
|---|---|---|---|---|
| 4294967295 (UINT32_MAX) | 4294967295 | OVERFLOW (UB) | Accept | Accept |
| 4294967296 (UINT32_MAX+1) | 0 | 0 | Accept(0) | Accept |
| MAX_SAFE_INTEGER | 4294967295 | OVERFLOW (UB) | Accept(ff) | Accept |
| Infinity | 0 | 0 | Accept(0) | Accept |
| -1 | 4294967295 | OVERFLOW (UB) | Accept(ff) | Accept |
The critical issue is that std::optional<int> cannot hold 4,294,967,295.
Storing UINT32_MAX in a signed int32 is undefined behavior in C++.
Root Cause
In third_party/webrtc/api/rtp_parameters.h:
max_bitrate_bpsisstd::optional<int>(signed 32-bit)- WebIDL produces uint32 values up to 4,294,967,295
- Assigning uint32 > INT32_MAX to int is undefined behavior
- UBSan would flag: “value 4294967295 is outside the range of representable values of type ‘int’”
Security Impact
- UndefinedBehavior in C++ can be exploited by compilers to optimize away safety checks
- The BitrateAllocator receives a corrupted max_bitrate value, potentially causing integer overflow in bandwidth calculations
- Multiple senders with UINT32_MAX maxBitrate: sum overflows both int32 and uint32 (3 × 4294967295 = 12,884,901,885)
Reproduction
- Open attached
v7_webidl_violation_poc.html - Wait for Phase 1 to complete
- Observe that values > UINT32_MAX are marked [!!!] ACCEPTED
- Phase 2 exercises these values with actual media flow
Suggested Fix
- Change
max_bitrate_bpsfromstd::optional<int>tostd::optional<uint32_t> - OR add range validation in Blink→WebRTC conversion: if (max_bitrate > INT32_MAX) reject with RangeError
Difference from CVE-2026-5912
CVE-2026-5912 is an integer overflow in SDP text parsing (ParseBandwidthLine). This finding is in the setParameters() API path, which is architecturally separate and was NOT addressed by the CVE-2026-5912 fix.
Form Field: Impact Analysis
The WebIDL unsigned long → C++ int32 type mismatch causes undefined behavior when maxBitrate values ≥ 2,147,483,648 are set via the JavaScript API.
Who can exploit: Any JavaScript running in a page with WebRTC access.
What they gain:
- Undefined behavior in the BitrateAllocator — compiler may optimize away subsequent safety checks
- Integer overflow when summing multiple senders’ max_bitrate values
- Combined with the setParameters(Infinity) policy bypass (separate report), provides two independent ways to corrupt bandwidth state
This is a defense-in-depth issue: the type mismatch (uint32→int32) should be fixed regardless of whether an exploitable crash results, as undefined behavior in C++ is a security anti-pattern.
#### Impact analysis
The WebIDL unsigned long to C++ int32 type mismatch causes undefined behavior when maxBitrate values >= 2,147,483,648 are set via the JavaScript API.
Who can exploit: Any JavaScript running in a page with WebRTC access.
What they gain:
1. Undefined behavior in the BitrateAllocator — compiler may optimize away subsequent safety checks
2. Integer overflow when summing multiple senders' max_bitrate values (3 x 4294967295 = 12,884,901,885 — exceeds both int32 and uint32)
3. Combined with the setParameters(Infinity) policy bypass (separate report), provides two independent ways to corrupt bandwidth state
This is a defense-in-depth issue: the type mismatch (uint32 to int32) should be fixed regardless of whether an exploitable crash results, as undefined behavior in C++ is a security anti-pattern.
---
### The cause
#### What version of Chrome have you found the security issue in?
146.0.0.0 [stable] + 147.0.7727.55 [stable]
#### Is the security issue related to a crash?
No, it is not related to a crash.
#### Choose the type of vulnerability
Other
#### How would you like to be publicly acknowledged for your report?
Ashutosh