You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[P0] Object.assign / object-spread of a Map or Set crashes (type confusion → SIGBUS) #6070
Severity: P0 (crash / memory-unsafe on a common operation) Found by: audit fable-audit-perry-2.md (2026-07-06), confirmed by probe (exit 138 / EXC_BAD_ACCESS).
What
Object.assign({}, someMap) and first-position object-spread {...someMap} (and the Set equivalents) crash with a SIGBUS/SIGSEGV instead of producing {}.
Spec (CopyDataProperties / [[OwnPropertyKeys]]): a Map/Set has no own enumerable string keys, so the result is {}.
Why
js_object_assign_one (crates/perry-runtime/src/object/alloc.rs:1194-1247) validates the source only as pointer/aligned/not-array, then does let src = src_raw as *const ObjectHeader; … (*src).keys_array. A MapHeader is {size@0, capacity@4, entries@8} (16 bytes) — reading keys_array at offset 16 walks past the header into adjacent arena bytes; the garbage pointer then passes the >= 0x10000 floor and js_array_length derefs it.
js_object_clone_with_extra (the {...src, k:v} first-source path, alloc.rs:634-739) is worse: it still uses the pre-audittop16 >= 0x7FF8 catch-all strip + src_raw < 0x10000 floor and reads (*src).field_count (offset 12 = high half of MapHeader.entries) → copies thousands of phantom fields.
This is a straggler the 2026-07-02 band-safety sweep missed: js_object_copy_own_fields (2nd+ spread source) was hardened; assign_one's non-array branch and the first-spread clone were not.
Fix
In both functions, classify the source via crate::value::addr_class::try_read_gc_header and require GC_TYPE_OBJECT (arrays are already special-cased; strings still owe their index props — pre-existing gap, but no longer UB). This is an exact mirror of the already-fixed js_object_copy_own_fields (alloc.rs:746-800):
matchcrate::value::addr_class::try_read_gc_header(src_raw){Some(h)if h.obj_type == crate::gc::GC_TYPE_OBJECT => {}
_ => return,// Map/Set/Promise/handle/etc. contribute no own enumerable string keys}
Add a gap test (Object.assign + spread of Map/Set/Date/Promise) and confirm the test262 assign/* suite still passes.
Confidence: high (probe + source + exact fixed-twin).
Activity
fix(runtime): #6070 — Object.assign with a Map/Set/exotic source no l…
fix(runtime): #6070 — Object.assign with a Map/Set/exotic source no l…