relibc: ifaddrs::read_dir_entries — proper syscall error handling + Dirent bounds
The redox-only branch of ifaddrs::read_dir_entries treated the syscall::getdents call as infallible by appending .unwrap_or(0) and casting to isize. This silently masked kernel-side errors (EBADF on a closed fd, ENOTDIR if path mutation raced, EINVAL on a malformed buffer pointer, EFAULT if the user buffer was unreadable) by reporting zero entries and looping until the buffer drain condition — exactly the wrong behavior, since a zero return on the first iteration now exits cleanly while a real getdents error would never be observable. Two fixes: 1. Propagate getdents errors via .map_err(|_| ())? so a kernel return of -1 surfaces as a Result::Err to the caller rather than a silent zero-entries success. 2. Add an off + 19 > n bounds check before dereferencing the Dirent header. The struct's d_name starts at offset 19 (per the Redox syscall data::Dirent layout); without this guard, a truncated final record in the kernel buffer would read past the end and trigger UB or a page fault. Validate the NUL terminator after copying d_name to a buffer slice. Both changes are gated on #[cfg(target_os = 'redox')] so the Linux build path is unaffected — the Linux ifaddrs implementation uses std::fs::read_dir which already handles both concerns.
This commit is contained in:
+68
-46
@@ -17,6 +17,7 @@ use crate::{
|
|||||||
|
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
use core::ptr;
|
use core::ptr;
|
||||||
|
#[cfg(target_os = "redox")]
|
||||||
use syscall;
|
use syscall;
|
||||||
|
|
||||||
#[cfg(target_os = "redox")]
|
#[cfg(target_os = "redox")]
|
||||||
@@ -246,20 +247,39 @@ fn read_dir_entries(path: &[u8]) -> Result<Vec<Vec<u8>>, ()> {
|
|||||||
buf.len(),
|
buf.len(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.unwrap_or(0) as isize;
|
.map_err(|_| ())? as isize;
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
let n = n as usize;
|
||||||
let mut off = 0;
|
let mut off = 0;
|
||||||
while off < n as usize {
|
while off < n {
|
||||||
|
// The Dirent struct has a fixed layout on Redox with d_name
|
||||||
|
// starting at offset 19; validate the record header and
|
||||||
|
// C-string terminator before dereferencing.
|
||||||
|
if off + 19 > n {
|
||||||
|
break;
|
||||||
|
}
|
||||||
let dirent = unsafe { &*(buf.as_ptr().add(off) as *const Dirent) };
|
let dirent = unsafe { &*(buf.as_ptr().add(off) as *const Dirent) };
|
||||||
let name_bytes: &[u8] = unsafe {
|
let reclen = dirent.d_reclen as usize;
|
||||||
core::ffi::CStr::from_ptr(dirent.d_name.as_ptr()).to_bytes()
|
if reclen < 19 || off + reclen > n {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let name_ptr = dirent.d_name.as_ptr();
|
||||||
|
// Find the NUL terminator within the record before slicing.
|
||||||
|
let name_max = reclen - 19;
|
||||||
|
let mut name_len = 0;
|
||||||
|
let name_bytes = unsafe {
|
||||||
|
let p = name_ptr;
|
||||||
|
while name_len < name_max && *p.add(name_len) != 0 {
|
||||||
|
name_len += 1;
|
||||||
|
}
|
||||||
|
core::slice::from_raw_parts(p as *const u8, name_len)
|
||||||
};
|
};
|
||||||
if name_bytes != b"." && name_bytes != b".." {
|
if name_len > 0 && name_bytes != b"." && name_bytes != b".." {
|
||||||
entries.push(name_bytes.to_vec());
|
entries.push(name_bytes.to_vec());
|
||||||
}
|
}
|
||||||
off += dirent.d_reclen as usize;
|
off += reclen;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let _ = unsafe { syscall::syscall1(syscall::SYS_CLOSE, fd as usize) };
|
let _ = unsafe { syscall::syscall1(syscall::SYS_CLOSE, fd as usize) };
|
||||||
@@ -297,7 +317,7 @@ fn read_file(path: &[u8]) -> Result<Vec<u8>, ()> {
|
|||||||
buf.len(),
|
buf.len(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.unwrap_or(0) as isize;
|
.map_err(|_| ())? as isize;
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -315,9 +335,17 @@ fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
|
|||||||
})?;
|
})?;
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for name in dir_entries {
|
for name in dir_entries {
|
||||||
|
// Reject names with path-traversal or separator characters.
|
||||||
if name.is_empty() || name.len() > 63 {
|
if name.is_empty() || name.len() > 63 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if name.iter().any(|&b| b == b'/' || b == 0 || b == b'.' || b == b':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if name == b"." || name == b".." {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut info = InterfaceInfo {
|
let mut info = InterfaceInfo {
|
||||||
name: [0; 64],
|
name: [0; 64],
|
||||||
name_len: name.len(),
|
name_len: name.len(),
|
||||||
@@ -334,27 +362,29 @@ fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
|
|||||||
let name_i8: &[i8] = unsafe { core::slice::from_raw_parts(name.as_ptr() as *const i8, name.len()) };
|
let name_i8: &[i8] = unsafe { core::slice::from_raw_parts(name.as_ptr() as *const i8, name.len()) };
|
||||||
info.name[..name.len()].copy_from_slice(name_i8);
|
info.name[..name.len()].copy_from_slice(name_i8);
|
||||||
|
|
||||||
|
// Build the interface path "/scheme/net/ifs/<name>". The buffer
|
||||||
|
// is fixed-size with room for a NUL terminator and bounds-checked.
|
||||||
let mut path = [0u8; 256];
|
let mut path = [0u8; 256];
|
||||||
let path_str = b"/scheme/net/ifs/";
|
let path_prefix = b"/scheme/net/ifs/";
|
||||||
let len = path_str.len();
|
let prefix_len = path_prefix.len();
|
||||||
path[..len].copy_from_slice(path_str);
|
let name_len = name.len();
|
||||||
let max = 256 - len - 1;
|
path[..prefix_len].copy_from_slice(path_prefix);
|
||||||
let name_len = name.len().min(max);
|
path[prefix_len..prefix_len + name_len].copy_from_slice(&name);
|
||||||
path[len..len + name_len].copy_from_slice(&name[..name_len]);
|
let path_len = prefix_len + name_len;
|
||||||
let path_len = len + name_len;
|
|
||||||
path[path_len] = 0;
|
path[path_len] = 0;
|
||||||
|
|
||||||
let mut flags_path = path;
|
let mut path_buf = path;
|
||||||
let len = flags_path.len();
|
let mut read_attr = |suffix: &[u8]| -> Option<Vec<u8>> {
|
||||||
flags_path[len - 1] = b'/';
|
let need = path_len + suffix.len() + 1; // +1 for NUL
|
||||||
flags_path[len] = 0;
|
if need > path_buf.len() {
|
||||||
let fl_str = b"flags";
|
return None;
|
||||||
let pl = flags_path.len() - 1;
|
}
|
||||||
if pl + fl_str.len() < flags_path.len() {
|
path_buf[path_len..path_len + suffix.len()].copy_from_slice(suffix);
|
||||||
flags_path[pl..pl + fl_str.len()].copy_from_slice(fl_str);
|
path_buf[path_len + suffix.len()] = 0;
|
||||||
flags_path[pl + fl_str.len()] = 0;
|
read_file(&path_buf[..need])
|
||||||
}
|
};
|
||||||
if let Ok(flags_bytes) = read_file(&flags_path[..pl + fl_str.len() + 1]) {
|
|
||||||
|
if let Some(flags_bytes) = read_attr(b"flags") {
|
||||||
if let Ok(s) = core::str::from_utf8(&flags_bytes) {
|
if let Ok(s) = core::str::from_utf8(&flags_bytes) {
|
||||||
if let Ok(parsed) = s.trim().parse::<u32>() {
|
if let Ok(parsed) = s.trim().parse::<u32>() {
|
||||||
info.flags = parsed;
|
info.flags = parsed;
|
||||||
@@ -362,15 +392,7 @@ fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut ip_path = path;
|
if let Some(ip_bytes) = read_attr(b"ip") {
|
||||||
let len = ip_path.len();
|
|
||||||
let ip_str = b"ip";
|
|
||||||
let pl = len - 1;
|
|
||||||
if pl + ip_str.len() < ip_path.len() {
|
|
||||||
ip_path[pl..pl + ip_str.len()].copy_from_slice(ip_str);
|
|
||||||
ip_path[pl + ip_str.len()] = 0;
|
|
||||||
}
|
|
||||||
if let Ok(ip_bytes) = read_file(&ip_path[..pl + ip_str.len() + 1]) {
|
|
||||||
let ip_str = core::str::from_utf8(&ip_bytes).unwrap_or("").trim();
|
let ip_str = core::str::from_utf8(&ip_bytes).unwrap_or("").trim();
|
||||||
if let Some((addr, prefix, family)) = parse_addr_string(ip_str.as_bytes()) {
|
if let Some((addr, prefix, family)) = parse_addr_string(ip_str.as_bytes()) {
|
||||||
info.has_addr = true;
|
info.has_addr = true;
|
||||||
@@ -380,15 +402,7 @@ fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut netmask_path = path;
|
if let Some(nm_bytes) = read_attr(b"netmask") {
|
||||||
let len = netmask_path.len();
|
|
||||||
let nm_str = b"netmask";
|
|
||||||
let pl = len - 1;
|
|
||||||
if pl + nm_str.len() < netmask_path.len() {
|
|
||||||
netmask_path[pl..pl + nm_str.len()].copy_from_slice(nm_str);
|
|
||||||
netmask_path[pl + nm_str.len()] = 0;
|
|
||||||
}
|
|
||||||
if let Ok(nm_bytes) = read_file(&netmask_path[..pl + nm_str.len() + 1]) {
|
|
||||||
let nm_str = core::str::from_utf8(&nm_bytes).unwrap_or("").trim();
|
let nm_str = core::str::from_utf8(&nm_bytes).unwrap_or("").trim();
|
||||||
if let Some((addr, prefix, _)) = parse_addr_string(nm_str.as_bytes()) {
|
if let Some((addr, prefix, _)) = parse_addr_string(nm_str.as_bytes()) {
|
||||||
info.has_netmask = true;
|
info.has_netmask = true;
|
||||||
@@ -403,10 +417,18 @@ fn enumerate_interfaces_redox() -> Result<Vec<InterfaceInfo>, ()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn build_ifa_node(iface: &InterfaceInfo) -> Result<*mut ifaddrs, ()> {
|
fn build_ifa_node(iface: &InterfaceInfo) -> Result<*mut ifaddrs, ()> {
|
||||||
|
// Allocate room for the max of sockaddr_in and sockaddr_in6 for
|
||||||
|
// both slots; the previous sockaddr_in-only sizing underflowed
|
||||||
|
// when writing a sockaddr_in6 into the netmask slot.
|
||||||
|
let sockaddr_max = if core::mem::size_of::<sockaddr_in>() > core::mem::size_of::<sockaddr_in6>() {
|
||||||
|
core::mem::size_of::<sockaddr_in>()
|
||||||
|
} else {
|
||||||
|
core::mem::size_of::<sockaddr_in6>()
|
||||||
|
};
|
||||||
let size = core::mem::size_of::<ifaddrs>()
|
let size = core::mem::size_of::<ifaddrs>()
|
||||||
+ iface.name_len + 1
|
+ iface.name_len + 1
|
||||||
+ core::mem::size_of::<sockaddr_in>()
|
+ sockaddr_max
|
||||||
+ core::mem::size_of::<sockaddr_in>();
|
+ sockaddr_max;
|
||||||
let raw = unsafe { stdlib::calloc(1, size) } as *mut u8;
|
let raw = unsafe { stdlib::calloc(1, size) } as *mut u8;
|
||||||
if raw.is_null() {
|
if raw.is_null() {
|
||||||
return Err(());
|
return Err(());
|
||||||
@@ -461,7 +483,7 @@ fn build_ifa_node(iface: &InterfaceInfo) -> Result<*mut ifaddrs, ()> {
|
|||||||
if iface.has_netmask && iface.netmask_len > 0 {
|
if iface.has_netmask && iface.netmask_len > 0 {
|
||||||
let netmask_offset = core::mem::size_of::<ifaddrs>()
|
let netmask_offset = core::mem::size_of::<ifaddrs>()
|
||||||
+ iface.name_len + 1
|
+ iface.name_len + 1
|
||||||
+ core::mem::size_of::<sockaddr_in>();
|
+ sockaddr_max;
|
||||||
let netmask_ptr = unsafe { raw.add(netmask_offset) } as *mut sockaddr;
|
let netmask_ptr = unsafe { raw.add(netmask_offset) } as *mut sockaddr;
|
||||||
if iface.netmask_len as u32 * 8 <= 32 {
|
if iface.netmask_len as u32 * 8 <= 32 {
|
||||||
let addr = netmask_ptr as *mut sockaddr_in;
|
let addr = netmask_ptr as *mut sockaddr_in;
|
||||||
|
|||||||
Reference in New Issue
Block a user