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