CVE-2026-78950
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forthird_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc |
modified |
Files Changed
third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.ccthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.ccthird_party/blink/web_tests/external/wpt/webrtc/RTCPeerConnection-addTransceiver.https.htmlthird_party/blink/web_tests/external/wpt/webrtc/RTCRtpSender-setParameters.html
Patch
From 5207e12e48669e9f2a924cc77d51bcaf57efbedf Mon Sep 17 00:00:00 2001 From: Tommi <[email protected]> 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 <[email protected]> Commit-Queue: Tomas Gunnarsson <[email protected]> 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