Files
Andrew Brown 7717d8fa55 wiggle: adapt Wiggle guest slices for unsafe shared use (#5229)
* wiggle: adapt Wiggle guest slices for `unsafe` shared use

When multiple threads can concurrently modify a WebAssembly shared
memory, the underlying data for a Wiggle `GuestSlice` and
`GuestSliceMut` could change due to access from other threads. This
breaks Rust guarantees when `&[T]` and `&mut [T]` slices are handed out.
This change modifies `GuestPtr` to make `as_slice` and `as_slice_mut`
return an `Option` which is `None` when the underlying WebAssembly
memory is shared.

But WASI implementations still need access to the underlying WebAssembly
memory, both to read to it and write from it. This change adds new APIs:
- `GuestPtr::to_vec` copies the  bytes from WebAssembly memory (from
  which we can safely take a `&[T]`)
- `GuestPtr::as_unsafe_slice_mut` returns a wrapper `struct` from which
  we can  `unsafe`-ly return a mutable slice (users must accept the
  unsafety of concurrently modifying a `&mut [T]`)

This approach allows us to maintain Wiggle's borrow-checking
infrastructure, which enforces the guarantee that Wiggle will not modify
overlapping regions, e.g. This is important because the underlying
system calls may expect this. Though other threads may modify the same
underlying region, this is impossible to prevent; at least Wiggle will
not be able to do so.

Finally, the changes to Wiggle's API are propagated to all WASI
implementations in Wasmtime. For now, code locations that attempt to get
a guest slice will panic if the underlying memory is shared. Note that
Wiggle is not enabled for shared memory (that will come later in
something like #5054), but when it is, these panics will be clear
indicators of locations that must be re-implemented in a thread-safe
way.

* review: remove double cast

* review: refactor to include more logic in 'UnsafeGuestSlice'

* review: add reference to #4203

* review: link all thread-safe WASI fixups to #5235

* fix: consume 'UnsafeGuestSlice' during conversion to safe versions

* review: remove 'as_slice' and 'as_slice_mut'

* review: use 'as_unsafe_slice_mut' in 'to_vec'

* review: add `UnsafeBorrowResult`
2022-11-10 21:54:52 +00:00

358 lines
10 KiB
Rust

use wiggle::{GuestErrorType, GuestPtr, GuestSlice};
use wiggle_test::WasiCtx;
// This test file exists to make sure that the entire `wasi.witx` file can be
// handled by wiggle, producing code that compiles correctly.
// The trait impls here are never executed, and just exist to validate that the
// witx is exposed with the type signatures that we expect.
wiggle::from_witx!({
witx: ["$CARGO_MANIFEST_DIR/tests/wasi.witx"],
});
// The only test in this file is to verify that the witx document provided by the
// proc macro in the `metadata` module is equal to the document on the disk.
#[test]
fn document_equivalent() {
let macro_doc = metadata::document();
let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("tests");
path.push("wasi.witx");
let disk_doc = witx::load(&[path]).expect("load wasi.witx from disk");
assert_eq!(macro_doc, disk_doc);
}
type Result<T> = std::result::Result<T, types::Errno>;
impl GuestErrorType for types::Errno {
fn success() -> types::Errno {
types::Errno::Success
}
}
impl<'a> crate::wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx<'a> {
fn args_get(&mut self, _argv: &GuestPtr<GuestPtr<u8>>, _argv_buf: &GuestPtr<u8>) -> Result<()> {
unimplemented!("args_get")
}
fn args_sizes_get(&mut self) -> Result<(types::Size, types::Size)> {
unimplemented!("args_sizes_get")
}
fn environ_get(
&mut self,
_environ: &GuestPtr<GuestPtr<u8>>,
_environ_buf: &GuestPtr<u8>,
) -> Result<()> {
unimplemented!("environ_get")
}
fn environ_sizes_get(&mut self) -> Result<(types::Size, types::Size)> {
unimplemented!("environ_sizes_get")
}
fn clock_res_get(&mut self, _id: types::Clockid) -> Result<types::Timestamp> {
unimplemented!("clock_res_get")
}
fn clock_time_get(
&mut self,
_id: types::Clockid,
_precision: types::Timestamp,
) -> Result<types::Timestamp> {
unimplemented!("clock_time_get")
}
fn fd_advise(
&mut self,
_fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
_advice: types::Advice,
) -> Result<()> {
unimplemented!("fd_advise")
}
fn fd_allocate(
&mut self,
_fd: types::Fd,
_offset: types::Filesize,
_len: types::Filesize,
) -> Result<()> {
unimplemented!("fd_allocate")
}
fn fd_close(&mut self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_close")
}
fn fd_datasync(&mut self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_datasync")
}
fn fd_fdstat_get(&mut self, _fd: types::Fd) -> Result<types::Fdstat> {
unimplemented!("fd_fdstat_get")
}
fn fd_fdstat_set_flags(&mut self, _fd: types::Fd, _flags: types::Fdflags) -> Result<()> {
unimplemented!("fd_fdstat_set_flags")
}
fn fd_fdstat_set_rights(
&mut self,
_fd: types::Fd,
_fs_rights_base: types::Rights,
_fs_rights_inherting: types::Rights,
) -> Result<()> {
unimplemented!("fd_fdstat_set_rights")
}
fn fd_filestat_get(&mut self, _fd: types::Fd) -> Result<types::Filestat> {
unimplemented!("fd_filestat_get")
}
fn fd_filestat_set_size(&mut self, _fd: types::Fd, _size: types::Filesize) -> Result<()> {
unimplemented!("fd_filestat_set_size")
}
fn fd_filestat_set_times(
&mut self,
_fd: types::Fd,
_atim: types::Timestamp,
_mtim: types::Timestamp,
_fst_flags: types::Fstflags,
) -> Result<()> {
unimplemented!("fd_filestat_set_times")
}
fn fd_pread(
&mut self,
_fd: types::Fd,
iovs: &types::IovecArray<'_>,
_offset: types::Filesize,
) -> Result<types::Size> {
// This is not functional code, but the type annotations demonstrate
// that we can use the wiggle API to create the datastructures we want
// for efficient implementation of this function elsewhere.
let mut slices: Vec<GuestSlice<'_, u8>> = Vec::new();
for iov_ptr in iovs.iter() {
let iov_ptr = iov_ptr.expect("iovec element pointer is valid");
// Borrow checker will make sure the pointee of this read() doesn't overlap with any
// existing borrows:
let iov: types::Iovec = iov_ptr.read().expect("read iovec element");
let base: GuestPtr<u8> = iov.buf;
let len: u32 = iov.buf_len;
let buf: GuestPtr<[u8]> = base.as_array(len);
// GuestSlice will remain borrowed until dropped:
let slice = buf
.as_slice()
.expect("borrow slice from iovec")
.expect("expected non-shared memory");
slices.push(slice);
}
println!("iovec slices: [");
for slice in slices {
println!(" {:?},", &*slice);
}
println!("]");
unimplemented!("fd_pread")
}
fn fd_prestat_get(&mut self, _fd: types::Fd) -> Result<types::Prestat> {
unimplemented!("fd_prestat_get")
}
fn fd_prestat_dir_name(
&mut self,
_fd: types::Fd,
_path: &GuestPtr<u8>,
_path_len: types::Size,
) -> Result<()> {
unimplemented!("fd_prestat_dir_name")
}
fn fd_pwrite(
&mut self,
_fd: types::Fd,
_ciovs: &types::CiovecArray<'_>,
_offset: types::Filesize,
) -> Result<types::Size> {
unimplemented!("fd_pwrite")
}
fn fd_read(&mut self, _fd: types::Fd, _iovs: &types::IovecArray<'_>) -> Result<types::Size> {
unimplemented!("fd_read")
}
fn fd_readdir(
&mut self,
_fd: types::Fd,
_buf: &GuestPtr<u8>,
_buf_len: types::Size,
_cookie: types::Dircookie,
) -> Result<types::Size> {
unimplemented!("fd_readdir")
}
fn fd_renumber(&mut self, _fd: types::Fd, _to: types::Fd) -> Result<()> {
unimplemented!("fd_renumber")
}
fn fd_seek(
&mut self,
_fd: types::Fd,
_offset: types::Filedelta,
_whence: types::Whence,
) -> Result<types::Filesize> {
unimplemented!("fd_seek")
}
fn fd_sync(&mut self, _fd: types::Fd) -> Result<()> {
unimplemented!("fd_sync")
}
fn fd_tell(&mut self, _fd: types::Fd) -> Result<types::Filesize> {
unimplemented!("fd_tell")
}
fn fd_write(&mut self, _fd: types::Fd, _ciovs: &types::CiovecArray<'_>) -> Result<types::Size> {
unimplemented!("fd_write")
}
fn path_create_directory(&mut self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_create_directory")
}
fn path_filestat_get(
&mut self,
_fd: types::Fd,
_flags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
) -> Result<types::Filestat> {
unimplemented!("path_filestat_get")
}
fn path_filestat_set_times(
&mut self,
_fd: types::Fd,
_flags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
_atim: types::Timestamp,
_mtim: types::Timestamp,
_fst_flags: types::Fstflags,
) -> Result<()> {
unimplemented!("path_filestat_set_times")
}
fn path_link(
&mut self,
_old_fd: types::Fd,
_old_flags: types::Lookupflags,
_old_path: &GuestPtr<'_, str>,
_new_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
) -> Result<()> {
unimplemented!("path_link")
}
fn path_open(
&mut self,
_fd: types::Fd,
_dirflags: types::Lookupflags,
_path: &GuestPtr<'_, str>,
_oflags: types::Oflags,
_fs_rights_base: types::Rights,
_fs_rights_inherting: types::Rights,
_fdflags: types::Fdflags,
) -> Result<types::Fd> {
unimplemented!("path_open")
}
fn path_readlink(
&mut self,
_fd: types::Fd,
_path: &GuestPtr<'_, str>,
_buf: &GuestPtr<u8>,
_buf_len: types::Size,
) -> Result<types::Size> {
unimplemented!("path_readlink")
}
fn path_remove_directory(&mut self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_remove_directory")
}
fn path_rename(
&mut self,
_fd: types::Fd,
_old_path: &GuestPtr<'_, str>,
_new_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
) -> Result<()> {
unimplemented!("path_rename")
}
fn path_symlink(
&mut self,
_old_path: &GuestPtr<'_, str>,
_fd: types::Fd,
_new_path: &GuestPtr<'_, str>,
) -> Result<()> {
unimplemented!("path_symlink")
}
fn path_unlink_file(&mut self, _fd: types::Fd, _path: &GuestPtr<'_, str>) -> Result<()> {
unimplemented!("path_unlink_file")
}
fn poll_oneoff(
&mut self,
_in_: &GuestPtr<types::Subscription>,
_out: &GuestPtr<types::Event>,
_nsubscriptions: types::Size,
) -> Result<types::Size> {
unimplemented!("poll_oneoff")
}
fn proc_exit(&mut self, _rval: types::Exitcode) -> anyhow::Error {
unimplemented!("proc_exit")
}
fn proc_raise(&mut self, _sig: types::Signal) -> Result<()> {
unimplemented!("proc_raise")
}
fn sched_yield(&mut self) -> Result<()> {
unimplemented!("sched_yield")
}
fn random_get(&mut self, _buf: &GuestPtr<u8>, _buf_len: types::Size) -> Result<()> {
unimplemented!("random_get")
}
fn sock_recv(
&mut self,
_fd: types::Fd,
_ri_data: &types::IovecArray<'_>,
_ri_flags: types::Riflags,
) -> Result<(types::Size, types::Roflags)> {
unimplemented!("sock_recv")
}
fn sock_send(
&mut self,
_fd: types::Fd,
_si_data: &types::CiovecArray<'_>,
_si_flags: types::Siflags,
) -> Result<types::Size> {
unimplemented!("sock_send")
}
fn sock_shutdown(&mut self, _fd: types::Fd, _how: types::Sdflags) -> Result<()> {
unimplemented!("sock_shutdown")
}
}