@@ -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" ) ]
8782pub struct GuestCounter {
8883 inner : Mutex < GuestCounterInner > ,
8984}
9085
9186#[ cfg( feature = "nanvix-unstable" ) ]
9287struct 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" ) ]
110101impl 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