Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions RELEASES.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
33 changes: 25 additions & 8 deletions crates/test-programs/src/bin/preview1_renumber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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();
Expand All @@ -135,4 +151,5 @@ fn main() {

// Run the tests.
unsafe { test_renumber(dir_fd) }
unsafe { test_renumber_loop(dir_fd) }
}
2 changes: 1 addition & 1 deletion crates/test-programs/src/bin/preview1_stdio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}

Expand Down
3 changes: 3 additions & 0 deletions crates/wasi-common/src/snapshots/preview_1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
18 changes: 4 additions & 14 deletions crates/wasi-preview1-component-adapter/src/descriptors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?,
_ => {}
Expand Down
8 changes: 4 additions & 4 deletions crates/wasi/src/preview0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 },
Expand Down Expand Up @@ -301,13 +301,13 @@ impl<T: Snapshot1 + Send> 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(())
}

Expand Down
38 changes: 23 additions & 15 deletions crates/wasi/src/preview1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand All @@ -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 },
Expand Down Expand Up @@ -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(())
}

Expand Down
Loading