fix(webidl): isolate internal descriptor records from prototype pollution

This commit is contained in:
ldm0
2026-10-12 14:59:03 +08:00
committed by Donough Liu
parent 06866c9474
commit d9928760ed
3 changed files with 63 additions and 5 deletions
+5
View File
@@ -64,6 +64,10 @@ pub trait WebApiObjectDeclaration<'s> {
#[derive(WebApiObject)]
#[webapi(plain, data_properties, enumerable, no_dynamic_constructor)]
pub struct DataPropertyDescriptorDeclaration<'scope> {
// V8 converts this internal record with ToPropertyDescriptor, which also
// reads inherited get/set fields. Author Object.prototype must not enter it.
#[webapi(prototype, value = v8::null(scope))]
prototype: (),
value: v8::Local<'scope, v8::Value>,
enumerable: bool,
configurable: bool,
@@ -73,6 +77,7 @@ pub struct DataPropertyDescriptorDeclaration<'scope> {
impl<'scope> DataPropertyDescriptorDeclaration<'scope> {
pub fn new(value: v8::Local<'scope, v8::Value>, writable: bool, enumerable: bool) -> Self {
Self {
prototype: (),
value,
enumerable,
configurable: true,
+6 -5
View File
@@ -68,11 +68,12 @@ where
let prototype = prototype
.to_v8_value(scope)
.ok_or_else(|| BindError::new("failed to convert declared prototype"))?;
let prototype = v8::Local::<v8::Object>::try_from(prototype)
.map_err(|_| BindError::new("declared prototype must be an object"))?;
let installed = object
.set_prototype(scope, prototype.into())
.unwrap_or(false);
if !prototype.is_object() && !prototype.is_null() {
return Err(BindError::new(
"declared prototype must be an object or null",
));
}
let installed = object.set_prototype(scope, prototype).unwrap_or(false);
if !installed {
return Err(BindError::new("failed to set declared prototype"));
}
+52
View File
@@ -3094,3 +3094,55 @@ mod interfaces {
pub(super) SampleTemplate;
}
}
#[test]
fn null_prototype_declarations_ignore_object_prototype_pollution() {
#[derive(WebApiObject)]
#[webapi(plain)]
struct NullPrototype {
#[webapi(prototype, value = v8::null(scope))]
prototype: (),
}
ensure_v8();
let mut isolate = v8::Isolate::new(v8::CreateParams::default());
let scope = pin!(v8::HandleScope::new(&mut isolate));
let scope = &mut scope.init();
let context = v8::Context::new(scope, Default::default());
let scope = &mut v8::ContextScope::new(scope, context);
let object = NullPrototype::new().bind(scope).unwrap();
assert!(object.get_prototype(scope).unwrap().is_null());
let invalid = v8::Integer::new(scope, 1);
assert!(moli_webapi_declare::set_declared_prototype(scope, object, &invalid).is_err());
assert!(object.get_prototype(scope).unwrap().is_null());
run_script(
scope,
"Object.prototype.get = () => { throw Error('inherited getter') };",
);
let value = v8::Integer::new(scope, 7);
let descriptor =
moli_webapi_declare::DataPropertyDescriptorDeclaration::new(value.into(), false, true)
.bind(scope)
.unwrap();
assert!(descriptor.get_prototype(scope).unwrap().is_null());
let global = context.global(scope);
assert_eq!(
global.create_data_property(
scope,
v8::String::new(scope, "descriptor").unwrap().into(),
descriptor.into()
),
Some(true)
);
assert!(
run_script(
scope,
r#"(() => {
const target={};Object.defineProperty(target,'value',descriptor);
const d=Object.getOwnPropertyDescriptor(target,'value');
return target.value===7 && d.writable===false && d.enumerable && d.configurable;
})()"#
)
.is_true()
);
}