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 { match err_code {
EC_DATA_ABORT_EL0 | EC_DATA_ABORT_ELX => { EC_DATA_ABORT_EL0 | EC_DATA_ABORT_ELX => {
let far = FAR_EL1.get() as usize; 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 thread = Thread::current();
let proc = thread.owner().unwrap(); let proc = thread.owner().unwrap();

View File

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

View File

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

View File

@ -1,6 +1,7 @@
//! System call argument ABI helpers //! System call argument ABI helpers
use crate::mem; use crate::mem;
use core::alloc::Layout;
use core::mem::size_of; use core::mem::size_of;
use libsys::error::Errno; 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; let mut res: usize;
unsafe { unsafe {
asm!("at s1e1r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res); if write {
} asm!("at s1e0w, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
if res & 1 == 0 {
Some(res & !(0xFFF | (0xFF << 56)))
} else { } else {
None asm!("at s1e0r, {}; mrs {}, par_el1", in(reg) virt, out(reg) res);
} }
}
res & 1 == 0
} }
/// Unwraps a slim structure pointer pub fn struct_ref<'a, T>(base: usize) -> Result<&'a T, Errno> {
pub fn validate_user_ptr_struct<'a, T>(base: usize) -> Result<&'a mut T, Errno> { let layout = Layout::new::<T>();
validate_user_ptr_struct_option(base).and_then(|e| e.ok_or(Errno::InvalidArgument)) 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 { if base == 0 {
Ok(None) Ok(None)
} else { } else {
let bytes = validate_user_ptr(base, size_of::<T>())?; struct_ref(base).map(Some)
Ok(Some(unsafe { &mut *(bytes.as_mut_ptr() as *mut T) }))
} }
} }
/// Unwraps an user buffer reference pub fn option_struct_mut<'a, T>(base: usize) -> Result<Option<&'a mut T>, Errno> {
pub fn validate_user_ptr<'a>(base: usize, len: usize) -> Result<&'a mut [u8], 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 { if base > mem::KERNEL_OFFSET || base + len > mem::KERNEL_OFFSET {
invalid_memory!( invalid_memory!(
"User region refers to kernel memory: base={:#x}, len={:#x}", "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) { 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!( 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, base,
len, len,
i * mem::PAGE_SIZE 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) }) Ok(unsafe { core::slice::from_raw_parts_mut(base as *mut u8, len) })
} }
/// Unwraps a nullable user buffer reference pub fn option_buf_ref<'a>(base: usize, len: usize) -> Result<Option<&'a [u8]>, Errno> {
pub fn validate_user_ptr_null<'a>(base: usize, len: usize) -> Result<Option<&'a mut [u8]>, Errno> {
if base == 0 { if base == 0 {
Ok(None) Ok(None)
} else { } 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 /// Unwraps user string argument
pub fn validate_user_str<'a>(base: usize, len: usize) -> Result<&'a str, Errno> { pub fn str_ref<'a>(base: usize, len: usize) -> Result<&'a str, Errno> {
let bytes = validate_user_ptr(base, len)?; let bytes = buf_ref(base, len)?;
core::str::from_utf8(bytes).map_err(|_| { core::str::from_utf8(bytes).map_err(|_| {
warnln!( warnln!(
"User string contains invalid UTF-8 characters: base={:#x}, len={:#x}", "User string contains invalid UTF-8 characters: base={:#x}, len={:#x}",

View File

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

View File

@ -41,16 +41,6 @@ fn main() -> i32 {
let mut buf = [0; 256]; let mut buf = [0; 256];
let mut stdin = io::stdin(); 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 { loop {
print!("> "); print!("> ");
let line = readline(&mut stdin, &mut buf).unwrap(); let line = readline(&mut stdin, &mut buf).unwrap();