diff --git a/crates/wasi-common/src/snapshots/preview_1/error.rs b/crates/wasi-common/src/snapshots/preview_1/error.rs index 157cdcc83cb6..f0b7cdf4ad94 100644 --- a/crates/wasi-common/src/snapshots/preview_1/error.rs +++ b/crates/wasi-common/src/snapshots/preview_1/error.rs @@ -235,9 +235,19 @@ impl From for Error { match err { InvalidFlagValue { .. } => Errno::Inval.into(), InvalidEnumValue { .. } => Errno::Inval.into(), - PtrOverflow { .. } => Errno::Fault.into(), - PtrOutOfBounds { .. } => Errno::Fault.into(), - PtrNotAligned { .. } => Errno::Inval.into(), + // As per + // https://github.com/WebAssembly/wasi/blob/main/legacy/tools/witx-docs.md#pointers + // + // > If a misaligned pointer is passed to a function, the function + // > shall trap. + // > + // > If an out-of-bounds pointer is passed to a function and the + // > function needs to dereference it, the function shall trap. + // + // so this turns OOB and misalignment errors into traps. + PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => { + Error::trap(err.into()) + } PtrBorrowed { .. } => Errno::Fault.into(), InvalidUtf8 { .. } => Errno::Ilseq.into(), TryFromIntError { .. } => Errno::Overflow.into(), diff --git a/crates/wasi/src/preview2/preview1/mod.rs b/crates/wasi/src/preview2/preview1/mod.rs index 0bbbe1c9b67d..f8616a9908e4 100644 --- a/crates/wasi/src/preview2/preview1/mod.rs +++ b/crates/wasi/src/preview2/preview1/mod.rs @@ -15,7 +15,7 @@ use std::slice; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use wiggle::tracing::instrument; -use wiggle::{GuestPtr, GuestSliceMut, GuestStrCow, GuestType}; +use wiggle::{GuestError, GuestPtr, GuestSliceMut, GuestStrCow, GuestType}; #[derive(Clone, Debug)] struct File { @@ -101,14 +101,14 @@ impl Descriptors { } /// Returns next descriptor number, which was never assigned - fn unused(&self) -> ErrnoResult { + fn unused(&self) -> Result { match self.last_key_value() { Some((fd, _)) => { if let Some(fd) = fd.checked_add(1) { return Ok(fd); } if self.len() == u32::MAX as usize { - return Err(types::Errno::Loop); + return Err(types::Errno::Loop.into()); } // TODO: Optimize Ok((0..u32::MAX) @@ -131,7 +131,7 @@ impl Descriptors { /// Pushes the [Descriptor] returning corresponding number. /// This operation will try to reuse numbers previously removed via [`Self::remove`] /// and rely on [`Self::unused`] if no free numbers are recorded - fn push(&mut self, desc: Descriptor) -> ErrnoResult { + fn push(&mut self, desc: Descriptor) -> Result { let fd = if let Some(fd) = self.free.pop() { fd } else { @@ -142,7 +142,7 @@ impl Descriptors { } /// Like [Self::push], but for [`File`] - fn push_file(&mut self, file: File) -> ErrnoResult { + fn push_file(&mut self, file: File) -> Result { self.push(Descriptor::File(file)) } } @@ -188,33 +188,35 @@ impl Transaction<'_, T> { /// # Errors /// /// Returns [`types::Errno::Badf`] if no [`Descriptor`] is found - fn get_descriptor(&mut self, fd: types::Fd) -> ErrnoResult<&Descriptor> { + fn get_descriptor(&mut self, fd: types::Fd) -> Result<&Descriptor> { let fd = fd.into(); - self.descriptors + let desc = self + .descriptors .get_mut() .get(&fd) - .ok_or(types::Errno::Badf) + .ok_or(types::Errno::Badf)?; + Ok(desc) } /// Borrows [`File`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type - fn get_file(&mut self, fd: types::Fd) -> ErrnoResult<&File> { + fn get_file(&mut self, fd: types::Fd) -> Result<&File> { let fd = fd.into(); match self.descriptors.get_mut().get(&fd) { Some(Descriptor::File(file @ File { fd, .. })) if self.view.table().is_file(*fd) => { Ok(file) } - _ => Err(types::Errno::Badf), + _ => Err(types::Errno::Badf.into()), } } /// Mutably borrows [`File`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type - fn get_file_mut(&mut self, fd: types::Fd) -> ErrnoResult<&mut File> { + fn get_file_mut(&mut self, fd: types::Fd) -> Result<&mut File> { let fd = fd.into(); match self.descriptors.get_mut().get_mut(&fd) { Some(Descriptor::File(file)) if self.view.table().is_file(file.fd) => Ok(file), - _ => Err(types::Errno::Badf), + _ => Err(types::Errno::Badf.into()), } } @@ -224,7 +226,7 @@ impl Transaction<'_, T> { /// # Errors /// /// Returns [`types::Errno::Spipe`] if the descriptor corresponds to stdio - fn get_seekable(&mut self, fd: types::Fd) -> ErrnoResult<&File> { + fn get_seekable(&mut self, fd: types::Fd) -> Result<&File> { let fd = fd.into(); match self.descriptors.get_mut().get(&fd) { Some(Descriptor::File(file @ File { fd, .. })) if self.view.table().is_file(*fd) => { @@ -232,14 +234,14 @@ impl Transaction<'_, T> { } Some(Descriptor::Stdin(..) | Descriptor::Stdout(..) | Descriptor::Stderr(..)) => { // NOTE: legacy implementation returns SPIPE here - Err(types::Errno::Spipe) + Err(types::Errno::Spipe.into()) } - _ => Err(types::Errno::Badf), + _ => Err(types::Errno::Badf.into()), } } /// Returns [`filesystem::Descriptor`] corresponding to `fd` - fn get_fd(&mut self, fd: types::Fd) -> ErrnoResult { + fn get_fd(&mut self, fd: types::Fd) -> Result { match self.get_descriptor(fd)? { Descriptor::File(File { fd, .. }) => Ok(*fd), Descriptor::PreopenDirectory((fd, _)) => Ok(*fd), @@ -250,19 +252,19 @@ impl Transaction<'_, T> { /// Returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] of [`crate::preview2::filesystem::File`] type - fn get_file_fd(&mut self, fd: types::Fd) -> ErrnoResult { + fn get_file_fd(&mut self, fd: types::Fd) -> Result { self.get_file(fd).map(|File { fd, .. }| *fd) } /// Returns [`filesystem::Descriptor`] corresponding to `fd` /// if it describes a [`Descriptor::File`] or [`Descriptor::PreopenDirectory`] /// of [`crate::preview2::filesystem::Dir`] type - fn get_dir_fd(&mut self, fd: types::Fd) -> ErrnoResult { + fn get_dir_fd(&mut self, fd: types::Fd) -> Result { let fd = fd.into(); match self.descriptors.get_mut().get(&fd) { Some(Descriptor::File(File { fd, .. })) if self.view.table().is_dir(*fd) => Ok(*fd), Some(Descriptor::PreopenDirectory((fd, _))) => Ok(*fd), - _ => Err(types::Errno::Badf), + _ => Err(types::Errno::Badf.into()), } } } @@ -356,13 +358,9 @@ impl wiggle::GuestErrorType for types::Errno { } } -fn systimespec( - set: bool, - ts: types::Timestamp, - now: bool, -) -> ErrnoResult { +fn systimespec(set: bool, ts: types::Timestamp, now: bool) -> Result { if set && now { - Err(types::Errno::Inval) + Err(types::Errno::Inval.into()) } else if set { Ok(filesystem::NewTimestamp::Timestamp(filesystem::Datetime { seconds: ts / 1_000_000_000, @@ -499,6 +497,41 @@ impl From for types::Errno { } } +impl From for types::Error { + fn from(_: std::num::TryFromIntError) -> Self { + types::Errno::Overflow.into() + } +} + +impl From for types::Error { + fn from(err: GuestError) -> Self { + use wiggle::GuestError::*; + match err { + InvalidFlagValue { .. } => types::Errno::Inval.into(), + InvalidEnumValue { .. } => types::Errno::Inval.into(), + // As per + // https://github.com/WebAssembly/wasi/blob/main/legacy/tools/witx-docs.md#pointers + // + // > If a misaligned pointer is passed to a function, the function + // > shall trap. + // > + // > If an out-of-bounds pointer is passed to a function and the + // > function needs to dereference it, the function shall trap. + // + // so this turns OOB and misalignment errors into traps. + PtrOverflow { .. } | PtrOutOfBounds { .. } | PtrNotAligned { .. } => { + types::Error::trap(err.into()) + } + PtrBorrowed { .. } => types::Errno::Fault.into(), + InvalidUtf8 { .. } => types::Errno::Ilseq.into(), + TryFromIntError { .. } => types::Errno::Overflow.into(), + SliceLengthsDiffer { .. } => types::Errno::Fault.into(), + BorrowCheckerOutOfHandles { .. } => types::Errno::Fault.into(), + InFunc { err, .. } => types::Error::from(*err), + } + } +} + impl From for types::Error { fn from(code: filesystem::ErrorCode) -> Self { types::Errno::from(code).into() @@ -542,78 +575,63 @@ impl From for types::Error { } } -type ErrnoResult = Result; +type Result = std::result::Result; fn write_bytes<'a>( ptr: impl Borrow>, buf: impl AsRef<[u8]>, -) -> ErrnoResult> { +) -> Result, types::Error> { // NOTE: legacy implementation always returns Inval errno let buf = buf.as_ref(); - let len = buf.len().try_into().or(Err(types::Errno::Inval))?; + let len = buf.len().try_into()?; let ptr = ptr.borrow(); - ptr.as_array(len) - .copy_from_slice(buf) - .or(Err(types::Errno::Inval))?; - ptr.add(len).or(Err(types::Errno::Inval)) + ptr.as_array(len).copy_from_slice(buf)?; + let next = ptr.add(len)?; + Ok(next) } -fn write_byte<'a>(ptr: impl Borrow>, byte: u8) -> ErrnoResult> { +fn write_byte<'a>(ptr: impl Borrow>, byte: u8) -> Result> { let ptr = ptr.borrow(); - ptr.write(byte).or(Err(types::Errno::Inval))?; - ptr.add(1).or(Err(types::Errno::Inval)) + ptr.write(byte)?; + let next = ptr.add(1)?; + Ok(next) } -fn read_str<'a>(ptr: impl Borrow>) -> ErrnoResult> { - // NOTE: legacy implementation always returns Inval errno - ptr.borrow().as_cow().or(Err(types::Errno::Inval)) +fn read_str<'a>(ptr: impl Borrow>) -> Result> { + let s = ptr.borrow().as_cow()?; + Ok(s) } -fn read_string<'a>(ptr: impl Borrow>) -> ErrnoResult { +fn read_string<'a>(ptr: impl Borrow>) -> Result { read_str(ptr).map(|s| s.to_string()) } // Find first non-empty buffer. -fn first_non_empty_ciovec(ciovs: &types::CiovecArray<'_>) -> ErrnoResult>> { - ciovs - .iter() - .map(|iov| { - let iov = iov - .or(Err(types::Errno::Inval))? - .read() - .or(Err(types::Errno::Inval))?; - if iov.buf_len == 0 { - return Ok(None); - } - iov.buf - .as_array(iov.buf_len) - .to_vec() - .or(Err(types::Errno::Inval)) - .map(Some) - }) - .find_map(Result::transpose) - .transpose() +fn first_non_empty_ciovec(ciovs: &types::CiovecArray<'_>) -> Result>> { + for iov in ciovs.iter() { + let iov = iov?.read()?; + if iov.buf_len == 0 { + continue; + } + return Ok(Some(iov.buf.as_array(iov.buf_len).to_vec()?)); + } + Ok(None) } // Find first non-empty buffer. fn first_non_empty_iovec<'a>( iovs: &types::IovecArray<'a>, -) -> ErrnoResult>> { +) -> Result>> { iovs.iter() .map(|iov| { - let iov = iov - .or(Err(types::Errno::Inval))? - .read() - .or(Err(types::Errno::Inval))?; + let iov = iov?.read()?; if iov.buf_len == 0 { return Ok(None); } - iov.buf - .as_array(iov.buf_len) - .as_slice_mut() - .map_err(|_| types::Errno::Inval) + let slice = iov.buf.as_array(iov.buf_len).as_slice_mut()?; + Ok(slice) }) .find_map(Result::transpose) .transpose() @@ -646,20 +664,15 @@ impl< .context("failed to call `get-arguments`") .map_err(types::Error::trap)? .into_iter() - .try_fold( - (*argv, *argv_buf), - |(argv, argv_buf), arg| -> ErrnoResult<_> { - // NOTE: legacy implementation always returns Inval errno + .try_fold((*argv, *argv_buf), |(argv, argv_buf), arg| -> Result<_> { + argv.write(argv_buf)?; + let argv = argv.add(1)?; - argv.write(argv_buf).map_err(|_| types::Errno::Inval)?; - let argv = argv.add(1).map_err(|_| types::Errno::Inval)?; + let argv_buf = write_bytes(argv_buf, arg)?; + let argv_buf = write_byte(argv_buf, 0)?; - let argv_buf = write_bytes(argv_buf, arg)?; - let argv_buf = write_byte(argv_buf, 0)?; - - Ok((argv, argv_buf)) - }, - )?; + Ok((argv, argv_buf)) + })?; Ok(()) } @@ -691,13 +704,9 @@ impl< .into_iter() .try_fold( (*environ, *environ_buf), - |(environ, environ_buf), (k, v)| -> ErrnoResult<_> { - // NOTE: legacy implementation always returns Inval errno - - environ - .write(environ_buf) - .map_err(|_| types::Errno::Inval)?; - let environ = environ.add(1).map_err(|_| types::Errno::Inval)?; + |(environ, environ_buf), (k, v)| -> Result<_, types::Error> { + environ.write(environ_buf)?; + let environ = environ.add(1)?; let environ_buf = write_bytes(environ_buf, k)?; let environ_buf = write_byte(environ_buf, b'=')?; @@ -716,16 +725,12 @@ impl< .get_environment() .context("failed to call `get-environment`") .map_err(types::Error::trap)?; - let num = environ - .len() - .try_into() - .map_err(|_| types::Errno::Overflow)?; + let num = environ.len().try_into()?; let len = environ .iter() .map(|(k, v)| k.len() + 1 + v.len() + 1) // Key/value pairs are expected to be joined with `=`s, and terminated with `\0`s. .sum::() - .try_into() - .map_err(|_| types::Errno::Overflow)?; + .try_into()?; Ok((num, len)) } @@ -1084,7 +1089,7 @@ impl< } .map_err(|_| types::Errno::Io)?; - let n = read.len().try_into().or(Err(types::Errno::Overflow))?; + let n = read.len().try_into()?; let pos = pos.checked_add(n).ok_or(types::Errno::Overflow)?; position.store(pos, Ordering::Relaxed); @@ -1110,7 +1115,7 @@ impl< } let (buf, _) = buf.split_at_mut(read.len()); buf.copy_from_slice(&read); - let n = read.len().try_into().or(Err(types::Errno::Overflow))?; + let n = read.len().try_into()?; Ok(n) } @@ -1159,7 +1164,7 @@ impl< } let (buf, _) = buf.split_at_mut(read.len()); buf.copy_from_slice(&read); - let n = read.len().try_into().or(Err(types::Errno::Overflow))?; + let n = read.len().try_into()?; Ok(n) } @@ -1221,7 +1226,7 @@ impl< } _ => return Err(types::Errno::Badf.into()), }; - let n = n.try_into().or(Err(types::Errno::Overflow))?; + let n = n.try_into()?; Ok(n) } @@ -1258,7 +1263,7 @@ impl< } _ => return Err(types::Errno::Badf.into()), }; - let n = n.try_into().or(Err(types::Errno::Overflow))?; + let n = n.try_into()?; Ok(n) } @@ -1266,7 +1271,7 @@ impl< #[instrument(skip(self))] fn fd_prestat_get(&mut self, fd: types::Fd) -> Result { if let Descriptor::PreopenDirectory((_, p)) = self.transact()?.get_descriptor(fd)? { - let pr_name_len = p.len().try_into().or(Err(types::Errno::Overflow))?; + let pr_name_len = p.len().try_into()?; return Ok(types::Prestat::Dir(types::PrestatDir { pr_name_len })); } Err(types::Errno::Badf.into()) // NOTE: legacy implementation returns BADF here @@ -1280,7 +1285,7 @@ impl< path: &GuestPtr<'a, u8>, path_max_len: types::Size, ) -> Result<(), types::Error> { - let path_max_len = path_max_len.try_into().or(Err(types::Errno::Overflow))?; + let path_max_len = path_max_len.try_into()?; if let Descriptor::PreopenDirectory((_, p)) = self.transact()?.get_descriptor(fd)? { if p.len() > path_max_len { return Err(types::Errno::Nametoolong.into()); @@ -1738,7 +1743,7 @@ impl< path: &GuestPtr<'a, str>, ) -> Result<(), types::Error> { let dirfd = self.get_dir_fd(dirfd)?; - let path = path.as_cow().map_err(|_| types::Errno::Inval)?.to_string(); + let path = path.as_cow()?.to_string(); self.unlink_file_at(dirfd, path).await.map_err(|e| { e.try_into() .context("failed to call `unlink-file-at`") diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index b5a33f59226d..d7fd98cc103f 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -717,3 +717,17 @@ fn wasm_flags_without_subcommand() -> Result<()> { ); Ok(()) } + +#[test] +fn wasi_misaligned_pointer() -> Result<()> { + let output = get_wasmtime_command()? + .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat") + .output()?; + assert!(!output.status.success()); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("Pointer not aligned"), + "bad stderr: {stderr}", + ); + Ok(()) +} diff --git a/tests/all/cli_tests/wasi_misaligned_pointer.wat b/tests/all/cli_tests/wasi_misaligned_pointer.wat new file mode 100644 index 000000000000..99b43f976108 --- /dev/null +++ b/tests/all/cli_tests/wasi_misaligned_pointer.wat @@ -0,0 +1,16 @@ +(module + (import "wasi_snapshot_preview1" "fd_write" (func $write (param i32 i32 i32 i32) (result i32))) + + (memory (export "memory") 1) + + (func (export "_start") + (call $write + (i32.const 1) ;; fd=1 + (i32.const 1) ;; ciovec_base=1 (misaligned) + (i32.const 1) ;; ciovec_len=1 + (i32.const 0) ;; retptr=0 + ) + drop + + ) +)