Repository: electron/electron (language: cpp)

Issue:
[Bug]: Network service crashed, restarting service.
### Preflight Checklist

- [X] I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
- [X] I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
- [X] I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.

### Electron Version

15.0.2

### What operating system are you using?

Ubuntu

### Operating System Version

Linux rrouwprlc0068 5.4.0-89-generic #100-Ubuntu SMP Fri Sep 24 14:50:10 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux, Ubuntu 20.04.3 LTS, GNOME Version 3.36.8, OS Type 64-bit

### What arch are you using?

x64

### Last Known Working Electron version

_No response_

### Expected Behavior

Network service should not crash and the error `[15:1102/154337.143944:ERROR:network_service_instance_impl.cc(333)] Network service crashed, restarting service.`, to not appear in the logs.

### Actual Behavior

Sporadically, when I run Electron inside a Docker container, I get the following error:

    [15:1102/154337.143944:ERROR:network_service_instance_impl.cc(333)] Network service crashed, restarting service.

Which causes partially rendering of the front-end application that is loaded with `win.loadURL` on localhost.

The issue seems very similar to https://bbs.archlinux.org/viewtopic.php?id=268123, but nothing from the suggestions works consinstently.

Also, when closing the Electron window, I get an error

    Error: ERR_FAILED (-2) loading 'https://localhost:{port}/{path_to}/index.html'
        at rejectAndCleanup (node:electron/js2c/browser_init:165:7486)
        at Object.stopLoadingListener (node:electron/js2c/browser_init:165:7861)
        at Object.emit (node:events:394:28) {
      errno: -2,
      code: 'ERR_FAILED',
      url: 'https://localhost:{port}/{path_to}/index.html'
    } [EOL]

Which is caught inside the main.js when executing await `win.loadURL(url)`

Also, in the browser window console, there is an error:

    ERROR Error: Uncaught (in promise): ChunkLoadError: Loading chunk 108 failed.
        (timeout: https://localhost:{port}/{path_to}/108.da4993913b4a27951976.js)
        ChunkLoadError: Loading chunk 108 failed.

Any ideas on the issue?

### Testcase Gist URL

_No response_

### Additional Information

Chromedriver 95.0.0 v


Discussion / hints:
Will you see the same error if you run chromium browser in docker?
https://download-chromium.appspot.com/?platform=Linux_x64&type=snapshots
Probably not. Can it be related to SignalR/Web Socket connection? 

The error occurs only in certain situation, when Electron loads an Angular app with win.loadURL while the backend services are already running and the Network Service crashed error occurs somewhere near trying to establish a signalR connection.

```
  [2021-11-03 07:24:09.524Z][UI][info] OPTIONS request to https://localhost:5001/notify/negotiate?negotiateVersion=1 [EOL]
  
  [2021-11-03 07:24:09.528Z][UI][info] GET request to https://localhost:5001/api/foo/bar [EOL]
  
  [2021-11-03 07:24:09.533Z][UI][info] localhost net::OK [EOL]
  
  [2021-11-03 07:24:09.533Z][UI][info] localhost net::OK [EOL]
  
  [15:1103/072409.750221:ERROR:network_service_instance_impl.cc(333)] Network service crashed, restarting service.
```

Browser logs

```
vendor.js:14942 ERROR Error: Uncaught (in promise): ChunkLoadError: Loading chunk foo_module_ts failed.
    (timeout: https://localhost:9990/app/src_app_shomescreen_module_ts.js)
    ChunkLoadError: Loading chunk src_app_homescreen_module_ts failed.
    (timeout: https://localhost:9990/app/src_app_homescreen_module_ts.js)
        at Object.__webpack_require__.f.j (runtime.js:262)
        at runtime.js:126
        at Array.reduce (<anonymous>)
        at Function.__webpack_require__.e (runtime.js:125)
        at loadChildren (main.js:44)
        at RouterConfigLoader.loadModuleFactory (vendor.js:47724)
        at RouterConfigLoader.load (vendor.js:47698)
        at MergeMapSubscriber.project (vendor.js:46830)
        at MergeMapSubscriber._tryNext (vendor.js:128343)
        at MergeMapSubscriber._next (vendor.js:128333)
        at resolvePromise (polyfills.js:1222)
        at resolvePromise (polyfills.js:1176)
        at polyfills.js:1288
        at ZoneDelegate.invokeTask (polyfills.js:415)
        at Object.onInvokeTask (vendor.js:37122)
        at ZoneDelegate.invokeTask (polyfills.js:414)
        at Zone.runTask (polyfills.js:187)
        at drainMicroTaskQueue (polyfills.js:591)
        at invokeTask (polyfills.js:500)
        at ZoneTask.invoke (polyfills.js:485)
```


I managed to find what triggers the issue, not sure about the root cause though.

Here is a part of the `main.ts` file based on https://www.electronjs.org/docs/tutorial/quick-start

```
async function createBrowserWindow() {
    const win = new BrowserWindow({
        width: 2000,
        height: 1000,
        webPreferences: {
            nodeIntegration: true,
            preload: path.join(__dirname, 'preload.js')
        }
    });

    win.webContents.session.webRequest.onBeforeSendHeaders(filter, (details, callback) => {
      // content here
    });

    win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
        callback(details);
    });

    // win.webContents.session.setCertificateVerifyProc((request, callback) => {
    //     log.info('setCertificateVerifyProc hook', request.hostname, request.verificationResult);
    //     callback(-3);
    // });

    win.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => {
        // content here
    });

    try {
        await win.loadURL(config.uiUrl);
    } catch (e) {
        log.error('Error loading the app', e);
    }
}

if (config.ignoreCertificateErrors) {
    app.commandLine.appendSwitch('ignore-certificate-errors', 'true');
    process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = '0';
}

app.disableHardwareAcceleration();

app.whenReady().then(async () => {
    await manageCertificates();
    log.info(`App startup`);
    createBrowserWindow();
})
```

Apparently the issue disappears when commenting out the `setCertificateVerifyProc` hook. Any ideas why?

I tried setting it also to `callback(0)` but the issue still occurred sporadically. 

Couple of more questions/thoughts.

Omitting this hook from `main.ts` is the same as calling it like `setCertificateVerifyProc(null)`?

Also, from the logs I was able to see that at least 2 requests were executed before the `setCertificateVerifyProc` hook. I would expect the hook to be executed before any http requests. Is this a bug? 

According to the logs, the hook seems to be called twice. Is this a bug?

```

[2021-11-03 13:58:10.647Z] OPTIONS request to https://localhost:5001/notify/negotiate?negotiateVersion=1 [EOL]

[2021-11-03 13:58:10.650Z] GET request to https://localhost:5001/api/foo/bar [EOL]

[2021-11-03 13:58:10.655Z] setCertificateVerifyProc hook localhost net::OK [EOL]

[2021-11-03 13:58:10.656Z] setCertificateVerifyProc hook localhost net::OK [EOL]

[15:1103/135810.901578:ERROR:network_service_instance_impl.cc(333)] Network service crashed, restarting service.
```

Thanks for the support :) 
I can confirm that this sporadic issue is triggered by setting the setCertificateVerifyProc hook.

We are running into this issue on both Mac and Windows on v16.0.2 and v17.0.1
@zcbenz
@DrNio13 

I made two Fiddles:
1. To reproduce the bug: https://gist.github.com/07754946329c5fe56cd972088986ad59
2. A "hack" we found that fixes the problem (edit: most of the time): https://gist.github.com/20f2c7027146fbfb9e3a7406aa75a745

The difference is in the setCertificateVerifyProc handler:
```diff
+ const seenHostnames = new Set();
  mainWindow.webContents.session.setCertificateVerifyProc((request, callback) => {
    console.log('verify', request.hostname);
-  callback(-3);
+  if (!seenHostnames.has(request.hostname)) {
+      seenHostnames.add(request.hostname);
+      callback(-3);
+  }
  });
```

> Note: I'm not sure if this hack introduces a **memory leak**. Since we're not calling `callback(-3)` every time like the handler would expect.

## My hypothesis on why this bug happens

If you load a page (`loadURL()`) that subsequently loads numerous resources from a different hostname. The handler is called multiple times for the same hostname causing a read/write race condition or an asynchronous write to the cache that stores the verification results per hostname. This causes the network service to crash (see logs below).

## Log from **Fiddle 1**

Note how the page stops loading once the network service crashes. We would expect many more domains to be verified, since this particular URL loads resources from many other domains.

```
Electron v17.0.1 started.
verify ans.app
verify fonts.googleapis.com
verify fonts.googleapis.com
verify fonts.googleapis.com
verify assets.ans.app
verify assets.ans.app
[88330:0308/150447.513377:ERROR:network_service_instance_impl.cc(975)] Network service crashed, restarting service.
```

## Logs from **Fiddle 2**

```
Electron v17.0.1 started.
verify ans.app
verify fonts.googleapis.com
verify fonts.googleapis.com
verify fonts.googleapis.com
verify assets.ans.app
verify www.recaptcha.net
verify www.gstatic.com
verify www.googletagmanager.com
verify static.cloudflareinsights.com
verify fonts.gstatic.com
verify code.sorryapp.com
verify www.google-analytics.com
verify js-agent.newrelic.com
verify bam.eu01.nr-data.net
verify ro-api.sorryapp.com
```
I managed to reproduce the bug on these versions:
- v14.2.16
- v15.4.0
- v16.0.10
- v17.0.1
- v17.1.1

using the [first Fiddle](https://github.com/electron/electron/issues/31675#issuecomment-1061827504).

Sometimes have you to run it a couple of times for the bug to show.
@nornagon This seems to be related to a patch you made a year ago: #28358
Crash dump:

```
Thread 6 (crashed)
 0  Electron Framework!net::CertVerifyResult::operator=(net::CertVerifyResult const&) [atomic : 1050 + 0x0]
    rax = 0xffffffff00000000   rdx = 0x000070000e7387e8
    rcx = 0xaaaaaaaaaaaaaaaa   rbx = 0x000070000e7387e8
    rsi = 0x0000003c00a0fc10   rdi = 0x000070000e7387e8
    rbp = 0x000070000e7387d0   rsp = 0x000070000e7387b0
     r8 = 0x0000003c00a0fc10    r9 = 0x0000000000000000
    r10 = 0x0000000120312490   r11 = 0x00000001243a2d70
    r12 = 0x000070000e7387e8   r13 = 0x0000003c00a0fc10
    r14 = 0x0000003c00a0fc10   r15 = 0x002f384f0803030a
    rip = 0x00000001203173b9
    Found by: given as instruction pointer in context
 1  Electron Framework!net::CachingCertVerifier::AddResultToCache(unsigned int, net::CertVerifier::RequestParams const&, base::Time, net::CertVerifyResult const&, int) [caching_cert_verifier.cc : 170 + 0x8]
    rbp = 0x000070000e7388c0   rsp = 0x000070000e7387e0
    rip = 0x000000012031259d
    Found by: previous frame's frame pointer
 2  Electron Framework!net::CachingCertVerifier::OnRequestFinished(unsigned int, net::CertVerifier::RequestParams const&, base::Time, base::OnceCallback<void (int)>, net::CertVerifyResult*, int) [caching_cert_verifier.cc : 128 + 0xb]
    rbp = 0x000070000e7388f0   rsp = 0x000070000e7388d0
    rip = 0x00000001203124ac
    Found by: previous frame's frame pointer
 3  Electron Framework!base::internal::Invoker<base::internal::BindState<void (net::CachingCertVerifier::*)(unsigned int, net::CertVerifier::RequestParams const&, base::Time, base::OnceCallback<void (int)>, net::CertVerifyResult*, int), base::internal::UnretainedWrapper<net::CachingCertVerifier>, unsigned int, net::CertVerifier::RequestParams, base::Time, base::OnceCallback<void (int)>, base::internal::UnretainedWrapper<net::CertVerifyResult> >, void (int)>::RunOnce(base::internal::BindStateBase*, int) [bind_internal.h : 535 + 0xc]
    rbp = 0x000070000e738920   rsp = 0x000070000e738900
    rip = 0x00000001203128e7
    Found by: previous frame's frame pointer
 4  Electron Framework!network::RemoteCertVerifier::OnRemoteResponse(net::CertVerifier::RequestParams const&, net::CertVerifyResult*, int, base::OnceCallback<void (int)>, int, net::CertVerifyResult const&) [network_context.cc : 0 + 0x3]
    rbp = 0x000070000e738960   rsp = 0x000070000e738930
    rip = 0x00000001211a9687
    Found by: previous frame's frame pointer
 5  Electron Framework!base::internal::Invoker<base::internal::BindState<void (network::RemoteCertVerifier::*)(net::CertVerifier::RequestParams const&, net::CertVerifyResult*, int, base::OnceCallback<void (int)>, int, net::CertVerifyResult const&), base::internal::UnretainedWrapper<network::RemoteCertVerifier>, net::CertVerifier::RequestParams, base::internal::UnretainedWrapper<net::CertVerifyResult>, int, base::OnceCallback<void (int)> >, void (int, net::CertVerifyResult const&)>::RunOnce(base::internal::BindStateBase*, int, net::CertVerifyResult const&) [bind_internal.h : 535 + 0xd]
    rbp = 0x000070000e738990   rsp = 0x000070000e738970
    rip = 0x00000001211a9706
    Found by: previous frame's frame pointer
 6  Electron Framework!network::mojom::CertVerifierClient_Verify_ForwardToCallback::Accept(mojo::Message*) [callback.h : 142 + 0x6]
    rbp = 0x000070000e738a50   rsp = 0x000070000e7389a0
    rip = 0x000000011d48ba45
    Found by: previous frame's frame pointer
 7  Electron Framework!mojo::InterfaceEndpointClient::HandleIncomingMessageThunk::Accept(mojo::Message*) [interface_endpoint_client.cc : 895 + 0xd]
    rbp = 0x000070000e738ac0   rsp = 0x000070000e738a60
    rip = 0x000000012029a599
    Found by: previous frame's frame pointer
 8  Electron Framework!mojo::MessageDispatcher::Accept(mojo::Message*) [message_dispatcher.cc : 43 + 0x9]
    rbp = 0x000070000e738b10   rsp = 0x000070000e738ad0
    rip = 0x000000012029e615
    Found by: previous frame's frame pointer
 9  Electron Framework!mojo::InterfaceEndpointClient::HandleIncomingMessage(mojo::Message*) [interface_endpoint_client.cc : 657 + 0x5]
    rbp = 0x000070000e738c70   rsp = 0x000070000e738b20
    rip = 0x000000012029ba5d
    Found by: previous frame's frame pointer
10  Electron Framework!mojo::internal::MultiplexRouter::Accept(mojo::Message*) [multiplex_router.cc : 1104 + 0xb]
    rbp = 0x000070000e738ee0   rsp = 0x000070000e738c80
    rip = 0x00000001202a8adf
    Found by: previous frame's frame pointer
11  Electron Framework!mojo::MessageDispatcher::Accept(mojo::Message*) [message_dispatcher.cc : 43 + 0x9]
    rbp = 0x000070000e738f30   rsp = 0x000070000e738ef0
    rip = 0x000000012029e615
    Found by: previous frame's frame pointer
12  Electron Framework!mojo::Connector::OnWatcherHandleReady(unsigned int) [connector.cc : 556 + 0xd]
    rbp = 0x000070000e7391b0   rsp = 0x000070000e738f40
    rip = 0x0000000120295b07
    Found by: previous frame's frame pointer
13  Electron Framework!mojo::SimpleWatcher::Context::CallNotify(MojoTrapEvent const*) [callback.h : 241 + 0x6]
    rbp = 0x000070000e7392a0   rsp = 0x000070000e7391c0
    rip = 0x00000001202ba1d6
    Found by: previous frame's frame pointer
14  Electron Framework!mojo::core::WatcherDispatcher::InvokeWatchCallback(unsigned long, unsigned int, mojo::core::HandleSignalsState const&, unsigned int) [watcher_dispatcher.cc : 93 + 0x4]
    rbp = 0x000070000e7392f0   rsp = 0x000070000e7392b0
    rip = 0x000000011e0e90bb
    Found by: previous frame's frame pointer
15  Electron Framework!mojo::core::Watch::InvokeCallback(unsigned int, mojo::core::HandleSignalsState const&, unsigned int) [watch.cc : 78 + 0xe]
    rbp = 0x000070000e739330   rsp = 0x000070000e739300
    rip = 0x000000011e0e84a0
    Found by: previous frame's frame pointer
16  Electron Framework!mojo::core::RequestContext::~RequestContext() [request_context.cc : 72 + 0xd]
    rbp = 0x000070000e7394e0   rsp = 0x000070000e739340
    rip = 0x000000011e0e33b5
    Found by: previous frame's frame pointer
17  Electron Framework!mojo::core::NodeChannel::OnChannelMessage(void const*, unsigned long, std::__1::vector<mojo::PlatformHandle, std::__1::allocator<mojo::PlatformHandle> >) [node_channel.cc : 834 + 0x5]
    rbp = 0x000070000e739800   rsp = 0x000070000e7394f0
    rip = 0x000000011e0d9f97
    Found by: previous frame's frame pointer
18  Electron Framework!mojo::core::Channel::TryDispatchMessage(base::span<char const, 18446744073709551615ul>, unsigned long*) [channel.cc : 933 + 0x10]
    rbp = 0x000070000e739a00   rsp = 0x000070000e739810
    rip = 0x000000011e0c8eec
    Found by: previous frame's frame pointer
19  Electron Framework!non-virtual thunk to mojo::core::(anonymous namespace)::ChannelMac::OnMachMessageReceived(unsigned int) [channel_mac.cc : 654 + 0x11]
    rbp = 0x000070000e739bf0   rsp = 0x000070000e739a10
    rip = 0x000000011e0ee04f
    Found by: previous frame's frame pointer
20  Electron Framework!base::MessagePumpKqueue::Run(base::MessagePump::Delegate*) [message_pump_kqueue.cc : 507 + 0x9]
    rbp = 0x000070000e739de0   rsp = 0x000070000e739c00
    rip = 0x000000011ffda696
    Found by: previous frame's frame pointer
21  Electron Framework!base::sequence_manager::internal::ThreadControllerWithMessagePumpImpl::Run(bool, base::TimeDelta) [thread_controller_with_message_pump_impl.cc : 468 + 0x6]
    rbp = 0x000070000e739e20   rsp = 0x000070000e739df0
    rip = 0x000000011ff717ea
    Found by: previous frame's frame pointer
22  Electron Framework!base::RunLoop::Run(base::Location const&) [run_loop.cc : 140 + 0x13]
    rbp = 0x000070000e739ed0   rsp = 0x000070000e739e30
    rip = 0x000000011ff3b711
    Found by: previous frame's frame pointer
23  Electron Framework!base::Thread::Run(base::RunLoop*) [thread.cc : 334 + 0x2a]
    rbp = 0x000070000e739f10   rsp = 0x000070000e739ee0
    rip = 0x000000011ff8d778
    Found by: previous frame's frame pointer
24  Electron Framework!base::Thread::ThreadMain() [thread.cc : 405 + 0xd]
    rbp = 0x000070000e739f80   rsp = 0x000070000e739f20
    rip = 0x000000011ff8d90d
    Found by: previous frame's frame pointer
25  Electron Framework!base::(anonymous namespace)::ThreadFunc(void*) [platform_thread_posix.cc : 99 + 0x9]
    rbp = 0x000070000e739fb0   rsp = 0x000070000e739f90
    rip = 0x000000011ffaa2e5
    Found by: previous frame's frame pointer
26  libsystem_pthread.dylib!_pthread_start + 0x7d
    rbp = 0x000070000e739fd0   rsp = 0x000070000e739fc0
    rip = 0x00007ff8058e84f4
    Found by: previous frame's frame pointer
27  libsystem_pthread.dylib!thread_start + 0xf
    rbx = 0x0000000000000000   rbp = 0x000070000e739ff0
    rsp = 0x000070000e739fe0   rip = 0x00007ff8058e400f
    Found by: call frame info
```

That's crashing [here](https://source.chromium.org/chromium/chromium/src/+/refs/tags/99.0.4767.0:net/cert/caching_cert_verifier.cc;l=170), so `verify_result` must be bad by the time `AddResultToCache` is called.

Looks like what's happening is that the SSLClientSocketImpl is getting destroyed while the CertificateVerifyProc is in-flight due to a SPDY upgrade:

```
2   Electron Framework                  0x0000000121542ce8 net::SSLClientSocketImpl::~SSLClientSocketImpl() + 136
3   Electron Framework                  0x0000000121542f1e net::SSLClientSocketImpl::~SSLClientSocketImpl() + 14
4   Electron Framework                  0x00000001215498a9 net::SSLConnectJob::~SSLConnectJob() + 233
5   Electron Framework                  0x000000012154996e net::SSLConnectJob::~SSLConnectJob() + 14
6   Electron Framework                  0x00000001215527ea net::TransportClientSocketPool::RemoveConnectJob(net::ConnectJob*, net::TransportClientSocketPool::Group*) + 218
7   Electron Framework                  0x0000000121551bfd net::TransportClientSocketPool::CancelRequest(net::ClientSocketPool::GroupId const&, net::ClientSocketHandle*, bool) + 1053
8   Electron Framework                  0x000000012152b9bb net::ClientSocketHandle::ResetInternal(bool, bool) + 315
9   Electron Framework                  0x000000012152be04 net::ClientSocketHandle::ResetAndCloseSocket() + 52
10  Electron Framework                  0x00000001214ac07c net::HttpStreamFactory::Job::OnSpdySessionAvailable(base::WeakPtr<net::SpdySession>) + 108
11  Electron Framework                  0x0000000121590612 net::SpdySessionPool::UpdatePendingRequests(net::SpdySessionKey const&) + 802
12  Electron Framework                  0x000000012159249a base::internal::Invoker<base::internal::BindState<void (net::SpdySessionPool::*)(net::SpdySessionKey const&), base::WeakPtr<net::SpdySessionPool>, net::SpdySessionKey>, void ()>::RunOnce(base::internal::BindStateBase*) + 138
```

We should be able to detect this and tear down the in-flight verify request gracefully when the underlying socket is destroyed to avoid crashing.
Looks like the way to get notified about request cancellation is by setting `out_req` to something here: https://github.com/electron/electron/blob/e41c3e960dd3a53c1f423c746b7079abb4359841/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch#L49

Destruction of that object signals cancellation: https://source.chromium.org/chromium/chromium/src/+/main:net/cert/cert_verifier.h;l=83;drc=459dba1945dd88c8dd98c6fbfd617f84e62eba99

CoalescingCertVerifier's logic is a bit complicated but does use this signal, as an example in upstream.

Fix patch:
diff --git a/patches/chromium/expose_setuseragent_on_networkcontext.patch b/patches/chromium/expose_setuseragent_on_networkcontext.patch
index 429dd72387ef7..b30988fba7e9c 100644
--- a/patches/chromium/expose_setuseragent_on_networkcontext.patch
+++ b/patches/chromium/expose_setuseragent_on_networkcontext.patch
@@ -33,10 +33,10 @@ index 14c71cc69388da46f62d9835e2a06fef0870da02..9481ea08401ae29ae9c1d960491b05b3
  
  }  // namespace net
 diff --git a/services/network/network_context.cc b/services/network/network_context.cc
-index ca62a13420aa9c114c00054bbe1215f96285a4e9..01be46b1eaed2aadfd24eac9d102da99521b175c 100644
+index 20373c0e86852446569c401c4a993512cb388fc7..9b6dbdad1a17148303ddecaada17a57d4ea22bb2 100644
 --- a/services/network/network_context.cc
 +++ b/services/network/network_context.cc
-@@ -1331,6 +1331,13 @@ void NetworkContext::SetNetworkConditions(
+@@ -1343,6 +1343,13 @@ void NetworkContext::SetNetworkConditions(
                                        std::move(network_conditions));
  }
  
diff --git a/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch b/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch
index d4aaf27702eea..d524259dd9428 100644
--- a/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch
+++ b/patches/chromium/network_service_allow_remote_certificate_verification_logic.patch
@@ -7,7 +7,7 @@ This adds a callback from the network service that's used to implement
 session.setCertificateVerifyCallback.
 
 diff --git a/services/network/network_context.cc b/services/network/network_context.cc
-index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..ca62a13420aa9c114c00054bbe1215f96285a4e9 100644
+index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..20373c0e86852446569c401c4a993512cb388fc7 100644
 --- a/services/network/network_context.cc
 +++ b/services/network/network_context.cc
 @@ -126,6 +126,11 @@
@@ -22,12 +22,38 @@ index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..ca62a13420aa9c114c00054bbe1215f9
  #if BUILDFLAG(IS_CT_SUPPORTED)
  #include "components/certificate_transparency/chrome_ct_policy_enforcer.h"
  #include "components/certificate_transparency/chrome_require_ct_delegate.h"
-@@ -433,6 +438,79 @@ bool GetFullDataFilePath(
+@@ -433,6 +438,91 @@ bool GetFullDataFilePath(
  
  }  // namespace
  
 +class RemoteCertVerifier : public net::CertVerifier {
 + public:
++  class Request : public net::CertVerifier::Request {
++   public:
++    Request() {}
++    ~Request() override = default;
++    void OnRemoteResponse(
++        const RequestParams& params,
++        net::CertVerifyResult* verify_result,
++        int error_from_upstream,
++        net::CompletionOnceCallback callback,
++        int error_from_client,
++        const net::CertVerifyResult& verify_result_from_client) {
++      if (error_from_client == net::ERR_ABORTED) {
++        // use the default
++        std::move(callback).Run(error_from_upstream);
++      } else {
++        // use the override
++        verify_result->Reset();
++        verify_result->verified_cert = verify_result_from_client.verified_cert;
++        std::move(callback).Run(error_from_client);
++      }
++    }
++    base::WeakPtr<Request> GetWeakPtr() { return weak_factory_.GetWeakPtr(); }
++   private:
++    base::WeakPtrFactory<Request> weak_factory_{this};
++  };
++
 +  RemoteCertVerifier(std::unique_ptr<net::CertVerifier> upstream): upstream_(std::move(upstream)) {
 +  }
 +  ~RemoteCertVerifier() override = default;
@@ -44,20 +70,14 @@ index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..ca62a13420aa9c114c00054bbe1215f9
 +  int Verify(const RequestParams& params,
 +             net::CertVerifyResult* verify_result,
 +             net::CompletionOnceCallback callback,
-+             std::unique_ptr<Request>* out_req,
++             std::unique_ptr<CertVerifier::Request>* out_req,
 +             const net::NetLogWithSource& net_log) override {
 +    out_req->reset();
 +
 +    net::CompletionOnceCallback callback2 = base::BindOnce(
 +        &RemoteCertVerifier::OnRequestFinished, base::Unretained(this),
-+        params, std::move(callback), verify_result);
-+    int result = upstream_->Verify(params, verify_result,
-+                                   std::move(callback2), out_req, net_log);
-+    if (result != net::ERR_IO_PENDING) {
-+      // Synchronous completion
-+    }
-+
-+    return result;
++        params, std::move(callback), verify_result, out_req);
++    return upstream_->Verify(params, verify_result, std::move(callback2), out_req, net_log);
 +  }
 +
 +
@@ -65,35 +85,27 @@ index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..ca62a13420aa9c114c00054bbe1215f9
 +    upstream_->SetConfig(config);
 +  }
 +
-+  void OnRequestFinished(const RequestParams& params, net::CompletionOnceCallback callback, net::CertVerifyResult* verify_result, int error) {
++  void OnRequestFinished(const RequestParams& params,
++                         net::CompletionOnceCallback callback,
++                         net::CertVerifyResult* verify_result,
++                         std::unique_ptr<CertVerifier::Request>* out_req,
++                         int error) {
 +    if (client_.is_bound()) {
++      // We take a weak pointer to the request because deletion of the request
++      // is what signals cancellation. Thus if the request is cancelled, the
++      // callback won't be called, thus avoiding UAF, because |verify_result|
++      // is freed when the request is cancelled.
++      *out_req = std::make_unique<Request>();
++      base::WeakPtr<Request> weak_req = static_cast<Request*>(out_req->get())->GetWeakPtr();
 +      client_->Verify(error, *verify_result, params.certificate(),
 +          params.hostname(), params.flags(), params.ocsp_response(),
-+          base::BindOnce(&RemoteCertVerifier::OnRemoteResponse,
-+            base::Unretained(this), params, verify_result, error,
-+            std::move(callback)));
++          base::BindOnce(&Request::OnRemoteResponse,
++            weak_req, params, verify_result, error, std::move(callback)));
 +    } else {
 +      std::move(callback).Run(error);
 +    }
 +  }
 +
-+  void OnRemoteResponse(
-+      const RequestParams& params,
-+      net::CertVerifyResult* verify_result,
-+      int error,
-+      net::CompletionOnceCallback callback,
-+      int error2,
-+      const net::CertVerifyResult& verify_result2) {
-+    if (error2 == net::ERR_ABORTED) {
-+      // use the default
-+      std::move(callback).Run(error);
-+    } else {
-+      // use the override
-+      verify_result->Reset();
-+      verify_result->verified_cert = verify_result2.verified_cert;
-+      std::move(callback).Run(error2);
-+    }
-+  }
 + private:
 +  std::unique_ptr<net::CertVerifier> upstream_;
 +  mojo::Remote<mojom::CertVerifierClient> client_;
@@ -102,7 +114,7 @@ index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..ca62a13420aa9c114c00054bbe1215f9
  constexpr uint32_t NetworkContext::kMaxOutstandingRequestsPerProcess;
  
  NetworkContext::PendingCertVerify::PendingCertVerify() = default;
-@@ -671,6 +749,13 @@ void NetworkContext::SetClient(
+@@ -671,6 +761,13 @@ void NetworkContext::SetClient(
    client_.Bind(std::move(client));
  }
  
@@ -116,7 +128,7 @@ index 8ff62f92ed6efdbfc18db53db3c5bb59c1acfe34..ca62a13420aa9c114c00054bbe1215f9
  void NetworkContext::CreateURLLoaderFactory(
      mojo::PendingReceiver<mojom::URLLoaderFactory> receiver,
      mojom::URLLoaderFactoryParamsPtr params) {
-@@ -2226,6 +2311,9 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
+@@ -2226,6 +2323,9 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
          std::move(cert_verifier));
      cert_verifier = base::WrapUnique(cert_verifier_with_trust_anchors_);
  #endif  // BUILDFLAG(IS_CHROMEOS)


Test patch: