deps: update lolhtml to v3.0.1 - #4
Open
github-actions[bot] wants to merge 1 commit into
Open
Conversation
igorls
pushed a commit
that referenced
this pull request
Apr 9, 2026
Fixes a segfault when reading `.fd` on the result of `Bun.listen({ tls:
{ ... } })`.
`Listener.getFD` was calling `uws_listener.socket(true).fd()` for TLS
listeners. For `is_ssl=true`, the uSockets wrapper
`us_internal_ssl_socket_get_native_handle` returns `s->ssl`, and `fd()`
then calls `SSL_get_fd()` on it. But a listen socket has no SSL object —
SSL is per-connection — so `s->ssl` is uninitialized memory (ASAN poison
`0xbebebe...`) and the call segfaults.
Listen sockets always have a plain poll fd regardless of TLS, so get it
via the non-SSL path.
```
#3 SSL_get_rfd (ssl=0xbebebe0000000018)
#4 SSL_get_fd (ssl=0xbebebe0000000018)
#5 deps.uws.socket.NewSocketHandler(true).fd () at src/deps/uws/socket.zig:283
bun.js.api.bun.socket.Listener.getFD at src/bun.js/api/bun/socket/Listener.zig:532
```
Repro (also triggered when `console.log()` introspects the listener):
```js
const s = Bun.listen({
hostname: "localhost", port: 0,
socket: { data(){}, open(){}, close(){} },
tls: { passphrase: "abc" },
});
console.log(s.fd);
```
Found by Fuzzilli.
---------
Co-authored-by: robobun <robobun@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
Apr 25, 2026
…yToRoot (oven-sh#29483) Fuzzilli found a use-after-poison in the runtime auto-install path. `enqueueDependencyToRoot` passed `&lockfile.buffers.dependencies.items[dep_id]` into `enqueueDependencyWithMainAndSuccessFn`. When the manifest for the requested package is already cached (on disk or in memory) but the extracted tarball is not, control reaches `getOrPutResolvedPackageWithFindResult`, which calls `Lockfile.Package.fromNPM`. That grows `buffers.dependencies` via `ensureUnusedCapacity` to make room for the package's own dependencies, reallocating the backing storage. The subsequent `.extract` branch then read `dependency.behavior.isRequired()` from the freed buffer. ``` #0 getOrPutResolvedPackageWithFindResult PackageManagerEnqueue.zig:1520 dependency.behavior.isRequired() #1 getOrPutResolvedPackage PackageManagerEnqueue.zig:1778 #2 enqueueDependencyWithMainAndSuccessFn PackageManagerEnqueue.zig:523 #3 enqueueDependencyToRoot PackageManagerEnqueue.zig:321 #4 Resolver.enqueueDependencyToResolve resolver.zig:2356 ... oven-sh#14 Bun__resolveSync oven-sh#15 functionImportMeta__resolveSyncPrivate (runtime require() path) ``` Two changes: - `enqueueDependencyToRoot` now copies the `Dependency` to the stack before taking its address, matching every other caller of `enqueueDependencyWithMainAndSuccessFn` (`processDependencyListItem`, `processPeerDependencyList`, etc.). - The one read that ran after `fromNPM` now uses the `behavior` parameter that was already passed by value, instead of re-dereferencing `dependency`. Repro (debug/ASAN only): auto-install a package with a warm on-disk manifest but no extracted tarball — `fromNPM` appending even a single dependency forces a realloc of the one-entry buffer. The new test warms the cache, removes the extracted tarballs, and runs `require()` via `-e` so it goes through `Bun__resolveSync` → `enqueueDependencyToRoot`.
igorls
pushed a commit
that referenced
this pull request
Apr 25, 2026
`ResolveMessage.create` stored the `referrer` path via `Fs.Path.init`
without cloning. Every caller passes a temporary buffer — the `toUTF8()`
of a `bun.String` that is `deinit()`'d on return — so reading
`.referrer` after the creating frame unwound was a use-after-free.
Found by Fuzzilli as a flaky `use-after-poison` via `vi.mock()` →
`Bun__resolveSyncWithSource` → `resolveMaybeNeedsTrailingSlash`, but it
reproduces deterministically under ASAN with any non-ASCII source path:
```js
let err;
try {
Bun.resolveSync("./does-not-exist", "/tmp/café-🎉/file.js");
} catch (e) { err = e; }
Bun.gc(true);
err.referrer; // use-after-poison
```
```
==3080==ERROR: AddressSanitizer: use-after-poison on address 0x77cca9db0000 ...
READ of size 44 at 0x77cca9db0000 thread T0
#0 in __asan_memcpy
#1 in Zig::toStringCopy(ZigString) helpers.h:217
#2 in ZigString__toValueGC bindings.cpp:3402
#3 in ZigString.toJS ZigString.zig:57
#4 in ResolveMessage.getReferrer ResolveMessage.zig:221
```
In release builds the first 8 bytes of the returned referrer are
overwritten by mimalloc's free-list pointer instead of crashing.
Clone the referrer in `create()` and free it in `finalize()`. Also
`deinit()` the `toUTF8()` temporaries in `processFetchLog` now that
`create()` copies.
Co-authored-by: robobun <robobun@users.noreply.github.com>
github-actions
Bot
force-pushed
the
deps/update-lolhtml
branch
from
May 3, 2026 01:40
2b4851b to
04a1a75
Compare
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
…nt boundary is demoted (oven-sh#29951) ## Problem `DirectoryWatchStore.Dep.source_file_path` borrows the key slice from `IncrementalGraph.bundled_files` (via `insertEmpty`, see the comment at `DirectoryWatchStore.zig:69-71`). When a client-component boundary loses its `"use client"` directive, `server_graph.receiveChunk` detects the demotion and calls `client_graph.disconnectAndDeleteFile`, which frees that key string and overwrites the slot with `""`. The `Dep` is never notified, so it keeps pointing at freed memory. The next time the directory watcher fires for that directory, `HotReloadEvent.processFileList` walks the dep chain and does ```zig bun.path.dirname(dep.source_file_path, .auto) ``` on the freed slice, then (on resolution success) hashes it into `event.files` and passes it to `IncrementalGraph.invalidate`. Under ASAN this is a `use-after-poison` at `mem.lastIndexOfScalar` → `resolve_path.dirname` → `HotReloadEvent.processFileList:106`. ## Repro Requires `separateSSRGraph: true` so a `"use client"` file's imports are resolved under the browser target (and therefore its resolution-failure `Dep` borrows the **client** graph's key): 1. `Comp.ts` compiles cleanly as a CCB — `is_client_component_boundary = true` in the server graph, client graph owns the key. 2. Edit `Comp.ts` to import `'./missing'` while still `"use client"` — `trackResolutionFailure(.client, Comp.ts, './missing')` inserts a `Dep` whose `source_file_path` is the client-graph key for `Comp.ts`. 3. Edit `Comp.ts` to drop both the directive and the failing import — server parse succeeds, `receiveChunk` sees `scb=false && was_ccb=true` → `client_graph.disconnectAndDeleteFile` frees the key. The `Dep` from (2) now dangles. 4. Create `components/missing.ts` — the directory watcher fires, `processFileList` reads `dep.source_file_path` → freed memory. ## Fix - Add `DirectoryWatchStore.removeDependenciesForFile(alloc, file_path)` which walks every watch and drops any `Dep` whose `source_file_path.ptr == file_path.ptr` (pointer identity, since the slice is shared), freeing the watch if it becomes empty. - Call it from `disconnectAndDeleteFile` just before `g.allocator().free(key)`. - Relax the cleanup assertion in `freeEntry` to `dependencies.items.len == dependencies_free_list.items.len` (and clear both) so non-sequential dependency frees don't trip the debug assert. ## Verification ``` # without the fix (bun bd, ASAN): ==8747==ERROR: AddressSanitizer: use-after-poison on address 0x74d289f06db2 #4 bake.DevServer.HotReloadEvent.processFileList (HotReloadEvent.zig:106) (fail) DEV:bundle-8: removing 'use client' from a component with a pending resolution failure # with the fix: (pass) DEV:bundle-8: removing 'use client' from a component with a pending resolution failure [4232ms] ``` All 18 tests in `test/bake/dev/bundle.test.ts` pass. Co-authored-by: robobun <robobun@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
…es (oven-sh#30077) ## What When a chunked (or HTTP/3) request body exceeds `maxRequestBodySize`, `onBufferedBodyChunk` writes the 413 directly on the raw uWS response: ```zig resp.writeStatus("413 Payload Too Large"); resp.endWithoutBody(comptime !http3); ``` `internalEnd` → `markDone()` nulls `onAborted`, so when the socket closes no abort ever fires to detach `ctx.resp` or release the base ref. `this.resp` is left pointing at a completed response whose socket is about to be freed by `us_internal_free_closed_sockets`. If the fetch handler returned a pending Promise: - **resolve**: `handleResolve` → `isAbortedOrEnded()` is false (`this.resp != null`) → `render()` → `runCorkedWithType` corks the freed socket → **heap-use-after-free** (ASAN trace below). - **reject**: `handleReject` reads `resp.hasResponded()` off freed memory, sees `true`, skips the error handler, and returns without ever releasing the base ref → **RequestContext leaks** (`server.pendingRequests` never returns to 0). ## Fix Route through `this.endWithoutBody()` (the `RequestContext` wrapper) instead of the raw `resp.endWithoutBody()`. That path does `detachResponse()` (nulls `this.resp`, clears `onData`/`onAborted`/`onTimeout`) and `deref()` (releases the base ref), matching every other end path in this file. The body promise is rejected with the specific `"Request body exceeded maxRequestBodySize"` error *before* `endWithoutBody()` so `endRequestStreaming()` doesn't overwrite it with a generic `ConnectionClosed`. `has_written_status` is set so any later `renderMissing`/`renderMetadata` knows the status line is already committed. ## Repro ``` ==ERROR: AddressSanitizer: heap-use-after-free #0 us_socket_group socket.c:77 #1 uWS::AsyncSocket<false>::getLoopData() AsyncSocket.h:69 #2 uWS::AsyncSocket<false>::isCorked() AsyncSocket.h:141 #3 uWS::HttpResponse<false>::cork(...) HttpResponse.h:647 #4 uws_res_cork libuwsockets.cpp:1740 #5 ...runCorkedWithType Response.zig:299 #6 ...doRenderBlob RequestContext.zig:1942 ... oven-sh#11 ...handleResolve RequestContext.zig:220 oven-sh#12 ...onResolve RequestContext.zig:154 freed by: #1 us_poll_free epoll_kqueue.c:73 #2 us_internal_free_closed_sockets loop.c:305 ``` ## Test `test/js/bun/http/serve-pending-promise-abort-leak.test.ts` — new case sends a raw `Transfer-Encoding: chunked` POST exceeding `maxRequestBodySize` with a handler that holds its resolve/reject, waits for the socket to be reclaimed, then settles the Promise. Asserts `pendingRequests` returns to 0 for both paths, the body was rejected with the right message, and a follow-up request still works. Without the fix: ASAN heap-use-after-free on the resolve path; on release builds the reject path shows `pendingAfterReject: 1` (leak). Co-authored-by: robobun <robobun@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
…en-sh#30196) ## What does this PR do? Fixes a use-after-free in `HTMLRewriter.transform()` that caused flaky SIGSEGV crashes found by fuzzing. When transforming a string or ArrayBuffer, the body is buffered synchronously and fed to lol-html via `write()` followed by `end()`. If a document/element handler returns a rejected promise for the final `lastInTextNode` chunk (emitted from `end()`), the `end() catch` branch in `BufferOutputSink.runOutputSink` would call `response.finalize()` directly on the output `Response`. That `Response` is already owned by its JS wrapper cell (created earlier in `init()` via `sink.response.toJS()`), so destroying it in-place left the wrapper's `m_ctx` pointing at freed memory. When GC later swept the wrapper, its destructor invoked `Response.finalize()` again on that freed pointer: ``` AddressSanitizer: use-after-poison #0 bun.js.bindings.JSRef.JSRef.deinit src/bun.js/bindings/JSRef.zig:188 #1 bun.js.bindings.JSRef.JSRef.finalize src/bun.js/bindings/JSRef.zig:200 #2 bun.js.webcore.Response.finalize src/bun.js/webcore/Response.zig:474 #3 ResponseClass__finalize codegen/ZigGeneratedClasses.zig:17250 #4 WebCore::JSResponse::~JSResponse() codegen/ZigGeneratedClasses.cpp:54979 ``` The `write()` error path (just above it) already handled this correctly by returning the error and letting the JS wrapper own the Response lifetime. This PR makes the `end()` error path do the same — drop the manual `response.finalize()` and `sink.response = undefined`. ## How did you verify your code works? Minimal repro that reliably triggers the ASAN error before the fix and passes cleanly after: ```js const rewriter = new HTMLRewriter(); rewriter.onDocument({ text(chunk) { if (chunk.lastInTextNode) { return Promise.reject(new Error("boom")); } }, }); try { rewriter.transform(new Uint8Array([97, 98, 99]).buffer); } catch (e) {} Bun.gc(true); ``` Added regression tests in `test/js/workerd/html-rewriter.test.js` covering both ArrayBuffer and string inputs. All existing HTMLRewriter tests pass. --------- Co-authored-by: robobun <robobun@users.noreply.github.com>
igorls
pushed a commit
that referenced
this pull request
May 6, 2026
…worker panic, never retry (oven-sh#30216) ## What `bun test --isolate` / `--parallel` crashes when a test file loads a native addon whose deferred napi finalizers outlive the file. The `--parallel` coordinator then silently retries the file once, which masks the panic and lets the run exit 0. Fixes oven-sh#30205, oven-sh#30191. Supersedes oven-sh#30214 (same NapiEnv fix, but without the coordinator change, the `cleanup_hooks` retarget, or a test that actually reproduces on unpatched `main`). ## Reproduction ```sh git clone https://github.com/workglow-dev/libs && cd libs bun i && bun run build:packages bun test --timeout=30000 --parallel=4 packages/test/src/test/{util,task}/*.test.ts ``` On `main` (d484fd6), 3–4 workers crash per run with either ``` ASSERTION FAILED: isMarked(cell) JavaScriptCore/heap/Heap.cpp:1232 : void JSC::Heap::addToRememberedSet(const JSCell *) ``` or (when the slot is already being reallocated) ``` ASSERTION FAILED: m_cellState == CellState::DefinitelyWhite JavaScriptCore/JSCellInlines.h:69 : JSC::JSCell::JSCell(VM &, Structure *) ``` and in release builds the segfaults at `0x68` / `0xD0` reported in oven-sh#30205. ## Root cause Frame-pointer walk from the assertion: ``` #3 Bun::NapiHandleScope::open(Zig::GlobalObject*, bool) #4 NapiHandleScope__open #6 napi.Finalizer.run #7 napi.NapiFinalizerTask.runOnJSThread oven-sh#10 event_loop.tick oven-sh#11 event_loop.waitForPromise oven-sh#13 VirtualMachine.loadEntryPointForTestRunner ← next test file ``` `NapiEnv::m_globalObject` is a raw `Zig::GlobalObject*`. For non-experimental addons (`nm_version != NAPI_VERSION_EXPERIMENTAL`, which is ~every real-world addon — sharp, better-sqlite3, etc.), `napi_wrap`/`napi_create_external` finalizers are **deferred** to the event loop as `NapiFinalizerTask` rather than run inside GC sweep. Objects rooted on the old global (module graph, `globalThis.*`) only become collectable when `Zig__GlobalObject__createForTestIsolation` runs `gcUnprotect(oldGlobal)`. The `DeferGC` from oven-sh#29573 ends at that function's `}`, so the next GC runs there, collects those objects, and enqueues their finalizers. Those tasks then run on the very next `eventLoop().tick()` — inside `loadEntryPointForTestRunner`'s `waitForPromise` for file N+1. `Finalizer.run` opens a `NapiHandleScope` via `env->globalObject()`, which reads `NapiHandleScopeImplStructure()` off the dead cell and writes `m_currentNapiHandleScopeImpl` on it → write barrier on an unmarked cell. The `--parallel` coordinator's `reapWorker` then re-queued the file once (`retries[idx] < 1`) into a fresh worker with no stale `NapiEnv`, which passed — so the run reported 0 fail despite multiple Bun panics in the log. ## Fix **NapiEnv retarget** (`ZigGlobalObject.cpp`, `napi.h`): `Zig__GlobalObject__createForTestIsolation` now calls `newGlobal->adoptNapiEnvsForTestIsolation(oldGlobal)` before `gcUnprotect`. Each `NapiEnv::m_globalObject` is repointed at the new global and the `Ref<NapiEnv>`s are moved over, so late finalizers open handle scopes on a live global and the envs stay owned after the old global is swept. `VirtualMachine.swapGlobalForTestIsolation` also repoints `rare_data.cleanup_hooks[*].globalThis` so `CleanupHook.eql()` stays accurate. **No retry, abort on panic** (`Coordinator.zig`): removed the per-file retry. A worker that dies mid-file is counted as one failure. If it died by a fatal signal (SIGILL/SIGTRAP/SIGABRT/SIGBUS/SIGFPE/SIGSEGV/SIGSYS — Bun's own `@trap()`, a JSC/WTF assertion, or native-addon crash), the whole run aborts with `error: a test worker process crashed with <SIG> while running <file>`. `process.exit()` / SIGKILL are still just a per-file failure and the run continues. ## Verification - `test/regression/issue/30205.test.ts` — 4 tests. Adds a tiny non-experimental addon (`isolate_finalizer_addon.c`) and a fixture pattern (`Bun.gc(true)` + module-scope `await 0` + objects rooted on `globalThis`) that crashes **8/8** on unpatched `main` and passes 8/8 with this change. - `workglow-dev/libs` full 201-file unit suite: 3× clean `--parallel=4` runs (was 3–4 crashes/run). - Gate: `git stash -- src/ && bun bd test test/regression/issue/30205.test.ts` → 3/4 fail; with fix → 4/4 pass. - `test/cli/test/isolation.test.ts`, `test/regression/issue/29519.test.ts` → pass (one pre-existing unrelated timeout in isolation.test.ts, same as oven-sh#29573). - `test/cli/test/parallel.test.ts` → all tests I touched pass; the 3 timing-sensitive scale-up/work-steal tests that fail in this container fail identically on unmodified `main`. --------- Co-authored-by: robobun <robobun@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
github-actions
Bot
force-pushed
the
deps/update-lolhtml
branch
from
May 10, 2026 01:40
04a1a75 to
9c88d5f
Compare
igorls
pushed a commit
that referenced
this pull request
May 31, 2026
…s longer than the comparand (oven-sh#31264) ### What does this PR do? Fixes an ASAN `global-buffer-overflow` found by fuzzing the CSS parser: ``` asan:global-buffer-overflow:strncasecmp|eql_case_insensitive_ascii|eql_case_insensitive_ascii|bun_core::string::immutable::eql_case_insensitive_ascii_ignore_length ``` **Repro** ```sh BUN_FEATURE_FLAG_INTERNAL_FOR_TESTING=1 bun -e 'require("bun:internal-for-testing").cssInternals.minifyTest(":nth-child(Nn", "")' ``` ``` ==ERROR: AddressSanitizer: global-buffer-overflow READ of size 2 ... #0 strncasecmp #1 bun_core::strings_impl::eql_case_insensitive_ascii src/bun_core/lib.rs #2 bun_core::string::immutable::eql_case_insensitive_ascii_ignore_length src/bun_core/string/immutable.rs #3 bun_css::css_parser::nth::parse_nth src/css/css_parser.rs #4 bun_css::selectors::parser::parse_nth_pseudo_class src/css/selectors/parser.rs ``` **Cause** `strings_impl::eql_case_insensitive_ascii(a, b, check_len)` defers to `strncasecmp(a, b, a.len())`, which reads up to `a.len()` bytes from *both* buffers. The Zig original (`strings.eqlCaseInsensitiveASCII`) compared against NUL-terminated comptime literals, so `strncasecmp` stopped at the sentinel and reported a mismatch whenever `a` was longer than `b`. Rust byte-string literals carry no terminator, so the An+B parser's ident branch (`parse_nth`), which compares an arbitrary user ident against the keywords `"even" / "odd" / "n" / "-n" / "n-" / "-n-"` with the ignore-length variant, reads past the end of the keyword literal as soon as the ident is longer than the keyword and shares its prefix (`Nn` vs `n`, `n-3` vs `n`, …). Besides the OOB read, the comparison result depended on whatever byte happens to follow the literal in rodata. **Fix** Reject `b.len() < a.len()` up front in `eql_case_insensitive_ascii` before calling `strncasecmp` — the same result the NUL sentinel produced in Zig, so observable behavior is unchanged for every in-bounds input (all other callers of the ignore-length variant already pass equal-length slices). `strncasecmp` now only ever reads within both slices. **Verification** - `bun bd test test/js/bun/css/nth-anplusb-ident.test.ts` without the fix (src/ stashed): aborts with the ASAN global-buffer-overflow above. - With the fix: passes. The new test covers valid `n-<digits>` idents that are longer than the `n`/`n-` keywords (`:nth-child(n-3)`, `:nth-child(N-3)`, `:nth-last-child(n- 42)`), keyword case-insensitivity (`:nth-child(N)`), an invalid ident (`:nth-child(NN)` → parse error), and the exact fuzzer-minimized input run in a subprocess. - `bun bd test test/js/bun/css/css.test.ts`: 1032 pass, 0 fail (no behavior change for the existing suite). - A second fuzz report hits the same overflow through `Bun.build` with a CSS entrypoint containing `:nth-child(Nn`; that path goes through the same `parse_nth` comparison and is covered by this fix (`Bun.build` now reports a parse error instead of aborting). - The `build-rust` CI failures on this PR (unused label / unnecessary `unsafe` warnings in `src/spawn`, `src/install`, `src/crash_handler`, `src/runtime/ffi`, `src/runtime/dns_jsc`) are present on current `main` commits that don't include this change and come from files this PR doesn't touch.
github-actions
Bot
force-pushed
the
deps/update-lolhtml
branch
from
June 7, 2026 01:50
9c88d5f to
a63a6bf
Compare
github-actions
Bot
force-pushed
the
deps/update-lolhtml
branch
from
August 2, 2026 01:39
a63a6bf to
0a2c626
Compare
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 lolhtml to version v3.0.1
Compare: cloudflare/lol-html@e9e16dc...608cc4a
Auto-updated by this workflow