Repository navigation
deps: update libarchive to v3.8.9 - #6
Open
github-actions[bot] wants to merge 1 commit into
Open
github-actions[bot] wants to merge 1 commit into
github-actions[bot] wants to merge 1 commit into
Conversation
github-actions
Bot
force-pushed
the
deps/update-libarchive
branch
from
August 2, 2026 04:14
969e632 to
86c3690
Compare
Seantheprogrammer93
pushed a commit
that referenced
this pull request
Sep 27, 2026
…h#36545) ## Repro Under a debug/ASAN build: ```js const result = Bun.spawnSync(["cat"], { stdin: "ignore" }); const copy = { ...result }; ``` ``` ASSERTION FAILED: hasInlineStorage() JSObject.h(437) : PropertyStorage JSC::JSObject::inlineStorage() frame #4: JSC::JSObject::inlineStorage frame #5: tryCreateObjectViaCloning (ObjectConstructorInlines.h:281) frame #6: globalFuncCloneObject (JSGlobalObjectFunctions.cpp:1006) ``` The same assert fires for a spread of an `S3Client.list()` result or one of its `contents` entries, for `const x = structuredClone({}); x.a = 1; ({ ...x })`, and for `const m = new (require("node:module").Module)("x"); m.exports.a = 1; ({ ...m.exports })`. Release builds are unaffected: the pointer returned by `inlineStorage()` is fed into a copy loop bounded by `inlineCapacity`, which is zero, so nothing is dereferenced. ## Cause Several bindings call `constructEmptyObject(globalObject, objectPrototype(), 0)` (either as a literal `0` or as a computed size that can be `0`), which yields a `JSFinalObject` with `inlineCapacity() == 0` and therefore `hasInlineStorage() == false`. Every property added to such an object lands out of line in the butterfly. The object-spread fast path `tryCreateObjectViaCloning` in the current WebKit build calls `source->inlineStorage()` unconditionally to seed `createWithButterflyCopyingInlineStorage`, and `inlineStorage()` asserts `hasInlineStorage()` in debug builds. No object produced by JavaScript has zero inline capacity (`{}` gets `JSFinalObject::defaultInlineCapacity`, and `ObjectAllocationProfile` asserts `inlineCapacity > 0`), so only host-created objects with an explicit `0` hint reach this state. ## Fix Treat a zero hint as "unsized" and route it through `constructEmptyObject(globalObject)`, which produces the same `defaultInlineCapacity` structure as a JS `{}` literal and gives the first handful of properties inline slots instead of forcing a butterfly. - `JSC__JSValue__createEmptyObject` and `JSC__JSObject__create` (the two Rust FFI entry points) gain an `if (!initialCapacity)` branch. This covers every Rust caller of `JSValue::create_empty_object(_, 0)` (25 call sites on main, `Bun.spawnSync` among them) and `JSObject::create_with_initializer(_, _, 0)`. The non-zero branch now clamps the `size_t` hint to `maxInlineCapacity` before it narrows to `unsigned`, so a hint that is a multiple of 2^32 cannot narrow to zero either. - The direct C++ callers that pass `0` (literal or computed) switch to the default-capacity overload: `SQLClient.cpp` row fallback, `ZigGlobalObject.cpp` worker serialized-env when empty (Windows only on current main), `bindings.cpp` `SystemError` `info`, `NodeModuleModule.cpp` `new Module().exports`, the two `SerializedScriptValue.cpp` fast-path deserializer sites (`structuredClone({})` / `structuredClone([{}])`), the `_NativeModule.h` `INIT_NATIVE_MODULE` macro (applied to the uncached construction), the `JSEnvironmentVariableMap.cpp` `process.env` target when `count == 0`, and the `JSONRowsToJS.cpp` object builder when the parsed object has no properties (reaches user code through `Bun.JSONC.parse`, `Bun.TOML.parse`, JSON5, and XML; this file landed on main after the first audit and review caught it). Two sites from the first version of this PR are gone after the merge with main: `ProcessBindingUV.cpp` got the same fix in oven-sh#34660, and the two `NodeHTTP.cpp` `assignHeaders*` sites were removed when request headers became lazy. The remaining computed-capacity `constructEmptyObject(.*objectPrototype(),` sites in `src/` are `fromEntries` (already guarded), `JSFetchHeaders.cpp` (early-returns on `size == 0`), `JSDOMFormData.cpp` / `JSURLSearchParams.cpp` (`size + 1`), and `JSSQLStatement.cpp` (`initializeColumnNames` returns early when `count < 1`). Everything else passes a literal nonzero constant. ## Verification New spread tests guarding each confirmed-aborting surface: - `test/js/bun/spawn/spawnSync.test.ts`: subprocess spreads the `Bun.spawnSync` result. - `test/js/bun/s3/s3-list-objects.test.ts`: subprocess spreads the top-level `list()` result, a `contents` entry, its `owner`, and a `commonPrefixes` entry. - `test/js/node/module/node-module-module.test.js`: subprocess spreads `new Module().exports` after a put. - `test/js/web/structured-clone-fastpath.test.ts`: spreads `structuredClone({})` and `structuredClone([{}])[0]` after adding a property. - `test/js/node/process-binding.test.ts`: spreads `process.binding("uv")` (the fix for this one is already on main, the test pins it). - `test/js/sql/sql.test.ts`: spreads the wide-row result on the null-structure fallback path. - `test/js/bun/jsonc/jsonc.test.ts`: spreads a parsed nested empty `{}` (JSONC) and an empty TOML table after adding a property. Each aborts (or, for the subprocess tests, produces empty stdout) against main's `src/` and passes with the fix. The assertion only exists in debug builds of JavaScriptCore, so these tests cannot fail against a release binary. The remaining sites are defensive rather than confirmed aborts and ship without a dedicated test: - `SystemError` `info`: its `DontDelete` properties bail `checkStructureForClone`. - `JSC__JSObject__create`: its sole caller early-returns through the guarded helper when the count is 0. - Worker serialized-env: the object is consumed before user code can spread it. - `INIT_NATIVE_MODULE(0)` (`node:constants`): 216 default-attribute properties push the structure to `Dictionary`, which bails the fast path. - `JSEnvironmentVariableMap.cpp` `process.env` target: unconditional `CustomAccessor` puts for TZ et al. bail `checkStructureForClone`. <!-- 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/node/module/node-module-module.test.js, test/js/bun/spawn/spawnSync.test.ts <!-- robobun:evidence:end --> --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Seantheprogrammer93
pushed a commit
that referenced
this pull request
Sep 27, 2026
…back closes the session (oven-sh#42647) ### Problem - A server `RST_STREAM` can make the HTTP/2 fetch client read and free a stream twice. A debug ASAN build of main (09bb546) reports `AddressSanitizer: heap-use-after-free` in `ClientSession::handle_data` (`src/http/h2_client/ClientSession.rs:848`, the `s.state != StreamState::Closed` read). The stream was freed by `drop_stream` in `ClientSession::fail_streams` (`ClientSession.rs:943`). A release build has no report, only heap corruption. - It needs a request with a custom TLS context (for example `tls: { serverName }`) whose entry left the 60-entry context cache. That request then holds the last ref. Its terminal callback drops the context, the drop closes the session's socket, and `on_close` runs `fail_streams` inside the deliver loop. ### Fix - While `delivering` is set, `fail_streams` fails each client and leaves the streams in `streams` and `by_http_id`. The deliver loop already removes a stream that has no client, through `remove_stream`, so each stream is freed once. - Outside the loop `fail_streams` behaves as before. - Correct because the loop's tail is safe on a session that is already closed: `maybe_release` returns at the `registry_index` sentinel, and a write to the closed socket writes nothing. - Verified: `test/js/web/fetch/fetch-http2-client.test.ts` (two new ASAN-only tests, 67 pass). With `src/` at main the RST test fails. Also `fetch-http2-leak.test.ts` (7 pass) and `fetch-http2-adversarial.test.ts` (20 pass). ### Background - `ClientSession` is one HTTP/2 connection of the fetch client. `streams` maps a stream id to a heap `Stream`. Each `Stream` points at the `HTTPClient` of one `fetch()`. - `handle_data` parses frames, then delivers each ready stream to its client in a loop, with `delivering` set. A terminal delivery runs the result callback at once, on the HTTP thread. - Custom TLS contexts are refcounted. The cache holds one ref and each request holds one. <details><summary>Notes</summary> This is the part of oven-sh#31788 that main still needs. oven-sh#31788 was closed as stale, and all five of its `src/` files conflict with main now. - Its first defect, `abort_by_http_id` with no ref on the session, is fixed on main: since oven-sh#37870 every entry point goes through `ClientSession::enter`, which holds a `RefPtr`. The abort test from oven-sh#31788 passes on main. It is included here because main has no test that evicts a TLS context. - Its third part, a `ctx` field on `PendingConnect`, has no reproduction and is not included. ASAN report on main, trimmed: ``` ERROR: AddressSanitizer: heap-use-after-free READ of size 1 thread T2 (HTTP Client) #0 <State as PartialEq>::eq src/http/h2_client/Stream.rs:104 #2 ClientSession::handle_data src/http/h2_client/ClientSession.rs:848 #4 ClientSession::enter src/http/h2_client/ClientSession.rs:242 #6 Handler<true>::on_data src/http/HTTPContext.rs:1386 freed by thread T2 (HTTP Client) here: oven-sh#12 drop_stream src/http/h2_client/ClientSession.rs:211 oven-sh#13 ClientSession::fail_streams src/http/h2_client/ClientSession.rs:943 ``` The two tests share one helper. The child holds a stream open on a context made by `serverName`, runs 61 more TLS configs to evict it, writes `evicted` to stderr, and then ends the held request: one test aborts it, the other has the server send `RST_STREAM(CANCEL)`. Both check that a later `fetch()` still works. They run only under ASAN and carry a 30 s timeout, because each child makes 62 TLS handshakes and a regression needs time to print its report. Without the longer timeout the failing run on main shows a timeout at 5 s, not the report. The tests are serial on purpose: each one fills the context cache, which would evict the context that a concurrent test depends on. One older test in the same file changed: `concurrent requests multiplex on one h2 session`. Its server held each stream for 100 ms and the test expected 8 open at once. On a loaded machine it saw 7 (2 of 9 runs of the file). The server now answers when the eighth stream is open, so the test uses no timer. After the change the file passed 4 of 4 runs on the same loaded machine. </details> <!-- robobun:evidence:begin --> --- **[human-review]** gate passed · iteration 1 · 2 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/pr_gate.xml" "test/js/web/fetch/fetch-http2-client.test.ts" bun test v1.4.3 (b993710) test/js/web/fetch/fetch-http2-client.test.ts: (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > GET: status, headers and body round-trip [1309.07ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > response body larger than one DATA frame [1215.67ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > concurrent requests multiplex on one h2 session [1497.63ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > POST with ReadableStream body streams as raw DATA frames [730.67ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > gzip content-encoding is decompressed [2353.52ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > POST: request body is delivered as DATA frames [2514.64ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > POST with ReadableStream body larger than initial send window [927.77 ... (truncated) release without fix: 2 skipped bun test v1.4.3-canary.1 (f92be71) test/js/web/fetch/fetch-http2-client.test.ts: (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > concurrent requests multiplex on one h2 session [110.34ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > gzip content-encoding is decompressed [115.04ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > response trailers are consumed without breaking the body [99.30ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > connection-specific request headers are stripped before HPACK [111.10ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > cold-start: parallel requests coalesce onto one TLS connect [135.66ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > response body larger than one DATA frame [147.00ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > GET: status, headers and body round-trip [160.06ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > keep-alive: sequential requests reuse one h2 session [139.51ms] (pass) fetch() over H ... (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/pr_gate.xml" "test/js/web/fetch/fetch-http2-client.test.ts" bun test v1.4.3 (b993710) test/js/web/fetch/fetch-http2-client.test.ts: (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > GET: status, headers and body round-trip [1532.08ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > response body larger than one DATA frame [1265.37ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > POST: request body is delivered as DATA frames [1731.76ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > POST with ReadableStream body streams as raw DATA frames [501.02ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > gzip content-encoding is decompressed [1802.42ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > concurrent requests multiplex on one h2 session [1743.84ms] (pass) fetch() over HTTP/2 (BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT) > concurrent ReadableStream uploads route each chunk to its own stream ... (truncated) release with fix: 2 skipped $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) in 710ms (unchanged) ninja: Entering directory `/workspace/bun/build/release' [0/5] cargo bun_runtime → libbun_runtime.a �[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_sys) �[1m�[92m Compiling�[0m bun_safety v0.0.0 (/workspace/bun/src/safety) �[1m�[92m Compiling�[0m bun_base64 v0.0.0 (/workspace/bun/src/base64) �[1m�[92m Compiling�[0m bun_cares_sys v0.0.0 (/workspace/bun/src/cares_sys) �[1m�[92m Compiling�[0m bun_zlib_sys v0.0.0 (/workspace/bun/src/zlib_sys) �[1m�[92m Compiling�[0m bun_zstd v0.0.0 (/workspace/bun/src/zstd) �[1m�[92m Compiling�[0m bun_picohttp v0.0.0 (/workspace/bun/src/picohttp) �[1m�[92m Compiling�[0m bun_brotli v0.0.0 (/workspace/bun/src/brotli) �[1m�[92m Compiling�[0m bun_output v0.0.0 (/workspace/bun/src/output) �[1m�[92m Compiling�[0m bun_clap v0.0.0 (/workspace/bun/src/clap) �[1m�[92m Compiling�[0m bu ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/http/h2_client/ClientSession.rs | 12 ++- test/js/web/fetch/fetch-http2-client.test.ts | 118 ++++++++++++++++++++++++--- 2 files changed, 117 insertions(+), 13 deletions(-) ``` </details> **gate history** · 1 passed · 1 rejected · iteration 1 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/http/h2_client/ClientSession.rs 2 1 24 test/js/web/fetch/fetch-http2-client.test.ts 1 2 24 ``` </details> <!-- robobun:evidence:end -->
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Updates libarchive to version v3.8.9
Compare: libarchive/libarchive@ded8229...27cbc78
Auto-updated by this workflow