fix: full_reset 存在 session 对象时强制重建 global 堵池路径 UAF

覆盖既有 global 槽的写入不递增 generation,脏检测漏判导致保留的
global 持有已释放的 session 对象指针。session 对象非空即强制 bump
global generation,借既有快照机制触发重建。
This commit is contained in:
2026-08-15 18:56:25 +08:00
parent 13e9ee3543
commit e718394072
+28
View File
@@ -253,6 +253,13 @@ impl Vm {
///
/// 用于在多次 JS 执行之间达到完全隔离:session 内未被污染的 builtin 保留原指针。
pub fn full_reset(&mut self) {
// session 对象只能来自用户写 + promote,跨 full_reset 若保留 global 会悬垂:
// 覆盖既有 global 槽的写入不递增 generation(脏检测依赖 generation 对比),
// 此处强制 bump 使带 session 对象时 global 必然重建。
if !self.gc_state.session_object_ptrs.is_empty() {
let global_ptr = self.session.global_object().as_ptr() as *mut JsObject;
unsafe { &mut *global_ptr }.bump_generation();
}
let dirty = self.session.selective_reset(&self.kernel_core);
if dirty.any_builtin_dirty() {
bindings::rebind_dirty_builtins(&self.kernel_core, &mut self.session, Some(&dirty));
@@ -633,6 +640,26 @@ mod tests {
vm.run(&module).expect("vm run failed")
}
#[test]
fn full_reset_with_session_objects_forces_global_rebuild() {
let mut vm = Vm::new();
// 池路径场景:`globalThis.Array = {}` 覆盖既有 global 槽(不递增 generation),
// 新值 `{}` 经 promote 进入 session——global 保留时该指针将悬垂。
let _ = run_source(&mut vm, "globalThis.Array = {}; 0");
assert!(!vm.gc_state.session_object_ptrs.is_empty(), "覆盖写应触发 promote 进入 session");
let old_global = vm.session.global_object.as_ptr();
vm.full_reset();
// global 必须重建:旧 global 与其 session 对象随 epoch 释放,Array 恢复内置构造器。
assert!(!std::ptr::eq(old_global, vm.session.global_object.as_ptr()));
assert!(std::ptr::eq(
global_prop(&vm, "Array").as_js_object_ptr(),
vm.session.builtin_world().array_constructor.as_ptr() as *mut JsObject
));
assert!(!vm.session.is_dirty_since_snapshot());
}
#[test]
fn full_reset_clean_keeps_session_objects() {
let mut vm = Vm::new();
@@ -952,3 +979,4 @@ mod tests {
assert_eq!(vm.lookup_str(te).as_deref(), Some("TypeError"));
}
}