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 <[email protected]> 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 <[email protected]> Commit-Queue: Johannes Kron <[email protected]> Reviewed-by: Harald Alvestrand <[email protected]> Commit-Queue: Harald Alvestrand <[email protected]> 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).