lucet_runtime_internals/module/
dl.rs

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