From adcfd6375c1d87242570262246c1213e7633600f Mon Sep 17 00:00:00 2001 From: John Hodnik Date: Wed, 19 Aug 2026 14:20:37 -0400 Subject: [PATCH] Fix deferred client certificate selection reading freed memory CefCertificateCallbackWrapper held the offered certificate list as `const X509CertificateList&`, bound to a stack local built in ClientAdapter::OnSelectClientCertificate. Once that handler returned the list was destroyed, so calling Select() at any later point walked freed memory and threw inside the thumbprint-matching loop, taking the host process down with it. CEF explicitly permits answering later. cef_request_handler.h says to return true and call Select "either in this method or at a later time", so a wrapper that outlives the handler has to own the list it selects from. It now holds a heap-allocated copy, freed in the finalizer. A ref class cannot contain a std::vector by value, hence the pointer. Copying the vector copies the reference-counted CefX509Certificate pointers, and those references are what keep the certificates alive. This is the remaining half of #2948. The comment above the caller reads "Create a copy of the vector in an attempt to fix #2948", and the copy is indeed made - but it is then bound by reference, so it dies at the same instant the original would have. Co-Authored-By: Claude Opus 5 (1M context) --- .../Internals/CefCertificateCallbackWrapper.h | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/CefSharp.Core.Runtime/Internals/CefCertificateCallbackWrapper.h b/CefSharp.Core.Runtime/Internals/CefCertificateCallbackWrapper.h index 2fe2883b46..07d632166b 100644 --- a/CefSharp.Core.Runtime/Internals/CefCertificateCallbackWrapper.h +++ b/CefSharp.Core.Runtime/Internals/CefCertificateCallbackWrapper.h @@ -19,11 +19,19 @@ namespace CefSharp { private: MCefRefPtr _callback; - const CefRequestHandler::X509CertificateList& _certificateList; + // Owned copy of the certificates Chromium offered, not a reference to the caller's. + // ClientAdapter::OnSelectClientCertificate builds that list as a stack local, so a + // reference to it dangles the moment the handler returns. CEF permits calling Select + // "either in this method or at a later time", so a wrapper that outlives the handler + // has to own the list it selects from, or a deferred Select reads freed memory. + // A ref class cannot hold a std::vector by value, hence the pointer. Copying the + // vector copies the reference-counted CefX509Certificate pointers, and those + // references are what keep the certificates themselves alive. + CefRequestHandler::X509CertificateList* _certificateList; public: CefCertificateCallbackWrapper(CefRefPtr& callback, const CefRequestHandler::X509CertificateList& certificates) - : _callback(callback), _certificateList(certificates) + : _callback(callback), _certificateList(new CefRequestHandler::X509CertificateList(certificates)) { } @@ -31,6 +39,9 @@ namespace CefSharp !CefCertificateCallbackWrapper() { _callback = nullptr; + + delete _certificateList; + _certificateList = nullptr; } ~CefCertificateCallbackWrapper() @@ -53,8 +64,8 @@ namespace CefSharp auto certThumbprint = cert->Thumbprint; std::vector>::const_iterator it = - _certificateList.begin(); - for (; it != _certificateList.end(); ++it) + _certificateList->begin(); + for (; it != _certificateList->end(); ++it) { auto bytes((*it)->GetDEREncoded()); auto byteSize = bytes->GetSize();