diff --git a/RELEASES.md b/RELEASES.md index 4c0fbfb6b27b..c2a60a8657bf 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,14 @@ +## 24.0.10 + +Released 2026-06-15. + +### Fixed + +* Leak in WASIp1 `fd_renumber` implementation. + [GHSA-3p27-qvp9-27qf](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-3p27-qvp9-27qf) + +-------------------------------------------------------------------------------- + ## 24.0.9 Released 2026-05-21. diff --git a/crates/test-programs/src/bin/preview1_renumber.rs b/crates/test-programs/src/bin/preview1_renumber.rs index 71f0b9ac6b49..2c38e8e11bc1 100644 --- a/crates/test-programs/src/bin/preview1_renumber.rs +++ b/crates/test-programs/src/bin/preview1_renumber.rs @@ -89,14 +89,8 @@ unsafe fn test_renumber(dir_fd: wasi::Fd) { "file descriptor range check", ); - wasi::fd_renumber(fd_file3, 127).expect("renumbering FD to 127"); - match wasi::fd_renumber(127, u32::MAX) { - Err(wasi::ERRNO_NOMEM) => { - // The preview1 adapter cannot handle more than 128 descriptors - eprintln!("fd_renumber({fd_file3}, {}) returned NOMEM", u32::MAX) - } - res => res.expect("renumbering FD to `u32::MAX`"), - } + wasi::fd_renumber(fd_file3, 127).unwrap_err(); + wasi::fd_renumber(127, u32::MAX).unwrap_err(); let fd_file4 = wasi::path_open( dir_fd, @@ -114,6 +108,28 @@ unsafe fn test_renumber(dir_fd: wasi::Fd) { ); } +unsafe fn test_renumber_loop(dir_fd: wasi::Fd) { + let mut cur = wasi::path_open( + dir_fd, + 0, + "file1", + wasi::OFLAGS_CREAT, + wasi::RIGHTS_FD_READ | wasi::RIGHTS_FD_WRITE, + 0, + 0, + ) + .expect("opening a file"); + + for _ in 0..2000 { + let next = wasi::path_open(dir_fd, 0, "file1", 0, wasi::RIGHTS_FD_READ, 0, 0) + .expect("opening a file"); + wasi::fd_renumber(cur, next).unwrap(); + cur = next; + } + + wasi::fd_close(cur).unwrap(); +} + fn main() { let mut args = env::args(); let prog = args.next().unwrap(); @@ -135,4 +151,5 @@ fn main() { // Run the tests. unsafe { test_renumber(dir_fd) } + unsafe { test_renumber_loop(dir_fd) } } diff --git a/crates/test-programs/src/bin/preview1_stdio.rs b/crates/test-programs/src/bin/preview1_stdio.rs index 49d5d430c960..0c82a60f2940 100644 --- a/crates/test-programs/src/bin/preview1_stdio.rs +++ b/crates/test-programs/src/bin/preview1_stdio.rs @@ -3,7 +3,7 @@ use test_programs::preview1::{STDERR_FD, STDIN_FD, STDOUT_FD}; unsafe fn test_stdio() { for fd in &[STDIN_FD, STDOUT_FD, STDERR_FD] { wasi::fd_fdstat_get(*fd).expect("fd_fdstat_get on stdio"); - wasi::fd_renumber(*fd, *fd + 100).expect("renumbering stdio"); + wasi::fd_renumber(*fd, *fd).expect("renumbering stdio"); } } diff --git a/crates/wasi-common/src/snapshots/preview_1.rs b/crates/wasi-common/src/snapshots/preview_1.rs index 74295b8aa235..d60f26837b63 100644 --- a/crates/wasi-common/src/snapshots/preview_1.rs +++ b/crates/wasi-common/src/snapshots/preview_1.rs @@ -578,6 +578,9 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiCtx { if !table.contains_key(from) { return Err(Error::badf()); } + if !table.contains_key(to) { + return Err(Error::badf()); + } table.renumber(from, to) } diff --git a/crates/wasi-preview1-component-adapter/src/descriptors.rs b/crates/wasi-preview1-component-adapter/src/descriptors.rs index 93143f278bdc..6aa1fc2d884b 100644 --- a/crates/wasi-preview1-component-adapter/src/descriptors.rs +++ b/crates/wasi-preview1-component-adapter/src/descriptors.rs @@ -341,23 +341,13 @@ impl Descriptors { Ok(()) } - // Expand the table by pushing a closed descriptor to the end. Used for renumbering. - fn push_closed(&mut self) -> Result<(), Errno> { - let old_closed = self.closed; - let new_closed = self.push(Descriptor::Closed(old_closed))?; - self.closed = Some(new_closed); - Ok(()) - } - // Implementation of fd_renumber pub fn renumber(&mut self, from_fd: Fd, to_fd: Fd) -> Result<(), Errno> { - // First, ensure from_fd is in bounds: - let _ = self.get(from_fd)?; - // Expand table until to_fd is in bounds as well: - while self.table_len.get() as u32 <= to_fd { - self.push_closed()?; + // Throw an error if renumbering to or from a closed fd + match self.get(to_fd)? { + Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?, + _ => {} } - // Throw an error if renumbering a closed fd match self.get(from_fd)? { Descriptor::Closed(_) => Err(wasi::ERRNO_BADF)?, _ => {} diff --git a/crates/wasi/src/preview0.rs b/crates/wasi/src/preview0.rs index dad6fb42b800..f04a3d8d3582 100644 --- a/crates/wasi/src/preview0.rs +++ b/crates/wasi/src/preview0.rs @@ -31,7 +31,7 @@ wiggle::from_witx!({ fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get, path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory, - path_rename, path_symlink, path_unlink_file + path_rename, path_symlink, path_unlink_file, fd_renumber } }, errors: { errno => trappable Error }, @@ -50,7 +50,7 @@ mod sync { fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get, path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory, - path_rename, path_symlink, path_unlink_file + path_rename, path_symlink, path_unlink_file, fd_renumber } }, errors: { errno => trappable Error }, @@ -301,13 +301,13 @@ impl wasi_unstable::WasiUnstable for T { Ok(()) } - fn fd_renumber( + async fn fd_renumber( &mut self, memory: &mut GuestMemory<'_>, from: types::Fd, to: types::Fd, ) -> Result<(), Error> { - Snapshot1::fd_renumber(self, memory, from.into(), to.into())?; + Snapshot1::fd_renumber(self, memory, from.into(), to.into()).await?; Ok(()) } diff --git a/crates/wasi/src/preview1.rs b/crates/wasi/src/preview1.rs index 44fd1ec6c09a..c9e002ca6979 100644 --- a/crates/wasi/src/preview1.rs +++ b/crates/wasi/src/preview1.rs @@ -910,7 +910,7 @@ wiggle::from_witx!({ fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get, path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory, - path_rename, path_symlink, path_unlink_file + path_rename, path_symlink, path_unlink_file, fd_renumber } }, errors: { errno => trappable Error }, @@ -929,7 +929,7 @@ pub(crate) mod sync { fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write, fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get, path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory, - path_rename, path_symlink, path_unlink_file + path_rename, path_symlink, path_unlink_file, fd_renumber } }, errors: { errno => trappable Error }, @@ -1911,25 +1911,33 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx { } /// Atomically replace a file descriptor by renumbering another file descriptor. - #[instrument(skip(self, _memory))] - fn fd_renumber( + #[instrument(skip(self, memory))] + async fn fd_renumber( &mut self, - _memory: &mut GuestMemory<'_>, - from: types::Fd, - to: types::Fd, + memory: &mut GuestMemory<'_>, + from_fd: types::Fd, + to_fd: types::Fd, ) -> Result<(), types::Error> { + let from = from_fd.into(); + let to = to_fd.into(); + { + let st = self.transact()?; + if !st.descriptors.used.contains_key(&to) || !st.descriptors.used.contains_key(&from) { + return Err(types::Errno::Badf.into()); + } + if from == to { + return Ok(()); + } + } + self.fd_close(memory, to_fd).await?; let mut st = self.transact()?; - let from = from.into(); let btree_map::Entry::Occupied(desc) = st.descriptors.used.entry(from) else { return Err(types::Errno::Badf.into()); }; - let to = to.into(); - if from != to { - let desc = desc.remove(); - st.descriptors.free.insert(from); - st.descriptors.free.remove(&to); - st.descriptors.used.insert(to, desc); - } + let desc = desc.remove(); + st.descriptors.free.insert(from); + st.descriptors.free.remove(&to); + st.descriptors.used.insert(to, desc); Ok(()) }