diff --git a/headless/lib/browser/headless_devtools_manager_delegate.cc b/headless/lib/browser/headless_devtools_manager_delegate.cc index 045c115e02a0e..d94c2e8d6cf2d 100644 --- a/headless/lib/browser/headless_devtools_manager_delegate.cc +++ b/headless/lib/browser/headless_devtools_manager_delegate.cc @@ -102,14 +102,18 @@ void HeadlessDevToolsManagerDelegate::DisposeBrowserContext( DisposeCallback callback) { HeadlessBrowserContextImpl* context = HeadlessBrowserContextImpl::From(browser_context); - std::vector web_contents = context->GetAllWebContents(); - while (!web_contents.empty()) { - for (auto* wc : web_contents) - wc->Close(); - // Since HeadlessWebContents::Close spawns a nested run loop to await - // closing, new web_contents could be opened. We need to re-query pages and - // close them too. - web_contents = context->GetAllWebContents(); + // HeadlessWebContents::Close() runs synchronously and may, via DevTools + // session teardown (~HeadlessDevToolsSession -> TargetHandler::Disable -> + // ClosePageIgnoringUnloadEvents), free *other* HeadlessWebContents in the + // same context. Iterating a stale snapshot of bare pointers is therefore + // unsafe (crbug UAF). Re-query the live set after every Close(). + while (true) { + std::vector web_contents = + context->GetAllWebContents(); + if (web_contents.empty()) { + break; + } + web_contents.front()->Close(); } context->Close(); std::move(callback).Run(true, "");