feature: extended user pointer validation

This commit is contained in:
Mark Poliakov 2021-11-21 14:01:48 +02:00
parent 1820009dee
commit 7c809f3b11
6 changed files with 97 additions and 58 deletions

View File

@ -88,8 +88,9 @@ extern "C" fn __aa64_exc_sync_handler(exc: &mut ExceptionFrame) {
match err_code {
EC_DATA_ABORT_EL0 | EC_DATA_ABORT_ELX => {
let far = FAR_EL1.get() as usize;
let iss = esr & 0x1FFFFFF;
if far < mem::KERNEL_OFFSET && sched::is_ready() {
if iss & (1 << 6) != 0 && far < mem::KERNEL_OFFSET && sched::is_ready() {
let thread = Thread::current();
let proc = thread.owner().unwrap();

View File

@ -8,7 +8,7 @@ use libsys::{
ioctl::IoctlCmd
};
use core::mem::size_of;
use crate::syscall::arg::validate_user_ptr_struct;
use crate::syscall::arg;
#[derive(Debug)]
struct CharRingInner<const N: usize> {
@ -45,12 +45,12 @@ pub trait TtyDevice<const N: usize>: SerialDevice {
match cmd {
IoctlCmd::TtyGetAttributes => {
// TODO validate size
let res = validate_user_ptr_struct::<Termios>(ptr)?;
let res = arg::struct_mut::<Termios>(ptr)?;
*res = self.ring().config.lock().clone();
Ok(size_of::<Termios>())
},
IoctlCmd::TtySetAttributes => {
let src = validate_user_ptr_struct::<Termios>(ptr)?;
let src = arg::struct_ref::<Termios>(ptr)?;
*self.ring().config.lock() = src.clone();
Ok(size_of::<Termios>())
},

View File

@ -271,12 +271,17 @@ impl Space {
todo!();
// res.map(virt_addr, dst_phys, flags)?;
} else {
// TODO only apply CoW to writable pages
flags |= MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW;
l2_table[l2i].set_cow();
unsafe {
asm!("tlbi vaae1, {}", in(reg) virt_addr);
let writable = flags & MapAttributes::AP_BOTH_READONLY == MapAttributes::AP_BOTH_READWRITE;
if writable {
flags |= MapAttributes::AP_BOTH_READONLY | MapAttributes::EX_COW;
l2_table[l2i].set_cow();
unsafe {
asm!("tlbi vaae1, {}", in(reg) virt_addr);
}
}
res.map(virt_addr, dst_phys, flags)?;
}
}

View File

@ -1,6 +1,7 @@
//! System call argument ABI helpers
use crate::mem;
use core::alloc::Layout;
use core::mem::size_of;
use libsys::error::Errno;
@ -22,34 +23,62 @@ macro_rules! invalid_memory {
}
}
fn translate(virt: usize) -> Option<usize> {
#[inline(always)]
fn is_el0_accessible(virt: usize, write: bool) -> bool {
let mut res: usize;
unsafe {
asm!("at s1e1r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
}
if res & 1 == 0 {
Some(res & !(0xFFF | (0xFF << 56)))
} else {
None
if write {
asm!("at s1e0w, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
} else {
asm!("at s1e0r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
}
}
res & 1 == 0
}
/// Unwraps a slim structure pointer
pub fn validate_user_ptr_struct<'a, T>(base: usize) -> Result<&'a mut T, Errno> {
validate_user_ptr_struct_option(base).and_then(|e| e.ok_or(Errno::InvalidArgument))
pub fn struct_ref<'a, T>(base: usize) -> Result<&'a T, Errno> {
let layout = Layout::new::<T>();
if base % layout.align() != 0 {
invalid_memory!(
"Structure pointer is misaligned: base={:#x}, expected {:?}",
base,
layout
);
}
let bytes = buf_ref(base, layout.size())?;
Ok(unsafe { &*(bytes.as_ptr() as *const T) })
}
pub fn validate_user_ptr_struct_option<'a, T>(base: usize) -> Result<Option<&'a mut T>, Errno> {
pub fn struct_mut<'a, T>(base: usize) -> Result<&'a mut T, Errno> {
let layout = Layout::new::<T>();
if base % layout.align() != 0 {
invalid_memory!(
"Structure pointer is misaligned: base={:#x}, expected {:?}",
base,
layout
);
}
let bytes = buf_mut(base, layout.size())?;
Ok(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) })
}
pub fn option_struct_ref<'a, T>(base: usize) -> Result<Option<&'a T>, Errno> {
if base == 0 {
Ok(None)
} else {
let bytes = validate_user_ptr(base, size_of::<T>())?;
Ok(Some(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }))
struct_ref(base).map(Some)
}
}
/// Unwraps an user buffer reference
pub fn validate_user_ptr<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Errno> {
pub fn option_struct_mut<'a, T>(base: usize) -> Result<Option<&'a mut T>, Errno> {
if base == 0 {
Ok(None)
} else {
struct_mut(base).map(Some)
}
}
fn validate_ptr(base: usize, len: usize, writable: bool) -> Result<(), Errno> {
if base > mem::KERNEL_OFFSET || base + len > mem::KERNEL_OFFSET {
invalid_memory!(
"User region refers to kernel memory: base={:#x}, len={:#x}",
@ -59,9 +88,9 @@ pub fn validate_user_ptr<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Er
}
for i in (base / mem::PAGE_SIZE)..((base + len + mem::PAGE_SIZE - 1) / mem::PAGE_SIZE) {
if translate(i * mem::PAGE_SIZE).is_none() {
if !is_el0_accessible(i * mem::PAGE_SIZE, writable) {
invalid_memory!(
"User region refers to unmapped memory: base={:#x}, len={:#x} (page {:#x})",
"User region refers to inaccessible/unmapped memory: base={:#x}, len={:#x} (page {:#x})",
base,
len,
i * mem::PAGE_SIZE
@ -69,21 +98,38 @@ pub fn validate_user_ptr<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Er
}
}
Ok(())
}
pub fn buf_ref<'a>(base: usize, len: usize) -> Result<&'a [u8], Errno> {
validate_ptr(base, len, false)?;
Ok(unsafe { core::slice::from_raw_parts(base as *const u8, len) })
}
pub fn buf_mut<'a>(base: usize, len: usize) -> Result<&'a mut [u8], Errno> {
validate_ptr(base, len, true)?;
Ok(unsafe { core::slice::from_raw_parts_mut(base as *mut u8, len) })
}
/// Unwraps a nullable user buffer reference
pub fn validate_user_ptr_null<'a>(base: usize, len: usize) -> Result<Option<&'a mut [u8]>, Errno> {
pub fn option_buf_ref<'a>(base: usize, len: usize) -> Result<Option<&'a [u8]>, Errno> {
if base == 0 {
Ok(None)
} else {
validate_user_ptr(base, len).map(Some)
buf_ref(base, len).map(Some)
}
}
pub fn option_buf_mut<'a>(base: usize, len: usize) -> Result<Option<&'a mut [u8]>, Errno> {
if base == 0 {
Ok(None)
} else {
buf_mut(base, len).map(Some)
}
}
/// Unwraps user string argument
pub fn validate_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Errno> {
let bytes = validate_user_ptr(base, len)?;
pub fn str_ref<'a>(base: usize, len: usize) -> Result<&'a str, Errno> {
let bytes = buf_ref(base, len)?;
core::str::from_utf8(bytes).map_err(|_| {
warnln!(
"User string contains invalid UTF-8 characters: base={:#x}, len={:#x}",

View File

@ -19,7 +19,6 @@ use libsys::{
use vfs::VnodeRef;
pub mod arg;
pub use arg::*;
/// Creates a "fork" process from current one using its register frame.
/// See [Process::fork()].
@ -59,7 +58,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
let proc = Process::current();
let fd = FileDescriptor::from(args[0] as u32);
let mut io = proc.io.lock();
let buf = validate_user_ptr(args[1], args[2])?;
let buf = arg::buf_mut(args[1], args[2])?;
io.file(fd)?.borrow_mut().read(buf)
},
@ -67,13 +66,13 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
let proc = Process::current();
let fd = FileDescriptor::from(args[0] as u32);
let mut io = proc.io.lock();
let buf = validate_user_ptr(args[1], args[2])?;
let buf = arg::buf_ref(args[1], args[2])?;
io.file(fd)?.borrow_mut().write(buf)
},
SystemCall::Open => {
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
let path = validate_user_str(args[1], args[2])?;
let path = arg::str_ref(args[1], args[2])?;
let mode = FileMode::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?;
let opts = OpenFlags::from_bits(args[4] as u32).ok_or(Errno::InvalidArgument)?;
@ -99,8 +98,8 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
},
SystemCall::FileStatus => {
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
let filename = validate_user_str(args[1], args[2])?;
let buf = validate_user_ptr_struct::<Stat>(args[3])?;
let filename = arg::str_ref(args[1], args[2])?;
let buf = arg::struct_mut::<Stat>(args[3])?;
let flags = args[4] as u32;
let proc = Process::current();
@ -119,8 +118,8 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
node.ioctl(cmd, args[2], args[3])
},
SystemCall::Select => {
let rfds = validate_user_ptr_struct_option::<FdSet>(args[0])?;
let wfds = validate_user_ptr_struct_option::<FdSet>(args[1])?;
let rfds = arg::option_struct_mut::<FdSet>(args[0])?;
let wfds = arg::option_struct_mut::<FdSet>(args[1])?;
let timeout = if args[2] == 0 {
None
} else {
@ -131,7 +130,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
},
SystemCall::Access => {
let at_fd = FileDescriptor::from_i32(args[0] as i32)?;
let path = validate_user_str(args[1], args[2])?;
let path = arg::str_ref(args[1], args[2])?;
let mode = AccessMode::from_bits(args[3] as u32).ok_or(Errno::InvalidArgument)?;
let flags = args[4] as u32;
@ -156,14 +155,14 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
let node = {
let proc = Process::current();
let mut io = proc.io.lock();
let filename = validate_user_str(args[0], args[1])?;
let filename = arg::str_ref(args[0], args[1])?;
// TODO argv, envp array passing ABI?
let node = io.ioctx().find(None, filename, true)?;
drop(io);
node
};
let file = node.open(OpenFlags::O_RDONLY)?;
Process::execve(|space| elf::load_elf(space, file), 0).unwrap();
Process::execve(move |space| elf::load_elf(space, file), 0).unwrap();
panic!();
},
SystemCall::Exit => {
@ -181,7 +180,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
SystemCall::WaitPid => {
// TODO special "pid" values
let pid = unsafe { Pid::from_raw(args[0] as u32) };
let status = validate_user_ptr_struct::<i32>(args[1])?;
let status = arg::struct_mut::<i32>(args[1])?;
match Process::waitpid(pid) {
Ok(exit) => {
@ -204,7 +203,7 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
SystemCall::GetPid => todo!(),
SystemCall::GetTid => Ok(Thread::current().id() as usize),
SystemCall::Sleep => {
let rem_buf = validate_user_ptr_null(args[1], size_of::<u64>() * 2)?;
let rem_buf = arg::option_buf_ref(args[1], size_of::<u64>() * 2)?;
let mut rem = Duration::new(0, 0);
let res = wait::sleep(Duration::from_nanos(args[0] as u64), &mut rem);
if res == Err(Errno::Interrupt) {
@ -249,11 +248,9 @@ pub fn syscall(num: SystemCall, args: &[usize]) -> Result<usize, Errno> {
// Debugging
SystemCall::DebugTrace => {
let buf = validate_user_ptr(args[0], args[1])?;
let buf = arg::str_ref(args[0], args[1])?;
print!(Level::Debug, "[trace] ");
for &byte in buf.iter() {
print!(Level::Debug, "{}", byte as char);
}
print!(Level::Debug, "{}", buf);
println!(Level::Debug, "");
Ok(args[1])
},

View File

@ -41,16 +41,6 @@ fn main() -> i32 {
let mut buf = [0; 256];
let mut stdin = io::stdin();
let delay = libusr::thread::spawn(|| {
let mut t = [0; 2];
libusr::sys::sys_ex_nanosleep(1_000_000_000, &mut t);
});
delay.join();
libusr::signal::set_handler(libusr::sys::Signal::Interrupt, libusr::signal::SignalHandler::Ignore);
libusr::sys::sys_ex_kill(libusr::sys::SignalDestination::This, libusr::sys::Signal::Interrupt);
loop {
print!("> ");
let line = readline(&mut stdin, &mut buf).unwrap();