MaulingMonkey / firehazard

Unopinionated low level API bindings focused on soundness, safety, and stronger types over raw FFI.
Other
8 stars 0 forks source link

Handle normalization and deduplication #16

Closed MaulingMonkey closed 2 years ago

MaulingMonkey commented 2 years ago

Kernel Object Types

https://docs.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects

Storage

Ownership

Ownership Clone Drop Desc
Owned DuplicateHandle? CloseHandle? Self explanatory
Borrowed<'a> Copy In structs?
Psuedo Copy -1 and other non-real handles
Weak ??? Some GetX functions return a handle which could be yanked out from under you
Remote DuplicateHandle? DUPLICATE_CLOSE_SOURCE Another process's handle
MaulingMonkey commented 2 years ago

v1: Undefined Behavior

Being able to Deref from OwnedHandle -> BorrowedHandle<'a> -> PsuedoHandle<'a> would be ideal, and is almost doable:

#![forbid(unsafe_op_in_unsafe_fn)]

use core::ffi::c_void;
use core::mem::transmute;
use core::ops::*;
use core::ptr::NonNull;

type HANDLE = *mut c_void;
fn some_handle() -> HANDLE { 0xF0usize as _ }

#[repr(transparent)] pub struct OwnedHandle(NonNull<c_void>);
#[repr(transparent)] pub struct BorrowedHandleValue(c_void);
#[repr(transparent)] pub struct PsuedoHandleValue  (c_void);
pub type BorrowedHandle<'a> = &'a BorrowedHandleValue;
pub type PsuedoHandle<'a>   = &'a PsuedoHandleValue;

impl Deref for OwnedHandle         { type Target = BorrowedHandleValue; fn deref(&self) -> BorrowedHandle { unsafe { transmute(self.0) } } }
impl Deref for BorrowedHandleValue { type Target = PsuedoHandleValue;   fn deref(&self) -> PsuedoHandle   { unsafe { transmute(self  ) } } }

impl OwnedHandle { pub unsafe fn from_raw(handle: HANDLE) -> Option<Self> { Some(Self(NonNull::new(handle)?)) } }

fn main() {
    let owned    : OwnedHandle    = unsafe { OwnedHandle::from_raw(some_handle()) }.unwrap();
    let borrowed : BorrowedHandle = &*owned;
    let psuedo   : PsuedoHandle   = &**owned;

    requires_owned(&owned);
    requires_borrowed(&owned);
    requires_psuedo(&owned);

    requires_borrowed(borrowed);
    requires_psuedo(borrowed);

    requires_psuedo(psuedo);
}

fn requires_owned(_: &OwnedHandle) {}
fn requires_borrowed(_: BorrowedHandle) {}
fn requires_psuedo(_: PsuedoHandle) {}

However, this angers Miri and invokes Undefined Behavior:


error: Undefined Behavior: constructing invalid value: encountered a dangling reference (address 0xf0 is unallocated)
  --> src/main.rs:17:118
   |
17 | impl Deref for OwnedHandle         { type Target = BorrowedHandleValue; fn deref(&self) -> BorrowedHandle { unsafe { transmute(self.0) } } }
   |                                                                                                                      ^^^^^^^^^^^^^^^^^ constructing invalid value: encountered a dangling reference (address 0xf0 is unallocated)
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: backtrace:
   = note: inside `<OwnedHandle as std::ops::Deref>::deref` at src/main.rs:17:118
note: inside `main` at src/main.rs:24:38
  --> src/main.rs:24:38
   |
24 |     let borrowed : BorrowedHandle = &*owned;
   |                                      ^^^^^^
MaulingMonkey commented 2 years ago

v2: Conversion spam

AsRef and From/Into spam is occasionally awkward, but it's not the absolute worst

#![forbid(unsafe_op_in_unsafe_fn)]

use core::ffi::c_void;
use core::marker::PhantomData;
use core::mem::transmute;
use core::ops::*;
use core::ptr::NonNull;

type HANDLE = *mut c_void;

// &'a Owned  = borrowed handle, but not C struct FFI compatible (extra indirection: ~NonNull<NonNull<c_void>>, not NonNull<c_void>)
// Handle<'a> = borrowed handle
// Psuedo<'a> = borrowed handle or psuedo handle

                       #[repr(transparent)] pub struct Owned     (NonNull<c_void>);
#[derive(Clone, Copy)] #[repr(transparent)] pub struct Handle<'a>(NonNull<c_void>, PhantomData<&'a c_void>);
#[derive(Clone, Copy)] #[repr(transparent)] pub struct Psuedo<'a>(NonNull<c_void>, PhantomData<&'a c_void>);

// impl Clone for Owned via DuplicateHandle?
impl Drop for Owned { fn drop(&mut self) { /* CloseHandle(...) */ } }

impl Owned           { pub unsafe fn from_raw(handle: HANDLE) -> Option<Self> { Some(Self(NonNull::new(handle)?)) } }
impl Psuedo<'static> { pub unsafe fn from_raw(handle: HANDLE) -> Option<Self> { Some(Self(NonNull::new(handle)?, PhantomData)) } }

impl     AsRef<Owned     > for     Owned  { fn as_ref(&self) -> &Owned      { self } }
impl<'a> AsRef<Handle<'a>> for &'a Owned  { fn as_ref(&self) -> &Handle<'a> { unsafe { transmute(*self) } } }
impl<'a> AsRef<Psuedo<'a>> for &'a Owned  { fn as_ref(&self) -> &Psuedo<'a> { unsafe { transmute(*self) } } }

impl<'a> AsRef<Handle<'a>> for Handle<'a> { fn as_ref(&self) -> &Handle<'a> { self } }
impl<'a> AsRef<Psuedo<'a>> for Handle<'a> { fn as_ref(&self) -> &Psuedo<'a> { unsafe { transmute(self) } } }

impl<'a> AsRef<Psuedo<'a>> for Psuedo<'a> { fn as_ref(&self) -> &Psuedo<'a> { self } }

impl<'a> From<&'a Owned > for Handle<'a> { fn from(h: &'a Owned ) -> Self { Self(h.0, PhantomData) } }
impl<'a> From<&'a Owned > for Psuedo<'a> { fn from(h: &'a Owned ) -> Self { Self(h.0, PhantomData) } }
impl<'a> From<Handle<'a>> for Psuedo<'a> { fn from(h: Handle<'a>) -> Self { Self(h.0, PhantomData) } }

fn get_current_process() -> Psuedo<'static> { unsafe { Psuedo::from_raw(!0usize as _) }.unwrap() } // ~ GetCurrentProcess()
fn open_current_process() -> Owned { unsafe { Owned::from_raw(42usize as _) }.unwrap() } // TODO: DuplicateHandle(...) to turn a psuedo-handle into a real one

#[repr(C)] struct Struct<'a> { handle: Option<Handle<'a>> }

fn main() {
    let owned    : Owned  = open_current_process();
    let psuedo   : Psuedo = get_current_process();

    let mut some_struct = Struct { handle: None };
    some_struct.handle = Some((&owned).into());
    // some_struct.handle = Some((&psuedo).into()); // forbidden: Struct requires a real handle, not a psuedo handle
    let borrowed : Handle = some_struct.handle.unwrap();

    requires_owned (&owned);
    requires_handle(&owned);
    requires_psuedo(&owned);

    // drop(owned); // forbidden: still borrowed by `borrowed`

    requires_handle(borrowed);
    requires_psuedo(borrowed);

    drop(owned);

    requires_psuedo(psuedo);
}

fn requires_owned     (_: impl AsRef<Owned     >) {}
fn requires_handle<'a>(_: impl AsRef<Handle<'a>>) {}
fn requires_psuedo<'a>(_: impl AsRef<Psuedo<'a>>) {}

Miri doesn't hate this