From 8f16dd91a4ed99297ec87925b334b809ff8aeea7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Fri, 10 Jul 2026 11:30:09 +0200 Subject: [PATCH] fix(stdlib): recognize Blob handles by the fetch handle band, not addr < 0x10000 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Blob handle ids start at FETCH_HANDLE_BAND_START (0x40000), but several Blob byte-extraction sites tested `addr < 0x10000` to detect a Blob handle. A real Blob therefore never matched and fell through to a path that dereferenced the synthetic id as a pointer → SIGSEGV. Recognize the whole handle band (via `addr_class::is_handle_band` / `is_above_handle_band`) and copy the blob's bytes through `blob_bytes_clone`: - new Blob([blob]) — nested Blob as a BlobPart (append_one_blob_part) - new Response(blob).arrayBuffer — Blob body (js_response_body_init_ptr) - new Request(url,{body: blob}) — Blob body (js_request_new) Also hardened append_blob_parts (a handle-band value is not an array header) and the Response object-body fallback (don't ToString a non-Blob handle-band id). Fixes #6231. (cherry picked from commit f7e45fc4206f877a985aec55e7fed52bc77d56d9) --- crates/perry-stdlib/src/fetch/dispatch.rs | 25 ++++++++++++++++++- crates/perry-stdlib/src/fetch/request_ctor.rs | 13 ++++++++-- crates/perry-stdlib/src/fetch_blob.rs | 22 ++++++++++++---- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/crates/perry-stdlib/src/fetch/dispatch.rs b/crates/perry-stdlib/src/fetch/dispatch.rs index abab8b97f9..e77bd4d615 100644 --- a/crates/perry-stdlib/src/fetch/dispatch.rs +++ b/crates/perry-stdlib/src/fetch/dispatch.rs @@ -39,6 +39,23 @@ pub extern "C" fn js_response_body_init_ptr(value: f64) -> i64 { if let Some(bytes) = unsafe { body_value_buffer_bytes(value) } { return unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32) } as i64; } + // A Blob / File body contributes its raw bytes. Blob handles are handle-band + // ids (>= FETCH_HANDLE_BAND_START, 0x40000), NOT real pointers, so they must + // be recognized here: otherwise the object-body fallback below runs + // `js_jsvalue_to_string` on the id and dereferences it → SIGSEGV. This is the + // Response-constructor twin of the nested-Blob crash (#6231). + { + let jsval = JSValue::from_bits(value.to_bits()); + if jsval.is_pointer() { + let addr = jsval.as_pointer::() as usize; + if perry_runtime::value::addr_class::is_handle_band(addr) { + if let Some(bytes) = crate::fetch::blob_bytes_clone(addr) { + return unsafe { js_string_from_bytes(bytes.as_ptr(), bytes.len() as u32) } + as i64; + } + } + } + } // A non-integral or out-of-range value can't be a stream, so skip the // registry probe for the common cases. if value.is_finite() @@ -97,7 +114,13 @@ pub extern "C" fn js_response_body_init_ptr(value: f64) -> i64 { // `toString`/`[Symbol.toPrimitive]`) — for a boxed `String` that yields its // underlying primitive, the result is always a real `StringHeader`. if JSValue::from_bits(value.to_bits()).is_pointer() { - return js_jsvalue_to_string(value) as i64; + let addr = JSValue::from_bits(value.to_bits()).as_pointer::() as usize; + // A handle-band id that wasn't a Blob (a Headers/Request/… handle used + // as a body) has no byte payload and is not a real heap object — don't + // deref it via ToString (that was the crash path); fall through instead. + if !perry_runtime::value::addr_class::is_handle_band(addr) { + return js_jsvalue_to_string(value) as i64; + } } js_get_string_pointer_unified(value) } diff --git a/crates/perry-stdlib/src/fetch/request_ctor.rs b/crates/perry-stdlib/src/fetch/request_ctor.rs index 85af77d9a4..1bd526891d 100644 --- a/crates/perry-stdlib/src/fetch/request_ctor.rs +++ b/crates/perry-stdlib/src/fetch/request_ctor.rs @@ -47,8 +47,17 @@ pub unsafe extern "C" fn js_request_new( // typed-array/buffer registries first and copy the real bytes verbatim; a // genuine string body falls through to the lossless StringHeader read so its // UTF-8 bytes are preserved. - let body: Option> = dispatch::body_addr_buffer_bytes(body_ptr as usize) - .or_else(|| dispatch::body_bytes_from_header(body_ptr)); + // A Blob / File body is a handle-band id (>= 0x40000), NOT a real + // StringHeader/Buffer pointer, so it must be resolved via the blob registry + // first: reading it through `body_bytes_from_header` would dereference the + // synthetic id → SIGSEGV (#6231, the Request-constructor twin). + let body: Option> = + if perry_runtime::value::addr_class::is_handle_band(body_ptr as usize) { + crate::fetch::blob_bytes_clone(body_ptr as usize) + } else { + dispatch::body_addr_buffer_bytes(body_ptr as usize) + .or_else(|| dispatch::body_bytes_from_header(body_ptr)) + }; // GET/HEAD requests may not carry a body (WHATWG fetch). Refs #2643. if body.is_some() && (method == "GET" || method == "HEAD") { throw_fetch_type_error("Request with GET/HEAD method cannot have body."); diff --git a/crates/perry-stdlib/src/fetch_blob.rs b/crates/perry-stdlib/src/fetch_blob.rs index 78c2642ad1..e2f71d15b0 100644 --- a/crates/perry-stdlib/src/fetch_blob.rs +++ b/crates/perry-stdlib/src/fetch_blob.rs @@ -61,7 +61,12 @@ unsafe fn append_blob_parts(parts: f64, out: &mut Vec) { let top16 = bits >> 48; if top16 == 0x7FFD { let addr = (bits & 0x0000_FFFF_FFFF_FFFF) as usize; - if addr >= 0x10000 && !perry_runtime::buffer::is_registered_buffer(addr) { + // A real heap pointer (array) lives above the synthetic handle band; + // handle-band ids (blobs/headers/… start at 0x40000) must NOT be read + // as an ArrayHeader or we dereference a fake pointer. + if perry_runtime::value::addr_class::is_above_handle_band(addr) + && !perry_runtime::buffer::is_registered_buffer(addr) + { let arr_ptr = addr as *const perry_runtime::array::ArrayHeader; if !arr_ptr.is_null() { let gc_header = (arr_ptr as *const u8).sub(perry_runtime::gc::GC_HEADER_SIZE) @@ -96,15 +101,22 @@ unsafe fn append_one_blob_part(part: f64, out: &mut Vec) { // POINTER_TAG ─ either a Blob handle or a Buffer/Uint8Array. if top16 == 0x7FFD { let addr = (bits & 0x0000_FFFF_FFFF_FFFF) as usize; - // Small id → registered Blob handle. - if addr != 0 && addr < 0x10000 { + // Handle-band id (a Blob handle or another synthetic fetch handle) — + // NOT a real pointer. #6231: this previously tested `addr < 0x10000`, + // but blob handle ids start at 0x40000 (FETCH_HANDLE_BAND_START), so a + // `Blob` used as a BlobPart never matched here and fell through to + // `js_jsvalue_to_string`, which dereferenced the id as a pointer and + // SIGSEGV'd. Recognize the whole handle band and copy the blob's bytes. + if perry_runtime::value::addr_class::is_handle_band(addr) { if let Some(body) = blob_bytes_clone(addr) { out.extend_from_slice(&body); - return; } + // A non-blob handle as a BlobPart: skip it rather than deref a + // synthetic id. (Spec would ToString it; skipping is crash-safe.) + return; } // BufferHeader (Buffer / Uint8Array / ArrayBuffer)? - if addr >= 0x1000 && perry_runtime::buffer::is_registered_buffer(addr) { + if perry_runtime::buffer::is_registered_buffer(addr) { let buf = addr as *const perry_runtime::buffer::BufferHeader; let len = (*buf).length as usize; let data = perry_runtime::buffer::buffer_data(buf);