Electron CVE-2026-34774: Brief Summary of a Use-After-Free in Offscreen Rendering Child Windows

A brief summary of CVE-2026-34774, a high severity use-after-free in Electron's offscreen rendering path that affects applications permitting child windows. Includes patch analysis and affected version details.

CVE Analysis

8 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-04-03

Electron CVE-2026-34774: Brief Summary of a Use-After-Free in Offscreen Rendering Child Windows
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A use-after-free in Electron's offscreen rendering path quietly introduced a memory corruption risk for any desktop application that combines offscreen rendering with permissive child window creation. Given that Electron powers applications like VS Code, Slack, Discord, Signal, Docker, Notion, and AI tools from OpenAI and Anthropic, the potential blast radius of framework-level memory safety bugs is substantial, even when the vulnerable configuration is narrow.

CVE-2026-34774 carries a CVSS 8.1 (High) score and affects Electron versions prior to 39.8.1, 40.7.0, and 41.0.0. The flaw is triggered when a parent offscreen WebContents is destroyed while a child window spawned via window.open() remains alive. Subsequent paint frames on the orphaned child dereference freed memory belonging to the now-destroyed parent. Applications that do not use offscreen rendering (webPreferences.offscreen: true) or that deny child windows through their setWindowOpenHandler are entirely unaffected.

Technical Information

Root Cause: Unsafe base::Unretained(this) in Paint Callback Binding

The vulnerability originates in how Electron wires up the paint callback when creating a child window from an offscreen-rendered parent. Inside MaybeOverrideCreateParamsForNewWindow in shell/browser/api/electron_api_web_contents.cc, the code constructs an OffScreenWebContentsView for the child and binds its paint callback directly to the parent WebContents using base::Unretained(this):

// Before the fix — vulnerable code path auto* view = new OffScreenWebContentsView( false, offscreen_use_shared_texture_, offscreen_shared_texture_pixel_format_, base::BindRepeating(&WebContents::OnPaint, base::Unretained(this)));

The problem is one of object lifetimes. At the point where MaybeOverrideCreateParamsForNewWindow executes, the child WebContents does not yet exist. The code fills this gap by pointing the callback at the parent. base::Unretained(this) creates a raw, non prevent dangling pointer; it performs no prevent lifetime checks and does not invalidate when the referenced object is destroyed.

The Use-After-Free Trigger

The sequence that triggers the vulnerability is:

  1. An Electron application enables offscreen rendering (webPreferences.offscreen: true) and configures setWindowOpenHandler to permit child windows.
  2. A child window is opened via window.open(). During creation, the child's OffScreenWebContentsView paint callback is bound to the parent WebContents via base::Unretained(this).
  3. The parent offscreen WebContents is destroyed (for example, the parent BrowserWindow is closed or garbage collected).
  4. The child window remains open and continues to render. When the next paint frame fires, the child's callback invokes WebContents::OnPaint on the now-freed parent memory.
  5. This dereference of freed memory constitutes a use-after-free (CWE-416). Depending on the state of the heap, this results in a crash or memory corruption.

Attack Vector Analysis

The CVSS 3.1 vector is AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H. The network attack vector (AV:N) means the exploit could be delivered remotely through malicious web content loaded into the vulnerable Electron application. The high attack complexity (AC:H) reflects the need for an attacker to carefully orchestrate the timing of parent destruction relative to child paint frames. Notably, no privileges (PR:N) and no user interaction (UI:N) are required. If successfully exploited, the impact spans confidentiality, integrity, and availability, all rated High.

An attacker targeting this vulnerability would need to:

  1. Identify an Electron application that uses offscreen rendering and permits child windows.
  2. Deliver content (likely via a loaded URL or injected script) that opens a child window via window.open().
  3. Trigger destruction of the parent offscreen WebContents while the child window is still alive.
  4. Wait for the child window to render a paint frame, invoking the dangling callback into freed memory.

The high attack complexity stems from the precise timing required and the specific configuration prerequisites.

Patch Information

The Electron team addressed CVE-2026-34774 in commit 5fded6ae, merged on March 10, 2026, via PR #50184. The fix was published as part of Electron versions 39.8.1, 40.7.0, and 41.0.0, and is tracked under GitHub Security Advisory GHSA-532v-xpq5-8h95.

The patch is a compact but precise 17 line addition across three files, correcting two design flaws in a single targeted refactor. Here is a walkthrough of each change.

Step 1: Neutralize the Unsafe Binding at Creation Time

In shell/browser/api/electron_api_web_contents.cc, within MaybeOverrideCreateParamsForNewWindow, the dangerous base::BindRepeating(&WebContents::OnPaint, base::Unretained(this)) was replaced with base::DoNothing():

// shell/browser/api/electron_api_web_contents.cc — MaybeOverrideCreateParamsForNewWindow if (is_offscreen) { + // Use a no-op callback here. The real OnPaint callback will be bound + // to the child WebContents in AddNewContents via SetCallback(). auto* view = new OffScreenWebContentsView( false, offscreen_use_shared_texture_, - offscreen_shared_texture_pixel_format_, - base::BindRepeating(&WebContents::OnPaint, base::Unretained(this))); + offscreen_shared_texture_pixel_format_, base::DoNothing()); create_params->view = view; create_params->delegate_view = view; }

At the time the create params are being assembled, the child WebContents does not yet exist. Previously, the code filled the gap by pointing at the parent, introducing the dangling pointer hazard. Now, a harmless no-op placeholder takes its place.

Step 2: Rebind the Callback to the Correct Owner

Later in the same file, inside AddNewContents (called once the child WebContents has been fully created), a new block rebinds the paint callback to the child using GetWeakPtr() instead of a raw pointer:

// shell/browser/api/electron_api_web_contents.cc — AddNewContents auto api_web_contents = CreateAndTake(isolate, std::move(new_contents), type); + // Rebind the paint callback to the child WebContents. + if (auto* osr_view = api_web_contents->GetOffScreenWebContentsView()) { + osr_view->SetCallback(base::BindRepeating(&WebContents::OnPaint, + api_web_contents->GetWeakPtr())); + }

The use of GetWeakPtr() is the critical safety improvement. A weak pointer automatically invalidates when the referenced object is destroyed, so even if lifetimes mismatch in an unexpected way, the callback will simply no-op rather than dereference freed memory.

Step 3: Expose SetCallback on the View Class

To support the deferred rebinding, a new public method SetCallback was added to OffScreenWebContentsView:

// shell/browser/osr/osr_web_contents_view.h void SetWebContents(content::WebContents*); void SetNativeWindow(NativeWindow* window); + void SetCallback(const OnPaintCallback& callback);
// shell/browser/osr/osr_web_contents_view.cc +void OffScreenWebContentsView::SetCallback(const OnPaintCallback& callback) { + callback_ = callback; +}

This method overwrites the stored callback_ member, replacing the initial base::DoNothing() placeholder with the properly bound child callback.

In summary, the patch eliminates the cross-lifetime ownership hazard by never binding the paint callback to the parent WebContents, and adds a safety net by using GetWeakPtr() rather than base::Unretained() for the replacement binding. The total change is 17 additions and 2 deletions across three files, touching only the offscreen rendering path.

Affected Systems and Versions

The vulnerability affects Electron applications that meet both of the following conditions:

  1. Offscreen rendering is enabled (webPreferences.offscreen: true)
  2. The setWindowOpenHandler permits child windows

Applications that do not use offscreen rendering, or that deny child windows, are not affected.

Release BranchVulnerable VersionsPatched Version
Branch 39Prior to 39.8.139.8.1
Branch 4040.0.0-alpha.1 to prior to 40.7.040.7.0
Branch 4141.0.0-alpha.1 to prior to 41.0.041.0.0

Vendor Security History

Electron operates under the OpenJS Foundation as an Impact Project, having graduated from incubation in June 2020. The project maintains a dedicated Security Working Group responsible for handling vulnerability reports and coordinating fixes. Their security policy provides a clear reporting process and an escalation path to the OpenJS Foundation if responses are delayed. For critical issues, the Security Working Group issues advance Need to Know notifications to trusted partners via private channels, allowing major application developers to prepare fixes before public disclosure. The timeline for CVE-2026-34774 reflects this maturity: the fix was merged on March 10, 2026, the GitHub Security Advisory was published on April 2, 2026, and the CVE record followed on April 3, 2026.

References

Detect & fix
what others miss

Security magnifying glass visualization