From c6277e1fe7f7c391df350b1ca29159b698bbea36 Mon Sep 17 00:00:00 2001 From: RSSchermer Date: Sun, 3 Apr 2022 19:23:37 +0200 Subject: [PATCH] Switch to serializing/deserializing on the Javascript side rather than the Rust side Should avoid potential provenance issues, see https://github.com/rust-lang/rust/pull/95583 --- arwa/src/html/custom_element.rs | 159 +++++++++++++++---------- arwa/src/html/define_custom_element.js | 30 ++++- arwa/src/lib.rs | 8 +- 3 files changed, 130 insertions(+), 67 deletions(-) diff --git a/arwa/src/html/custom_element.rs b/arwa/src/html/custom_element.rs index 6d65b29..0ce868d 100644 --- a/arwa/src/html/custom_element.rs +++ b/arwa/src/html/custom_element.rs @@ -1,6 +1,8 @@ use std::any::{Any, TypeId}; +use std::mem::MaybeUninit; use std::ops::Deref; -use std::{marker, mem}; +use std::ptr::DynMetadata; +use std::{marker, mem, ptr}; use js_sys::{Array, Function, Reflect, Uint8Array}; use wasm_bindgen::closure::Closure; @@ -11,7 +13,6 @@ use web_sys::HtmlElement; use crate::dom::{impl_shadow_host_for_element, DynamicElement, Name, ParentNode}; use crate::finalization_registry::FinalizationRegistry; use crate::html::{impl_html_element_traits, CustomElementName}; -use crate::util::type_id_to_u64; use crate::InvalidCast; use crate::{dom_exception_wrapper, impl_common_wrapper_traits}; @@ -20,24 +21,19 @@ thread_local! { let callback = |held_value: JsValue| { // Reconstruct the Box that holds the data, then drop it. - let pointer_data: Uint8Array = held_value.unchecked_into(); + let serialized_data: Uint8Array = held_value.unchecked_into(); - // Copy pointer data to WASM linear memory that we can operate on. - let mut scratch = [0u8; 24]; - let size_of_usize = mem::size_of::(); + let mut uninit_custom_element_data = MaybeUninit::::uninit(); + let data_ptr = uninit_custom_element_data.as_mut_ptr(); - pointer_data.copy_to(&mut scratch[..size_of_usize * 2 + 8]); + deserialize_custom_element_data(&wasm_bindgen::memory(), data_ptr, &serialized_data); - let (address_bytes, rest) = scratch.split_at(size_of_usize); - let (vtable_bytes, _) = rest.split_at(size_of_usize); - - let address_usize = usize::from_ne_bytes(address_bytes.try_into().unwrap_throw()); - let vtable_usize = usize::from_ne_bytes(vtable_bytes.try_into().unwrap_throw()); - - let ptr: *mut dyn Any = unsafe { mem::transmute((address_usize, vtable_usize)) }; + let custom_element_data = unsafe { + uninit_custom_element_data.assume_init() + }; unsafe { - mem::drop(Box::from_raw(ptr)); + mem::drop(Box::from_raw(custom_element_data.to_dyn_any_ptr())); } }; @@ -51,6 +47,18 @@ thread_local! { }; } +struct CustomElementData { + address: *mut (), + metadata: DynMetadata, + type_id: TypeId, +} + +impl CustomElementData { + fn to_dyn_any_ptr(&self) -> *mut dyn Any { + ptr::from_raw_parts_mut(self.address, self.metadata) + } +} + pub(crate) mod extendable_element_seal { pub trait Seal { const EXTENDED_NAME: Option<&'static str>; @@ -95,14 +103,14 @@ where E: ExtendableElement, { fn from_raw_unchecked(raw: RawCustomElement) -> Self { - let mut scratch = [0u8; 24]; - let size_of_usize = mem::size_of::(); + let mut uninit_custom_element_data = MaybeUninit::::uninit(); + let data_ptr = uninit_custom_element_data.as_mut_ptr(); + + raw.deserialize_custom_element_data(&wasm_bindgen::memory(), data_ptr); - raw.data().copy_to(&mut scratch[0..size_of_usize * 2 + 8]); + let custom_element_data = unsafe { uninit_custom_element_data.assume_init() }; - let data_ptr_bits = - usize::from_ne_bytes(scratch[..size_of_usize].try_into().unwrap_throw()); - let data = <*const T>::from_bits(data_ptr_bits); + let data = custom_element_data.address as *const T; let extended = E::from_web_sys_html_element_unchecked(raw.into()); CustomElement { data, extended } @@ -139,36 +147,36 @@ where fn try_from(element: DynamicElement) -> Result { let element: web_sys::Element = element.into(); - if let Ok(value) = Reflect::get(element.as_ref(), &"__arwa_custom_element_data".into()) { - if !value.is_undefined() { - let data: Uint8Array = value.unchecked_into(); - let target_type_num = type_id_to_u64(TypeId::of::>()); + let is_custom_element = Reflect::has( + element.as_ref(), + &"__deserialize_custom_element_data".into(), + ) + .unwrap_or(false); - let mut scratch = [0u8; 24]; - let size_of_usize = mem::size_of::(); - let type_num_start = size_of_usize * 2; - let type_num_end = size_of_usize * 2 + 8; + if is_custom_element { + let raw = element.unchecked_into::(); - data.copy_to(&mut scratch[0..size_of_usize * 2 + 8]); + let mut uninit_custom_element_data = MaybeUninit::::uninit(); + let data_ptr = uninit_custom_element_data.as_mut_ptr(); - let type_num = u64::from_ne_bytes( - scratch[type_num_start..type_num_end] - .try_into() - .unwrap_throw(), - ); + raw.deserialize_custom_element_data(&wasm_bindgen::memory(), data_ptr); - if type_num == target_type_num { - let data_ptr_bits = - usize::from_ne_bytes(scratch[..size_of_usize].try_into().unwrap_throw()); - let data = <*const T>::from_bits(data_ptr_bits); - let extended = E::from_web_sys_html_element_unchecked(element.unchecked_into()); + let custom_element_data = unsafe { uninit_custom_element_data.assume_init() }; + let target_type_id = TypeId::of::>(); - return Ok(CustomElement { data, extended }); - } + if custom_element_data.type_id == target_type_id { + let data = custom_element_data.address as *const T; + let extended = E::from_web_sys_html_element_unchecked(raw.unchecked_into()); + + Ok(CustomElement { data, extended }) + } else { + Err(InvalidCast::new(DynamicElement::from( + raw.unchecked_into::(), + ))) } + } else { + Err(InvalidCast::new(DynamicElement::from(element))) } - - Err(InvalidCast::new(DynamicElement::from(element))) } } @@ -256,7 +264,6 @@ impl CustomElementRegistry { } = descriptor; let type_id = TypeId::of::>(); - let type_num = type_id_to_u64(type_id); let constructor = move |extended: web_sys::HtmlElement| { let extended = E::from_web_sys_html_element_unchecked(extended); @@ -265,25 +272,31 @@ impl CustomElementRegistry { let data = Box::new(data) as Box; let data_ptr = Box::into_raw(data); - let (address_ptr, vtable_ptr): (usize, usize) = unsafe { mem::transmute(data_ptr) }; - - let mut scratch = [0u8; 24]; - let size_of_usize = mem::size_of::(); - let type_num_start = size_of_usize * 2; - let type_num_end = size_of_usize * 2 + 8; - - scratch[0..size_of_usize].copy_from_slice(&address_ptr.to_ne_bytes()); - scratch[size_of_usize..type_num_start].copy_from_slice(&vtable_ptr.to_ne_bytes()); - scratch[type_num_start..type_num_end].copy_from_slice(&type_num.to_ne_bytes()); + let (address, metadata) = data_ptr.to_raw_parts(); + let mut custom_element_data = CustomElementData { + address, + metadata, + type_id, + }; + let ptr = &mut custom_element_data as *mut CustomElementData; - let data = Uint8Array::new_with_length(type_num_end as u32); + let serialized = serialize_custom_element_data( + &wasm_bindgen::memory(), + ptr, + mem::size_of::() as u32, + ); - data.copy_from(&scratch[..type_num_end]); + // Make sure it doesn't drop early + mem::drop(custom_element_data); - CUSTOM_ELEMENT_FINALIZATION_REGISTRY - .with(|r| r.register(extended.as_web_sys_html_element().as_ref(), data.as_ref())); + CUSTOM_ELEMENT_FINALIZATION_REGISTRY.with(|r| { + r.register( + extended.as_web_sys_html_element().as_ref(), + serialized.as_ref(), + ) + }); - data + serialized }; let constructor_boxed = @@ -389,10 +402,14 @@ dom_exception_wrapper!(RegisterCustomElementError); #[wasm_bindgen] extern "C" { #[wasm_bindgen(extends = HtmlElement)] - pub type RawCustomElement; - - #[wasm_bindgen(method, getter, js_name = "__arwa_custom_element_data")] - pub fn data(this: &RawCustomElement) -> Uint8Array; + type RawCustomElement; + + #[wasm_bindgen(method, js_name = "__deserialize_custom_element_data")] + fn deserialize_custom_element_data( + this: &RawCustomElement, + wasm_memory: &JsValue, + ptr: *mut CustomElementData, + ); } #[wasm_bindgen(module = "/src/html/define_custom_element.js")] @@ -408,6 +425,20 @@ extern "C" { attribute_changed_callback: &Function, observed_attributes: &Array, ) -> Result; + + #[wasm_bindgen] + fn serialize_custom_element_data( + wasm_memory: &JsValue, + pointer: *mut CustomElementData, + size: u32, + ) -> Uint8Array; + + #[wasm_bindgen] + fn deserialize_custom_element_data( + wasm_memory: &JsValue, + pointer: *mut CustomElementData, + serialized_data: &Uint8Array, + ); } macro_rules! impl_extendable_element { diff --git a/arwa/src/html/define_custom_element.js b/arwa/src/html/define_custom_element.js index 6ac16b2..28ef51f 100644 --- a/arwa/src/html/define_custom_element.js +++ b/arwa/src/html/define_custom_element.js @@ -25,8 +25,12 @@ export function define_custom_element( this.#arwa_custom_element_data = constructor(this); } - get __arwa_custom_element_data() { - return this.#arwa_custom_element_data; + __deserialize_custom_element_data(wasm_linear_memory_buffer, pointer) { + return deserialize_custom_element_data( + wasm_linear_memory_buffer, + pointer, + this.#arwa_custom_element_data + ); } connectedCallback() { @@ -48,3 +52,25 @@ export function define_custom_element( extendedName ? { extends: extendedName } : undefined ); } + +export function serialize_custom_element_data( + wasm_memory, + pointer, + size +) { + // Create a view the relevant region of the WASM linear memory buffer. + let view = new Uint8Array(wasm_memory.buffer, pointer, size); + + // Copy it to a new non-view Uint8Array and return + return new Uint8Array(view); +} + +export function deserialize_custom_element_data( + wasm_memory, + pointer, + custom_element_data +) { + let buffer_view = new Uint8Array(wasm_memory.buffer); + + buffer_view.set(custom_element_data, pointer); +} diff --git a/arwa/src/lib.rs b/arwa/src/lib.rs index 0f1bc52..95ce563 100644 --- a/arwa/src/lib.rs +++ b/arwa/src/lib.rs @@ -1,4 +1,10 @@ -#![feature(const_type_id, get_mut_unchecked, iter_intersperse, ptr_to_from_bits)] +#![feature( + const_type_id, + get_mut_unchecked, + iter_intersperse, + ptr_metadata, + ptr_to_from_bits +)] pub mod collection; pub mod connection;