-
Notifications
You must be signed in to change notification settings - Fork 123
openhcl_boot: provide stronger safety guarantees in the hypercall fra… #1398
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…mework * The statics are hidden into a nested module to prevent bare access. * The input and output pages are now assosiated functions wiith signatures that ensure exclusive mutable access at compile-time
static HVCALL_OUTPUT: SingleThreaded<UnsafeCell<PageAlign<[u8; HV_PAGE_SIZE as usize]>>> = | ||
SingleThreaded(zeroed()); | ||
|
||
static HVCALL: SingleThreaded<RefCell<HvCall>> = SingleThreaded(RefCell::new(HvCall { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder... what if instead we do something like this?
struct HvCall {
input: &'static mut PageAlign<[u8; HV_PAGE_SIZE]>,
output: &'static mut ...,
...
}
static HVCALL: SingleThreaded<RefCell<HvCall>> = {
static INPUT: SingleThreaded<UnsafeCell<...>> = ...;
static OUTPUT: ...;
let input = unsafe { &mut *INPUT.as_ptr() };
let output = unsafe { &mut *OUTPUT.as_ptr() };
SingleThreaded(RefCell::new(HvCall { initialized: false, vtl: Vtl::Vtl0, input, output }))
};
Then we don't need the accessor methods at all, we can just reach for self.input
/self.output
. All the safety is encapsulated in the object's construction, so we can avoid the nested module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, let me try that!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for
pub struct HvCall {
input: &'static mut PageAlign<[u8; HV_PAGE_SIZE as usize]>,
output: &'static mut PageAlign<[u8; HV_PAGE_SIZE as usize]>,
initialized: bool,
vtl: Vtl,
}
static HVCALL: SingleThreaded<RefCell<HvCall>> = {
static INPUT: SingleThreaded<UnsafeCell<PageAlign<[u8; HV_PAGE_SIZE as usize]>>> =
SingleThreaded(zeroed());
static OUTPUT: SingleThreaded<UnsafeCell<PageAlign<[u8; HV_PAGE_SIZE as usize]>>> =
SingleThreaded(zeroed());
let input = unsafe { &mut *INPUT.get() };
let output = unsafe { &mut *OUTPUT.get() };
SingleThreaded(RefCell::new(HvCall {
initialized: false,
vtl: Vtl::Vtl0,
input,
output,
}))
};
rust errors out with
error[E0015]: cannot perform non-const deref coercion on `SingleThreaded<UnsafeCell<PageAlign<[u8; 4096]>>>` in statics
--> openhcl/openhcl_boot/src/hypercall.rs:38:32
|
38 | let input = unsafe { &mut *INPUT.get() };
| ^^^^^^^^^^^
Not sure of the solution yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Try let input = unsafe { INPUT.0.get_mut() }
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I think the problem is that INPUT.get()
is really calling INPUT.deref().get()
, and deref()
is not a const fn.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rustc yelled at me for trying to mutate the immutable static... Anyway, implemented with off_stack!
. The whole story would include throwing in a leaking and bumping allocator but decided to hold my horses lol.
For the posterity:
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
//! Simple leaking bump allocator.
use core::ops::Deref;
use core::ops::DerefMut;
use core::sync::atomic::AtomicBool;
use core::sync::atomic::AtomicUsize;
use core::sync::atomic::Ordering;
static HEAP_START: AtomicUsize = AtomicUsize::new(!0);
static HEAP_END: AtomicUsize = AtomicUsize::new(!0);
static HEAP_INITIALIZED: AtomicBool = AtomicBool::new(false);
static HEAP_BUSY: AtomicBool = AtomicBool::new(false);
fn heap_acquire() {
while HEAP_BUSY
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
}
fn heap_release() {
HEAP_BUSY.store(false, Ordering::Release);
}
/// Blocks the heap until an operation is complete.
struct HeapGuard;
impl HeapGuard {
fn new() -> Self {
heap_acquire();
Self
}
}
impl Drop for HeapGuard {
fn drop(&mut self) {
heap_release();
}
}
/// Initialize the heap.
pub fn init_heap(start: usize, end: usize) {
let _guard = HeapGuard::new();
if HEAP_INITIALIZED.load(Ordering::Acquire) {
panic!("Heap already initialized");
}
HEAP_INITIALIZED.store(true, Ordering::Release);
if end == 0 || end == !0 || start >= end {
panic!("Invalid heap range");
}
HEAP_START.store(start, Ordering::Release);
HEAP_END.store(end, Ordering::Release);
}
/// Allocates a block of memory of the given size.
fn alloc<T>() -> *mut T {
let _guard = HeapGuard::new();
if !HEAP_INITIALIZED.load(Ordering::Acquire) {
panic!("Heap not initialized");
}
let alloc_size = size_of::<T>();
let align = align_of::<T>();
let start = HEAP_START.load(Ordering::Acquire);
let start = if start % align == 0 {
start
} else {
start + (align - start % align)
};
let end = HEAP_END.load(Ordering::Acquire);
if start + alloc_size > end {
panic!("Heap out of memory");
}
HEAP_START.store(start + alloc_size, Ordering::Release);
start as *mut T
}
/// A simple bump allocator that allocates memory for a type `T`.
/// There is no deallocation, the memory will be leaked when the
/// instance is dropped.
pub struct EatMem<T> {
_marker: core::marker::PhantomData<T>,
raw: *mut T,
}
impl<T> EatMem<T> {
/// Allocates memory for `T` and initializes it using the provided
/// initialization function.
pub fn new<F: FnOnce(&mut T) -> ()>(init_fn: F) -> Self {
// Get a chunk of memory for `T`.
let raw = alloc::<T>();
// Ask the caller to initialize the memory.
init_fn(
// SAFETY: The caller guarantees that the memory is initialized
// to a valid bit pattern for `T`.
unsafe { raw.as_mut().expect("allocation succeeded") },
);
// The caller ate some more memory!
EatMem {
_marker: core::marker::PhantomData,
raw,
}
}
}
impl<T> Deref for EatMem<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe { &*self.raw }
}
}
impl<T> DerefMut for EatMem<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe { &mut *self.raw }
}
}
struct HvcallPage { | ||
buffer: [u8; HV_PAGE_SIZE as usize], | ||
} | ||
mod details { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not feedback on this pr, but what do people usually call such a module (internal implementation details that are non-public)? ive seen details, private, other things...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've used details
so far although always wanted to use rewrite_me
😄
Will update the PR description and the commit message once the implementation is agreed upon |
…mework