Skip to content

Commit e9fbe97

Browse files
authored
[45.0.x] Fix leak in fd_renumber (#13648)
* Fix leak in `fd_renumber` This commit fixes a file descriptor leak in the WASIp1 implementation of `fd_renumber` in the `wasmtime-wasi` crate. Notably the previous implementation did not fully close the file descriptor being renumbered-to which meant that the host's resources for the file, including the file descriptor, stayed alive. The fix here is to validate both fds exist and then delegate to the `fd_close` call to close the destination. * Add release notes
1 parent 83166ba commit e9fbe97

4 files changed

Lines changed: 61 additions & 22 deletions

File tree

RELEASES.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
## 45.0.2
2+
3+
Released 2026-06-15.
4+
5+
### Fixed
6+
7+
* Leak in WASIp1 `fd_renumber` implementation.
8+
[GHSA-3p27-qvp9-27qf](https://github.com/bytecodealliance/wasmtime/security/advisories/GHSA-3p27-qvp9-27qf)
9+
10+
--------------------------------------------------------------------------------
11+
112
## 45.0.1
213

314
Released 2026-06-05.

crates/test-programs/src/bin/p1_renumber.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,28 @@ unsafe fn test_renumber(dir_fd: wasip1::Fd) {
113113
);
114114
}
115115

116+
unsafe fn test_renumber_loop(dir_fd: wasip1::Fd) {
117+
let mut cur = wasip1::path_open(
118+
dir_fd,
119+
0,
120+
"file1",
121+
wasip1::OFLAGS_CREAT,
122+
wasip1::RIGHTS_FD_READ | wasip1::RIGHTS_FD_WRITE,
123+
0,
124+
0,
125+
)
126+
.expect("opening a file");
127+
128+
for _ in 0..2000 {
129+
let next = wasip1::path_open(dir_fd, 0, "file1", 0, wasip1::RIGHTS_FD_READ, 0, 0)
130+
.expect("opening a file");
131+
wasip1::fd_renumber(cur, next).unwrap();
132+
cur = next;
133+
}
134+
135+
wasip1::fd_close(cur).unwrap();
136+
}
137+
116138
fn main() {
117139
let mut args = env::args();
118140
let prog = args.next().unwrap();
@@ -134,4 +156,5 @@ fn main() {
134156

135157
// Run the tests.
136158
unsafe { test_renumber(dir_fd) }
159+
unsafe { test_renumber_loop(dir_fd) }
137160
}

crates/wasi/src/p0.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ wiggle::from_witx!({
3131
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
3232
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
3333
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
34-
path_rename, path_symlink, path_unlink_file
34+
path_rename, path_symlink, path_unlink_file, fd_renumber
3535
}
3636
},
3737
errors: { errno => trappable Error },
@@ -50,7 +50,7 @@ mod sync {
5050
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
5151
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
5252
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
53-
path_rename, path_symlink, path_unlink_file
53+
path_rename, path_symlink, path_unlink_file, fd_renumber
5454
}
5555
},
5656
errors: { errno => trappable Error },
@@ -300,13 +300,13 @@ impl<T: Snapshot1 + Send> wasi_unstable::WasiUnstable for T {
300300
Ok(())
301301
}
302302

303-
fn fd_renumber(
303+
async fn fd_renumber(
304304
&mut self,
305305
memory: &mut GuestMemory<'_>,
306306
from: types::Fd,
307307
to: types::Fd,
308308
) -> Result<(), Error> {
309-
Snapshot1::fd_renumber(self, memory, from.into(), to.into())?;
309+
Snapshot1::fd_renumber(self, memory, from.into(), to.into()).await?;
310310
Ok(())
311311
}
312312

crates/wasi/src/p1.rs

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ wiggle::from_witx!({
863863
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
864864
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
865865
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
866-
path_rename, path_symlink, path_unlink_file
866+
path_rename, path_symlink, path_unlink_file, fd_renumber
867867
}
868868
},
869869
errors: { errno => trappable Error },
@@ -882,7 +882,7 @@ pub(crate) mod sync {
882882
fd_filestat_set_times, fd_read, fd_pread, fd_seek, fd_sync, fd_readdir, fd_write,
883883
fd_pwrite, poll_oneoff, path_create_directory, path_filestat_get,
884884
path_filestat_set_times, path_link, path_open, path_readlink, path_remove_directory,
885-
path_rename, path_symlink, path_unlink_file
885+
path_rename, path_symlink, path_unlink_file, fd_renumber
886886
}
887887
},
888888
errors: { errno => trappable Error },
@@ -1854,28 +1854,33 @@ impl wasi_snapshot_preview1::WasiSnapshotPreview1 for WasiP1Ctx {
18541854
}
18551855

18561856
/// Atomically replace a file descriptor by renumbering another file descriptor.
1857-
#[instrument(skip(self, _memory))]
1858-
fn fd_renumber(
1857+
#[instrument(skip(self, memory))]
1858+
async fn fd_renumber(
18591859
&mut self,
1860-
_memory: &mut GuestMemory<'_>,
1861-
from: types::Fd,
1862-
to: types::Fd,
1860+
memory: &mut GuestMemory<'_>,
1861+
from_fd: types::Fd,
1862+
to_fd: types::Fd,
18631863
) -> Result<(), types::Error> {
1864-
let mut st = self.transact()?;
1865-
let from = from.into();
1866-
let to = to.into();
1867-
if !st.descriptors.used.contains_key(&to) {
1868-
return Err(types::Errno::Badf.into());
1864+
let from = from_fd.into();
1865+
let to = to_fd.into();
1866+
{
1867+
let st = self.transact()?;
1868+
if !st.descriptors.used.contains_key(&to) || !st.descriptors.used.contains_key(&from) {
1869+
return Err(types::Errno::Badf.into());
1870+
}
1871+
if from == to {
1872+
return Ok(());
1873+
}
18691874
}
1875+
self.fd_close(memory, to_fd).await?;
1876+
let mut st = self.transact()?;
18701877
let btree_map::Entry::Occupied(desc) = st.descriptors.used.entry(from) else {
18711878
return Err(types::Errno::Badf.into());
18721879
};
1873-
if from != to {
1874-
let desc = desc.remove();
1875-
st.descriptors.free.insert(from);
1876-
st.descriptors.free.remove(&to);
1877-
st.descriptors.used.insert(to, desc);
1878-
}
1880+
let desc = desc.remove();
1881+
st.descriptors.free.insert(from);
1882+
st.descriptors.free.remove(&to);
1883+
st.descriptors.used.insert(to, desc);
18791884
Ok(())
18801885
}
18811886

0 commit comments

Comments
 (0)