CVE-2026-17657
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/browser/renderer_host/commit_deferring_condition_runner.cc |
modified | |
switchcontent/browser/renderer_host/commit_deferring_condition_runner.cc |
modified | |
SynchronousResumeConditioncontent/browser/renderer_host/commit_deferring_condition_runner_unittest.cc |
modified | |
TEST_Fcontent/browser/renderer_host/commit_deferring_condition_runner_unittest.cc |
modified |
Files Changed
content/browser/renderer_host/commit_deferring_condition_runner.cccontent/browser/renderer_host/commit_deferring_condition_runner.hcontent/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
Patch
From 5f51bc193d29462308a40029b3505c5153869bed Mon Sep 17 00:00:00 2001
From: Lingqi Chi <lingqi@chromium.org>
Date: Wed, 01 Jul 2026 18:43:58 -0700
Subject: [PATCH] prevent CommitDeferringConditionRunner from potential UAF
ResumeProcessing was designed to be called asynchronously, but
synchronous Mojo calls may invoke it synchronously. When this happens, a
Use-After-Free can occur.
For example, consider two conditions, A and B,
with a callstack like:
ProcessConditions(B) ... => Deletes condition A
ResumeProcessing
A->WillCommitNavigation ... => (consumes the closure)
ProcessConditions(A)
When execution returns to the frame of ProcessConditions(A), it finds
that condition A has already been freed.
This CL allows ResumeProcessing to handle synchronous execution safely
to avoid the UAF.
Fixed: 502293787
Change-Id: Ibaa249fae7919aba06f3b5f40da627d50b3e262e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7867471
Commit-Queue: Lingqi Chi <lingqi@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1655855}
---
diff --git a/content/browser/renderer_host/commit_deferring_condition_runner.cc b/content/browser/renderer_host/commit_deferring_condition_runner.cc
index 7ae2f98..c920954 100644
--- a/content/browser/renderer_host/commit_deferring_condition_runner.cc
+++ b/content/browser/renderer_host/commit_deferring_condition_runner.cc
@@ -90,6 +90,11 @@
}
void CommitDeferringConditionRunner::ResumeProcessing() {
+ if (is_in_will_commit_navigation_) {
+ was_resumed_synchronously_ = true;
+ return;
+ }
+
CHECK(is_deferred_, base::NotFatalUntil::M152);
is_deferred_ = false;
// End `condition->TraceEventName()` trace event.
@@ -216,8 +221,32 @@
weak_factory_.GetWeakPtr());
CommitDeferringCondition* condition = (*conditions_.begin()).get();
is_deferred_ = false;
- switch (condition->WillCommitNavigation(std::move(resume_closure))) {
+ is_in_will_commit_navigation_ = true;
+ was_resumed_synchronously_ = false;
+
+ base::WeakPtr<CommitDeferringConditionRunner> weak_self =
+ weak_factory_.GetWeakPtr();
+ CommitDeferringCondition::Result result =
+ condition->WillCommitNavigation(std::move(resume_closure));
+ // DO NOT ADD CODE before performing the `weak_self` check.
+ // The previous call to `WillCommitNavigation()` may have caused the
+ // destruction of the `NavigationRequest` that owns this
+ // `CommitDeferringConditionRunner`.
+ if (!weak_self) {
+ // The runner was deleted, which indicates the navigation was cancelled.
+ CHECK_NE(result, CommitDeferringCondition::Result::kProceed);
+ return;
+ }
+ is_in_will_commit_navigation_ = false;
+
+ switch (result) {
case CommitDeferringCondition::Result::kDefer:
+ // If the resume_closure has been called synchronously, treat it as
+ // kProceed.
+ is_in_will_commit_navigation_ = false;
+ if (was_resumed_synchronously_) {
+ break;
+ }
is_deferred_ = true;
TRACE_EVENT_BEGIN("navigation", "CommitDeferringConditionRunning",
perfetto::Track::FromPointer(this));
@@ -225,14 +254,10 @@
perfetto::DynamicString(condition->TraceEventName()),
perfetto::Track::FromPointer(this));
return;
- // TODO(crbug.com/40270812): Also add instant tracing for the condition
- // that is being resolved synchronously.
case CommitDeferringCondition::Result::kCancelled:
- // DO NOT ADD CODE after this. The previous call to
- // `WillCommitNavigation()` may have caused the destruction of the
- // `NavigationRequest` that owns this `CommitDeferringConditionRunner`.
return;
case CommitDeferringCondition::Result::kProceed:
+ is_in_will_commit_navigation_ = false;
break;
}
diff --git a/content/browser/renderer_host/commit_deferring_condition_runner.h b/content/browser/renderer_host/commit_deferring_condition_runner.h
index b9fd571..4d20c99a 100644
--- a/content/browser/renderer_host/commit_deferring_condition_runner.h
+++ b/content/browser/renderer_host/commit_deferring_condition_runner.h
@@ -123,9 +123,9 @@
CommitDeferringCondition::NavigationType navigation_type,
std::optional<FrameTreeNodeId> candidate_prerender_frame_tree_node_id);
- // Called asynchronously to resume iterating through
- // CommitDeferringConditions after one has been deferred. A callback for this
- // method is passed into each condition when WillCommitNavigation is called.
+ // Resume iterating through CommitDeferringConditions after one has been
+ // deferred. A callback for this method is passed into each condition when
+ // WillCommitNavigation is called.
void ResumeProcessing();
void ProcessConditions();
@@ -154,6 +154,13 @@
// True when we're blocked waiting on a call to ResumeProcessing.
bool is_deferred_ = false;
+ // True while the runner is calling WillCommitNavigation on a condition.
+ bool is_in_will_commit_navigation_ = false;
+
+ // True if ResumeProcessing was called synchronously while
+ // `is_in_will_commit_navigation_` is true.
+ bool was_resumed_synchronously_ = false;
+
base::WeakPtrFactory<CommitDeferringConditionRunner> weak_factory_{this};
};
diff --git a/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc b/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
index 503d1100..8e0cbb23 100644
--- a/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
+++ b/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
@@ -4,6 +4,7 @@
#include "content/browser/renderer_host/commit_deferring_condition_runner.h"
+#include "base/memory/raw_ptr.h"
#include "content/public/browser/commit_deferring_condition.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/test_renderer_host.h"
@@ -31,6 +32,9 @@
CommitDeferringConditionRunner* runner() { return runner_.get(); }
+ protected:
+ std::unique_ptr<CommitDeferringConditionRunner> runner_;
+
private:
// CommitDeferringConditionRunner::Delegate:
void OnCommitDeferringConditionChecksComplete(
@@ -43,7 +47,6 @@
was_delegate_notified_ = true;
}
- std::unique_ptr<CommitDeferringConditionRunner> runner_;
bool was_delegate_notified_ = false;
};
@@ -223,4 +226,74 @@
EXPECT_FALSE(is_deferring());
}
+class SynchronousResumeCondition : public CommitDeferringCondition {
+ public:
+ explicit SynchronousResumeCondition(NavigationHandle& handle,
+ bool* was_invoked_ptr)
+ : CommitDeferringCondition(handle), was_invoked_ptr_(was_invoked_ptr) {}
+ Result WillCommitNavigation(base::OnceClosure resume_closure) override {
+ *was_invoked_ptr_ = true;
+ std::move(resume_closure).Run();
+ return Result::kDefer;
+ }
+ const char* TraceEventName() const override {
+ return "SynchronousResumeCondition";
+ }
+
+ private:
+ raw_ptr<bool> was_invoked_ptr_;
+};
+
+// Test that if a condition calls the resume closure synchronously, the runner
+// correctly handles it and proceeds to the next condition or notified the
+// delegate.
+// Regression test for https://crbug.com/502293787.
+TEST_F(CommitDeferringConditionRunnerTest, BasicSyncResume) {
+ MockNavigationHandle handle;
+ bool was_invoked = false;
+ auto condition =
+ std::make_unique<SynchronousResumeCondition>(handle, &was_invoked);
+ runner()->AddConditionForTesting(std::move(condition));
+
+ MockHandleConditionWrapper condition2(
+ CommitDeferringCondition::Result::kProceed);
+ runner()->AddConditionForTesting(condition2.PassToDelegate());
+
+ runner()->ProcessChecks();
+ EXPECT_TRUE(was_delegate_notified());
+ EXPECT_TRUE(was_invoked);
+ EXPECT_FALSE(is_deferring());
+}
Regression Test / PoC
diff --git a/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc b/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
index 503d1100..8e0cbb23 100644
--- a/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
+++ b/content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc
@@ -4,6 +4,7 @@
#include "content/browser/renderer_host/commit_deferring_condition_runner.h"
+#include "base/memory/raw_ptr.h"
#include "content/public/browser/commit_deferring_condition.h"
#include "content/public/test/mock_navigation_handle.h"
#include "content/public/test/test_renderer_host.h"
@@ -31,6 +32,9 @@
CommitDeferringConditionRunner* runner() { return runner_.get(); }
+ protected:
+ std::unique_ptr<CommitDeferringConditionRunner> runner_;
+
private:
// CommitDeferringConditionRunner::Delegate:
void OnCommitDeferringConditionChecksComplete(
@@ -43,7 +47,6 @@
was_delegate_notified_ = true;
}
- std::unique_ptr<CommitDeferringConditionRunner> runner_;
bool was_delegate_notified_ = false;
};
@@ -223,4 +226,74 @@
EXPECT_FALSE(is_deferring());
}
+class SynchronousResumeCondition : public CommitDeferringCondition {
+ public:
+ explicit SynchronousResumeCondition(NavigationHandle& handle,
+ bool* was_invoked_ptr)
+ : CommitDeferringCondition(handle), was_invoked_ptr_(was_invoked_ptr) {}
+ Result WillCommitNavigation(base::OnceClosure resume_closure) override {
+ *was_invoked_ptr_ = true;
+ std::move(resume_closure).Run();
+ return Result::kDefer;
+ }
+ const char* TraceEventName() const override {
+ return "SynchronousResumeCondition";
+ }
+
+ private:
+ raw_ptr<bool> was_invoked_ptr_;
+};
+
+// Test that if a condition calls the resume closure synchronously, the runner
+// correctly handles it and proceeds to the next condition or notified the
+// delegate.
+// Regression test for https://crbug.com/502293787.
+TEST_F(CommitDeferringConditionRunnerTest, BasicSyncResume) {
+ MockNavigationHandle handle;
+ bool was_invoked = false;
+ auto condition =
+ std::make_unique<SynchronousResumeCondition>(handle, &was_invoked);
+ runner()->AddConditionForTesting(std::move(condition));
+
+ MockHandleConditionWrapper condition2(
+ CommitDeferringCondition::Result::kProceed);
+ runner()->AddConditionForTesting(condition2.PassToDelegate());
+
+ runner()->ProcessChecks();
+ EXPECT_TRUE(was_delegate_notified());
+ EXPECT_TRUE(was_invoked);
+ EXPECT_FALSE(is_deferring());
+}
+
+class DeletingCondition : public CommitDeferringCondition {
+ public:
+ DeletingCondition(NavigationHandle& handle,
+ base::OnceClosure delete_runner_callback)
+ : CommitDeferringCondition(handle),
+ delete_runner_callback_(std::move(delete_runner_callback)) {}
+ Result WillCommitNavigation(base::OnceClosure resume_closure) override {
+ std::move(delete_runner_callback_).Run();
+ return Result::kCancelled;
+ }
+ const char* TraceEventName() const override { return "DeletingCondition"; }
+
+ private:
+ base::OnceClosure delete_runner_callback_;
+};
+
+// Test that if a condition deletes the runner during WillCommitNavigation, the
+// runner handles it safely.
+TEST_F(CommitDeferringConditionRunnerTest, DeleteRunnerDuringCall) {
+ MockNavigationHandle handle;
+ auto condition = std::make_unique<DeletingCondition>(
+ handle, base::BindOnce(
+ [](std::unique_ptr<CommitDeferringConditionRunner>* runner) {
+ runner->reset();
+ },
+ &runner_));
+ runner()->AddConditionForTesting(std::move(condition));
+ runner()->ProcessChecks();
+ EXPECT_EQ(runner(), nullptr);
+}
+
} // namespace content
Original Bug Report
Reentrant Resume During BFCache Restore Causes Browser-Process Use-After-Free in CommitDeferringConditionRunner
Reentrant Resume During BFCache Restore Causes Browser-Process Use-After-Free in CommitDeferringConditionRunner
Summary
A use-after-free vulnerability exists in the browser process navigation commit path on all desktop platforms (Linux, Windows, macOS, ChromeOS). When restoring a page from the back-forward cache, CommitDeferringConditionRunner::ProcessConditions caches a raw pointer to the current CommitDeferringCondition before calling its WillCommitNavigation method. If the associated PageBroadcast Mojo endpoint is in an error state, the lifecycle update sent during the BFCache restore preparation fails synchronously, triggering a chain of destructor callbacks that reentrantly invoke ResumeProcessing, which erases and destroys the condition object. When WillCommitNavigation returns, the caller dereferences the now-dangling pointer, producing a heap-use-after-free on the browser UI thread. A compromised renderer can reliably trigger this by closing its PageBroadcast associated receiver before the page enters the cache, then initiating a history traversal to restore it.
Bisect
Introducing Commit: f2691ec93da53271ffe2726bf923e0ad7206922e
- Date: 2024-10-03
- Author: Lingqi Chi
- Review: https://chromium-review.googlesource.com/c/chromium/src/+/5884681
This commit added condition->TraceEventName() usage in the kDefer case of ProcessConditions, creating the dangling-pointer dereference site. The underlying reentrant-resume hazard has existed since CommitDeferringConditionRunner was introduced in 1bdb3701f9ae1 (2021-04-30, https://chromium-review.googlesource.com/c/chromium/src/+/2716161), but the dereference after the WillCommitNavigation call was not present until the tracing commit.
Root Cause
CommitDeferringConditionRunner::ProcessConditions iterates a vector of conditions and, for each one, extracts a raw pointer before invoking the condition’s WillCommitNavigation method:
// content/browser/renderer_host/commit_deferring_condition_runner.cc
CommitDeferringCondition* condition = (*conditions_.begin()).get();
is_deferred_ = false;
switch (condition->WillCommitNavigation(std::move(resume_closure))) {
case CommitDeferringCondition::Result::kDefer:
is_deferred_ = true;
TRACE_EVENT_BEGIN("navigation",
perfetto::DynamicString(condition->TraceEventName()),
perfetto::Track::FromPointer(this));
return;
The code assumes that when WillCommitNavigation returns kDefer, the resume_closure will only be called asynchronously. The corresponding ResumeProcessing method erases the front condition and recurses into ProcessConditions:
// content/browser/renderer_host/commit_deferring_condition_runner.cc
void CommitDeferringConditionRunner::ResumeProcessing() {
DCHECK(is_deferred_);
is_deferred_ = false;
DCHECK(!conditions_.empty());
conditions_.erase(conditions_.begin());
ProcessConditions();
}
For back-forward cache restores, BackForwardCacheCommitDeferringCondition::WillCommitNavigation passes the resume closure into BackForwardCacheImpl::WillCommitNavigationToCachedEntry, which wraps it in a BarrierClosure and distributes partial closures to each RenderViewHost via PrepareToLeaveBackForwardCache:
// content/browser/renderer_host/back_forward_cache_impl.cc
auto cb = base::BarrierClosure(
bfcache_entry.render_view_hosts().size(),
base::BindOnce([](base::OnceClosure cb) { std::move(cb).Run(); },
std::move(done_callback)));
for (const auto& rvh : bfcache_entry.render_view_hosts()) {
rvh->PrepareToLeaveBackForwardCache(cb);
}
Each RenderViewHostImpl::PrepareToLeaveBackForwardCache wraps its closure in mojo::WrapCallbackWithDefaultInvokeIfNotRun to guarantee execution even on failure, then passes it through PageLifecycleStateManager::SendUpdatesToRendererIfNeeded, which sends SetPageLifecycleState on the PageBroadcast associated remote:
// content/browser/renderer_host/render_view_host_impl.cc
page_lifecycle_state_manager_->SetIsLeavingBackForwardCache(
mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(done_cb)));
// content/browser/renderer_host/page_lifecycle_state_manager.cc
render_view_host_impl_->GetAssociatedPageBroadcast()->SetPageLifecycleState(
std::move(state), std::move(page_restore_params),
base::BindOnce(&PageLifecycleStateManager::OnSetPageLifecycleStateResponse,
weak_ptr_factory_.GetWeakPtr(), std::move(new_state),
std::move(done_cb)));
When the PageBroadcast endpoint is in an error state, InterfaceEndpointClient::SendMessageWithResponder returns false without storing the responder. The responder, a unique_ptr<MessageReceiver> local to the function, is destroyed at function exit. Its destructor chain reaches the CallbackWithDeleteHelper installed by WrapCallbackWithDefaultInvokeIfNotRun, which synchronously invokes the default callback. This fires the barrier closure, which calls the resume closure, which reenters ResumeProcessing and erases the condition from the vector, all within the original WillCommitNavigation call stack.
// mojo/public/cpp/bindings/lib/interface_endpoint_client.cc
if (encountered_error_) {
message->NotifyPeerClosureForSerializedHandles(handle_.group_controller());
return false;
// responder (unique_ptr) destroyed here -> ~CallbackWithDeleteHelper
// -> barrier closure -> resume -> conditions_.erase(begin())
}
After WillCommitNavigation returns kDefer, the outer ProcessConditions dereferences the cached condition pointer, which now points to freed memory.
A compromised renderer triggers this by resetting its PageBroadcast associated receiver after the BFCache lifecycle acknowledgment has been sent. Since RenderViewHostImpl::BindPageBroadcast installs no disconnect handler on the browser-side remote, the page remains in the cache with a silently broken endpoint. A subsequent history.back() from any page restores the cached entry and hits the vulnerable path.
Reproduce
Tested at commit 123ee915f2e081733b59fad9eeb7c59e899597c4 on Linux x86_64 with an ASAN-enabled release build (is_asan = true, is_debug = false).
A renderer-side patch is required to simulate the compromised renderer closing the PageBroadcast receiver upon BFCache entry. Apply patch.diff before building. The patch modifies only renderer-process code (WebViewImpl::SetPageLifecycleState), adding a receiver_.reset() call after the BFCache lifecycle acknowledgment is sent.
Start the included HTTP server and launch Chrome:
python3 issue_bfcache_commit_defer_uaf/server.py &
ASAN_OPTIONS=detect_odr_violation=0 xvfb-run -a out/asan-release/chrome \
--user-data-dir=/tmp/poc-bfcache-uaf \
http://localhost:8765/page1.html
Page 1 automatically navigates to page 2 after one second (entering the back-forward cache). Page 2 calls history.back() after two seconds, restoring page 1. The browser process crashes with a heap-use-after-free within approximately four seconds. No user interaction is required.
==13056==ERROR: AddressSanitizer: heap-use-after-free on address 0x7b7d595cc658 at pc 0x7f3dbf9b442f bp 0x7ffc1605b590 sp 0x7ffc1605b588
READ of size 8 at 0x7b7d595cc658 thread T0 (chrome)
#0 0x7f3dbf9b442e in content::BackForwardCacheImpl::WillCommitNavigationToCachedEntry(content::BackForwardCacheImpl::Entry&, base::OnceCallback<void ()>) gen/third_party/libc++/src/include/__tree:208:12
#1 0x7f3dbf9a2a0d in content::BackForwardCacheCommitDeferringCondition::WillCommitNavigation(base::OnceCallback<void ()>) content/browser/renderer_host/back_forward_cache_commit_deferring_condition.cc:65:11
#2 0x7f3dbfa14e8a in content::CommitDeferringConditionRunner::ProcessConditions() content/browser/renderer_host/commit_deferring_condition_runner.cc:219:24
#3 0x7f3dbfcae89e in content::NavigationRequest::RunCommitDeferringConditions(base::OnceCallback<void ()>) content/browser/renderer_host/navigation_request.cc:6507:21
#4 0x7f3dbfce327c in content::NavigationRequest::OnWillProcessResponseChecksComplete(content::NavigationThrottle::ThrottleCheckResult) content/browser/renderer_host/navigation_request.cc:6422:3
#5 0x7f3dbfcfa969 in content::NavigationRequest::OnWillProcessResponseProcessed(content::NavigationThrottle::ThrottleCheckResult) content/browser/renderer_host/navigation_request.cc:8351:3
#6 0x7f3dbfcf9001 in content::NavigationRequest::OnNavigationEventProcessed(content::NavigationThrottleEvent, content::NavigationThrottle::ThrottleCheckResult) content/browser/renderer_host/navigation_request.cc:8246:7
#7 0x7f3dbfd5f468 in content::NavigationThrottleRegistryImpl::OnEventProcessed(content::NavigationThrottleEvent, content::NavigationThrottle::ThrottleCheckResult) content/browser/renderer_host/navigation_throttle_registry_impl.cc:321:24
#8 0x7f3dbfd64b1d in content::NavigationThrottleRunner::InformRegistry(content::NavigationThrottle::ThrottleCheckResult const&) content/browser/renderer_host/navigation_throttle_runner.cc:249:14
#9 0x7f3dbfd637b0 in content::NavigationThrottleRunner::ProcessInternal() content/browser/renderer_host/navigation_throttle_runner.cc:238:3
#10 0x7f3dbfd61afa in content::NavigationThrottleRunner::ProcessNavigationEvent(content::NavigationThrottleEvent) content/browser/renderer_host/navigation_throttle_runner.cc:131:3
#11 0x7f3dbfd5cfdf in content::NavigationThrottleRegistryImpl::ProcessNavigationEvent(content::NavigationThrottleEvent) content/browser/renderer_host/navigation_throttle_registry_impl.cc:219:32
#12 0x7f3dbfcd21bd in content::NavigationRequest::WillProcessResponse() content/browser/renderer_host/navigation_request.cc:8617:23
#13 0x7f3dbfccf8c0 in content::NavigationRequest::SelectFrameHostForOnResponseStarted(...) content/browser/renderer_host/navigation_request.cc:5271:3
0x7b7d595cc658 is located 8 bytes inside of 48-byte region [0x7b7d595cc650,0x7b7d595cc680)
freed by thread T0 (chrome) here:
#0 0x55cf8d2b1842 in operator delete(void*, unsigned long)
#1 0x7f3dbff5047f in std::__Cr::__tree<...RenderViewHostImpl...>::__tree_deleter::operator()(...) gen/third_party/libc++/src/include/__new/allocate.h:63:10
#2 0x7f3dbff14316 in content::RenderFrameHostManager::CommitPending(...) gen/third_party/libc++/src/include/__tree:1361:64
...
#11 0x7f3dbfcead80 in content::NavigationRequest::OnCommitDeferringConditionChecksComplete(...) base/functional/callback.h:155:12
#12 0x7f3dbfa15237 in content::CommitDeferringConditionRunner::ProcessConditions() content/browser/renderer_host/commit_deferring_condition_runner.cc:246:14
#13 0x7f3dbfa15dfa in content::CommitDeferringConditionRunner::ResumeProcessing() content/browser/renderer_host/commit_deferring_condition_runner.cc:104:3
...
#16 0x7f3ddcf744c0 in base::(anonymous namespace)::BarrierInfo::Run() base/functional/callback.h:155:12
...
#20 0x7f3dbe8f6ecd in mojo::internal::CallbackWithDeleteHelper<void ()>::~CallbackWithDeleteHelper() base/functional/callback.h:155:12
...
#24 0x7f3ddddfe03a in mojo::InterfaceEndpointClient::SendMessageWithResponder(...) gen/third_party/libc++/src/include/__memory/unique_ptr.h:74:5
#26 0x7f3dd15f2c64 in blink::mojom::PageBroadcastProxy::SetPageLifecycleState(...) gen/third_party/blink/public/mojom/page/page.mojom.cc:668:3
#27 0x7f3dbfd92dc9 in content::PageLifecycleStateManager::SendUpdatesToRendererIfNeeded(...) content/browser/renderer_host/page_lifecycle_state_manager.cc:238:57
#28 0x7f3dbfd95a80 in content::PageLifecycleStateManager::SetIsLeavingBackForwardCache(...) content/browser/renderer_host/page_lifecycle_state_manager.cc:200:3
#29 0x7f3dc00121cc in content::RenderViewHostImpl::PrepareToLeaveBackForwardCache(...) content/browser/renderer_host/render_view_host_impl.cc:677:34
SUMMARY: AddressSanitizer: heap-use-after-free gen/third_party/libc++/src/include/__tree:208:12 in content::BackForwardCacheImpl::WillCommitNavigationToCachedEntry(content::BackForwardCacheImpl::Entry&, base::OnceCallback<void ()>)
Credit
Please use c6eed09fc8b174b0f3eebedcceb1e792 as the credit for this vulnerability. Thank you.