1 use crate::error::Error;
2 use crate::module::{AddrDetails, GlobalSpec, HeapSpec, Module, ModuleInternal, TableElement};
3 use libc::c_void;
4 use libloading::Library;
5 use lucet_module::{
6     FunctionHandle, FunctionIndex, FunctionPointer, FunctionSpec, ModuleData, ModuleFeatures,
7     SerializedModule, Signature, LUCET_MODULE_SYM,
8 };
9 #[cfg(feature = "signature_checking")]
10 use lucet_module::{ModuleSignature, PublicKey};
11 use std::ffi::CStr;
12 use std::mem::MaybeUninit;
13 use std::path::Path;
14 use std::slice;
15 use std::slice::from_raw_parts;
16 use std::sync::Arc;
17 
18 use raw_cpuid::CpuId;
19 
check_feature_support(module_features: &ModuleFeatures) -> Result<(), Error>20 fn check_feature_support(module_features: &ModuleFeatures) -> Result<(), Error> {
21     let cpuid = CpuId::new();
22 
23     fn missing_feature(feature: &str) -> Error {
24         Error::Unsupported(format!(
25             "Module requires feature host does not support: {}",
26             feature
27         ))
28     }
29 
30     let info = cpuid
31         .get_feature_info()
32         .ok_or_else(|| Error::Unsupported("Unable to obtain host CPU feature info!".to_string()))?;
33 
34     if module_features.sse3 && !info.has_sse3() {
35         return Err(missing_feature("SSE3"));
36     }
37     if module_features.ssse3 && !info.has_ssse3() {
38         return Err(missing_feature("SSS3"));
39     }
40     if module_features.sse41 && !info.has_sse41() {
41         return Err(missing_feature("SSE4.1"));
42     }
43     if module_features.sse42 && !info.has_sse42() {
44         return Err(missing_feature("SSE4.2"));
45     }
46     if module_features.avx && !info.has_avx() {
47         return Err(missing_feature("AVX"));
48     }
49     if module_features.popcnt && !info.has_popcnt() {
50         return Err(missing_feature("POPCNT"));
51     }
52 
53     if module_features.bmi1 || module_features.bmi2 {
54         let info = cpuid.get_extended_feature_info().ok_or_else(|| {
55             Error::Unsupported("Unable to obtain host CPU extended feature info!".to_string())
56         })?;
57 
58         if module_features.bmi1 && !info.has_bmi1() {
59             return Err(missing_feature("BMI1"));
60         }
61 
62         if module_features.bmi2 && !info.has_bmi2() {
63             return Err(missing_feature("BMI2"));
64         }
65     }
66 
67     if module_features.lzcnt {
68         let info = cpuid.get_extended_function_info().ok_or_else(|| {
69             Error::Unsupported("Unable to obtain host CPU extended function info!".to_string())
70         })?;
71 
72         if module_features.lzcnt && !info.has_lzcnt() {
73             return Err(missing_feature("LZCNT"));
74         }
75     }
76 
77     // Features are fine, we're compatible!
78     Ok(())
79 }
80 
81 /// A Lucet module backed by a dynamically-loaded shared object.
82 pub struct DlModule {
83     lib: Library,
84 
85     /// Base address of the dynamically-loaded module
86     fbase: *const c_void,
87 
88     /// Metadata decoded from inside the module
89     module: lucet_module::Module<'static>,
90 }
91 
92 // for the one raw pointer only
93 unsafe impl Send for DlModule {}
94 unsafe impl Sync for DlModule {}
95 
96 impl DlModule {
97     /// Create a module, loading code from a shared object on the filesystem.
load<P: AsRef<Path>>(so_path: P) -> Result<Arc<Self>, Error>98     pub fn load<P: AsRef<Path>>(so_path: P) -> Result<Arc<Self>, Error> {
99         Self::load_and_maybe_verify(so_path, |_module_data| Ok(()))
100     }
101 
102     /// Create a module, loading code from a shared object on the filesystem
103     /// and verifying it using a public key if one has been supplied.
104     #[cfg(feature = "signature_checking")]
load_and_verify<P: AsRef<Path>>(so_path: P, pk: PublicKey) -> Result<Arc<Self>, Error>105     pub fn load_and_verify<P: AsRef<Path>>(so_path: P, pk: PublicKey) -> Result<Arc<Self>, Error> {
106         Self::load_and_maybe_verify(so_path, |module_data| {
107             // Public key has been provided, verify the module signature
108             // The TOCTOU issue is unavoidable without reimplenting `dlopen(3)`
109             ModuleSignature::verify(so_path, &pk, &module_data)
110         })
111     }
112 
load_and_maybe_verify<P: AsRef<Path>>( so_path: P, verifier: fn(&ModuleData) -> Result<(), Error>, ) -> Result<Arc<Self>, Error>113     fn load_and_maybe_verify<P: AsRef<Path>>(
114         so_path: P,
115         verifier: fn(&ModuleData) -> Result<(), Error>,
116         // pk: Option<PublicKey>,
117     ) -> Result<Arc<Self>, Error> {
118         // Load the dynamic library. The undefined symbols corresponding to the lucet_syscall_
119         // functions will be provided by the current executable.  We trust our wasm->dylib compiler
120         // to make sure these function calls are the way the dylib can touch memory outside of its
121         // stack and heap.
122         // let abs_so_path = so_path.as_ref().canonicalize().map_err(Error::DlError)?;
123         let lib = Library::new(so_path.as_ref().as_os_str()).map_err(Error::DlError)?;
124 
125         let serialized_module_ptr = unsafe {
126             lib.get::<*const SerializedModule>(LUCET_MODULE_SYM.as_bytes())
127                 .map_err(|e| {
128                     lucet_incorrect_module!("error loading required symbol `lucet_module`: {}", e)
129                 })?
130         };
131 
132         let serialized_module: &SerializedModule =
133             unsafe { serialized_module_ptr.as_ref().unwrap() };
134 
135         // Deserialize the slice into ModuleData, which will hold refs into the loaded
136         // shared object file in `module_data_slice`. Both of these get a 'static lifetime because
137         // Rust doesn't have a safe way to describe that their lifetime matches the containing
138         // struct (and the dll).
139         //
140         // The exposed lifetime of ModuleData will be the same as the lifetime of the
141         // dynamically loaded library. This makes the interface safe.
142         let module_data_slice: &'static [u8] = unsafe {
143             slice::from_raw_parts(
144                 serialized_module.module_data_ptr as *const u8,
145                 serialized_module.module_data_len as usize,
146             )
147         };
148         let module_data = ModuleData::deserialize(module_data_slice)?;
149 
150         check_feature_support(module_data.features())?;
151 
152         verifier(&module_data)?;
153 
154         let fbase = if let Some(dli) =
155             dladdr(serialized_module as *const SerializedModule as *const c_void)
156         {
157             dli.dli_fbase
158         } else {
159             std::ptr::null()
160         };
161 
162         if serialized_module.tables_len > std::u32::MAX as u64 {
163             lucet_incorrect_module!("table segment too long: {}", serialized_module.tables_len);
164         }
165         let tables: &'static [&'static [TableElement]] = unsafe {
166             from_raw_parts(
167                 serialized_module.tables_ptr as *const &[TableElement],
168                 serialized_module.tables_len as usize,
169             )
170         };
171 
172         let function_manifest = if serialized_module.function_manifest_ptr != 0 {
173             unsafe {
174                 from_raw_parts(
175                     serialized_module.function_manifest_ptr as *const FunctionSpec,
176                     serialized_module.function_manifest_len as usize,
177                 )
178             }
179         } else {
180             &[]
181         };
182 
183         Ok(Arc::new(DlModule {
184             lib,
185             fbase,
186             module: lucet_module::Module {
187                 module_data,
188                 tables,
189                 function_manifest,
190             },
191         }))
192     }
193 }
194 
195 impl Module for DlModule {}
196 
197 impl ModuleInternal for DlModule {
heap_spec(&self) -> Option<&HeapSpec>198     fn heap_spec(&self) -> Option<&HeapSpec> {
199         self.module.module_data.heap_spec()
200     }
201 
globals(&self) -> &[GlobalSpec<'_>]202     fn globals(&self) -> &[GlobalSpec<'_>] {
203         self.module.module_data.globals_spec()
204     }
205 
get_sparse_page_data(&self, page: usize) -> Option<&[u8]>206     fn get_sparse_page_data(&self, page: usize) -> Option<&[u8]> {
207         if let Some(ref sparse_data) = self.module.module_data.sparse_data() {
208             *sparse_data.get_page(page)
209         } else {
210             None
211         }
212     }
213 
sparse_page_data_len(&self) -> usize214     fn sparse_page_data_len(&self) -> usize {
215         self.module
216             .module_data
217             .sparse_data()
218             .map(|d| d.len())
219             .unwrap_or(0)
220     }
221 
table_elements(&self) -> Result<&[TableElement], Error>222     fn table_elements(&self) -> Result<&[TableElement], Error> {
223         match self.module.tables.get(0) {
224             Some(table) => Ok(table),
225             None => Err(lucet_incorrect_module!("table 0 is not present")),
226         }
227     }
228 
get_export_func(&self, sym: &str) -> Result<FunctionHandle, Error>229     fn get_export_func(&self, sym: &str) -> Result<FunctionHandle, Error> {
230         self.module
231             .module_data
232             .get_export_func_id(sym)
233             .ok_or_else(|| Error::SymbolNotFound(sym.to_string()))
234             .map(|id| {
235                 let ptr = self.function_manifest()[id.as_u32() as usize].ptr();
236                 FunctionHandle { ptr, id }
237             })
238     }
239 
get_func_from_idx(&self, table_id: u32, func_id: u32) -> Result<FunctionHandle, Error>240     fn get_func_from_idx(&self, table_id: u32, func_id: u32) -> Result<FunctionHandle, Error> {
241         if table_id != 0 {
242             return Err(Error::FuncNotFound(table_id, func_id));
243         }
244         let table = self.table_elements()?;
245         let func = table
246             .get(func_id as usize)
247             .map(|element| element.function_pointer())
248             .ok_or(Error::FuncNotFound(table_id, func_id))?;
249 
250         Ok(self.function_handle_from_ptr(func))
251     }
252 
get_start_func(&self) -> Result<Option<FunctionHandle>, Error>253     fn get_start_func(&self) -> Result<Option<FunctionHandle>, Error> {
254         // `guest_start` is a pointer to the function the module designates as the start function,
255         // since we can't have multiple symbols pointing to the same function and guest code might
256         // call it in the normal course of execution
257         if let Ok(start_func) = unsafe { self.lib.get::<*const extern "C" fn()>(b"guest_start") } {
258             if start_func.is_null() {
259                 lucet_incorrect_module!("`guest_start` is defined but null");
260             }
261             Ok(Some(self.function_handle_from_ptr(
262                 FunctionPointer::from_usize(unsafe { **start_func } as usize),
263             )))
264         } else {
265             Ok(None)
266         }
267     }
268 
function_manifest(&self) -> &[FunctionSpec]269     fn function_manifest(&self) -> &[FunctionSpec] {
270         self.module.function_manifest
271     }
272 
addr_details(&self, addr: *const c_void) -> Result<Option<AddrDetails>, Error>273     fn addr_details(&self, addr: *const c_void) -> Result<Option<AddrDetails>, Error> {
274         if let Some(dli) = dladdr(addr) {
275             let file_name = if dli.dli_fname.is_null() {
276                 None
277             } else {
278                 Some(unsafe { CStr::from_ptr(dli.dli_fname).to_owned().into_string()? })
279             };
280             let sym_name = if dli.dli_sname.is_null() {
281                 None
282             } else {
283                 Some(unsafe { CStr::from_ptr(dli.dli_sname).to_owned().into_string()? })
284             };
285             Ok(Some(AddrDetails {
286                 in_module_code: dli.dli_fbase as *const c_void == self.fbase,
287                 file_name,
288                 sym_name,
289             }))
290         } else {
291             Ok(None)
292         }
293     }
294 
get_signature(&self, fn_id: FunctionIndex) -> &Signature295     fn get_signature(&self, fn_id: FunctionIndex) -> &Signature {
296         self.module.module_data.get_signature(fn_id)
297     }
298 
get_signatures(&self) -> &[Signature]299     fn get_signatures(&self) -> &[Signature] {
300         self.module.module_data.signatures()
301     }
302 }
303 
304 // TODO: PR to nix or libloading?
305 // TODO: possibly not safe to use without grabbing the mutex within libloading::Library?
dladdr(addr: *const c_void) -> Option<libc::Dl_info>306 fn dladdr(addr: *const c_void) -> Option<libc::Dl_info> {
307     let mut info = MaybeUninit::<libc::Dl_info>::uninit();
308     let res = unsafe { libc::dladdr(addr, info.as_mut_ptr()) };
309     if res != 0 {
310         Some(unsafe { info.assume_init() })
311     } else {
312         None
313     }
314 }
315