Skip to content

Navigation Menu

Sign in
Sign up

deps: update libarchive to v3.7.9 - #20

Open
github-actions[bot] wants to merge 1 commit into
main from
deps/update-libarchive-3
Open

deps: update libarchive to v3.7.9 #20
github-actions[bot] wants to merge 1 commit into
main from
deps/update-libarchive-3

Conversation

@github-actions

@github-actions github-actions Bot commented Apr 13, 2025

Copy link
Copy Markdown

What does this PR do?

Updates libarchive to version v3.7.9

Compare: libarchive/libarchive@898dc83...ccf9843

Auto-updated by this workflow

pull Bot pushed a commit that referenced this pull request Aug 8, 2025
<details>
<summary> observed in
https://buildkite.com/bun/bun/builds/22442#annotation-test/js/node/zlib/leak.test.ts
</summary>
```
==5045==ERROR: AddressSanitizer: heap-use-after-free on address 0x5220000243c0 at pc 0x00000dad671b bp 0x14f22d4a4990 sp 0x14f22d4a4988
READ of size 8 at 0x5220000243c0 thread T5 (HeapHelper)
======== Stack trace from GDB for HeapHelper-5045.core: ========
Program terminated with signal SIGABRT, Aborted.
#0 0x000014f2c3672eec in ?? () from /lib/x86_64-linux-gnu/libc.so.6
[Current thread is 1 (Thread 0x14f22d4f46c0 (LWP 5050))]
#0 0x000014f2c3672eec in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1 0x000014f2c3623fb2 in raise () from /lib/x86_64-linux-gnu/libc.so.6
#2 0x000014f2c360e472 in abort () from /lib/x86_64-linux-gnu/libc.so.6
#3 0x000000000e3b2ae2 in uw_init_context_1[cold] ()
#4 0x000000000e3b29fc in _Unwind_Backtrace ()
#5 0x00000000046a6bab in __sanitizer::BufferedStackTrace::UnwindSlow(unsigned long, unsigned int) ()
#6 0x00000000046a181d in __sanitizer::BufferedStackTrace::Unwind(unsigned int, unsigned long, unsigned long, void*, unsigned long, unsigned long, bool) ()
#7 0x00000000046885bd in __sanitizer::BufferedStackTrace::UnwindImpl(unsigned long, unsigned long, void*, bool, unsigned int) ()
#8 0x0000000004601127 in __asan::ErrorGeneric::Print() ()
#9 0x0000000004683180 in __asan::ScopedInErrorReport::~ScopedInErrorReport() ()
#10 0x0000000004686567 in __asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) ()
#11 0x0000000004686d46 in __asan_report_load8 ()
#12 0x000000000dad671b in ZSTD_sizeof_CCtx (cctx=<optimized out>) at ./build/release-asan/zstd/vendor/zstd/lib/compress/zstd_compress.c:210
#13 0x0000000006d2284d in bun.js.node.zlib.NativeZstd.estimatedSize () at /var/lib/buildkite-agent/builds/ip-172-31-72-121/bun/bun/src/bun.js/node/zlib/NativeZstd.zig:57
#14 ZigGeneratedClasses.JSNativeZstd.JavaScriptCoreBindings.NativeZstd__estimatedSize (thisValue=<optimized out>) at /var/lib/buildkite-agent/builds/ip-172-31-72-121/bun/bun/build/release-asan/codegen/ZigGeneratedClasses.zig:11122
#15 0x000000000852803b in WebCore::JSNativeZstd::visitChildrenImpl<JSC::SlotVisitor> (cell=0x14f22e190840, visitor=...) at ./build/release-asan/./build/release-asan/codegen/ZigGeneratedClasses.cpp:30728
#16 WebCore::JSNativeZstd::visitChildren (cell=0x14f22e190840, visitor=...) at ./build/release-asan/./build/release-asan/codegen/ZigGeneratedClasses.cpp:30734
#17 0x000000000aa99d6c in JSC::MethodTable::visitChildren (this=<optimized out>, cell=<optimized out>, visitor=...) at vendor/WebKit/Source/JavaScriptCore/runtime/ClassInfo.h:115
#18 0x000000000aa99d6c in JSC::SlotVisitor::visitChildren (this=0x14f277028300, cell=0x14f22e190840)
#19 JSC::SlotVisitor::drain(WTF::MonotonicTime)::$_0::operator()(JSC::MarkStackArray&) const (this=<optimized out>, stack=...) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp:509
#20 0x000000000aa8f130 in JSC::SlotVisitor::forEachMarkStack<JSC::SlotVisitor::drain(WTF::MonotonicTime)::$_0>(JSC::SlotVisitor::drain(WTF::MonotonicTime)::$_0 const&) (this=0x14f277028300, func=...) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitorInlines.h:193
#21 JSC::SlotVisitor::drain (this=this@entry=0x14f277028300, timeout=<error reading variable: That operation is not available on integers of more than 8 bytes.>, timeout@entry=...) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp:499
#22 0x000000000aa90590 in JSC::SlotVisitor::drainFromShared (this=0x14f277028300, sharedDrainMode=JSC::SlotVisitor::HelperDrain, timeout=<error reading variable: That operation is not available on integers of more than 8 bytes.>) at vendor/WebKit/Source/JavaScriptCore/heap/SlotVisitor.cpp:699
#23 0x000000000aa08726 in JSC::Heap::runBeginPhase(JSC::GCConductor)::$_1::operator()() const (this=<optimized out>) at vendor/WebKit/Source/JavaScriptCore/heap/Heap.cpp:1508
#24 WTF::SharedTaskFunctor<void (), JSC::Heap::runBeginPhase(JSC::GCConductor)::$_1>::run() (this=<optimized out>) at .WTF/Headers/wtf/SharedTask.h:91
#25 0x000000000aa3b596 in WTF::ParallelHelperClient::runTask(WTF::RefPtr<WTF::SharedTask<void ()>, WTF::RawPtrTraits<WTF::SharedTask<void ()> >, WTF::DefaultRefDerefTraits<WTF::SharedTask<void ()> > > const&) (this=0x14f22e000428, task=...) at vendor/WebKit/Source/WTF/wtf/ParallelHelperPool.cpp:110
#26 0x000000000aa3d976 in WTF::ParallelHelperPool::Thread::work (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/ParallelHelperPool.cpp:201
#27 0x000000000aa4210d in WTF::AutomaticThread::start(WTF::AbstractLocker const&)::$_0::operator()() const (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/AutomaticThread.cpp:225
#28 WTF::Detail::CallableWrapper<WTF::AutomaticThread::start(WTF::AbstractLocker const&)::$_0, void>::call() (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/Function.h:53
#29 0x0000000008958ada in WTF::Function<void ()>::operator()() const (this=<optimized out>) at vendor/WebKit/Source/WTF/wtf/Function.h:82
#30 WTF::Thread::entryPoint (newThreadContext=<optimized out>) at vendor/WebKit/Source/WTF/wtf/Threading.cpp:272
#31 0x0000000008a65689 in WTF::wtfThreadEntryPoint (context=0x13b5) at vendor/WebKit/Source/WTF/wtf/posix/ThreadingPOSIX.cpp:255
#32 0x000000000467d347 in asan_thread_start(void*) ()
#33 0x000014f2c36711f5 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#34 0x000014f2c36f189c in ?? () from /lib/x86_64-linux-gnu/libc.so.6
```
</details>
`ZSTD_sizeof_CCtx` and `ZSTD_sizeof_DCtx` can not be relied upon to be
thread-safe and estimatedSize may be called from any thread
pull Bot pushed a commit that referenced this pull request Aug 20, 2025
...Worker" (oven-sh#21994)
Reverts oven-sh#21962
`vm.ensureTerminationException` allocates a JSString, which is not safe
to do from a thread that doesn't own the API lock.
```ts
Bun Canary v1.2.21-canary.1 (f706382a) Linux x64 (baseline)
Linux Kernel v6.12.38 | musl
CPU: sse42 popcnt avx avx2 avx512
Args: "/var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/release/bun-linux-x64-musl-baseline-profile/bun-profile" "/var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/test/js/node/worker_threads"...
Features: bunfig http_server jsc tsconfig(3) tsconfig_paths workers_spawned(40) workers_terminated(34)
Builtins: "bun:main" "node:worker_threads"
Elapsed: 362ms | User: 518ms | Sys: 63ms
RSS: 0.34GB | Peak: 100.36MB | Commit: 0.34GB | Faults: 0 | Machine: 8.17GB
 
panic(main thread): Segmentation fault at address 0x0
oh no: Bun has crashed. This indicates a bug in Bun, not your code.
 
To send a redacted crash report to Bun's team,
please file a GitHub issue using the link below:
 
 http://localhost:38809/1.2.21/Ba2f706382wNgkgUu11luEm6yX+lwy+Dgtt+oEurthoD8214mE___07+09DA2AA
 
 
 6 | describe("Worker destruction", () => {
 7 | const method = ["Bun.connect", "Bun.listen", "fetch"];
 8 | describe.each(method)("bun when %s is used in a Worker that is terminating", method => {
 9 | // fetch: ASAN failure
10 | test.skipIf(isBroken && method == "fetch")("exits cleanly", () => {
11 | expect([join(import.meta.dir, "worker_thread_check.ts"), method]).toRun();
 ^
error:
 
Command /var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/test/js/node/worker_threads/worker_thread_check.ts Bun.connect failed:
Spawned 10 workers RSS 79 MB
Spawned 10 workers RSS 87 MB
Spawned 10 workers RSS 90 MB
 
 at <anonymous> (/var/lib/buildkite-agent/builds/ip-172-31-38-185/bun/bun/test/js/node/worker_threads/worker_destruction.test.ts:11:73)
✗ Worker destruction > bun when Bun.connect is used in a Worker that is terminating > exits cleanly [597.56ms]
✓ Worker destruction > bun when Bun.listen is used in a Worker that is terminating > exits cleanly [503.47ms]
» Worker destruction > bun when fetch is used in a Worker that is terminating > exits cleanly
 
 
 1 pass
 1 skip
 1 fail
 2 expect() calls
Ran 3 tests across 1 file. [1125.00ms]
======== Stack trace from GDB for bun-profile-28234.core: ========
Program terminated with signal SIGILL, Illegal instruction.
#0 crash_handler.crash () at crash_handler.zig:1523
[Current thread is 1 (LWP 28234)]
#0 crash_handler.crash () at crash_handler.zig:1523
#1 0x0000000002db77aa in crash_handler.crashHandler (reason=..., error_return_trace=0x0, begin_addr=...) at crash_handler.zig:471
#2 0x0000000002db2b55 in crash_handler.handleSegfaultPosix (sig=<optimized out>, info=<optimized out>) at crash_handler.zig:792
#3 0x0000000004716b58 in WTF::jscSignalHandler (sig=11, info=0x7ffe54051e90, ucontext=0x0) at vendor/WebKit/Source/WTF/wtf/threads/Signals.cpp:548
#4 <signal handler called>
#5 JSC::VM::currentThreadIsHoldingAPILock (this=0x148296c30000) at vendor/WebKit/Source/JavaScriptCore/runtime/VM.h:840
#6 JSC::sanitizeStackForVM (vm=...) at vendor/WebKit/Source/JavaScriptCore/runtime/VM.cpp:1369
#7 0x0000000003f4a060 in JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::{lambda()#1}::operator()() const (this=<optimized out>) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/LocalAllocatorInlines.h:46
#8 JSC::FreeList::allocateWithCellSize<JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::{lambda()#1}>(JSC::LocalAllocator::allocate(JSC::Heap&, unsigned long, JSC::GCDeferralContext*, JSC::AllocationFailureMode)::{lambda()#1} const&, unsigned long) (this=0x148296c38e48, cellSize=16, slowPath=...) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/FreeListInlines.h:46
#9 JSC::LocalAllocator::allocate (this=0x148296c38e30, heap=..., cellSize=16, deferralContext=0x0, failureMode=JSC::AllocationFailureMode::Assert) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/LocalAllocatorInlines.h:44
#10 JSC::GCClient::IsoSubspace::allocate (this=0x148296c38e30, vm=..., cellSize=16, deferralContext=0x0, failureMode=JSC::AllocationFailureMode::Assert) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/IsoSubspaceInlines.h:34
#11 JSC::tryAllocateCellHelper<JSC::JSString, (JSC::AllocationFailureMode)0> (vm=..., size=16, deferralContext=0x0) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/JSCellInlines.h:192
#12 JSC::allocateCell<JSC::JSString> (vm=..., size=16) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/JSCellInlines.h:212
#13 JSC::JSString::create (vm=..., value=...) at cache/webkit-a73e665a39b281c5/include/JavaScriptCore/JSString.h:204
#14 0x0000000004479ad1 in JSC::jsNontrivialString (vm=..., s=...) at vendor/WebKit/Source/JavaScriptCore/runtime/JSString.h:846
#15 JSC::VM::ensureTerminationException (this=0x148296c30000) at vendor/WebKit/Source/JavaScriptCore/runtime/VM.cpp:627
#16 JSGlobalObject__requestTermination (globalObject=<optimized out>) at ./build/release/./src/bun.js/bindings/ZigGlobalObject.cpp:3979
#17 0x0000000003405ab8 in bun.js.web_worker.notifyNeedTermination (this=0x542904f0d80) at /var/lib/buildkite-agent/builds/ip-172-31-16-28/bun/bun/src/bun.js/web_worker.zig:558
#18 0x0000000004362b6f in WebCore::Worker::terminate (this=0x984c900000000000) at ./src/bun.js/bindings/webcore/Worker.cpp:266
#19 WebCore::jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSWorker*)::{lambda()#1}::operator()() const (this=<optimized out>) at ./build/release/./src/bun.js/bindings/webcore/JSWorker.cpp:549
#20 WebCore::toJS<WebCore::IDLUndefined, WebCore::jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSWorker*)::{lambda()#1}>(JSC::JSGlobalObject&, JSC::ThrowScope&, WebCore::jsWorkerPrototypeFunction_terminateBody(JSC::JSGlobalObject*, JSC::CallFrame*, WebCore::JSWorker*)::{lambda()#1}&&) (lexicalGlobalObject=..., throwScope=..., valueOrFunctor=...) at ./src/bun.js/bindings/webcore/JSDOMConvertBase.h:174
#21 WebCore::jsWorkerPrototypeFunction_terminateBody (lexicalGlobalObject=<optimized out>, callFrame=<optimized out>, castedThis=<optimized out>) at ./build/release/./src/bun.js/bindings/webcore/JSWorker.cpp:549
#22 WebCore::IDLOperation<WebCore::JSWorker>::call<&WebCore::jsWorkerPrototypeFunction_terminateBody, (WebCore::CastedThisErrorBehavior)0> (lexicalGlobalObject=..., operationName=..., callFrame=...) at ./src/bun.js/bindings/webcore/JSDOMOperation.h:63
#23 WebCore::jsWorkerPrototypeFunction_terminate (lexicalGlobalObject=<optimized out>, callFrame=0x7ffe540536b8) at ./build/release/./src/bun.js/bindings/webcore/JSWorker.cpp:554
#24 0x000014825580c038 in ?? ()
#25 0x00007ffe540537b0 in ?? ()
#26 0x0000148255a626cb in ?? ()
#27 0x0000000000000000 in ?? ()
1 crashes reported during this test
```
pull Bot pushed a commit that referenced this pull request May 16, 2026
...s, http (oven-sh#30722)
Hardens 36 reachable security findings across the runtime, package
manager, parsers, HTTP client/server, and SQL drivers. Three
auto-applied fixes (#61 SSL exception leak, #68 YAML merge dedup, #104
archive overwrite precheck) were dropped: #61 introduced a
use-after-free, #68 stored a non-`'static` byte view in a `'static`
field, and #104 added dead gating that did not close the traversal.
### Memory safety / lifetime
- #2 — Dangling proxy slice across reentrant JS getter — copy
`process.env` proxy href to an owned `Vec` before reentrant getters can
free the env map (`Blob.rs`)
- #15 — Rollback restores dangling editor name pointer — preserve and
restore `name_storage` on `detect_editor` failure (`BunObject.rs`)
- #81 — Reentrant reconnect frees live handlers — only free previous
handlers when `active_connections == 0` (`Listener.rs`)
- #110 — Async randomFill uses stale resizable buffer pointer — fill a
worker-owned scratch buffer; copy back on the JS thread after
re-validating bounds (`node_crypto_binding.rs`)
- #119 — Null zero-length slice UB in DOMJIT fast path — use
`ffi::slice` which tolerates `(null, 0)` (`Crypto.rs`)
- #67 — Raw serialization reads struct padding bytes — add explicit
`_padding_*` fields with `offset_of!` proof asserts (`npm.rs`)
- #74 — TLS rejection path leaks websocket refcount — route SSL/auth
failures through `self.fail()` which clears `outgoing_websocket`
(`websocket_client.rs`)
- #108 — FD-backed fetch body leaks duplicated descriptor — close
`opened_fd` unconditionally after `read_file` (`fetch.rs`)
### Untrusted-input bounds / panics
- #10 — Invalid lockfile tag causes panic DoS — replace `unreachable!()`
with logged error + `Tag::Uninitialized` (`dependency.rs`)
- #20 — Unchecked lockfile string offsets cause OOB slice — bounds-check
non-inline `String` pointers against `ctx.buffer` (`dependency.rs`)
- #91 — Panic on unvalidated resolution tag — validate `ResolutionTag`
discriminants on lockfile load (`Package.rs`)
- #24 — Unwrap panic on unexpected 304 response — return
`UnexpectedNotModified` when no cached manifest exists (`npm.rs`)
- #44 — UDP port getter unwrap panic on transient state — return
`undefined` when `socket` is `None` (`udp_socket.rs`)
- #36 — Close reason length mismatch causes panic — clamp `body_len` to
125 and bail on overlong UTF-8 transcode (`websocket_client.rs`)
- #100 — Windows pipe name length panic DoS — `debug_assert` → real
bounds check (`Listener.rs`)
- #60 / #111 — Windows shim stack buffer overflows — bounds-check
argument and filename writes against `BUF1_LEN`/`BUF2_U16_LEN` before
`copy_nonoverlapping` (`bun_shim_impl.rs`)
- #76 / #101 — Unchecked bin name/entry name copies — bounds-check
before slicing into `abs_dest_buf` (`bin.rs`)
- #79 — `if` keyword misclassification causes parser panic — require a
delimiter token before classifying (`shell_parser/parse.rs`)
- #32 — Bounds check occurs after UTF-16 write — pre-flight key/value
lengths before `convert_utf8_to_utf16_in_buffer` (`env_loader.rs`)
- #95 — PBKDF2 digest validation allows panic-only algorithm — reject
digests with no `EVP_MD` (`PBKDF2.rs`)
### DoS / resource caps
- #17 — Unbounded recursion on deep TOML dotted keys — cap dotted-key
segments at 512 (`toml.rs`)
- #39 — Unbounded brace expansion preallocation — cap expansion count at
65536 in `Bun.$` and `Bun.braces` (`BunObject.rs`, `Expansion.rs`)
- #31 — SCRAM PBKDF2 parameters accepted from server — clamp iteration
count to `[4096, 10M]`, salt length to `[1, 1024]`
(`PostgresSQLConnection.rs`)
### Auth / injection / traversal
- #19 — Cleartext password sent after TLS downgrade — require
`TLSStatus::SslOk`, not just `ssl_mode != Disable`
(`MySQLConnection.rs`)
- #83 — Strict TLS request reuses lax-verified pooled socket — track
`established_with_reject_unauthorized` and refuse pool reuse for strict
callers (`HTTPContext.rs`, `lib.rs`, `ClientSession.rs`)
- #73 — IPv6 loopback prefix auth bypass — exact-match `::1` instead of
`starts_with` (`server_body.rs`)
- #56 — Unsanitized filename injects response headers — reject
`\r`/`\n`/NUL/`"` in `content-disposition` filenames
(`RequestContext.rs`)
- #43 — Missing CRLF checks for signed host/auth headers — also validate
`region`, `access_key_id`, and `host` (`s3_signing/credentials.rs`)
- #34 — Bucket slash enables S3 host confusion — reject buckets
containing `/` (`s3_signing/credentials.rs`)
- #25 — Lexical symlink check permits extraction escape — track created
symlinks during extraction and refuse paths that traverse them
(`libarchive/lib.rs`)
- #71 — bunx executes untrusted temp-cache binary — `lstat` cached
binary; refuse symlinks and other-uid files (`bunx_command.rs`)
### Permission hygiene
- #6 — Bin target chmod always sets mode 0777 — `0o777 & !umask` instead
of `umask | 0o777` (`bin.rs`)
- #23 — Process umask cleared and never restored — restore umask after
probing it in `ensure_umask` (`bin.rs`)
### Parser correctness
- #22 — Sign-prefixed scalar misparsed as infinity — fix Zig→Rust
`&&`/`||` precedence transliteration (`yaml.rs`)
pull Bot pushed a commit that referenced this pull request Jul 13, 2026
...wn (oven-sh#34035)
Fixes `test/bake/dev/request-cookies.test.ts` going red on the `debian
13 x64-asan` lane (seen in [build
72183](https://buildkite.com/bun/bun/builds/72183#019f55d5-6839-41cf-b0f5-3c56ada43ef9)
and [build 71964](https://buildkite.com/bun/bun/builds/71964)):
```
dev| ==1715==ERROR: AddressSanitizer: SEGV on unknown address 0x000000007490
error: DevServer panicked
 at gracefulExit (test/bake/bake-harness.ts:614:17)
✗ DEV:request-cookies-1: request.cookies.get() basic functionality
```
### Cause
`~DevServerSourceProvider` held a raw `Zig::GlobalObject*` and called
`m_globalObject->bunVM()` to reach `Bun__removeDevServerSourceProvider`.
Under `BUN_DESTRUCT_VM_ON_EXIT=1` (set by the CI runner for the asan
lane), the harness's `process.exit(0)` runs
`Zig__GlobalObject__destructOnExit`, which does
`gcUnprotect(globalObject)` then `collectNow(Sync, Full)` then two
`vm.derefSuppressingSaferCPPChecking()`. The global object cell is swept
during `collectNow`, but the provider's last `Ref` is only released
later from `~CodeCache` inside `~JSC::VM`, so the destructor read
`m_bunVM` out of a freed cell.
With bmalloc the freed cell usually still holds the old value and the
read happens to work, which is why this was ~0.5% in CI and never
reproduced locally. When the memory is reused with a zero at that offset
the Rust side receives a null `VirtualMachine*` and the next access is
`(null)->source_mappings.mutex`, which lands at exactly 0x7490.
Deterministic ASAN backtrace with `Malloc=1`:
```
==79839==ERROR: AddressSanitizer: heap-use-after-free ...
 #0 Zig::GlobalObject::bunVM() const ZigGlobalObject.h:353
 #1 Bake::DevServerSourceProvider::~DevServerSourceProvider() DevServerSourceProvider.h:65
 ...
 #7 JSC::SourceCodeKey::~SourceCodeKey()
 #12 JSC::CodeCacheMap::~CodeCacheMap()
 #16 JSC::VM::~VM()
 #18 Zig__GlobalObject__destructOnExit ZigGlobalObject.cpp:4049
 #19 VirtualMachine::global_exit VirtualMachine.rs:1603
 #20 Bun__Process__exit
```
### Fix
Store the Rust `VirtualMachine*` directly (`void* m_bunVM`), captured in
`create()`, so the destructor no longer indirects through a GC cell.
This mirrors `Zig::SourceProvider`, which already stores `m_bunVM` for
the same reason. The Rust `VirtualMachine` outlives every GC cell (step
10 of `global_exit` is `self.destroy()`, after `destructOnExit` has
finished).
### Verification
New ASAN-only case in `test/bake/dev/server-sourcemap.test.ts` runs the
dev server with `Malloc=1` + `BUN_DESTRUCT_VM_ON_EXIT=1` so ASAN poisons
the swept global-object cell, making the UAF deterministic. Added an
`env` option to the `devTest` harness so the test can set those for the
spawned dev server.
```
# fail-before (src/ stashed)
SUMMARY: AddressSanitizer: heap-use-after-free ZigGlobalObject.h:353:48 in Zig::GlobalObject::bunVM() const
(fail) DEV:server-sourcemap-5: DevServerSourceProvider destructor does not touch the swept global object on process exit
# pass-after
(pass) DEV:server-sourcemap-5: DevServerSourceProvider destructor does not touch the swept global object on process exit
```
`test/bake/dev/server-sourcemap.test.ts` (5 tests) and
`test/bake/dev/request-cookies.test.ts` (2 tests) are green.
`request-cookies.test.ts` now also passes under the full CI LeakSan
config (`BUN_DESTRUCT_VM_ON_EXIT=1` + `detect_leaks=1`).
The bug is from a89e61f (oven-sh#22138), which introduced
`DevServerSourceProvider` with the raw global-object pointer.
<!-- robobun:evidence:begin -->
---
**[review]** gate passed · iteration 1 · 3 files touched
<details><summary>fails on main (without fix)</summary>
```console
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bake/dev/request-cookies.test.ts test/bake/dev/server-sourcemap.test.ts
info: syncing channel updates for nightly-2026年05月06日-x86_64-unknown-linux-gnu
info: latest update on 2026年05月06日 for version 1.97.0-nightly (e95e73209 2026年05月05日)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (722d6f0)
test/bake/dev/server-sourcemap.test.ts:
Dev server testing directory: /tmp/bun-dev-test-tI2Y82
bun add v1.4.0 (722d6f0)
Resolving dependencies
Resolved, downloaded and extracted [2]
Saved lockfile
installed react@0.0.0-experimental-603e6108-20241029
installed react-dom@0.0.0-experimental-603e6108-20241029
installed react-server-dom-bun@0.0.0-experimental-603e6108-20241029
installed react-refresh@0.0.0-experimental-603e6108-20241029
6 packages installed [462.00ms]
bun install v1.4.0 (722d6f0)
Checked 6 installs across 7 packages (no changes) [167.00ms]
�[0;30mdev|�[0m Started development server: http://localhost:37377
�[0;30mdev|�[0m �[32mBundled page in 2125ms�[0m�[2m:�[0
... (truncated)
release without fix: all passed
bun test v1.4.0-canary.1 (1498d7b)
test/bake/dev/server-sourcemap.test.ts:
Dev server testing directory: /tmp/bun-dev-test-7LPeWv
bun add v1.4.0-canary.1 (1498d7b)
Resolving dependencies
Resolved, downloaded and extracted [0]
Saved lockfile
installed react@0.0.0-experimental-603e6108-20241029
installed react-dom@0.0.0-experimental-603e6108-20241029
installed react-server-dom-bun@0.0.0-experimental-603e6108-20241029
installed react-refresh@0.0.0-experimental-603e6108-20241029
6 packages installed [9.00ms]
bun install v1.4.0-canary.1 (1498d7b)
Checked 6 installs across 7 packages (no changes) [0.00ms]
�[0;30mdev|�[0m Started development server: http://localhost:43275
�[0;30mdev|�[0m �[32mBundled page in 47ms�[0m�[2m:�[0m pages/[...slug].tsx �[2m+ 2 more�[0m
�[0;30mdev|�[0m �[0m�[1m1 |�[0m �[0m�[35mexport�[0m �[0m�[35mdefault�[0m �[0m�[35masync�[0m �[0m�[35mfunction�[0m MyPage(params) {
�[0;30mdev|�[0m �[0m�[1m2 |�[0m myFunc()�[0m�[2m;�[0m
�[0;30mdev|�[0m �[0m�[1m3 |�[0m �[0m�[35mreturn�[0m �[0m<�[0mh1>{JSON�[0m�[3m�[1m.stringify�[0m(params)}�[0m<�[0m/h1>�[0m�[2m;�[0m
�[0;30mdev|�[0m �[0m�[1m4 |�[0m }
�[0;30mdev|�[0m �[0m�[1m5 |�[0m 
�[0;30mdev|�[0m �[0m�
... (truncated)
```
</details>
<details><summary>passes on PR (with fix)</summary>
```console
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/bake/dev/request-cookies.test.ts test/bake/dev/server-sourcemap.test.ts
info: syncing channel updates for nightly-2026年05月06日-x86_64-unknown-linux-gnu
info: latest update on 2026年05月06日 for version 1.97.0-nightly (e95e73209 2026年05月05日)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (722d6f0)
test/bake/dev/server-sourcemap.test.ts:
Dev server testing directory: /tmp/bun-dev-test-dkR1se
bun add v1.4.0 (722d6f0)
Resolving dependencies
Resolved, downloaded and extracted [0]
Saved lockfile
installed react@0.0.0-experimental-603e6108-20241029
installed react-dom@0.0.0-experimental-603e6108-20241029
installed react-server-dom-bun@0.0.0-experimental-603e6108-20241029
installed react-refresh@0.0.0-experimental-603e6108-20241029
6 packages installed [119.00ms]
bun install v1.4.0 (722d6f0)
Checked 6 installs across 7 packages (no changes) [97.00ms]
�[0;30mdev|�[0m Started development server: http://localhost:44249
�[0;30mdev|�[0m �[32mBundled page in 2351ms�[0m�[2m:�[0m
... (truncated)
release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026年05月06日-x86_64-unknown-linux-gnu
info: latest update on 2026年05月06日 for version 1.97.0-nightly (e95e73209 2026年05月05日)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 690ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[0/6] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu)
info: syncing channel updates for nightly-2026年05月06日-x86_64-unknown-linux-gnu
info: latest update on 2026年05月06日 for version 1.97.0-nightly (e95e73209 2026年05月05日)
info: component rust-src is up to date
info: component rust-std is up to date
 nightly-2026年05月06日-x86_64-unknown-linux-gnu unchanged - rustc 1.97.0-nightly (e95e73209 2026年05月05日)
info: checking for self-update (current version: 1.29.0)
�[1m�[92m Compiling�[0m bun_core v0.0.0 (/workspace/bun/src/bun_core)
�[1m�[92m Compiling�[0m bun_errno v0.0.0 (/workspace/bun/src/errno)
�[1m�[92m Compiling�[0m bun_ptr v0.0.0 (/workspace/bun/src/ptr)
�[1m�[92m Compiling�[0m bun_boringssl_sys v0.0.0 (/workspace/bun/src/boringssl
... (truncated)
```
</details>
<details><summary>diff hotspot</summary>
```
src/runtime/bake/DevServerSourceProvider.h | 13 +++++++-----
 test/bake/bake-harness.ts | 5 +++++
 test/bake/dev/server-sourcemap.test.ts | 34 ++++++++++++++++++++++++++++++
 3 files changed, 47 insertions(+), 5 deletions(-)
```
</details>
**gate history** · 1 passed · 0 rejected · iteration 1
<details><summary>evidence per changed file</summary>
```
file reads edits tests
src/runtime/bake/DevServerSourceProvider.h 1 2 0
test/bake/bake-harness.ts 8 2 0
test/bake/dev/server-sourcemap.test.ts 1 4 0
```
</details>
<!-- robobun:evidence:end -->
pull Bot pushed a commit that referenced this pull request Jul 16, 2026
...ry rewrite (oven-sh#34271)
`test/js/bun/util/filesystem_router.test.ts` went red on alpine x64 in
build [73276](https://buildkite.com/bun/bun/builds/73276): the `reload()
while Bun.build() resolves the same directory` subprocess segfaulted in
`bust_dir_cache_recursive`, inlined from `NonNull::new`.
## Cause
`RealFS::entries_at` (`src/resolver/lib.rs`) replaces a cached
`DirEntry` in place when the caller's resolver generation is newer than
the cached listing's. The replacement at `*e_ptr = new_entry` drops the
old `DirEntry`, which drops its `data: StringHashMap<*mut Entry>` and
frees the hashmap's bucket allocation. The function's comment says
`entries_mutex held by caller`, but that is only true on one of the five
paths that reach it: `dir_info_uncached`, when entered from
`dir_info_cached_miss`. The other callers (`finalize_result`,
`handle_esm_resolution`, `load_index_with_extension`,
`Transpiler::run_env_loader`) all reach `entries_at` after
`dir_info_cached_maybe_log` has already returned and released both
`RESOLVER_MUTEX` and `entries_mutex`.
`FileSystemRouter::reload()` and `RouteLoader::load` iterate the same
`DirEntry.data` map under `entries_mutex` (the snapshot pattern oven-sh#33056
introduced for exactly this kind of concurrent rewrite). With
`entries_at`'s rewrite unsynchronized, a `Bun.build()` on the bundler
thread can drop the map while `reload()` on the JS thread is
mid-iteration.
The generation mismatch is what makes `entries_at` enter its rewrite
branch, so the window only opens once the bundle thread has processed at
least one batch (it bumps its own generation after every queue drain);
every subsequent `Bun.build()` then re-reads any directory that
`reload()` just refreshed to generation 0.
ASAN catches it as a heap-use-after-free with the two sides of the race
laid out exactly:
```
READ of size 16 (thread T0):
 #6 HashMap::values
 #7 StringHashMap<*mut Entry>::values src/collections/array_hash_map.rs:1864
 #8 FileSystemRouter::bust_dir_cache_recursive src/runtime/api/filesystem_router.rs:395
 #9 FileSystemRouter::bust_dir_cache src/runtime/api/filesystem_router.rs:451
 #10 FileSystemRouter::reload src/runtime/api/filesystem_router.rs:476
freed by thread T11 (Bundler):
 #11 drop_in_place<bun_resolver::fs_full::DirEntry>
 #12 bun_resolver::fs::RealFS::entries_at src/resolver/lib.rs:1639
 #13 DirInfo::get_entries_ref src/resolver/dir_info.rs:266
 #14 Resolver::finalize_result src/resolver/resolver.rs:1714
 #15 Resolver::resolve_and_auto_install src/resolver/resolver.rs:1485
 ...
 #23 BundleThread::generate_in_new_thread src/bundler/BundleThread.rs:276
previously allocated by thread T0:
 #17 HashMap::reserve
 #18 Resolver::dir_info_cached_miss src/resolver/resolver.rs:4591
 #19 Resolver::dir_info_cached_maybe_log src/resolver/resolver.rs:4201
 #20 Resolver::read_dir_info src/resolver/resolver.rs:4118
 #21 FileSystemRouter::reload src/runtime/api/filesystem_router.rs:492
```
(The use side is sometimes `RouteLoader::load` at
`src/router/lib.rs:816` instead; same map, same lock.)
This has been the shape of `entries_at` since the Rust port; oven-sh#33056
narrowed the race by snapshotting under the lock but assumed the rewrite
side already held it.
## Fix
`entries_at` now takes `entries_mutex` itself, matching
`read_directory_with_iterator` which already does. The one call path
that reaches it with the lock already held (`dir_info_cached_miss` ->
`dir_info_uncached` -> `parent_.get_entries_ref`) routes through a new
`entries_at_locked` / `get_entries_ref_locked` pair so the non-recursive
mutex is not re-entered. That path is the only one that passes a
non-`None` parent to `dir_info_uncached`; the other caller
(`dir_info_for_resolution`) passes `None`, so the parent branch
containing the accessor never runs there.
## Test
The existing concurrency test now awaits one `Bun.build()` first, so the
bundle thread's generation is already past zero when the concurrent
rounds start, and then runs forty reload/build rounds instead of one.
That is the shape that reaches the stale-generation rewrite at all; the
original single-round fixture usually completes with every build still
on generation 0.
The race is scheduling-dependent. Pinning the fixture to a single core
reproduces the ASAN use-after-free on roughly 3 in 10 runs against an
unpatched debug build and 0 in 15 with this change; with all 16 cores
available the unpatched build reproduces at roughly 1 in 30. The
assertions are otherwise the same as before, so the test continues to
cover the behavior oven-sh#33056 added.
Also ran the full `filesystem_router.test.ts`,
`test/bundler/bun-build-api.test.ts` (including the thousands-of-builds
test that exercises the generation path heavily),
`test/js/bun/resolve/resolve.test.ts`, `test/cli/hot/hot.test.ts`,
`test/cli/watch/watch.test.ts`, `test/bake/framework-router.test.ts`,
and `bun run rust:check-all` (10/10 targets).
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 0 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/js/bun/util/filesystem_router.test.ts
<!-- robobun:evidence:end -->
pull Bot pushed a commit that referenced this pull request Aug 18, 2026
)
### Problem
- Every eviction of a watchlist entry whose path is heap-owned leaks
that path. LSan on the unfixed build, after saving an imported file
three times under `bun --hot`:
`Direct leak of 111 byte(s) in 3 object(s) allocated from: ...
<bun_watcher::watcher_impl::Watcher>::append_file_assume_capacity::<true>
src/watcher/Watcher.rs:543` (full report below).
- Cause: `Watcher::flush_evictions` (`src/watcher/Watcher.rs:441`)
removes entries with `MultiArrayList::swap_remove`, and `swap_remove`
(`src/collections/multi_array_list.rs:994`) only copies the last row
over the removed one. The removed row's `WatchItem.file_path:
Cow<'static, [u8]>` is never dropped, and the list's own `Drop` is
slab-only by design, so nothing else frees it either.
- Heap-owned paths are every entry added with `CLONE_FILE_PATH = true`:
the modules `--hot` watches from the runtime transpiler
(`src/jsc/RuntimeTranspilerStore.rs:939`, `:967`,
`src/runtime/jsc_hooks.rs:3603`, `:3717`), the entrypoint
(`add_file_by_path_slow`), plugin-loaded files in `BundleV2`, dev server
directory watches (`add_directory::<true>`), and every entry on Windows.
- Under `--hot`, saving a watched file raises a directory event that
evicts the file's entry (`src/jsc/hot_reloader.rs:1218`) and the reload
re-adds it with a new copy, so a long-running session leaks one path per
save. `Borrowed` entries were never affected.
- Related, not fixed: oven-sh#11083 (`--hot` RSS growth per reload). Its loop
hits this leak too, but only for a few dozen bytes per reload, so the
growth reported there is mostly something else.
### Fix
- `MultiArrayList::swap_remove` and `ordered_remove` return the removed
element, transferring ownership to the caller exactly as `pop` already
does. `flush_evictions` drops the returned `WatchItem`; dropping frees
an `Owned` path and is a no-op for a `Borrowed` one.
- The row is gathered before the other rows are copied over it and `len`
shrinks, so the list never refers to it again: neither `drop_elements`
nor `Drop` (both of which only cover rows still in the list) can free it
a second time. The new `remove_returns_owned_element` unit test checks
this under Miri.
- Nothing holds a pointer into an evicted path when it is freed: both
`on_file_update` implementations (`src/jsc/hot_reloader.rs`,
`src/runtime/bake/DevServer.rs`) read the `file_path` column only before
their deferred `flush_evictions` and copy whatever they keep
(`StringSet` / `StringArrayHashMap` / `StringHashMap` keys are owned),
the Windows event scan indexes live rows only, and
`src/runtime/bake/dev_server/mod.rs:1439` already documents that the
watcher owns the copy until eviction runs.
- The fix is in the collection because that is where the ownership was
dropped; the only other production caller of either function is
`src/http/lib.rs` (`header_entries`, a `Copy` element type), which
compiles and behaves unchanged. `set()` intentionally keeps overwriting
without dropping: `append_assume_capacity` uses it on slots that hold no
element.
- Out of scope: a `Watcher` dropped while it still has live entries
leaks them too. That is the teardown path oven-sh#30644 covers with `impl Drop
for Watcher`; this PR only changes eviction.
- `--hot` / `append_file` coverage: new test in
`test/cli/hot/watch-many-dirs.test.ts` ("evicting watchlist entries does
not leak their paths", Linux + ASAN builds only). It runs `bun --hot`
with LSan enabled, saves an import three times, lets the child exit, and
asserts that the reloader logged the evictions (so the cycle cannot stop
evicting and pass vacuously), that the exit-time leak check ran, and
that it reported nothing and the exit code is 0. On the unfixed build it
fails with exactly one report block, the one quoted below; with the fix
it passes (5 of 5 runs, about 0.5 s each).
- Dev server / `append_directory` coverage: `test/bake/dev/css.test.ts`
is removed from `test/no-validate-leaksan.txt`, so CI's ASAN lane now
applies its exit-time leak check to that file. Without the fix, css-13
("changing html file with link tag works") and css-14 ("css import
before create") fail there with `Direct leak of 30 byte(s)` from
`append_directory_assume_capacity::<true>` via
`DirectoryWatchStore::insert`
(`src/runtime/bake/dev_server/mod.rs:1447`); with the fix all 15 cases
pass under the same environment. The file was excluded in the same batch
as the rest of the "Watcher Thread" block, with no css-specific reason.
The other entries in that block are left alone: `bundle.test.ts` still
has an unrelated leak (oven-sh#38004), and the rest are not affected by this
change.
- Also run with the fix: `cargo test -p bun_collections`, `bun run
rust:miri -p bun_collections`, `bun bd test test/cli/hot/` (17 pass),
`test/bake/dev/bundle.test.ts` (dev server file and
`DirectoryWatchStore` evictions, 21 pass), `test/bake/dev/hot.test.ts`,
`test/bake/dev/incremental-graph-edge-deletion.test.ts`,
`test/js/web/fetch/fetch-redirect.test.ts` (the `ordered_remove` caller,
30 pass); `cargo clippy -p bun_collections -p bun_watcher` is clean.
### Background
- `MultiArrayList<T>` is a struct-of-arrays list: each field of `T`
lives in its own column, so a row is never a single `T` in memory.
Removing a row is a byte copy per column, and the list's `Drop` frees
only the backing slab (bitwise clones of a list can share columns, see
the comment on the `Drop` impl), so element destructors run only when a
caller asks for them: `pop`, `drop_elements`, and now the two `*_remove`
functions.
- The watcher stores one `WatchItem` per watched file or directory in
such a list. `file_path` is a `Cow`: callers whose path string is
interned for the life of the process store a borrow (`CLONE_FILE_PATH =
false`); callers holding a transient buffer store a heap copy (`true`).
- Eviction is two-phase. `remove_at_index` only records an index in
`evict_list`; `flush_evictions`, run on the watcher thread at the end of
each `on_file_update` batch, closes the entries' fds and then
`swap_remove`s the rows, largest index first so the remaining recorded
indices stay valid.
- LSan (LeakSanitizer) ships inside the ASAN build and, when the process
exits, reports heap blocks that nothing references any more, with their
allocation stacks. Bun's `__asan_default_options` turns it off; CI's
ASAN lane turns it back on for every test process not listed in
`test/no-validate-leaksan.txt` (together with
`BUN_DESTRUCT_VM_ON_EXIT=1`, which tears the VM down first so
allocations still referenced from JS are not reported). The new `--hot`
test sets the same variables itself so it also works under a plain `bun
bd test`.
<details>
<summary>LSan report from the unfixed build (3 saves of
lib/dep.js)</summary>
```
Direct leak of 111 byte(s) in 3 object(s) allocated from:
 #0 0x00000820f5c8 in malloc crtstuff.c
 ...
 #16 0x00001367ff6b in <[u8]>::to_vec
 #17 0x0000119a10fd in <bun_watcher::watcher_impl::Watcher>::append_file_assume_capacity::<true> src/watcher/Watcher.rs:543:34
 #18 0x0000119a01a3 in <bun_watcher::watcher_impl::Watcher>::append_file_maybe_lock::<true, false> src/watcher/Watcher.rs:740:20
 #19 0x0000119a2c75 in <bun_watcher::watcher_impl::Watcher>::add_file::<true> src/watcher/Watcher.rs:902:22
 #20 0x00000f2055a2 in <bun_jsc::hot_reloader::ImportWatcher>::add_file::<true> src/jsc/hot_reloader.rs:90:18
 #21 0x00000f362cf2 in <bun_jsc::runtime_transpiler_store::TranspilerJob>::run src/jsc/RuntimeTranspilerStore.rs:967:60
 #22 0x00000f35febd in <bun_jsc::runtime_transpiler_store::TranspilerJob>::run_from_worker_thread src/jsc/RuntimeTranspilerStore.rs:626:30
 #23 0x000012e3753e in <bun_threading::thread_pool::Thread>::run src/threading/ThreadPool.rs:1249:26
```
One 37-byte object per save (the length of the temp dir path of
`lib/dep.js`); five saves give five objects.
</details>
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 0 · Platform-specific test(s) that do not
run on this machine. Deferring to CI, which covers all platforms:
test/cli/hot/watch-many-dirs.test.ts
<!-- robobun:evidence:end -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /