From aeddfca1f034aca4248638bbbc23295bc80bbba0 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Mon, 9 Feb 2026 20:54:46 +0000 Subject: [PATCH] fix(shell): fix use-after-free in shell interpreter error path on Windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `runFromJS()` encounters an IO setup failure, it was calling `#deinitFromExec()` which directly frees the interpreter via `allocator.destroy(this)`. However, in the JS code path the interpreter is a GC-managed object — the GC finalizer (`deinitFromFinalizer`) will later attempt to access and free the already-freed memory, causing a use-after-free / segfault. Replace `#deinitFromExec()` with `#derefRootShellAndIOIfNeeded(true)` which properly cleans up only runtime resources (IO, shell env) and marks `cleanup_state = .runtime_cleaned`, allowing the GC finalizer to safely handle the final destruction of the interpreter object. Closes #26836 Co-Authored-By: Claude --- src/shell/interpreter.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shell/interpreter.zig b/src/shell/interpreter.zig index cc79cb8971..300f8d42bd 100644 --- a/src/shell/interpreter.zig +++ b/src/shell/interpreter.zig @@ -1154,7 +1154,11 @@ pub const Interpreter = struct { _ = callframe; // autofix if (this.setupIOBeforeRun().asErr()) |e| { - defer this.#deinitFromExec(); + // Clean up runtime resources but NOT the interpreter itself — + // this is a GC-managed object, so let the finalizer handle destruction. + // Using #deinitFromExec() here would call allocator.destroy(this), + // causing a use-after-free when the GC later runs deinitFromFinalizer(). + defer this.#derefRootShellAndIOIfNeeded(true); const shellerr = bun.shell.ShellErr.newSys(e); return try throwShellErr(&shellerr, .{ .js = globalThis.bunVM().event_loop }); }