Mio's tokens for named pipes may be delivered after deregistration
Description
Mio is a Metal I/O library for Rust. When using named pipes on Windows, mio will under some circumstances return invalid tokens that correspond to named pipes that have already been deregistered from the mio registry. The impact of this vulnerability depends on how mio is used. For some applications, invalid tokens may be ignored or cause a warning or a crash. On the other hand, for applications that store pointers in the tokens, this vulnerability may result in a use-after-free. For users of Tokio, this vulnerability is serious and can result in a use-after-free in Tokio. The vulnerability is Windows-specific, and can only happen if you are using named pipes. Other IO resources are not affected. This vulnerability has been fixed in mio v0.8.11. All versions of mio between v0.7.2 and v0.8.10 are vulnerable. Tokio is vulnerable when you are using a vulnerable version of mio AND you are using at least Tokio v1.30.0. Versions of Tokio prior to v1.30.0 will ignore invalid tokens, so they are not vulnerable. Vulnerable libraries that use mio can work around this issue by detecting and ignoring invalid tokens.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
miocrates.io | >= 0.7.2, < 0.8.11 | 0.8.11 |
Affected products
1Patches
190d4fe00df87named-pipes: fix receiving IOCP events after deregister
2 files changed · +79 −12
src/sys/windows/event.rs+8 −0 modified@@ -41,6 +41,14 @@ impl Event { pub(super) fn to_completion_status(&self) -> CompletionStatus { CompletionStatus::new(self.flags, self.data as usize, std::ptr::null_mut()) } + + #[cfg(feature = "os-ext")] + pub(super) fn to_completion_status_with_overlapped( + &self, + overlapped: *mut super::Overlapped, + ) -> CompletionStatus { + CompletionStatus::new(self.flags, self.data as usize, overlapped) + } } pub(crate) const READABLE_FLAGS: u32 = afd::POLL_RECEIVE
src/sys/windows/named_pipe.rs+71 −12 modified@@ -84,6 +84,7 @@ struct Inner { connect: Overlapped, read: Overlapped, write: Overlapped, + event: Overlapped, // END NOTE. handle: Handle, connecting: AtomicBool, @@ -110,10 +111,16 @@ impl Inner { /// Same as [`ptr_from_conn_overlapped`] but for `Inner.write`. unsafe fn ptr_from_write_overlapped(ptr: *mut OVERLAPPED) -> *const Inner { - // `read` is after `connect: Overlapped` and `read: Overlapped`. + // `write` is after `connect: Overlapped` and `read: Overlapped`. (ptr as *mut Overlapped).wrapping_sub(2) as *const Inner } + /// Same as [`ptr_from_conn_overlapped`] but for `Inner.event`. + unsafe fn ptr_from_event_overlapped(ptr: *mut OVERLAPPED) -> *const Inner { + // `event` is after `connect: Overlapped`, `read: Overlapped`, and `write: Overlapped`. + (ptr as *mut Overlapped).wrapping_sub(3) as *const Inner + } + /// Issue a connection request with the specified overlapped operation. /// /// This function will issue a request to connect a client to this server, @@ -478,6 +485,7 @@ impl FromRawHandle for NamedPipe { connecting: AtomicBool::new(false), read: Overlapped::new(read_done), write: Overlapped::new(write_done), + event: Overlapped::new(event_done), io: Mutex::new(Io { cp: None, token: None, @@ -724,7 +732,7 @@ impl Inner { // out the error. Err(e) => { io.read = State::Err(e); - io.notify_readable(events); + io.notify_readable(me, events); true } } @@ -787,7 +795,7 @@ impl Inner { Ok(None) => (), Err(e) => { io.write = State::Err(e); - io.notify_writable(events); + io.notify_writable(me, events); } } } @@ -797,7 +805,7 @@ impl Inner { #[allow(clippy::needless_option_as_deref)] if Inner::schedule_read(me, &mut io, events.as_deref_mut()) { if let State::None = io.write { - io.notify_writable(events); + io.notify_writable(me, events); } } } @@ -877,7 +885,7 @@ fn read_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) { } // Flag our readiness that we've got data. - io.notify_readable(events); + io.notify_readable(&me, events); } fn write_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) { @@ -895,7 +903,7 @@ fn write_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) { // `Ok` here means, that the operation was completed immediately // `bytes_transferred` is already reported to a client State::Ok(..) => { - io.notify_writable(events); + io.notify_writable(&me, events); return; } State::Pending(buf, pos) => (buf, pos), @@ -909,20 +917,46 @@ fn write_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) { let new_pos = pos + (status.bytes_transferred() as usize); if new_pos == buf.len() { me.put_buffer(buf); - io.notify_writable(events); + io.notify_writable(&me, events); } else { Inner::schedule_write(&me, buf, new_pos, &mut io, events); } } Err(e) => { debug_assert_eq!(status.bytes_transferred(), 0); io.write = State::Err(e); - io.notify_writable(events); + io.notify_writable(&me, events); } } } } +fn event_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) { + let status = CompletionStatus::from_entry(status); + + // Acquire the `Arc<Inner>`. Note that we should be guaranteed that + // the refcount is available to us due to the `mem::forget` in + // `schedule_write` above. + let me = unsafe { Arc::from_raw(Inner::ptr_from_event_overlapped(status.overlapped())) }; + + let io = me.io.lock().unwrap(); + + // Make sure the I/O handle is still registered with the selector + if io.token.is_some() { + // This method is also called during `Selector::drop` to perform + // cleanup. In this case, `events` is `None` and we don't need to track + // the event. + if let Some(events) = events { + let mut ev = Event::from_completion_status(&status); + // Reverse the `.data` alteration done in `schedule_event`. This + // alteration was done so the selector recognized the event as one from + // a named pipe. + ev.data >>= 1; + events.push(ev); + } + } +} + impl Io { fn check_association(&self, registry: &Registry, required: bool) -> io::Result<()> { match self.cp { @@ -938,28 +972,53 @@ impl Io { } } - fn notify_readable(&self, events: Option<&mut Vec<Event>>) { + fn notify_readable(&self, me: &Arc<Inner>, events: Option<&mut Vec<Event>>) { if let Some(token) = self.token { let mut ev = Event::new(token); ev.set_readable(); if let Some(events) = events { events.push(ev); } else { - let _ = self.cp.as_ref().unwrap().post(ev.to_completion_status()); + self.schedule_event(me, ev); } } } - fn notify_writable(&self, events: Option<&mut Vec<Event>>) { + fn notify_writable(&self, me: &Arc<Inner>, events: Option<&mut Vec<Event>>) { if let Some(token) = self.token { let mut ev = Event::new(token); ev.set_writable(); if let Some(events) = events { events.push(ev); } else { - let _ = self.cp.as_ref().unwrap().post(ev.to_completion_status()); + self.schedule_event(me, ev); + } + } + } + + fn schedule_event(&self, me: &Arc<Inner>, mut event: Event) { + // Alter the token so that the selector will identify the IOCP event as + // one for a named pipe. This will be reversed in `event_done` + // + // `data` for named pipes is an auto-incrementing counter. Because + // `data` is `u64` we do not risk losing the most-significant bit + // (unless a user creates 2^62 named pipes during the lifetime of the + // process). + event.data <<= 1; + event.data += 1; + + let completion_status = + event.to_completion_status_with_overlapped(me.event.as_ptr() as *mut _); + + match self.cp.as_ref().unwrap().post(completion_status) { + Ok(_) => { + // Increase the ref count of `Inner` for the completion event. + mem::forget(me.clone()); + } + Err(_) => { + // Nothing to do here } } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-r8w9-5wcg-vfj7ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-27308ghsaADVISORY
- github.com/tokio-rs/mio/commit/90d4fe00df870acd3d38f3dc4face9aacab8fbb9ghsax_refsource_MISCWEB
- github.com/tokio-rs/mio/pull/1760ghsax_refsource_MISCWEB
- github.com/tokio-rs/mio/security/advisories/GHSA-r8w9-5wcg-vfj7ghsax_refsource_CONFIRMWEB
- github.com/tokio-rs/tokio/issues/6369ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.