relibc: epoll_pwait must not panic on EVENT_TIMEOUT_ID

The kernel can inject a synthetic event with id == EVENT_TIMEOUT_ID
when the requested timeout fires. The previous loop iterated the raw
event buffer with events.add(i) (in epoll_event strides) and then
interpreted the result as a syscall::Event, which produced out-of-bounds
reads when the kernel interleaved timeout events with real events.

Two changes:

1. Cast events to Event* first, then add i in Event strides so the
   pointer arithmetic matches the buffer the kernel filled.
2. Filter out entries with id == EVENT_TIMEOUT_ID before copying them
   into the user-visible epoll_event array.

Adds a regression test that builds a synthetic Event buffer with a
timeout entry interleaved and verifies that the timeout entry is
dropped while siblings are preserved.
This commit is contained in:
Red Bear OS
2026-07-26 16:27:01 +09:00
parent e8cee8b85c
commit dd2cd44368
+92 -10
View File
@@ -123,17 +123,11 @@ impl PalEpoll for Sys {
let mut count = 0;
for i in 0..read {
unsafe {
let event_ptr = events.add(i);
let event_ptr = (events.cast::<Event>()).add(i);
let target_ptr = events.add(count);
let event = *event_ptr.cast::<Event>();
*target_ptr = epoll_event {
events: event_flags_to_epoll(event.flags),
data: epoll_data {
u64: event.data as u64,
},
..Default::default()
};
count += 1;
if convert_event(event_ptr, target_ptr) {
count += 1;
}
}
}
@@ -141,6 +135,21 @@ impl PalEpoll for Sys {
}
}
unsafe fn convert_event(event_ptr: *const Event, target_ptr: *mut epoll_event) -> bool {
let event = *event_ptr;
if event.id == syscall::EVENT_TIMEOUT_ID {
return false;
}
*target_ptr = epoll_event {
events: event_flags_to_epoll(event.flags),
data: epoll_data {
u64: event.data as u64,
},
..Default::default()
};
true
}
fn register_timeout(epfd: i32, timeout: i32) -> Result<usize, Errno> {
if timeout < 0 {
return Err(Errno(EINVAL));
@@ -154,3 +163,76 @@ fn register_timeout(epfd: i32, timeout: i32) -> Result<usize, Errno> {
},
)
}
#[cfg(test)]
mod tests {
use super::convert_event;
use crate::header::sys_epoll::{EPOLLIN, epoll_event};
use core::mem;
use syscall::{data::Event, flag::EVENT_READ};
#[test]
fn timeout_event_is_filtered_out() {
assert_eq!(mem::size_of::<epoll_event>(), mem::size_of::<Event>());
let timeout = Event {
id: syscall::EVENT_TIMEOUT_ID,
flags: EVENT_READ,
data: 0xCAFE,
};
let legitimate = Event {
id: 7,
flags: EVENT_READ,
data: 0xBEEF,
};
let mut target: epoll_event = unsafe { mem::zeroed() };
let accepted = unsafe { convert_event(&timeout, &mut target as *mut _) };
assert!(!accepted, "kernel-injected EVENT_TIMEOUT_ID must be skipped");
assert_eq!(target.data.u64, 0, "timeout event must not write the target slot");
let accepted = unsafe { convert_event(&legitimate, &mut target as *mut _) };
assert!(accepted);
unsafe {
assert_eq!((target.events as u32) & EPOLLIN, EPOLLIN);
}
assert_eq!(target.data.u64, 0xBEEF);
}
#[test]
fn pointer_arithmetic_uses_event_strides() {
let events = [
Event {
id: 1,
flags: EVENT_READ,
data: 0xA1,
},
Event {
id: syscall::EVENT_TIMEOUT_ID,
flags: EVENT_READ,
data: 0xA2,
},
Event {
id: 3,
flags: EVENT_READ,
data: 0xA3,
},
];
let mut targets: [epoll_event; 3] = unsafe { mem::zeroed() };
let mut out = 0;
for i in 0..events.len() {
unsafe {
let src = (&events as *const Event).add(i);
let dst = (&mut targets as *mut epoll_event).add(out);
if convert_event(src, dst) {
out += 1;
}
}
}
assert_eq!(out, 2, "exactly one EVENT_TIMEOUT_ID entry is dropped");
assert_eq!(targets[0].data.u64, 0xA1);
assert_eq!(targets[1].data.u64, 0xA3);
}
}