CVE-2026-10943
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fpc/peer_connection_jsep_unittest.cc |
modified |
Files Changed
pc/peer_connection_jsep_unittest.ccpc/sdp_offer_answer.cc
Patch
From f685c00979b202fc5b05df337485664e56b6c581 Mon Sep 17 00:00:00 2001 From: Tommi <[email protected]> Date: Wed, 22 Apr 2026 22:10:46 +0200 Subject: [PATCH] Prevent transceiver crash during SDP local rejection Ensure that transceivers remain valid when handling locally rejected media content during SDP updates. Previously, the transceiver was std::move()ed out of the update vector prematurely, which could cause it to be destroyed before associated worker tasks had finished executing. Fixed: chromium:504194151 Change-Id: Id38ce125cbe83a4ea709adfd1951613642cafc8e Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/466140 Reviewed-by: Harald Alvestrand <[email protected]> Commit-Queue: Tomas Gunnarsson <[email protected]> Cr-Commit-Position: refs/heads/main@{#47528} --- diff --git a/pc/peer_connection_jsep_unittest.cc b/pc/peer_connection_jsep_unittest.cc index 104591b..a4ebec6 100644 --- a/pc/peer_connection_jsep_unittest.cc +++ b/pc/peer_connection_jsep_unittest.cc @@ -1031,6 +1031,20 @@ EXPECT_EQ(second_mid, caller_second_transceiver->mid()); } +TEST_F(PeerConnectionJsepTest, LocallyRejectedTransceiverDoesNotCrash) { + auto caller = CreatePeerConnection(); + auto transceiver = caller->AddTransceiver(MediaType::AUDIO); + + ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); + + transceiver->StopInternal(); + + // The reoffer will have a rejected media section. + // Setting it as local description triggers + // MaybeHandleLocallyRejectedTransceiver. + ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer())); +} + // Test that an m= section is *not* recycled if the media section is only // rejected in the pending remote description and there is no current local // description. diff --git a/pc/sdp_offer_answer.cc b/pc/sdp_offer_answer.cc index c3a66d6..09f9ca5 100644 --- a/pc/sdp_offer_answer.cc +++ b/pc/sdp_offer_answer.cc @@ -4311,9 +4311,11 @@ // Handle locally rejected content. This code path is only needed for apps // that SDP munge. Remote rejected content is handled in // ApplyRemoteDescriptionUpdateTransceiverState(). + // Do not use std::move here to ensure that the transceiver stays alive + // in the `transceivers_to_update` vector until the end of this function. + // This guarantees that it outlives the execution of `worker_tasks.Run()`. MaybeHandleLocallyRejectedTransceiver(source, new_session, update.content, - std::move(update.transceiver), - worker_tasks); + update.transceiver, worker_tasks); } error = network_teardown_tasks.Run();
Regression Test / PoC
diff --git a/pc/peer_connection_jsep_unittest.cc b/pc/peer_connection_jsep_unittest.cc
index 104591b..a4ebec6 100644
--- a/pc/peer_connection_jsep_unittest.cc
+++ b/pc/peer_connection_jsep_unittest.cc
@@ -1031,6 +1031,20 @@
EXPECT_EQ(second_mid, caller_second_transceiver->mid());
}
+TEST_F(PeerConnectionJsepTest, LocallyRejectedTransceiverDoesNotCrash) {
+ auto caller = CreatePeerConnection();
+ auto transceiver = caller->AddTransceiver(MediaType::AUDIO);
+
+ ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
+
+ transceiver->StopInternal();
+
+ // The reoffer will have a rejected media section.
+ // Setting it as local description triggers
+ // MaybeHandleLocallyRejectedTransceiver.
+ ASSERT_TRUE(caller->SetLocalDescription(caller->CreateOffer()));
+}
+
// Test that an m= section is *not* recycled if the media section is only
// rejected in the pending remote description and there is no current local
// description.
Original Bug Report
Heap-UAF in RtpSenderBase::DetachTrackAndGetStopTask during SDP rollback
Steps to reproduce the problem
- Create two RTCPeerConnection objects (caller, callee)
- caller.addTransceiver(“audio”)
- offer = await caller.createOffer()
- await callee.setRemoteDescription(offer)
- await callee.setLocalDescription({type:“rollback”, sdp:""})
Step 5 triggers heap-use-after-free at pc/rtp_sender.cc:864.
Tested on WebRTC HEAD commit 67615c4df4 (2026-04-19). 100% reproducible under ASAN with separate threads.
Standalone C++ repro and ASAN stack trace attached as files.
Problem Description
Heap-use-after-free in pc/rtp_sender.cc:864 during SDP rollback.
ROOT CAUSE: SdpOfferAnswerHandler::Rollback() uses ScopedOperationsBatcher to defer GetStopTransceiverProcedure() tasks. DetachTrackAndGetStopTask() returns a lambda capturing raw [this] (RtpSenderBase). During rollback, the transceiver is removed from the stable-states map, dropping the last ref to AudioRtpSender. When the ScopedOperationsBatcher destructor runs the batched lambda on the worker thread, this points to freed heap.
ASAN SUMMARY: READ of size 8, thread T2 (worker) #0 RtpSenderBase::DetachTrackAndGetStopTask()::$_0 rtp_sender.cc:864 freed by thread T3 (signaling) in: #0 AudioRtpSender::Release() #16 SdpOfferAnswerHandler::Rollback()
REGRESSION: 191a6abd692a (Feb 12 2026) “Batch worker thread tasks in [Get]StopTransceiverProcedure” - changed Rollback() from synchronous StopTransceiverProcedure() to deferred GetStopTransceiverProcedure() via ScopedOperationsBatcher. Internal bug: webrtc:42222804 Review: https://webrtc-review.googlesource.com/c/src/+/448460
WEB REACHABLE: Yes - standard RTCPeerConnection JS API, no flags, no user interaction, no extensions. Any webpage can trigger this.
IMPACT: Renderer process heap-UAF. Potential for exploitation via heap grooming to achieve type confusion or controlled read/write.
Additional Comments
Regression: 191a6abd692a (2026-02-12) “Batch worker thread tasks in [Get]StopTransceiverProcedure” - webrtc:42222804
Standalone multi-threaded C++ PoC attached (uaf_repro_standalone.cc). Crashes 100% on first iteration under ASAN. Build with: autoninja -C out/fuzzers test/fuzzers:uaf_repro_standalone Run with: ASAN_OPTIONS=detect_leaks=0 ./out/fuzzers/uaf_repro_standalone
Also found via custom fuzzer (pc_sdp_negotiation_fuzzer) with two independent 4-byte minimal crash inputs attached.
JS equivalent (for Chrome testing): const pc1 = new RTCPeerConnection(); const pc2 = new RTCPeerConnection(); pc1.addTransceiver(“audio”); const o = await pc1.createOffer(); await pc2.setRemoteDescription(o); await pc2.setLocalDescription({type:“rollback”,sdp:""});
Summary
Heap-UAF in RtpSenderBase::DetachTrackAndGetStopTask during SDP rollback
Custom Questions
Type of crash:
Tab
Crash state:
heap-use-after-free READ size 8 at pc/rtp_sender.cc:864 Thread T2 (worker): #0 RtpSenderBase::DetachTrackAndGetStopTask()::$_0 pc/rtp_sender.cc:864 #1 RtpTransceiver::GetStopSendingAndReceiving()::$_0 any_invocable.h:774 #2 ScopedOperationsBatcher::Run()::$_1 any_invocable.h:774 #3 Thread::Dispatch() any_invocable.h:774 #4 Thread::Run() thread.cc:884
Freed by thread T3 (signaling): #0 operator delete #1 AudioRtpSender::Release() ref_counted_object.h:42 #7 RtpTransceiver::~RtpTransceiver() vector.h:259 #16 SdpOfferAnswerHandler::Rollback() tree:1361
Allocated by thread T2 (worker): #1 AudioRtpSender::Create() make_ref_counted.h:90 #7 SdpOfferAnswerHandler::AssociateTransceiver() sdp_offer_answer.cc:4281
Full symbolized ASAN trace attached as stacktrace.txt
Reporter credit:
Rayyan Kadar
Additional Data
Category: Security
Chrome Channel: Dev
Regression: N/A \