1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
//! A hack to support deserialization of arbitrary trait objects. //! //! This an implementation of a workaround for [rust-lang/rfcs#668 "Encodable //! trait objects"][rust-lang/rfcs#668]. //! //! **See [README.md][README.md] for the caveats and security implications.** //! //! [rust-lang/rfcs#668]: https://github.com/rust-lang/rfcs/issues/668 //! [README.md]: https://github.com/Rufflewind/detrojt/blob/master/README.md //! //! The core of the library rests upon the trio `TyConst`, `get_ty_const`, and //! `get_ty_const_key`. They provide a mechanism for looking up data //! associated with a type using a persistent identifier ("key"). //! //! [`D: TyConst<T>`](trait.TyConst.html) is a trait used to define such data. //! Each implementation associates an arbitrary value of type `D` //! (i.e. `Self`) with the type parameter `T`. Conceptually, it's as if every //! `D` has its own table of data, indexed by `T`. Within a given table, //! every `T` is associated with a unique `usize` key. //! //! [`get_ty_const_key`](fn.get_ty_const_key.html) returns the unique key for //! the data associated with `T` in the table of `D`. The key is persistent //! (serializable): it can be used in a later execution of the same program. //! The key is only guaranteed to be unique for a given `D` (i.e. the key is //! meaningless without knowing what `D` is). //! //! [`get_ty_const`](fn.get_ty_const.html) uses the key to retrieve the data //! `D` associated with `T` without knowing what `T` was. If the key is //! invalid, then `None` is returned. //! //! ## Example //! //! For a more interesting example, see the [`serde`](serde/index.html) //! submodule, which uses `TyConst` under the hood. //! //! ``` //! use detrojt::{TyConst, get_ty_const, get_ty_const_key}; //! //! #[derive(Debug, PartialEq, Eq)] //! struct Size(usize); //! //! impl<T: 'static> TyConst<T> for Size { //! fn get_data() -> Self { Size(std::mem::size_of::<T>()) } //! } //! //! assert_eq!(get_ty_const(get_ty_const_key::<Size, ()>()), Some(Size(0))); //! assert_eq!(get_ty_const(get_ty_const_key::<Size, i64>()), Some(Size(8))); //! assert_eq!(get_ty_const::<Size>(1), None); //! ``` extern crate serde as libserde; extern crate serde_json; pub mod serde; use std::any::TypeId; use std::io::Write; use std::marker::PhantomData; #[cfg(any(unix))] unsafe fn ptr_try_read<T>(p: *const T) -> Option<T> { let mut opener = std::fs::OpenOptions::new(); opener.write(true); let mut f = match opener.open("/dev/random").or_else(|_| { opener.open("/dev/null") }) { Err(_) => return None, Ok(f) => f, }; let s = std::slice::from_raw_parts(p as *const u8, std::mem::size_of::<T>()); if f.write(s).is_ok() { Some(std::ptr::read(p)) } else { None } } #[derive(Debug)] #[repr(C)] struct TraitObject { data: *mut (), vtable: *mut (), } #[derive(Debug)] #[repr(C)] struct Vtable { destructor: fn(*mut ()), size: usize, align: usize, } trait TyConstImpl<D> { fn get_type_id(&self) -> TypeId; fn get(&self) -> D; } const MAGIC: usize = 0x625f405b5af9; struct Dummy<T: ?Sized> { _dummy: [u8; MAGIC], phantom: PhantomData<T>, } impl<D: TyConst<T>, T: ?Sized + 'static> TyConstImpl<D> for Dummy<T> { fn get_type_id(&self) -> TypeId { TypeId::of::<D>() } fn get(&self) -> D { D::get_data() } } /// This represents a mapping from a type T to some data of type `Self` (also /// referred to as `D` in other places). /// /// You can implement this trait for your own datatype `D` to associate /// arbitrary data with types. The data can be retrieved using /// [`get_ty_const`](fn.get_ty_const.html) even if `T` is not statically /// known. /// /// The ordering of type parameters here is needed to avoid problems due to /// orphan rules. pub trait TyConst<T: ?Sized + 'static>: Sized + 'static { /// Retrieve the data. fn get_data() -> Self; } /// Get the key associated with `TyConst<T>` for `D`. Instantiations of this /// function determine what goes into the type constant table for `D`. /// /// The key can be used to retrieve the data `D` via /// [`get_ty_const`](fn.get_ty_const.html) even if `T` is not statically /// known. /// /// ## Implementation details /// /// This returns the memory offset of the vtable associated with `T` relative /// to some other vtable. Naturally, we are making an assumption that the /// relative offsets of vtables don't change! pub fn get_ty_const_key<D: TyConst<T>, T: ?Sized + 'static>() -> usize { unsafe { let r0: TraitObject = std::mem::transmute(&() as &Send); let p: &'static Dummy<T> = std::mem::transmute(&()); let r: TraitObject = std::mem::transmute(p as &TyConstImpl<D>); (r.vtable as usize) - (r0.vtable as usize) } } /// Get the data in the impl for the type that matches the given key. If the /// key is invalid, returns `None`. /// /// Keys may be obtained using [`get_ty_const_key`](fn.get_ty_const_key.html). /// /// **Due to limitations of the current implementation, calling this on an /// invalid key may sometimes cause arbitrary code execution (or a crash if /// you're lucky).** /// /// ## Implementation details /// /// As noted in [`get_ty_const_key`](fn.get_ty_const_key.html), the key is /// actually the memory address to the corresponding vtable. Extracting /// information from the vtable is pretty straightforward application of /// unsafe code. /// /// The hard part is making sure the key is valid. Since we would rather /// return `None` than to segfault, the first steps is to ask the OS whether /// we can even read that memory. Then, we pull out the vtable and check /// whether it looks sensible. /// /// Unfortunately, we have very little control over the contents of the /// vtable: most of it are just function pointers, and it's not easy to tell /// if a function pointer is right since every one is unique. The vtable does /// have size and alignment information, so we can in principle squeeze in a /// 128-bit magic number and check for that. Alas, this is limited by the /// fact that Rust doesn't like exabyte-sized arrays, and the fact that Rust /// has not yet implemented support for custom alignments (and even if it /// does, we don't know if it would support alignments that aren't powers of /// two). It would be even better if we could pick a random magic number for /// each build. pub fn get_ty_const<D: 'static>(key: usize) -> Option<D> { unsafe { let r0: TraitObject = std::mem::transmute(&() as &Send); let r = TraitObject { data: &mut (), vtable: ((r0.vtable as usize) + key) as _, }; match ptr_try_read(r.vtable as *const Vtable) { None => return None, Some(ref vt) if vt.size == MAGIC && vt.align == 1 => (), _ => return None, } let r: &TyConstImpl<D> = std::mem::transmute(r); if r.get_type_id() != TypeId::of::<D>() { return None; } Some(r.get()) } }