CVE-2026-7336
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TESTcall/rtp_video_sender_unittest.cc |
modified |
Files Changed
call/BUILD.gncall/rtp_video_sender.cccall/rtp_video_sender_unittest.cc
Patch
From 77d265670fa7d7f358c86c54704a6e97c27a6005 Mon Sep 17 00:00:00 2001
From: Harald Alvestrand <hta@webrtc.org>
Date: Fri, 10 Apr 2026 11:52:19 +0000
Subject: [PATCH] Fix TOCTOU race in RtpVideoSender::OnVideoLayersAllocationUpdated
RtpVideoSender::OnVideoLayersAllocationUpdated runs on the encoder thread,
checks if it's active, and then posts a task to the transport queue to
update module status.
If SetSending(false) is called on the transport queue before the posted
task runs, the task would previously re-register modules in PacketRouter
even though active_ was false. This lead to dangling pointers in
PacketRouter when the RtpVideoSender was destroyed.
This CL adds an IsActive() check inside the posted task to ensure we don't
re-register modules if the sender has been deactivated.
Bug: chromium:500767595
Test: RtpVideoSenderTest.PostTaskRaceDoesNotLeadToDanglingPointer
Change-Id: I18827c34bece7f5ebc41d159d9e19e356afbc404
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/462880
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#47384}
---
diff --git a/call/BUILD.gn b/call/BUILD.gn
index 815d28a..a784332 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -550,6 +550,7 @@
"../api/video:video_codec_constants",
"../api/video:video_frame",
"../api/video:video_frame_type",
+ "../api/video:video_layers_allocation",
"../api/video:video_rtp_headers",
"../api/video_codecs:video_codecs_api",
"../audio",
diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
index 73e8898..7267add 100644
--- a/call/rtp_video_sender.cc
+++ b/call/rtp_video_sender.cc
@@ -695,6 +695,11 @@
transport_queue_.PostTask(
SafeTask(safety_.flag(), [this, sending = std::move(sending)] {
RTC_DCHECK_RUN_ON(&transport_checker_);
+ // It's possible for another task to be scheduled on the transport
+ // checker ahead of this call that makes the sender not active.
+ if (!IsActive()) {
+ return;
+ }
RTC_CHECK_EQ(sending.size(), rtp_streams_.size());
for (size_t i = 0; i < sending.size(); ++i) {
SetModuleIsActive(sending[i], *rtp_streams_[i].rtp_rtcp);
diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc
index a979e05..d7d30df 100644
--- a/call/rtp_video_sender_unittest.cc
+++ b/call/rtp_video_sender_unittest.cc
@@ -40,6 +40,7 @@
#include "api/video/encoded_image.h"
#include "api/video/video_codec_type.h"
#include "api/video/video_frame_type.h"
+#include "api/video/video_layers_allocation.h"
#include "api/video_codecs/video_encoder.h"
#include "call/rtp_config.h"
#include "call/rtp_transport_config.h"
@@ -1607,4 +1608,73 @@
EXPECT_EQ(retransmitted_rtp_sequence_numbers, base_rtp_sequence_numbers);
}
+TEST(RtpVideoSenderTest, PostTaskRaceDoesNotLeadToDanglingPointer) {
+ NiceMock<MockTransport> transport;
+ NiceMock<MockRtcpIntraFrameObserver> encoder_feedback;
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1000000));
+ Environment env = CreateEnvironment(time_controller.GetClock(),
+ time_controller.CreateTaskQueueFactory());
+
+ VideoSendStream::Config config(&transport);
+ config.rtp.ssrcs = {kSsrc1};
+
+ SendStatisticsProxy stats_proxy(
+ time_controller.GetClock(), config,
+ VideoEncoderConfig::ContentType::kRealtimeVideo, env.field_trials());
+
+ BitrateConstraints bitrate_config = GetBitrateConfig();
+ RtpTransportConfig transport_config{.env = env,
+ .bitrate_config = bitrate_config};
+ RtpTransportControllerSend transport_controller(transport_config);
+ transport_controller.EnsureStarted();
+
+ RateLimiter retransmission_rate_limiter(time_controller.GetClock(),
+ kRetransmitWindowSizeMs);
+
+ std::map<uint32_t, RtpState> suspended_ssrcs;
+ std::map<uint32_t, RtpPayloadState> suspended_payload_states;
+
+ auto router = std::make_unique<RtpVideoSender>(
+ env, time_controller.GetMainThread(), suspended_ssrcs,
+ suspended_payload_states, config.rtp, config.rtcp_report_interval_ms,
+ &transport,
+ CreateObservers(&encoder_feedback, &stats_proxy, &stats_proxy,
+ &stats_proxy, nullptr, &stats_proxy),
+ &transport_controller, &retransmission_rate_limiter,
+ std::make_unique<FecControllerDefault>(env), nullptr, CryptoOptions{},
+ nullptr);
+
+ router->SetSending(true);
+ // Verify it's registered
+ EXPECT_TRUE(
+ transport_controller.packet_router()->SsrcOfFirstSender().has_value());
+
+ // Trigger race:
+ // 1. OnVideoLayersAllocationUpdated posts a task to re-register modules
+ // because it sees active_ is true.
+ VideoLayersAllocation allocation;
+ allocation.active_spatial_layers.push_back({.rtp_stream_index = 0});
+ router->OnVideoLayersAllocationUpdated(allocation);
+
+ // 2. Immediately set active_ to false. This happens BEFORE the posted task
+ // runs.
+ router->SetSending(false);
+
+ // 3. Let the posted task run. With the fix, it should return early and not
+ // re-register the module because active_ is now false.
+ time_controller.AdvanceTime(TimeDelta::Zero());
+
+ // 4. Verify that PacketRouter does NOT have a sender.
+ EXPECT_FALSE(
+ transport_controller.packet_router()->SsrcOfFirstSender().has_value());
+
+ // 5. Destroy the router.
+ router.reset();
+
+ // 6. Verify that PacketRouter still does not have a sender.
+ EXPECT_FALSE(
+ transport_controller.packet_router()->SsrcOfFirstSender().has_value());
+}
+
} // namespace webrtc
Regression Test / PoC
diff --git a/call/rtp_video_sender_unittest.cc b/call/rtp_video_sender_unittest.cc
index a979e05..d7d30df 100644
--- a/call/rtp_video_sender_unittest.cc
+++ b/call/rtp_video_sender_unittest.cc
@@ -40,6 +40,7 @@
#include "api/video/encoded_image.h"
#include "api/video/video_codec_type.h"
#include "api/video/video_frame_type.h"
+#include "api/video/video_layers_allocation.h"
#include "api/video_codecs/video_encoder.h"
#include "call/rtp_config.h"
#include "call/rtp_transport_config.h"
@@ -1607,4 +1608,73 @@
EXPECT_EQ(retransmitted_rtp_sequence_numbers, base_rtp_sequence_numbers);
}
+TEST(RtpVideoSenderTest, PostTaskRaceDoesNotLeadToDanglingPointer) {
+ NiceMock<MockTransport> transport;
+ NiceMock<MockRtcpIntraFrameObserver> encoder_feedback;
+
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(1000000));
+ Environment env = CreateEnvironment(time_controller.GetClock(),
+ time_controller.CreateTaskQueueFactory());
+
+ VideoSendStream::Config config(&transport);
+ config.rtp.ssrcs = {kSsrc1};
+
+ SendStatisticsProxy stats_proxy(
+ time_controller.GetClock(), config,
+ VideoEncoderConfig::ContentType::kRealtimeVideo, env.field_trials());
+
+ BitrateConstraints bitrate_config = GetBitrateConfig();
+ RtpTransportConfig transport_config{.env = env,
+ .bitrate_config = bitrate_config};
+ RtpTransportControllerSend transport_controller(transport_config);
+ transport_controller.EnsureStarted();
+
+ RateLimiter retransmission_rate_limiter(time_controller.GetClock(),
+ kRetransmitWindowSizeMs);
+
+ std::map<uint32_t, RtpState> suspended_ssrcs;
+ std::map<uint32_t, RtpPayloadState> suspended_payload_states;
+
+ auto router = std::make_unique<RtpVideoSender>(
+ env, time_controller.GetMainThread(), suspended_ssrcs,
+ suspended_payload_states, config.rtp, config.rtcp_report_interval_ms,
+ &transport,
+ CreateObservers(&encoder_feedback, &stats_proxy, &stats_proxy,
+ &stats_proxy, nullptr, &stats_proxy),
+ &transport_controller, &retransmission_rate_limiter,
+ std::make_unique<FecControllerDefault>(env), nullptr, CryptoOptions{},
+ nullptr);
+
+ router->SetSending(true);
+ // Verify it's registered
+ EXPECT_TRUE(
+ transport_controller.packet_router()->SsrcOfFirstSender().has_value());
+
+ // Trigger race:
+ // 1. OnVideoLayersAllocationUpdated posts a task to re-register modules
+ // because it sees active_ is true.
+ VideoLayersAllocation allocation;
+ allocation.active_spatial_layers.push_back({.rtp_stream_index = 0});
+ router->OnVideoLayersAllocationUpdated(allocation);
+
+ // 2. Immediately set active_ to false. This happens BEFORE the posted task
+ // runs.
+ router->SetSending(false);
+
+ // 3. Let the posted task run. With the fix, it should return early and not
+ // re-register the module because active_ is now false.
+ time_controller.AdvanceTime(TimeDelta::Zero());
+
+ // 4. Verify that PacketRouter does NOT have a sender.
+ EXPECT_FALSE(
+ transport_controller.packet_router()->SsrcOfFirstSender().has_value());
+
+ // 5. Destroy the router.
+ router.reset();
+
+ // 6. Verify that PacketRouter still does not have a sender.
+ EXPECT_FALSE(
+ transport_controller.packet_router()->SsrcOfFirstSender().has_value());
+}
+
} // namespace webrtc
Original Bug Report
Heap-use-after-free in [@ webrtc::PacketRouter::SendCombinedRtcpPacket] via TOCTOU in RtpVideoSender::OnVideoLayersAllocationUpdated
Security Bug
As reported on the Firefox bugzilla.
RtpVideoSender::OnVideoLayersAllocationUpdated runs on the encoder thread, takes mutex_, checks IsActiveLocked(), and then posts a SafeTask to the transport queue that calls SetModuleIsActive(sending[i], rtp_streams_[i].rtp_rtcp) without re-validating active_. If SetSending(false) executes on the transport queue between the encoder’s check and the posted task’s execution, it sets active_=false and deregisters all RTP modules from PacketRouter. The stale SafeTask then runs and re-registers the raw ModuleRtpRtcpImpl2 via transport_->RegisterSendingRtpStream() while active_ is already false.
When the RtpVideoSender is subsequently destroyed (e.g. when the send stream is recreated after setting an RTCRtpScriptTransform), ~RtpVideoSender calls SetActiveModulesLocked(false), which early-returns at line 510 because active_ is already false. The re-registered module is therefore never removed from PacketRouter::send_modules_list_, and rtp_streams_ is destroyed, freeing the ModuleRtpRtcpImpl2. PacketRouter is owned by RtpTransportControllerSend and outlives the RtpVideoSender, so it now holds a dangling pointer.
The next periodic transport-cc feedback tick invokes PacketRouter::SendCombinedRtcpPacket, which iterates send_modules_list_ and performs a virtual call rtp_module->RTCP() on the freed object, reading the vtable from freed heap memory. The code path is reachable from unprivileged web content by SDP-munging the video-layers-allocation00 RTP header extension into the negotiated session (Firefox’s JSEP layer accepts the added extmap), using only canvas.captureStream() and standard RTCPeerConnection APIs with no permission prompt. The natural race window is narrow; a usleep(100000) inside the locked region was used to demonstrate it deterministically without altering program semantics.
Build Info
- Branch: main
- Revision: 6164ea4bacaeaed1f617c11911df7fc32f2e6ec2
- Timestamp: 2026-04-02T19:36:24+00:00
Affected Code
File: third_party/libwebrtc/call/rtp_video_sender.cc, line 669-700
void RtpVideoSender::OnVideoLayersAllocationUpdated(
const VideoLayersAllocation& allocation) {
MutexLock lock(&mutex_);
if (IsActiveLocked()) {
for (size_t i = 0; i < rtp_streams_.size(); ++i) {
VideoLayersAllocation stream_allocation = allocation;
stream_allocation.rtp_stream_index = i;
rtp_streams_[i].sender_video->SetVideoLayersAllocation(
std::move(stream_allocation));
}
...
transport_queue_.PostTask(
SafeTask(safety_.flag(), [this, sending = std::move(sending)] {
RTC_DCHECK_RUN_ON(&transport_checker_);
RTC_CHECK_EQ(sending.size(), rtp_streams_.size());
for (size_t i = 0; i < sending.size(); ++i) {
SetModuleIsActive(sending[i], *rtp_streams_[i].rtp_rtcp); // no re-check of active_
}
}));
}
}
File: third_party/libwebrtc/call/rtp_video_sender.cc, line 494-523
RtpVideoSender::~RtpVideoSender() {
RTC_DCHECK_RUN_ON(&transport_checker_);
SetActiveModulesLocked(/*sending=*/false);
}
void RtpVideoSender::SetSending(bool enabled) {
RTC_DCHECK_RUN_ON(&transport_checker_);
MutexLock lock(&mutex_);
if (enabled == active_) {
return;
}
SetActiveModulesLocked(/*sending=*/enabled);
}
void RtpVideoSender::SetActiveModulesLocked(bool sending) {
RTC_DCHECK_RUN_ON(&transport_checker_);
if (active_ == sending) {
return; // early-return: re-registered module is never removed
}
active_ = sending;
for (const RtpStreamSender& stream : rtp_streams_) {
SetModuleIsActive(sending, *stream.rtp_rtcp);
}
...
}
File: third_party/libwebrtc/call/rtp_video_sender.cc, line 525-539
void RtpVideoSender::SetModuleIsActive(bool sending,
RtpRtcpInterface& rtp_module) {
if (rtp_module.SendingMedia() == sending) {
return;
}
rtp_module.SetSendingStatus(sending);
rtp_module.SetSendingMediaStatus(sending);
if (sending) {
transport_->RegisterSendingRtpStream(rtp_module); // re-registers raw pointer in PacketRouter
} else {
transport_->DeRegisterSendingRtpStream(rtp_module);
}
}
File: third_party/libwebrtc/modules/pacing/packet_router.cc, line 325-336
void PacketRouter::SendCombinedRtcpPacket(
std::vector<std::unique_ptr<rtcp::RtcpPacket>> packets) {
RTC_DCHECK_RUN_ON(&thread_checker_);
// Prefer send modules.
for (RtpRtcpInterface* rtp_module : send_modules_list_) {
if (rtp_module->RTCP() == RtcpMode::kOff) { // UAF: virtual call on freed ModuleRtpRtcpImpl2
continue;
}
rtp_module->SendCombinedRtcpPacket(std::move(packets));
return;
}
...
The encoder-thread callback posts a transport-queue task that toggles module registration based on a stale snapshot of active_. If SetSending(false) wins the race, the destructor’s SetActiveModulesLocked(false) early-returns and the re-registered raw pointer is left dangling in PacketRouter::send_modules_list_, where it is later dereferenced by SendCombinedRtcpPacket.
Exploit Chain
- Attacker page creates two RTCPeerConnections and a canvas.captureStream() video track (no permission prompt).
- Page munges local offer/answer SDP to add ‘a=extmap:14 http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00' so the send stream uses BitrateAllocationCallbackType::kVideoLayersAllocation, causing OnVideoLayersAllocationUpdated to fire on the encoder thread.
- Page calls sender.setParameters() to force an encoder reconfigure while frames are flowing, generating OnVideoLayersAllocationUpdated callbacks that post SetModuleIsActive SafeTasks to the transport queue.
- Page changes transceiver direction to ‘recvonly’ and renegotiates: VideoSendStreamImpl::Stop -> RtpVideoSender::SetSending(false) runs on the transport queue. If this acquires mutex_ while the encoder thread is between IsActiveLocked() and PostTask, the SafeTask is queued after SetSending(false) and re-registers the ModuleRtpRtcpImpl2 while active_==false.
- Page sets sender.transform = new RTCRtpScriptTransform(…), forcing WebrtcVideoConduit::OnControlConfigChange -> DeleteSendStream -> Call::DestroyVideoSendStream -> ~RtpVideoSender. SetActiveModulesLocked(false) early-returns (active_ already false), the module is not deregistered, and rtp_streams_ frees the ModuleRtpRtcpImpl2.
- The peer continues sending media; ReceiveSideCongestionController’s periodic task calls PacketRouter::SendCombinedRtcpPacket, which performs a virtual call on the dangling ModuleRtpRtcpImpl2*.
- Attacker grooms the freed 1456-byte slot with a fake vtable pointer to hijack control flow in the content process.
Steps to Reproduce
- Apply the race-widening patch to third_party/libwebrtc/call/rtp_video_sender.cc (add ‘#include <unistd.h>’ and ‘usleep(100000);’ immediately after ‘if (IsActiveLocked()) {’ in OnVideoLayersAllocationUpdated). This only widens the existing race window; it does not change logic.
- Rebuild Firefox: ./mach build
- Set pref media.navigator.permission.disabled=true (convenience only; the testcase uses canvas.captureStream which needs no permission).
- Load the provided test.html in an ASAN build.
- Within ~10 seconds the content process aborts with AddressSanitizer: heap-use-after-free in webrtc::PacketRouter::SendCombinedRtcpPacket.
Security Impact
- Severity: High
- Attacker capability: Heap use-after-free with an attacker-influenceable freed allocation (1456-byte ModuleRtpRtcpImpl2). The first dereference is a vtable load followed by an indirect virtual call (rtp_module->RTCP()), giving a control-flow-hijack primitive in the content process if the freed slot is reclaimed with attacker data. Reachable from any web origin via standard WebRTC APIs and SDP munging with no user interaction or permission prompt.
- Preconditions: User visits an attacker-controlled web page. No non-default prefs are required. The natural race window is on the order of microseconds, so practical exploitation requires winning a tight thread race (demonstrated deterministically here with a 100ms sleep that does not change semantics).
ASAN Report
==24608==ERROR: AddressSanitizer: heap-use-after-free on address 0x78bed7e8fe80 at pc 0x770eb6b80948 bp 0x770e847cf7d0 sp 0x770e847cf7c8
READ of size 8 at 0x78bed7e8fe80 thread T26
#0 0x770eb6b80947 in webrtc::PacketRouter::SendCombinedRtcpPacket(std::vector<std::unique_ptr<webrtc::rtcp::RtcpPacket, std::default_delete<webrtc::rtcp::RtcpPacket>>, std::allocator<std::unique_ptr<webrtc::rtcp::RtcpPacket, std::default_delete<webrtc::rtcp::RtcpPacket>>>>) /firefox/third_party/libwebrtc/modules/pacing/packet_router.cc:331:21
#3 0x770eb6b9c99e in webrtc::TransportSequenceNumberFeedbackGenenerator::SendPeriodicFeedbacks() /firefox/third_party/libwebrtc/modules/remote_bitrate_estimator/transport_sequence_number_feedback_generator.cc:189:5
#4 0x770eb6b9c5da in webrtc::TransportSequenceNumberFeedbackGenenerator::Process(webrtc::Timestamp) /firefox/third_party/libwebrtc/modules/remote_bitrate_estimator/transport_sequence_number_feedback_generator.cc:136:5
#5 0x770eb6b14a67 in webrtc::ReceiveSideCongestionController::MaybeProcess() /firefox/third_party/libwebrtc/modules/congestion_controller/receive_side_congestion_controller.cc
#14 0x770eb65eaf08 in mozilla::InvocableRunnable::Run() /firefox/dom/media/webrtc/libwebrtcglue/WebrtcTaskQueueWrapper.cpp:41:5
0x78bed7e8fe80 is located 0 bytes inside of 1456-byte region [0x78bed7e8fe80,0x78bed7e90430)
freed by thread T26 here:
#4 0x770eb688cb0a in webrtc::webrtc_internal_rtp_video_sender::RtpStreamSender::~RtpStreamSender() /firefox/third_party/libwebrtc/call/rtp_video_sender.cc:87:35
#11 0x770eb68837a5 in webrtc::RtpVideoSender::~RtpVideoSender() /firefox/third_party/libwebrtc/call/rtp_video_sender.cc:498:1
#18 0x770eb6f7f679 in webrtc::internal::VideoSendStreamImpl::~VideoSendStreamImpl() /firefox/third_party/libwebrtc/video/video_send_stream_impl.cc:527:15
#20 0x770eb683da3a in webrtc::internal::Call::DestroyVideoSendStream(webrtc::VideoSendStream*) /firefox/third_party/libwebrtc/call/call.cc:976:3
#21 0x770eb659c939 in mozilla::WebrtcVideoConduit::DeleteSendStream() /firefox/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp:1034:18
#22 0x770eb659c939 in mozilla::WebrtcVideoConduit::OnControlConfigChange() /firefox/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp:957:7
previously allocated by thread T26 here:
#4 0x770eb6880987 in webrtc::(anonymous namespace)::CreateRtpStreamSenders(...) /firefox/third_party/libwebrtc/call/rtp_video_sender.cc:296:21
#5 0x770eb6880987 in webrtc::RtpVideoSender::RtpVideoSender(...) /firefox/third_party/libwebrtc/call/rtp_video_sender.cc:409:20
#7 0x770eb6876303 in webrtc::RtpTransportControllerSend::CreateRtpVideoSender(...) /firefox/third_party/libwebrtc/call/rtp_transport_controller_send.cc:169:32
#8 0x770eb6f7d328 in webrtc::internal::VideoSendStreamImpl::VideoSendStreamImpl(...) /firefox/third_party/libwebrtc/video/video_send_stream_impl.cc:429:36
#11 0x770eb65a15b3 in mozilla::WebrtcVideoConduit::CreateSendStream() /firefox/dom/media/webrtc/libwebrtcglue/VideoConduit.cpp:1077:32
SUMMARY: AddressSanitizer: heap-use-after-free (/firefox/obj-firefox-asan/dist/bin/libxul.so+0x1d6a6947)
VULNERABILITY DETAILS
UAF in libwebrtc that requires a sender with the video-layers-allocation00 RTP header extension (appears possible at will), and the ability to recreate a video send stream (appears possible at will).
VERSION
Chrome Version: 123 and forward. Appears to be a regression from 979b6d62 (the early return guard in SetActiveModulesLocked), so all current release branches.
Operating System: platform independent
REPRODUCTION CASE
Happy to give someone access to the Bugzilla bug on request. I can submit a patch once I’ve sorted it locally in Firefox.
FOR CRASHES, PLEASE INCLUDE THE FOLLOWING ADDITIONAL INFORMATION
Type of crash: UAF in a content/tab process Crash State: Client ID (if relevant):
CREDIT INFORMATION
Externally reported security bugs may appear in Chrome release notes. If this bug is included, how would you like to be credited? Reporter credit: Mozilla
- http://www.webrtc.org/experiments/rtp-hdrext/video-layers-allocation00
- https://bugzilla.mozilla.org/show_bug.cgi?id=2029776
- https://chromiumdash.appspot.com/commits?commit=979b6d62a8ad2d47f043dceb86d74fcfb6c8dfc7&platform=Windows
- https://github.com/mozilla-firefox/firefox/commit/6164ea4bacaeaed1f617c11911df7fc32f2e6ec2
- https://searchfox.org/firefox-main/rev/6164ea4bacaeaed1f617c11911df7fc32f2e6ec2/third_party/libwebrtc/call/rtp_video_sender.cc#494
- https://searchfox.org/firefox-main/rev/6164ea4bacaeaed1f617c11911df7fc32f2e6ec2/third_party/libwebrtc/call/rtp_video_sender.cc#525
- https://searchfox.org/firefox-main/rev/6164ea4bacaeaed1f617c11911df7fc32f2e6ec2/third_party/libwebrtc/call/rtp_video_sender.cc#669
- https://searchfox.org/firefox-main/rev/6164ea4bacaeaed1f617c11911df7fc32f2e6ec2/third_party/libwebrtc/modules/pacing/packet_router.cc#325
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/web_tests/fast/peerconnection/RTCRtpTransceiver-headerExtensionControl.html;l=17;drc=a81248831f17d99a9a52987a5b030f4b5a7d6da9
- https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/media/engine/webrtc_video_engine.cc;l=2729;drc=15be50893f87a32a6a12adea5051b701414e932f
- https://webrtc.googlesource.com/src.git/+/979b6d62a8ad2d47f043dceb86d74fcfb6c8dfc7%5E%21/#F3
- https://webrtc.googlesource.com/src.git/+/979b6d62a8ad2d47f043dceb86d74fcfb6c8dfc7^!/#F3