Skip to content

Commit

Permalink
Fix realpath and strerror_r to handle uninitialized buffers. (#143)
Browse files Browse the repository at this point in the history
* Fix `realpath` and `strerror_r` to handle uninitialized buffers.

Use `copy_nonoverlapping` instead of `copy_from_slice` in a few places
to avoid creating Rust slices from buffers which may be uninitialized,
which has UB.

* Convert read-style functions to use copy_nonoverlapping.
  • Loading branch information
sunfishcode authored Nov 8, 2022
1 parent 19be674 commit c2aac4c
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 106 deletions.
162 changes: 81 additions & 81 deletions c-scape/src/error_str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,91 +7,91 @@ pub(crate) const fn error_str(e: Errno) -> Option<&'static str> {
// Recognize errors documented in POSIX and use the documented strings.
// <https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html>
Some(match e {
Errno::TOOBIG => "Argument list too long.",
Errno::ACCESS => "Permission denied.",
Errno::ADDRINUSE => "Address in use.",
Errno::ADDRNOTAVAIL => "Address not available.",
Errno::AFNOSUPPORT => "Address family not supported.",
Errno::AGAIN => "Resource unavailable, try again.",
Errno::ALREADY => "Connection already in progress.",
Errno::BADF => "Bad file descriptor.",
Errno::BADMSG => "Bad message.",
Errno::BUSY => "Device or resource busy.",
Errno::CANCELED => "Operation canceled.",
Errno::CHILD => "No child processes.",
Errno::CONNABORTED => "Connection aborted.",
Errno::CONNREFUSED => "Connection refused.",
Errno::CONNRESET => "Connection reset.",
Errno::DEADLK => "Resource deadlock would occur.",
Errno::DESTADDRREQ => "Destination address required.",
Errno::DOM => "Mathematics argument out of domain of function.",
Errno::DQUOT => "Reserved.",
Errno::EXIST => "File exists.",
Errno::FAULT => "Bad address.",
Errno::FBIG => "File too large.",
Errno::HOSTUNREACH => "Host is unreachable.",
Errno::IDRM => "Identifier removed.",
Errno::ILSEQ => "Illegal byte sequence.",
Errno::INPROGRESS => "Operation in progress.",
Errno::INTR => "Interrupted function.",
Errno::INVAL => "Invalid argument.",
Errno::IO => "I/O error.",
Errno::ISCONN => "Socket is connected.",
Errno::ISDIR => "Is a directory.",
Errno::LOOP => "Too many levels of symbolic links.",
Errno::MFILE => "File descriptor value too large.",
Errno::MLINK => "Too many links.",
Errno::MSGSIZE => "Message too large.",
Errno::MULTIHOP => "Reserved.",
Errno::NAMETOOLONG => "Filename too long.",
Errno::NETDOWN => "Network is down.",
Errno::NETRESET => "Connection aborted by network.",
Errno::NETUNREACH => "Network unreachable.",
Errno::NFILE => "Too many files open in system.",
Errno::NOBUFS => "No buffer space available.",
Errno::TOOBIG => "Argument list too long",
Errno::ACCESS => "Permission denied",
Errno::ADDRINUSE => "Address in use",
Errno::ADDRNOTAVAIL => "Address not available",
Errno::AFNOSUPPORT => "Address family not supported",
Errno::AGAIN => "Resource unavailable, try again",
Errno::ALREADY => "Connection already in progress",
Errno::BADF => "Bad file descriptor",
Errno::BADMSG => "Bad message",
Errno::BUSY => "Device or resource busy",
Errno::CANCELED => "Operation canceled",
Errno::CHILD => "No child processes",
Errno::CONNABORTED => "Connection aborted",
Errno::CONNREFUSED => "Connection refused",
Errno::CONNRESET => "Connection reset",
Errno::DEADLK => "Resource deadlock would occur",
Errno::DESTADDRREQ => "Destination address required",
Errno::DOM => "Mathematics argument out of domain of function",
Errno::DQUOT => "Reserved",
Errno::EXIST => "File exists",
Errno::FAULT => "Bad address",
Errno::FBIG => "File too large",
Errno::HOSTUNREACH => "Host is unreachable",
Errno::IDRM => "Identifier removed",
Errno::ILSEQ => "Illegal byte sequence",
Errno::INPROGRESS => "Operation in progress",
Errno::INTR => "Interrupted function",
Errno::INVAL => "Invalid argument",
Errno::IO => "I/O error",
Errno::ISCONN => "Socket is connected",
Errno::ISDIR => "Is a directory",
Errno::LOOP => "Too many levels of symbolic links",
Errno::MFILE => "File descriptor value too large",
Errno::MLINK => "Too many links",
Errno::MSGSIZE => "Message too large",
Errno::MULTIHOP => "Reserved",
Errno::NAMETOOLONG => "Filename too long",
Errno::NETDOWN => "Network is down",
Errno::NETRESET => "Connection aborted by network",
Errno::NETUNREACH => "Network unreachable",
Errno::NFILE => "Too many files open in system",
Errno::NOBUFS => "No buffer space available",
#[cfg(not(target_os = "wasi"))]
Errno::NODATA => "No message is available on the STREAM head read queue.",
Errno::NODEV => "No such device.",
Errno::NOENT => "No such file or directory.",
Errno::NOEXEC => "Executable file format error.",
Errno::NOLCK => "No locks available.",
Errno::NOLINK => "Reserved.",
Errno::NOMEM => "Not enough space.",
Errno::NOMSG => "No message of the desired type.",
Errno::NOPROTOOPT => "Protocol not available.",
Errno::NOSPC => "No space left on device.",
Errno::NODATA => "No message is available on the STREAM head read queue",
Errno::NODEV => "No such device",
Errno::NOENT => "No such file or directory",
Errno::NOEXEC => "Executable file format error",
Errno::NOLCK => "No locks available",
Errno::NOLINK => "Reserved",
Errno::NOMEM => "Not enough space",
Errno::NOMSG => "No message of the desired type",
Errno::NOPROTOOPT => "Protocol not available",
Errno::NOSPC => "No space left on device",
#[cfg(not(target_os = "wasi"))]
Errno::NOSR => "No STREAM resources.",
Errno::NOSR => "No STREAM resources",
#[cfg(not(target_os = "wasi"))]
Errno::NOSTR => "Not a STREAM.",
Errno::NOSYS => "Functionality not supported.",
Errno::NOTCONN => "The socket is not connected.",
Errno::NOTDIR => "Not a directory or a symbolic link to a directory.",
Errno::NOTEMPTY => "Directory not empty.",
Errno::NOTRECOVERABLE => "State not recoverable.",
Errno::NOTSOCK => "Not a socket.",
Errno::NOTSUP => "Not supported.",
Errno::NOTTY => "Inappropriate I/O control operation.",
Errno::NXIO => "No such device or address.",
//Errno::OPNOTSUPP => "Operation not supported on socket.", // same as `NOTSUP`
Errno::OVERFLOW => "Value too large to be stored in data type.",
Errno::OWNERDEAD => "Previous owner died.",
Errno::PERM => "Operation not permitted.",
Errno::PIPE => "Broken pipe.",
Errno::PROTO => "Protocol error.",
Errno::PROTONOSUPPORT => "Protocol not supported.",
Errno::PROTOTYPE => "Protocol wrong type for socket.",
Errno::RANGE => "Result too large.",
Errno::ROFS => "Read-only file system.",
Errno::SPIPE => "Invalid seek.",
Errno::SRCH => "No such process.",
Errno::STALE => "Reserved.",
Errno::NOSTR => "Not a STREAM",
Errno::NOSYS => "Functionality not supported",
Errno::NOTCONN => "The socket is not connected",
Errno::NOTDIR => "Not a directory or a symbolic link to a directory",
Errno::NOTEMPTY => "Directory not empty",
Errno::NOTRECOVERABLE => "State not recoverable",
Errno::NOTSOCK => "Not a socket",
Errno::NOTSUP => "Not supported",
Errno::NOTTY => "Inappropriate I/O control operation",
Errno::NXIO => "No such device or address",
//Errno::OPNOTSUPP => "Operation not supported on socket", // same as `NOTSUP`
Errno::OVERFLOW => "Value too large to be stored in data type",
Errno::OWNERDEAD => "Previous owner died",
Errno::PERM => "Operation not permitted",
Errno::PIPE => "Broken pipe",
Errno::PROTO => "Protocol error",
Errno::PROTONOSUPPORT => "Protocol not supported",
Errno::PROTOTYPE => "Protocol wrong type for socket",
Errno::RANGE => "Result too large",
Errno::ROFS => "Read-only file system",
Errno::SPIPE => "Invalid seek",
Errno::SRCH => "No such process",
Errno::STALE => "Reserved",
#[cfg(not(target_os = "wasi"))]
Errno::TIME => "Stream ioctl() timeout.",
Errno::TIMEDOUT => "Connection timed out.",
Errno::TXTBSY => "Text file busy.",
//Errno::WOULDBLOCK => "Operation would block.", // same as `AGAIN`
Errno::XDEV => "Cross-device link.",
Errno::TIME => "Stream ioctl() timeout",
Errno::TIMEDOUT => "Connection timed out",
Errno::TXTBSY => "Text file busy",
//Errno::WOULDBLOCK => "Operation would block", // same as `AGAIN`
Errno::XDEV => "Cross-device link",
#[cfg(target_os = "wasi")]
Errno::NOTCAPABLE => "Capabilities insufficient",
_ => return None,
Expand Down
3 changes: 2 additions & 1 deletion c-scape/src/fs/readlink.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use alloc::vec::Vec;
use core::ffi::CStr;
use core::ptr::copy_nonoverlapping;
use rustix::fd::BorrowedFd;

use libc::{c_char, c_int};
Expand Down Expand Up @@ -32,6 +33,6 @@ unsafe extern "C" fn readlinkat(
};
let bytes = path.as_bytes();
let min = core::cmp::min(bytes.len(), bufsiz);
core::slice::from_raw_parts_mut(buf.cast(), min).copy_from_slice(bytes);
copy_nonoverlapping(bytes.as_ptr(), buf.cast(), min);
min as isize
}
4 changes: 2 additions & 2 deletions c-scape/src/fs/realpath.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use core::ffi::CStr;

use core::ptr::null_mut;
use core::ptr::{copy_nonoverlapping, null_mut};
use errno::{set_errno, Errno};
use libc::{c_char, c_void, malloc, memcpy};

Expand All @@ -21,7 +21,7 @@ unsafe extern "C" fn realpath(path: *const c_char, resolved_path: *mut c_char) -
set_errno(Errno(libc::ENOMEM));
return null_mut();
}
core::slice::from_raw_parts_mut(ptr, len).copy_from_slice(&buf[..len]);
copy_nonoverlapping(buf.as_ptr().cast(), ptr, len);
*ptr.add(len) = b'\0';
ptr.cast::<c_char>()
} else {
Expand Down
27 changes: 20 additions & 7 deletions c-scape/src/io/read.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use rustix::fd::BorrowedFd;
use rustix::io::IoSliceMut;

use alloc::vec;
use core::ptr::copy_nonoverlapping;
use core::slice;
use errno::{set_errno, Errno};
use libc::{c_int, c_void, iovec, off64_t, off_t};
Expand All @@ -11,11 +13,15 @@ use crate::convert_res;
unsafe extern "C" fn read(fd: c_int, ptr: *mut c_void, len: usize) -> isize {
libc!(libc::read(fd, ptr, len));

match convert_res(rustix::io::read(
BorrowedFd::borrow_raw(fd),
slice::from_raw_parts_mut(ptr.cast::<u8>(), len),
)) {
Some(nread) => nread as isize,
// `slice::from_raw_parts_mut` assumes that the memory is initialized,
// which our C API here doesn't guarantee. Since rustix currently requires
// a slice, use a temporary copy.
let mut tmp = vec![0u8; len];
match convert_res(rustix::io::read(BorrowedFd::borrow_raw(fd), &mut tmp)) {
Some(nread) => {
copy_nonoverlapping(tmp.as_ptr(), ptr.cast::<u8>(), len);
nread as isize
}
None => -1,
}
}
Expand Down Expand Up @@ -53,12 +59,19 @@ unsafe extern "C" fn pread(fd: c_int, ptr: *mut c_void, len: usize, offset: off_
unsafe extern "C" fn pread64(fd: c_int, ptr: *mut c_void, len: usize, offset: off64_t) -> isize {
libc!(libc::pread64(fd, ptr, len, offset));

// `slice::from_raw_parts_mut` assumes that the memory is initialized,
// which our C API here doesn't guarantee. Since rustix currently requires
// a slice, use a temporary copy.
let mut tmp = vec![0u8; len];
match convert_res(rustix::io::pread(
BorrowedFd::borrow_raw(fd),
slice::from_raw_parts_mut(ptr.cast::<u8>(), len),
&mut tmp,
offset as u64,
)) {
Some(nread) => nread as isize,
Some(nread) => {
copy_nonoverlapping(tmp.as_ptr(), ptr.cast::<u8>(), len);
nread as isize
}
None => -1,
}
}
Expand Down
30 changes: 19 additions & 11 deletions c-scape/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ use core::convert::TryInto;
use core::ffi::c_void;
#[cfg(not(target_os = "wasi"))]
use core::mem::{size_of, zeroed};
use core::ptr::{self, null, null_mut};
use core::slice;
use core::ptr::{self, copy_nonoverlapping, null, null_mut};
use errno::{set_errno, Errno};
use error_str::error_str;
#[cfg(target_vendor = "mustang")]
Expand Down Expand Up @@ -133,14 +132,18 @@ unsafe extern "C" fn strerror(errnum: c_int) -> *mut c_char {
unsafe extern "C" fn __xpg_strerror_r(errnum: c_int, buf: *mut c_char, buflen: usize) -> c_int {
libc!(libc::strerror_r(errnum, buf, buflen));

if buflen == 0 {
return libc::ERANGE;
}

let message = match error_str(rustix::io::Errno::from_raw_os_error(errnum)) {
Some(s) => s.to_owned(),
None => format!("Unknown error {}", errnum),
};
let min = core::cmp::min(buflen, message.len());
let out = slice::from_raw_parts_mut(buf.cast::<u8>(), min);
out.copy_from_slice(message.as_bytes());
out[out.len() - 1] = b'\0';

let min = core::cmp::min(buflen - 1, message.len());
copy_nonoverlapping(message.as_ptr().cast(), buf, min);
buf.add(min).write(b'\0' as libc::c_char);
0
}

Expand All @@ -156,11 +159,16 @@ unsafe extern "C" fn getrandom(buf: *mut c_void, buflen: usize, flags: u32) -> i
}

let flags = rustix::rand::GetRandomFlags::from_bits(flags & !0x4).unwrap();
match convert_res(rustix::rand::getrandom(
slice::from_raw_parts_mut(buf.cast::<u8>(), buflen),
flags,
)) {
Some(num) => num as isize,

// `slice::from_raw_parts_mut` assumes that the memory is initialized,
// which our C API here doesn't guarantee. Since rustix currently requires
// a slice, use a temporary copy.
let mut tmp = alloc::vec![0u8; buflen];
match convert_res(rustix::rand::getrandom(&mut tmp, flags)) {
Some(num) => {
core::ptr::copy_nonoverlapping(tmp.as_ptr(), buf.cast::<u8>(), buflen);
num as isize
}
None => -1,
}
}
Expand Down
23 changes: 19 additions & 4 deletions c-scape/src/net/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ mod inet;

#[cfg(feature = "sync-resolve")]
use alloc::string::ToString;
use alloc::vec;
use core::convert::TryInto;
use core::ffi::{c_void, CStr};
#[cfg(not(target_os = "wasi"))]
use core::mem::{size_of, zeroed};
use core::ptr::null_mut;
use core::ptr::{copy_nonoverlapping, null_mut};
use core::slice;
use errno::{set_errno, Errno};
use libc::{c_char, c_int, c_uint};
Expand Down Expand Up @@ -590,12 +591,20 @@ unsafe extern "C" fn recv(fd: c_int, ptr: *mut c_void, len: usize, flags: c_int)
libc!(libc::recv(fd, ptr, len, flags));

let flags = RecvFlags::from_bits(flags as _).unwrap();

// `slice::from_raw_parts_mut` assumes that the memory is initialized,
// which our C API here doesn't guarantee. Since rustix currently requires
// a slice, use a temporary copy.
let mut tmp = vec![0u8; len];
match convert_res(rustix::net::recv(
BorrowedFd::borrow_raw(fd),
slice::from_raw_parts_mut(ptr.cast::<u8>(), len),
&mut tmp,
flags,
)) {
Some(nread) => nread as isize,
Some(nread) => {
copy_nonoverlapping(tmp.as_ptr(), ptr.cast::<u8>(), len);
nread as isize
}
None => -1,
}
}
Expand All @@ -614,12 +623,18 @@ unsafe extern "C" fn recvfrom(
libc!(libc::recvfrom(fd, buf, len, flags, from.cast(), from_len));

let flags = RecvFlags::from_bits(flags as _).unwrap();

// `slice::from_raw_parts_mut` assumes that the memory is initialized,
// which our C API here doesn't guarantee. Since rustix currently requires
// a slice, use a temporary copy.
let mut tmp = vec![0u8; len];
match convert_res(rustix::net::recvfrom(
BorrowedFd::borrow_raw(fd),
slice::from_raw_parts_mut(buf.cast::<u8>(), len),
&mut tmp,
flags,
)) {
Some((nread, addr)) => {
copy_nonoverlapping(tmp.as_ptr(), buf.cast::<u8>(), len);
if let Some(addr) = addr {
let encoded_len = addr.write(from);
*from_len = encoded_len.try_into().unwrap();
Expand Down

0 comments on commit c2aac4c

Please sign in to comment.