From aa018cd835219a3869197ee040c4a4cd5f1ee60a Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Tue, 19 May 2026 07:52:37 +0000 Subject: [PATCH] install(security-scanner): gate post-start() deref on Ok to avoid over-deref StaticPipeWriter::start() is now refcount-neutral on Err (it releases its own +1 and leaves started == false). The security-scanner caller was still unconditionally dropping start()'s +1, which over-derefs on the Err path: refcount hits zero while json_writer still holds a pointer, then the errdefer scopeguard touches freed memory (source.detach()) and double-frees on w.deref(). Also fix two stale doc comments in SSLConfig.rs that still referenced mi_free / mimalloc after dupe_z / free_sensitive moved to default_alloc. Follow-up to #30875. --- src/install/PackageManager/security_scanner.rs | 12 ++++++++---- src/runtime/socket/SSLConfig.rs | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/install/PackageManager/security_scanner.rs b/src/install/PackageManager/security_scanner.rs index c672c888b236..23d2631209c3 100644 --- a/src/install/PackageManager/security_scanner.rs +++ b/src/install/PackageManager/security_scanner.rs @@ -1390,10 +1390,14 @@ impl<'a> SecurityScanSubprocess<'a> { // SAFETY: `writer_local` holds a live ref; `start()` mutates the writer // in place (raw intrusive object — no Rust aliasing across the RefPtr). let start_result = unsafe { (*writer_ptr).start() }; - // SAFETY: `writer_local` keeps `*writer_ptr` live; we own the `start()` ref. - unsafe { RefCount::::deref(writer_ptr) }; - // SAFETY: `writer_local` keeps `*writer_ptr` live. - unsafe { (*writer_ptr).started = false }; + // `start()` is refcount-neutral on `Err` (it releases its own +1 and + // leaves `started == false`); only the `Ok` path leaves a +1 to drop. + if start_result.is_ok() { + // SAFETY: `writer_local` keeps `*writer_ptr` live; we own the `start()` ref. + unsafe { RefCount::::deref(writer_ptr) }; + // SAFETY: `writer_local` keeps `*writer_ptr` live. + unsafe { (*writer_ptr).started = false }; + } match start_result { Err(e) => { writer_local.deref(); diff --git a/src/runtime/socket/SSLConfig.rs b/src/runtime/socket/SSLConfig.rs index 3995090efc18..db4083811a11 100644 --- a/src/runtime/socket/SSLConfig.rs +++ b/src/runtime/socket/SSLConfig.rs @@ -60,13 +60,13 @@ impl From for ReadFromBlobError { // so `free_sensitive` would not pair with `Box`-owned memory. // ────────────────────────────────────────────────────────────────────────── -/// `ZBox` is global-allocator memory; re-allocate via `dupe_z` so `mi_free` can free it. +/// `ZBox` is global-allocator memory; re-allocate via `dupe_z` so `free_sensitive` can free it. #[inline] fn zbox_into_raw(z: &bun_core::ZBox) -> *const c_char { bun_core::dupe_z(z.as_bytes()) } -/// `dupeZ` a byte slice into a fresh mimalloc allocation. +/// `dupeZ` a byte slice into a fresh default-allocator allocation. #[inline] fn dupe_z(bytes: &[u8]) -> *const c_char { bun_core::dupe_z(bytes)