Repository navigation
Conversation
`free_owned_href` freed a `Box<[u8]>` through its own `&'static [u8]` parameter, and a `bool` beside a `URL<'static>` said who owned the bytes. mordant reports the free as `freed_while_borrowed`, and Miri rejects it under Tree Borrows. `preconnect()` now takes a `bun_url::OwnedURL`. The `Preconnect` record holds it in a field declared after `async_http`, so the href drops after the request that views it. `fetch.rs` validates through an ordinary borrow of the `OwnedURL`: the leak, the forged `&'static [u8]` and the four hand-written frees are gone. `--fetch-preconnect` passes a copy of its argument, so the forged `'static` in `do_preconnect` goes too. `bun_http` joins the Miri crates with one test of the record. The test is undefined behaviour on the old shape. Removes the `freed_while_borrowed:src/http/AsyncHTTP.rs` line from mordant-baseline.toml.
Collaborator
Author
|
Updated 6:33 PM PT - Oct 26th, 2026
✅ @robobun, your commit b75f4fc2876412a4722311525cfea29dd837d5bf passed in 🧪 To try this PR locally: bunx bun-pr 47023That installs a local version of the PR into your bun-47023 --bun |
Collaborator
Author
|
Status: draft. It waits for #46528 and #46522, then it is cut again on top of them. The branch conflicts with main in
|
Collaborator
Author
|
#47098 changes two things that this PR meets when it is cut again.
Both PRs add the same |
This branch has not been deployed
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.
Draft until #46528 and #46522 are merged. See "Landing order" in Notes.
Behaviour change: none
Problem
free_owned_href(href: &'static [u8])(src/http/AsyncHTTP.rs:93) frees theBox<[u8]>behind its own reference parameter. mordant reportsthis call to destroy frees the value behind href while href is still borrowed. Miri rejects the free. No crash is known.fetch.preconnect(url).fetch.rsleaks the box and frees it by hand on four early returns.Fix
preconnect()takes abun_url::OwnedURL. ThePreconnectrecord holds it afterasync_http, so the href drops after the request that views it.fetch.rsborrows theOwnedURLto validate.--fetch-preconnectpasses a copy of its argument.free_owned_href,is_url_owned,reclaim_href!and the baseline line"freed_while_borrowed:src/http/AsyncHTTP.rs" = 1are gone.bun_httpjoins the Miri crates with one test of the record. On main's shape the test fails:Undefined Behavior: deallocation through <tag> ... is forbidden.bun run rust:miri -p bun_http,test/js/web/fetch/fetch-preconnect.test.ts, mordant, clippy. Self-reviewed: 15 concerns raised, 15 addressed.Background
Preconnectis the heap record of one preconnect. The HTTP thread frees it inPreconnect::on_result.bun_url::URL<'a>is a view of the href.bun_url::OwnedURLowns the href and parses a view on demand.--fetch-preconnect: a public enum to save one copy per flag. fetch: remove unsafe from FetchTasklet and fetch.rs #40202 and HTTP client: remove unsafe from src/http (h1/h2/h3 client, proxy tunnel, HTTP thread, keep-alive pool) #40385 have the same owner record.Downsides
fetch.preconnect()parses the href twice: +697 instructions (1,103 to 1,799, release build). A--fetch-preconnectflag copies and parses its URL once more: +716 instructions, one allocation, one free.Notes
Measurements (main 449877f against this branch)
bun run rust:miri -p bun_http, Tree Borrows): the record test passes on this branch. The same test written for main's record (url: URL<'static>,is_url_owned: true) fails withUndefined Behavior: deallocation through <tag> at alloc[0x0] is forbidden. The conflicting tag isFrozen, created atsrc/http/AsyncHTTP.rs:99:42(core::ptr::from_ref(href)).trying to retag ... for Unique permission ..., but that tag only grants SharedReadOnly permission). In that reduction a raw-pointer parameter forfree_owned_href, which is what the lint suggests, still fails under both models.URL::parseper scheduledfetch.preconnect: 1 -> 2 (gdb hit count over 20 calls). Per throwing call: 1 -> 1. Per--fetch-preconnectflag: 1 -> 2 (the instruction count ofURL::parse_hostgoes from 304 to 608 per flag).bun-profile,qemu-x86_64 -one-insn-per-tbexact count inBun__Bindgen__Fetch__preconnect,async_http::preconnect,Preconnect::on_result,RunCommand::do_preconnectand thebun_url::URLparse functions:async_http::preconnectno longer clones the 280-byte view.--fetch-preconnect: 1,039 -> 1,755 per flag (1 and 21 flags). This count does not include the allocator.perfis not installed in the container, so there is noperf statline.fetch.preconnect4.06 -> 4.06mi_mallocand 4.03 -> 4.03mi_free(200 calls). Per--fetch-preconnectflag 5.2 -> 6.25mi_mallocand 3.6 -> 4.65mi_free(1 and 21 flags): one more of each, for the copy of the href.size_of::<Preconnect>(): 2496 -> 2224 bytes (gdb, debug builds)..text82,164,617 -> 82,163,849 bytes,.rodatathe same (size -A;bloatyis not installed).async_http::preconnect1,333 -> 633 bytes,Preconnect::on_result166 -> 96,do_preconnect1,089 -> 1,208.src/http35 -> 34, new findings 0.bun_httpadds six crates to its build (bun_http,bun_picohttp,bun_dns,bun_brotli,bun_brotli_sys,bun_libdeflate_sys). In my container that is 42 s of build after the 146 s for the other 18 crates, and 8 s to run.cargo clippy -p bun_http -p bun_runtime --no-depsandcargo check -p bun_http --all-targets: no errors.fetch-session.test.ts("fetch.preconnect does not dial an origin the environment proxies") passes on the debug ASAN build.Landing order
tls_propsparameter topreconnectand change both call lines.git merge-filereports 4 and 3 conflict hunks against them: the signature and the call lines. Bound the remote image prefetch ofbun file.mdby a deadline and a byte budget #45818 conflicts in 1 hunk inAsyncHTTP.rs.preconnect(url: OwnedURL, tls_props).mordantjob is advisory, and no other check sees a conflict resolution that bringsfree_owned_hrefback.What does not change
fetch.preconnect()and--fetch-preconnect.fetch.preconnect()still allocates the href before it validates it, and frees it on an early return.Preconnect::on_resultas before. It is now freed after theAsyncHTTPdrop, not before. Nothing reads it in between.do_preconnectkeeps its signature. The CLI strings stay in the context and are never freed. The record owns a copy, so thefrom_raw_partsforge of a'staticslice indo_preconnectis gone.Not in this change
Boxfrom a shared slice:impl Drop for HTTPResponseMetadata(src/http/lib.rs:185) andTranspilerJob::reset_for_pool(src/jsc/RuntimeTranspilerStore.rs:506). mordant does not report them, because the reference is a field and not a parameter. They need their own owner types and are being handled separately.HTTPClient::dispatch_preconnect_result(&mut self)still ends in a free of the HTTP-thread request copy that embedsself. It is the&mut selfteardown class that fetch: release the FetchTasklet through its raw pointer, not a &mut receiver #37703 addresses for another type.freed_while_borrowed("take a raw pointer in place of the reference") makes mordant silent and leaves the free rejected by Miri. A line of this lint is only done when the owner frees through the pointer or value it holds.Self-review
PreconnectUrl::{Owned, Static}) asked for one owner for both callers, a blocking guard, the landing order above and the exclusions above. All 15 concerns are taken. Where the review offeredmem::takeof the CLI vector or a copy per flag, this takes the copy:do_preconnectand its two call sites stay as they are on http: consolidate the open fetch, Bun.serve, node:http, node:http2 and HTTP/3 pull requests #46528, net, tls: consolidate the open node:net, node:tls, Bun.connect, Bun.listen and usockets pull requests #46522 and HTTP client: remove unsafe from src/http (h1/h2/h3 client, proxy tunnel, HTTP thread, keep-alive pool) #40385.