CVE-2024-27308: Insufficient Information (NVD-CWE-noinfo)
value: 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.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
Published: 2024-03-06T20:15Z
Repository: https://github.com/tokio-rs/mio
Language: Rust
Files changed: src/sys/windows/event.rs, src/sys/windows/named_pipe.rs
Commit date: 2024-03-01 21:04:39 +0100
Commit message: named-pipes: fix receiving IOCP events after deregister

Backport of the following commits
 * 04c3bb6ef23d217ee7f8f68e3e6d0d9fcddd1520
 * dc3b3f63ba34e2189d9e57fd83b1d4ce5a52385e
 * 6fd5dd0d615a66a30a449d3f8eb1266dad96af62

Diff: diff --git a/src/sys/windows/event.rs b/src/sys/windows/event.rs
index 731bd60..66656d0 100644
--- a/src/sys/windows/event.rs
+++ b/src/sys/windows/event.rs
@@ -39,10 +39,18 @@ 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
     | afd::POLL_DISCONNECT
     | afd::POLL_ACCEPT
diff --git a/src/sys/windows/named_pipe.rs b/src/sys/windows/named_pipe.rs
index 23f85d1..eb35d37 100644
--- a/src/sys/windows/named_pipe.rs
+++ b/src/sys/windows/named_pipe.rs
@@ -82,10 +82,11 @@ struct Inner {
     // NOTE: careful modifying the order of these three fields, the `ptr_from_*`
     // methods depend on the layout!
     connect: Overlapped,
     read: Overlapped,
     write: Overlapped,
+    event: Overlapped,
     // END NOTE.
     handle: Handle,
     connecting: AtomicBool,
     io: Mutex<Io>,
     pool: Mutex<BufferPool>,
@@ -108,14 +109,20 @@ impl Inner {
         (ptr as *mut Overlapped).wrapping_sub(1) as *const 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,
     /// returning immediately after starting the overlapped operation.
     ///
@@ -476,10 +483,11 @@ impl FromRawHandle for NamedPipe {
                 handle: Handle::new(handle as HANDLE),
                 connect: Overlapped::new(connect_done),
                 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,
                     read: State::None,
                     write: State::None,
@@ -722,11 +730,11 @@ impl Inner {
 
             // If some other error happened, though, we're now readable to give
             // out the error.
             Err(e) => {
                 io.read = State::Err(e);
-                io.notify_readable(events);
+                io.notify_readable(me, events);
                 true
             }
         }
     }
 
@@ -785,21 +793,21 @@ impl Inner {
                 mem::forget(me.clone());
             }
             Ok(None) => (),
             Err(e) => {
                 io.write = State::Err(e);
-                io.notify_writable(events);
+                io.notify_writable(me, events);
             }
         }
     }
 
     fn post_register(me: &Arc<Inner>, mut events: Option<&mut Vec<Event>>) {
         let mut io = me.io.lock().unwrap();
         #[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);
             }
         }
     }
 
     fn get_buffer(&self) -> Vec<u8> {
@@ -875,11 +883,11 @@ 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>>) {
     let status = CompletionStatus::from_entry(status);
 
@@ -893,11 +901,11 @@ fn write_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) {
     let mut io = me.io.lock().unwrap();
     let (buf, pos) = match mem::replace(&mut io.write, State::None) {
         // `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),
         _ => unreachable!(),
     };
@@ -907,24 +915,50 @@ fn write_done(status: &OVERLAPPED_ENTRY, events: Option<&mut Vec<Event>>) {
             Ok(n) => {
                 debug_assert_eq!(status.bytes_transferred() as usize, n);
                 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 {
             Some(ref cp) if !registry.selector().same_port(cp) => Err(io::Error::new(
                 io::ErrorKind::AlreadyExists,
@@ -936,32 +970,57 @@ impl Io {
             )),
             _ => Ok(()),
         }
     }
 
-    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
             }
         }
     }
 }
 

Vulnerable code:         // `read` is after `connect: Overlapped` and `read: Overlapped`.
                io.notify_readable(events);
                io.notify_writable(events);
                io.notify_writable(events);
    io.notify_readable(events);
            io.notify_writable(events);
                    io.notify_writable(events);
                io.notify_writable(events);
    fn notify_readable(&self, events: Option<&mut Vec<Event>>) {
                let _ = self.cp.as_ref().unwrap().post(ev.to_completion_status());
    fn notify_writable(&self, events: Option<&mut Vec<Event>>) {
                let _ = self.cp.as_ref().unwrap().post(ev.to_completion_status());
Fixed code: 
    #[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)
    }
    event: Overlapped,
        // `write` is after `connect: Overlapped` and `read: Overlapped`.
    /// 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
    }

                event: Overlapped::new(event_done),
                io.notify_readable(me, events);
                io.notify_writable(me, events);
                io.notify_writable(me, events);
    io.notify_readable(&me, events);
            io.notify_writable(&me, events);
                    io.notify_writable(&me, 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);
        }
    }
}

    fn notify_readable(&self, me: &Arc<Inner>, events: Option<&mut Vec<Event>>) {
                self.schedule_event(me, ev);
    fn notify_writable(&self, me: &Arc<Inner>, events: Option<&mut Vec<Event>>) {
                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