Skip to content

Commit 4d829b2

Browse files
committed
refactor: use deferred HostSharedMemory for GuestCounter
Replace the raw-pointer GuestCounter with an approach that uses Arc<Mutex<Option<HostSharedMemory>>> shared between ExclusiveSharedMemory and GuestCounter. The Option is None until build() populates it, so: - ExclusiveSharedMemory retains its lock-free exclusive phase - GuestCounter uses HostSharedMemory::write() (volatile, RwLock-guarded) - No unsafe code in the counter — constructor is safe (&mut self) - Counter operations fail with a clear error before build() Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent c896a2a commit 4d829b2

2 files changed

Lines changed: 102 additions & 55 deletions

File tree

src/hyperlight_host/src/mem/shared_mem.rs

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use std::io::Error;
2020
use std::mem::{align_of, size_of};
2121
#[cfg(target_os = "linux")]
2222
use std::ptr::null_mut;
23-
use std::sync::{Arc, RwLock};
23+
use std::sync::{Arc, Mutex, RwLock};
2424

2525
use hyperlight_common::mem::PAGE_SIZE_USIZE;
2626
use tracing::{Span, instrument};
@@ -136,7 +136,12 @@ impl Drop for HostMapping {
136136
/// and taking snapshots.
137137
#[derive(Debug)]
138138
pub struct ExclusiveSharedMemory {
139-
region: Arc<HostMapping>,
139+
pub(crate) region: Arc<HostMapping>,
140+
/// Populated by [`build()`](Self::build) with a [`HostSharedMemory`]
141+
/// view of this region. Code that needs host-style volatile access
142+
/// before `build()` (e.g. `GuestCounter`) can clone this `Arc` and
143+
/// will see `Some` once `build()` completes.
144+
pub(crate) deferred_hshm: Arc<Mutex<Option<HostSharedMemory>>>,
140145
}
141146
unsafe impl Send for ExclusiveSharedMemory {}
142147

@@ -321,8 +326,8 @@ unsafe impl Send for GuestSharedMemory {}
321326
/// becoming completely divorced from the view of the VM.
322327
#[derive(Clone, Debug)]
323328
pub struct HostSharedMemory {
324-
region: Arc<HostMapping>,
325-
lock: Arc<RwLock<()>>,
329+
pub(crate) region: Arc<HostMapping>,
330+
pub(crate) lock: Arc<RwLock<()>>,
326331
}
327332
unsafe impl Send for HostSharedMemory {}
328333

@@ -424,6 +429,7 @@ impl ExclusiveSharedMemory {
424429
ptr: addr as *mut u8,
425430
size: total_size,
426431
}),
432+
deferred_hshm: Arc::new(Mutex::new(None)),
427433
})
428434
}
429435

@@ -542,6 +548,7 @@ impl ExclusiveSharedMemory {
542548
size: total_size,
543549
handle,
544550
}),
551+
deferred_hshm: Arc::new(Mutex::new(None)),
545552
})
546553
}
547554

@@ -648,14 +655,18 @@ impl ExclusiveSharedMemory {
648655
/// the GuestSharedMemory.
649656
pub fn build(self) -> (HostSharedMemory, GuestSharedMemory) {
650657
let lock = Arc::new(RwLock::new(()));
658+
let hshm = HostSharedMemory {
659+
region: self.region.clone(),
660+
lock: lock.clone(),
661+
};
662+
// Publish the HostSharedMemory so any pre-existing GuestCounter
663+
// can begin issuing volatile writes via the proper protocol.
664+
*self.deferred_hshm.lock().unwrap() = Some(hshm.clone());
651665
(
652-
HostSharedMemory {
653-
region: self.region.clone(),
654-
lock: lock.clone(),
655-
},
666+
hshm,
656667
GuestSharedMemory {
657668
region: self.region.clone(),
658-
lock: lock.clone(),
669+
lock,
659670
},
660671
)
661672
}
@@ -847,6 +858,7 @@ impl SharedMemory for GuestSharedMemory {
847858
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
848859
let mut excl = ExclusiveSharedMemory {
849860
region: self.region.clone(),
861+
deferred_hshm: Arc::new(Mutex::new(None)),
850862
};
851863
let ret = f(&mut excl);
852864
drop(excl);
@@ -1212,6 +1224,7 @@ impl SharedMemory for HostSharedMemory {
12121224
.map_err(|e| new_error!("Error locking at {}:{}: {}", file!(), line!(), e))?;
12131225
let mut excl = ExclusiveSharedMemory {
12141226
region: self.region.clone(),
1227+
deferred_hshm: Arc::new(Mutex::new(None)),
12151228
};
12161229
let ret = f(&mut excl);
12171230
drop(excl);

src/hyperlight_host/src/sandbox/uninitialized.rs

Lines changed: 80 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -61,36 +61,32 @@ pub(crate) struct SandboxRuntimeConfig {
6161
///
6262
/// Created via [`UninitializedSandbox::guest_counter()`]. The host owns
6363
/// the counter value and is the only writer: [`increment()`](Self::increment)
64-
/// and [`decrement()`](Self::decrement) update the cached value and issue a
65-
/// single `write_volatile` to shared memory. [`value()`](Self::value) returns
66-
/// the cached value — the host never reads back from guest memory, so a
67-
/// malicious guest cannot influence the host's view of the counter.
64+
/// and [`decrement()`](Self::decrement) update the cached value and write
65+
/// to shared memory via [`HostSharedMemory::write()`]. [`value()`](Self::value)
66+
/// returns the cached value — the host never reads back from guest memory,
67+
/// so a malicious guest cannot influence the host's view of the counter.
6868
///
6969
/// Thread safety is provided by an internal `Mutex`, so `increment()` and
7070
/// `decrement()` take `&self` rather than `&mut self`.
7171
///
72-
/// # Implementation note
72+
/// The counter holds an `Arc<Mutex<Option<HostSharedMemory>>>` that is
73+
/// shared with [`ExclusiveSharedMemory`]. The `Option` is `None` until
74+
/// [`build()`](ExclusiveSharedMemory::build) populates it, at which point
75+
/// the counter can issue volatile writes via the proper protocol. This
76+
/// avoids adding lock overhead to `ExclusiveSharedMemory` during the
77+
/// exclusive setup phase.
7378
///
74-
/// `GuestCounter` uses raw `write_volatile` instead of going through
75-
/// [`SharedMemory`] methods because it is created on an
76-
/// [`UninitializedSandbox`] (which holds `ExclusiveSharedMemory`), but
77-
/// must survive across [`evolve()`](UninitializedSandbox::evolve) into
78-
/// `MultiUseSandbox` (which holds `HostSharedMemory`). The pointer
79-
/// remains valid because `evolve()` internally clones the backing
80-
/// `Arc<HostMapping>` into both `HostSharedMemory` and
81-
/// `GuestSharedMemory`, keeping the mmap alive.
82-
///
83-
/// Only **one** `GuestCounter` should be created per sandbox. Creating
84-
/// multiple instances is safe from a memory perspective, but the
85-
/// internal cached values will diverge, leading to incorrect writes.
79+
/// The constructor takes `&mut self` on `UninitializedSandbox`, which
80+
/// prevents creating multiple instances simultaneously.
8681
#[cfg(feature = "nanvix-unstable")]
8782
pub struct GuestCounter {
8883
inner: Mutex<GuestCounterInner>,
8984
}
9085

9186
#[cfg(feature = "nanvix-unstable")]
9287
struct GuestCounterInner {
93-
ptr: *mut u64,
88+
deferred_hshm: Arc<Mutex<Option<crate::mem::shared_mem::HostSharedMemory>>>,
89+
offset: usize,
9490
value: u64,
9591
}
9692

@@ -101,36 +97,45 @@ impl core::fmt::Debug for GuestCounter {
10197
}
10298
}
10399

104-
// SAFETY: The pointer targets mmap'd shared memory owned by the sandbox.
105-
// The internal Mutex serialises all host-side access.
106-
#[cfg(feature = "nanvix-unstable")]
107-
unsafe impl Send for GuestCounterInner {}
108-
109100
#[cfg(feature = "nanvix-unstable")]
110101
impl GuestCounter {
111-
/// Increments the counter by one and writes it to guest memory with
112-
/// a volatile store.
102+
/// Increments the counter by one and writes it to guest memory.
113103
pub fn increment(&self) -> Result<()> {
114104
let mut inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
105+
let shm = {
106+
let guard = inner.deferred_hshm.lock().map_err(|e| new_error!("{e}"))?;
107+
guard
108+
.as_ref()
109+
.ok_or_else(|| {
110+
new_error!("GuestCounter cannot be used before shared memory is built")
111+
})?
112+
.clone()
113+
};
115114
inner.value = inner
116115
.value
117116
.checked_add(1)
118117
.ok_or_else(|| new_error!("GuestCounter overflow"))?;
119-
// SAFETY: `ptr` was validated as aligned and in-bounds at construction time.
120-
unsafe { core::ptr::write_volatile(inner.ptr, inner.value) };
118+
shm.write::<u64>(inner.offset, inner.value)?;
121119
Ok(())
122120
}
123121

124-
/// Decrements the counter by one and writes it to guest memory with
125-
/// a volatile store.
122+
/// Decrements the counter by one and writes it to guest memory.
126123
pub fn decrement(&self) -> Result<()> {
127124
let mut inner = self.inner.lock().map_err(|e| new_error!("{e}"))?;
125+
let shm = {
126+
let guard = inner.deferred_hshm.lock().map_err(|e| new_error!("{e}"))?;
127+
guard
128+
.as_ref()
129+
.ok_or_else(|| {
130+
new_error!("GuestCounter cannot be used before shared memory is built")
131+
})?
132+
.clone()
133+
};
128134
inner.value = inner
129135
.value
130136
.checked_sub(1)
131137
.ok_or_else(|| new_error!("GuestCounter underflow"))?;
132-
// SAFETY: `ptr` was validated as aligned and in-bounds at construction time.
133-
unsafe { core::ptr::write_volatile(inner.ptr, inner.value) };
138+
shm.write::<u64>(inner.offset, inner.value)?;
134139
Ok(())
135140
}
136141

@@ -262,17 +267,16 @@ impl UninitializedSandbox {
262267
/// the top of scratch memory, so both host and guest can locate it
263268
/// without an explicit GPA parameter.
264269
///
265-
/// # Safety
270+
/// The returned counter holds an `Arc` clone of the scratch memory's
271+
/// `deferred_hshm`, so it will automatically gain access to the
272+
/// [`HostSharedMemory`] once [`build()`](ExclusiveSharedMemory::build)
273+
/// is called during [`evolve()`](Self::evolve).
266274
///
267-
/// The caller must ensure:
268-
/// - The returned `GuestCounter` does not outlive the sandbox's
269-
/// underlying shared memory mapping (which stays alive through
270-
/// `evolve()` into `MultiUseSandbox`).
271-
/// - Only **one** `GuestCounter` is created per sandbox. Multiple
272-
/// instances are memory-safe but their cached values will diverge,
273-
/// leading to incorrect writes.
275+
/// Takes `&mut self` to prevent creating multiple instances
276+
/// simultaneously (multiple instances would have divergent cached
277+
/// values).
274278
#[cfg(feature = "nanvix-unstable")]
275-
pub unsafe fn guest_counter(&mut self) -> Result<GuestCounter> {
279+
pub fn guest_counter(&mut self) -> Result<GuestCounter> {
276280
use hyperlight_common::layout::SCRATCH_TOP_GUEST_COUNTER_OFFSET;
277281

278282
let scratch_size = self.mgr.scratch_mem.mem_size();
@@ -285,10 +289,15 @@ impl UninitializedSandbox {
285289
}
286290

287291
let offset = scratch_size - SCRATCH_TOP_GUEST_COUNTER_OFFSET as usize;
288-
let ptr = unsafe { self.mgr.scratch_mem.base_ptr().add(offset) as *mut u64 };
289-
let value = unsafe { core::ptr::read_volatile(ptr) };
292+
let deferred_hshm = self.mgr.scratch_mem.deferred_hshm.clone();
293+
let value = self.mgr.scratch_mem.read_u64(offset)?;
294+
290295
Ok(GuestCounter {
291-
inner: Mutex::new(GuestCounterInner { ptr, value }),
296+
inner: Mutex::new(GuestCounterInner {
297+
deferred_hshm,
298+
offset,
299+
value,
300+
}),
292301
})
293302
}
294303

@@ -1440,9 +1449,12 @@ mod tests {
14401449

14411450
#[cfg(feature = "nanvix-unstable")]
14421451
mod guest_counter_tests {
1452+
use std::sync::{Arc, RwLock};
1453+
14431454
use hyperlight_testing::simple_guest_as_string;
14441455

14451456
use crate::UninitializedSandbox;
1457+
use crate::mem::shared_mem::HostSharedMemory;
14461458
use crate::sandbox::uninitialized::GuestBinary;
14471459

14481460
fn make_sandbox() -> UninitializedSandbox {
@@ -1453,17 +1465,37 @@ mod tests {
14531465
.expect("Failed to create sandbox")
14541466
}
14551467

1468+
/// Simulate `build()` for the scratch region by storing a
1469+
/// `HostSharedMemory` into the deferred slot.
1470+
fn simulate_build(sandbox: &UninitializedSandbox) {
1471+
let lock = Arc::new(RwLock::new(()));
1472+
let hshm = HostSharedMemory {
1473+
region: sandbox.mgr.scratch_mem.region.clone(),
1474+
lock,
1475+
};
1476+
*sandbox.mgr.scratch_mem.deferred_hshm.lock().unwrap() = Some(hshm);
1477+
}
1478+
14561479
#[test]
14571480
fn create_guest_counter() {
14581481
let mut sandbox = make_sandbox();
1459-
let counter = unsafe { sandbox.guest_counter() };
1482+
let counter = sandbox.guest_counter();
14601483
assert!(counter.is_ok());
14611484
}
14621485

1486+
#[test]
1487+
fn fails_before_build() {
1488+
let mut sandbox = make_sandbox();
1489+
let counter = sandbox.guest_counter().unwrap();
1490+
assert!(counter.increment().is_err());
1491+
assert!(counter.decrement().is_err());
1492+
}
1493+
14631494
#[test]
14641495
fn increment_decrement() {
14651496
let mut sandbox = make_sandbox();
1466-
let counter = unsafe { sandbox.guest_counter() }.unwrap();
1497+
let counter = sandbox.guest_counter().unwrap();
1498+
simulate_build(&sandbox);
14671499

14681500
let initial = counter.value().unwrap();
14691501
counter.increment().unwrap();
@@ -1477,7 +1509,9 @@ mod tests {
14771509
#[test]
14781510
fn underflow_returns_error() {
14791511
let mut sandbox = make_sandbox();
1480-
let counter = unsafe { sandbox.guest_counter() }.unwrap();
1512+
let counter = sandbox.guest_counter().unwrap();
1513+
simulate_build(&sandbox);
1514+
14811515
assert_eq!(counter.value().unwrap(), 0);
14821516
let result = counter.decrement();
14831517
assert!(result.is_err());

0 commit comments

Comments
 (0)