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:
+92
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user