wasmer_wasix/syscalls/wasix/
dlopen.rs

1use super::*;
2use crate::{state::DlModuleSpec, syscalls::*};
3
4// TODO: add journal events for dl-related syscalls
5#[instrument(level = "trace", skip_all, fields(path = field::Empty, ld_library_path = field::Empty, flags), ret)]
6pub fn dlopen<M: MemorySize>(
7    mut ctx: FunctionEnvMut<'_, WasiEnv>,
8    path: WasmPtr<u8, M>,
9    path_len: M::Offset,
10    flags: DlFlags,
11    err_buf: WasmPtr<u8, M>,
12    err_buf_len: M::Offset,
13    ld_library_path: WasmPtr<u8, M>,
14    ld_library_path_len: M::Offset,
15    out_handle: WasmPtr<DlHandle, M>,
16) -> Result<Errno, WasiError> {
17    WasiEnv::do_pending_operations(&mut ctx)?;
18
19    let (env, mut store) = ctx.data_and_store_mut();
20    let memory = unsafe { env.memory_view(&store) };
21
22    let env_inner = unsafe { env.inner() };
23    let Some(linker) = env_inner.linker() else {
24        wasi_dl_err!(
25            "The current instance is not a dynamically-linked instance",
26            memory,
27            err_buf,
28            err_buf_len
29        );
30    };
31
32    if path.is_null() {
33        // A null file name symbolizes the main module, which has a static handle
34        wasi_try_mem_ok!(out_handle.write(&memory, crate::state::MAIN_MODULE_HANDLE.into()));
35        return Ok(Errno::Success);
36    }
37
38    let path = unsafe { get_input_str_ok!(&memory, path, path_len) };
39    Span::current().record("path", path.as_str());
40
41    let ld_library_path =
42        unsafe { get_input_str_ok!(&memory, ld_library_path, ld_library_path_len) };
43    Span::current().record("ld_library_path", ld_library_path.as_str());
44    let ld_library_path = ld_library_path
45        .split(':')
46        .map(Path::new)
47        .collect::<Vec<_>>();
48
49    let linker = linker.clone();
50
51    let location = DlModuleSpec::FileSystem {
52        module_spec: Path::new(&path),
53        ld_library_path: ld_library_path.as_slice(),
54    };
55    let module_handle = linker.load_module(location, &mut ctx);
56
57    // Reborrow to keep rust happy
58    let (env, mut store) = ctx.data_and_store_mut();
59    let memory = unsafe { env.memory_view(&store) };
60
61    let module_handle = wasi_try_dl!(
62        module_handle,
63        "failed to load module: {}",
64        memory,
65        err_buf,
66        err_buf_len
67    );
68
69    wasi_try_mem_ok!(out_handle.write(&memory, module_handle.into()));
70
71    Ok(Errno::Success)
72}
73
74pub(crate) fn write_dl_error<M: MemorySize>(
75    mut err: &str,
76    memory: &MemoryView,
77    err_buf: WasmPtr<u8, M>,
78    err_buf_len: u64,
79) -> Result<(), MemoryAccessError> {
80    let err_buf_len = err_buf_len as usize;
81
82    // The message is always written with a trailing NUL, so a zero-length
83    // buffer leaves no room for anything.
84    if err_buf_len == 0 {
85        return Ok(());
86    }
87
88    // Reserve one byte for the trailing NUL.
89    let max_err_len = err_buf_len - 1;
90    let mut err_len = err.len();
91
92    if err_len > max_err_len {
93        err_len = max_err_len;
94        err = &err[..err_len];
95    }
96
97    let Ok(err_len_offset) = M::Offset::try_from(err_len + 1) else {
98        panic!("Failed to convert size to offset")
99    };
100    let mut err_buf = err_buf.slice(memory, err_len_offset)?.access()?;
101    let dst = err_buf.as_mut();
102    dst[..err_len].copy_from_slice(err.as_bytes());
103    dst[err_len] = 0;
104
105    Ok(())
106}
107
108#[cfg(all(test, not(target_arch = "wasm32")))]
109mod tests {
110    use super::write_dl_error;
111    use wasmer::{Memory, Memory32, MemoryType, Store, WasmPtr};
112
113    #[test]
114    fn write_dl_error_zero_len_buffer_writes_nothing() {
115        let mut store = Store::default();
116        let memory = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap();
117        let view = memory.view(&store);
118
119        // A guest-supplied err_buf_len of 0 leaves no room even for the NUL
120        // terminator. The old accounting computed `0 - 1`, underflowing usize.
121        let err_buf = WasmPtr::<u8, Memory32>::new(0);
122        let res = write_dl_error::<Memory32>("failed to load module", &view, err_buf, 0);
123        assert!(res.is_ok());
124        assert_eq!(view.read_u8(0).unwrap(), 0);
125    }
126
127    #[test]
128    fn write_dl_error_reserves_room_for_nul() {
129        let mut store = Store::default();
130        let memory = Memory::new(&mut store, MemoryType::new(1, None, false)).unwrap();
131        let view = memory.view(&store);
132
133        // err_buf_len equal to the message length must still keep the NUL
134        // inside the buffer rather than spilling one byte past it.
135        let msg = "abcd";
136        let err_buf = WasmPtr::<u8, Memory32>::new(0);
137        write_dl_error::<Memory32>(msg, &view, err_buf, msg.len() as u64).unwrap();
138
139        let mut got = [1u8; 5];
140        view.read(0, &mut got).unwrap();
141        assert_eq!(&got[..4], b"abc\0");
142        assert_eq!(got[4], 0);
143    }
144}