Skip to main content

usercopy/
lib.rs

1// Copyright 2023 The Fuchsia Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::mem::MaybeUninit;
6use std::ops::Range;
7
8use zerocopy::FromBytes;
9use zx::Task;
10
11unsafe extern "C" {
12    // This function performs a data copy like `memcpy`.
13    //
14    // Returns the last accessed destination address when `ret_dest` is `true`,
15    // or the last accessed source address when `ret_dest` is `false`.
16    fn hermetic_copy(dest: *mut u8, source: *const u8, len: usize, ret_dest: bool) -> usize;
17    fn hermetic_copy_end();
18
19    // Performs a data copy like `strncpy`.
20    //
21    // Returns the last accessed destination address when `ret_dest` is `true`,
22    // or the last accessed source address when `ret_dest` is `false`.
23    fn hermetic_copy_until_null_byte(
24        dest: *mut u8,
25        source: *const u8,
26        len: usize,
27        ret_dest: bool,
28    ) -> usize;
29    fn hermetic_copy_until_null_byte_end();
30
31    // This function performs a `memset` to 0.
32    //
33    // Returns the last accessed destination address.
34    fn hermetic_zero(dest: *mut u8, len: usize) -> usize;
35    fn hermetic_zero_end();
36
37    // This function generates a "return" from the usercopy routine with an error.
38    fn hermetic_copy_error();
39
40    // This generates a return from an error generated by an atomic routine.
41    fn atomic_error();
42
43    // This performs a relaxed atomic load of a 32 bit value at `addr`.
44    // On success the loaded value will be in the lower 32 bits of the returned value and the high
45    // bits will be zero. If a fault occurred, the high bits will be one.
46    fn atomic_load_u32_relaxed(addr: usize) -> u64;
47
48    // Symbol representing the end of the atomic_load_u32_relaxed() function.
49    fn atomic_load_u32_relaxed_end();
50
51    // This performs an atomic load-acquire of a 32 bit value at `addr`.
52    // On success the loaded value will be in the lower 32 bits of the returned value and the high
53    // bits will be zero. If a fault occurred, the high bits will be one.
54    fn atomic_load_u32_acquire(addr: usize) -> u64;
55
56    // Symbol representing the end of the atomic_load_u32_acquire() function.
57    fn atomic_load_u32_acquire_end();
58
59    // This performs a relaxed atomic store of a 32 bit value to `addr`.
60    // On success zero is returned. On fault a nonzero value is returned.
61    fn atomic_store_u32_relaxed(addr: usize, value: u32) -> u64;
62
63    // Symbol representing the end of the atomic_store_u32_relaxed() function.
64    fn atomic_store_u32_relaxed_end();
65
66    // This performs an atomic store-release of a 32 bit value to `addr`.
67    // On success zero is returned. On fault a nonzero value is returned.
68    fn atomic_store_u32_release(addr: usize, value: u32) -> u64;
69
70    // Symbol representing the end of the atomic_store_u32_release() function.
71    fn atomic_store_u32_release_end();
72
73    // This performs an atomic compare-and-exchange operation of the 32 bit value at `addr`.
74    // If the operation succeeded, stores `desired` to `addr` and returns 1.
75    //
76    // If the operation failed because `addr` did not contain the value `*expected`, stores the
77    // observed value to `*expected`.
78    //
79    // Memory ordering:
80    // On success, the read-modify-write has both acquire and release semantics.
81    // On failure, the load from 'addr' has acquire semantics.
82    //
83    // If the operation encountered a fault, the high bits of the returned value will be one.
84    fn atomic_compare_exchange_u32_acq_rel(addr: usize, expected: *mut u32, desired: u32) -> u64;
85
86    // Symbol representing the end of the atomic_compare_exchange_u32_acq_rel() function.
87    fn atomic_compare_exchange_u32_acq_rel_end();
88
89    // This performs an atomic compare-and-exchange operation of the 32 bit value at `addr`.
90    // If the operation succeeded, stores `desired` to `addr` and returns 1.
91    // If the operation failed (perhaps because `addr` did not contain the value `*expected`),
92    // stores the observed value to `*expected` and returns 0.
93    //
94    // This operation can fail spuriously.
95    //
96    // Memory ordering:
97    // On success, the read-modify-write has both acquire and release semantics.
98    // On failure, the load from 'addr' has acquire semantics.
99    //
100    // If the operation encountered a fault, the high bits of the returned value will be one.
101    fn atomic_compare_exchange_weak_u32_acq_rel(
102        addr: usize,
103        expected: *mut u32,
104        desired: u32,
105    ) -> u64;
106
107    // Symbol representing the end of the atomic_compare_exchange_weak_u32_relaxed() function.
108    fn atomic_compare_exchange_weak_u32_acq_rel_end();
109}
110
111/// Converts a slice to an equivalent MaybeUninit slice.
112pub fn slice_to_maybe_uninit_mut<T>(slice: &mut [T]) -> &mut [MaybeUninit<T>] {
113    let ptr = slice.as_mut_ptr();
114    let ptr = ptr as *mut MaybeUninit<T>;
115    // SAFETY: This is effectively reinterpreting the `slice` reference as a
116    // slice of uninitialized T's. `MaybeUninit<T>` has the same layout[1] as
117    // `T` and we know the original slice is initialized and its okay to from
118    // initialized to maybe initialized.
119    //
120    // [1]: https://doc.rust-lang.org/std/mem/union.MaybeUninit.html#layout-1
121    unsafe { std::slice::from_raw_parts_mut(ptr, slice.len()) }
122}
123
124type HermeticCopyFn =
125    unsafe extern "C" fn(dest: *mut u8, source: *const u8, len: usize, ret_dest: bool) -> usize;
126
127#[derive(Debug)]
128pub struct Usercopy {
129    // This is an event used to signal the exception handling thread to shut down.
130    shutdown_event: zx::Event,
131
132    // Handle to the exception handling thread.
133    join_handle: Option<std::thread::JoinHandle<()>>,
134
135    // The range of the restricted address space.
136    restricted_address_range: Range<usize>,
137}
138
139/// Parses a fault exception.
140///
141/// Returns `(pc, fault_address)`, where `pc` is the address of the instruction
142/// that triggered the fault and `fault_address` is the address that faulted.
143fn parse_fault_exception(
144    regs: &mut zx::sys::zx_thread_state_general_regs_t,
145    report: zx::ExceptionReport,
146) -> (usize, usize) {
147    #[cfg(target_arch = "x86_64")]
148    {
149        let pc = regs.rip as usize;
150        let fault_address = report.arch.cr2;
151
152        (pc, fault_address as usize)
153    }
154
155    #[cfg(target_arch = "aarch64")]
156    {
157        let pc = regs.pc as usize;
158        let fault_address = report.arch.far;
159
160        (pc, fault_address as usize)
161    }
162
163    #[cfg(target_arch = "riscv64")]
164    {
165        let pc = regs.pc as usize;
166        let fault_address = report.arch.tval;
167
168        (pc, fault_address as usize)
169    }
170}
171
172fn set_registers_for_hermetic_error(
173    regs: &mut zx::sys::zx_thread_state_general_regs_t,
174    fault_address: usize,
175) {
176    #[cfg(target_arch = "x86_64")]
177    {
178        regs.rip = hermetic_copy_error as *const () as u64;
179        regs.rax = fault_address as u64;
180    }
181
182    #[cfg(target_arch = "aarch64")]
183    {
184        regs.pc = hermetic_copy_error as *const () as u64;
185        regs.r[0] = fault_address as u64;
186    }
187
188    #[cfg(target_arch = "riscv64")]
189    {
190        regs.pc = hermetic_copy_error as *const () as u64;
191        regs.a0 = fault_address as u64;
192    }
193}
194
195const ATOMIC_ERROR_MASK: u64 = 0xFFFFFFFF00000000;
196
197fn set_registers_for_atomic_error(regs: &mut zx::sys::zx_thread_state_general_regs_t) {
198    #[cfg(target_arch = "x86_64")]
199    {
200        regs.rax = ATOMIC_ERROR_MASK;
201        regs.rip = atomic_error as *const () as u64;
202    }
203
204    #[cfg(target_arch = "aarch64")]
205    {
206        regs.r[0] = ATOMIC_ERROR_MASK;
207        regs.pc = atomic_error as *const () as u64;
208    }
209
210    #[cfg(target_arch = "riscv64")]
211    {
212        regs.a0 = ATOMIC_ERROR_MASK;
213        regs.pc = atomic_error as *const () as u64;
214    }
215}
216
217/// Assumes the buffer's first `initialized_until` bytes are initialized and
218/// returns the initialized and uninitialized portions.
219///
220/// # Safety
221///
222/// The caller must guarantee that `buf`'s first `initialized_until` bytes are
223/// initialized.
224unsafe fn assume_initialized_until(
225    buf: &mut [MaybeUninit<u8>],
226    initialized_until: usize,
227) -> (&mut [u8], &mut [MaybeUninit<u8>]) {
228    let (init_bytes, uninit_bytes) = buf.split_at_mut(initialized_until);
229    debug_assert_eq!(init_bytes.len(), initialized_until);
230
231    #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
232    let init_bytes = unsafe {
233        std::slice::from_raw_parts_mut(init_bytes.as_mut_ptr() as *mut u8, init_bytes.len())
234    };
235
236    (init_bytes, uninit_bytes)
237}
238
239/// Copies bytes from the source address to the destination address using the
240/// provided copy function.
241///
242/// # Safety
243///
244/// Only one of `source`/`dest` may be an address to a buffer owned by user/restricted-mode.
245/// The other must be a valid Starnix/normal-mode buffer that will never cause a fault
246/// when the first `count` bytes are read/written.
247unsafe fn do_hermetic_copy(
248    f: HermeticCopyFn,
249    dest: usize,
250    source: usize,
251    count: usize,
252    ret_dest: bool,
253) -> usize {
254    #[allow(
255        clippy::undocumented_unsafe_blocks,
256        reason = "Force documented unsafe blocks in Starnix"
257    )]
258    let unread_address = unsafe { f(dest as *mut u8, source as *const u8, count, ret_dest) };
259
260    let ret_base = if ret_dest { dest } else { source };
261
262    debug_assert!(
263        unread_address >= ret_base,
264        "unread_address={:#x}, ret_base={:#x}",
265        unread_address,
266        ret_base,
267    );
268    let copied = unread_address - ret_base;
269    debug_assert!(
270        copied <= count,
271        "copied={}, count={}; unread_address={:#x}, ret_base={:#x}",
272        copied,
273        count,
274        unread_address,
275        ret_base,
276    );
277    copied
278}
279
280impl Usercopy {
281    /// Returns a new instance of `Usercopy` if unified address spaces is
282    /// supported on the target architecture.
283    pub fn new(restricted_address_range: Range<usize>) -> Result<Self, zx::Status> {
284        let hermetic_copy_addr_range =
285            hermetic_copy as *const () as usize..hermetic_copy_end as *const () as usize;
286
287        let hermetic_copy_until_null_byte_addr_range = hermetic_copy_until_null_byte as *const ()
288            as usize
289            ..hermetic_copy_until_null_byte_end as *const () as usize;
290
291        let hermetic_zero_addr_range =
292            hermetic_zero as *const () as usize..hermetic_zero_end as *const () as usize;
293
294        let atomic_load_relaxed_range = atomic_load_u32_relaxed as *const () as usize
295            ..atomic_load_u32_relaxed_end as *const () as usize;
296
297        let atomic_load_acquire_range = atomic_load_u32_acquire as *const () as usize
298            ..atomic_load_u32_acquire_end as *const () as usize;
299
300        let atomic_store_relaxed_range = atomic_store_u32_relaxed as *const () as usize
301            ..atomic_store_u32_relaxed_end as *const () as usize;
302
303        let atomic_store_release_range = atomic_store_u32_release as *const () as usize
304            ..atomic_store_u32_release_end as *const () as usize;
305
306        let atomic_compare_exchange_range = atomic_compare_exchange_u32_acq_rel as *const ()
307            as usize
308            ..atomic_compare_exchange_u32_acq_rel_end as *const () as usize;
309
310        let atomic_compare_exchange_weak_range = atomic_compare_exchange_weak_u32_acq_rel
311            as *const () as usize
312            ..atomic_compare_exchange_weak_u32_acq_rel_end as *const () as usize;
313
314        let (tx, rx) = std::sync::mpsc::channel::<Result<(), zx::Status>>();
315
316        let shutdown_event = zx::Event::create();
317        let shutdown_event_clone =
318            shutdown_event.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap();
319
320        let faultable_addresses = restricted_address_range.clone();
321        let join_handle = std::thread::spawn(move || {
322            let exception_channel_result =
323                fuchsia_runtime::job_default().create_exception_channel();
324
325            let exception_channel = match exception_channel_result {
326                Ok(c) => c,
327                Err(e) => {
328                    let _ = tx.send(Err(e));
329                    return;
330                }
331            };
332
333            // register exception handler
334            let _ = tx.send(Ok(()));
335
336            // loop on exceptions
337            loop {
338                let mut wait_items = [
339                    exception_channel.wait_item(zx::Signals::CHANNEL_READABLE),
340                    shutdown_event_clone.wait_item(zx::Signals::USER_0),
341                ];
342                let _ = zx::object_wait_many(&mut wait_items, zx::MonotonicInstant::INFINITE);
343                if wait_items[1].pending() == zx::Signals::USER_0 {
344                    break;
345                }
346                let mut buf = zx::MessageBuf::new();
347                exception_channel.read(&mut buf).unwrap();
348
349                let excp_info = zx::sys::zx_exception_info_t::read_from_bytes(buf.bytes()).unwrap();
350
351                if excp_info.type_ != zx::sys::ZX_EXCP_FATAL_PAGE_FAULT {
352                    // Only process page faults.
353                    continue;
354                }
355
356                let excp = zx::Exception::from(buf.take_handle(0).unwrap());
357                let thread = excp.get_thread().unwrap();
358                let mut regs = thread.read_state_general_regs().unwrap();
359                let report = thread.exception_report().unwrap();
360
361                // Get the address of the instruction that triggered the fault and
362                // the address that faulted. Setup the registers such that execution
363                // restarts in the `hermetic_copy_error` method with the faulting
364                // address in the platform-specific register where the first argument
365                // is held.
366                //
367                // Note that even though the registers are modified, the registers
368                // are not written to the thread's CPU until some checks below are
369                // performed.
370                let (pc, fault_address) = parse_fault_exception(&mut regs, report);
371
372                // Only handle faults if the faulting address is within the range
373                // of faultable addresses.
374                if !faultable_addresses.contains(&fault_address) {
375                    continue;
376                }
377
378                // Only handle faults that occur within one of our usercopy routines.
379                if hermetic_copy_addr_range.contains(&pc)
380                    || hermetic_copy_until_null_byte_addr_range.contains(&pc)
381                    || hermetic_zero_addr_range.contains(&pc)
382                {
383                    set_registers_for_hermetic_error(&mut regs, fault_address);
384                } else if atomic_load_relaxed_range.contains(&pc)
385                    || atomic_load_acquire_range.contains(&pc)
386                    || atomic_store_relaxed_range.contains(&pc)
387                    || atomic_store_release_range.contains(&pc)
388                    || atomic_compare_exchange_range.contains(&pc)
389                    || atomic_compare_exchange_weak_range.contains(&pc)
390                {
391                    set_registers_for_atomic_error(&mut regs);
392                } else {
393                    continue;
394                }
395
396                thread.write_state_general_regs(regs).unwrap();
397                excp.set_exception_state(&zx::sys::ZX_EXCEPTION_STATE_HANDLED).unwrap();
398            }
399        });
400
401        rx.recv().unwrap()?;
402
403        Ok(Self { shutdown_event, join_handle: Some(join_handle), restricted_address_range })
404    }
405
406    /// Copies bytes from the source address to the destination address.
407    ///
408    /// # Safety
409    ///
410    /// Only one of `source`/`dest` may be an address to a buffer owned by user/restricted-mode
411    /// (`ret_dest` indicates whether the user-owned buffer is `dest` when `true`).
412    /// The other must be a valid Starnix/normal-mode buffer that will never cause a fault
413    /// when the first `count` bytes are read/written.
414    pub unsafe fn raw_hermetic_copy(
415        &self,
416        dest: *mut u8,
417        source: *const u8,
418        count: usize,
419        ret_dest: bool,
420    ) -> usize {
421        #[allow(clippy::undocumented_unsafe_blocks, reason = "2024 edition migration")]
422        unsafe {
423            do_hermetic_copy(hermetic_copy, dest as usize, source as usize, count, ret_dest)
424        }
425    }
426
427    /// Zeros `count` bytes to starting at `dest_addr`.
428    ///
429    /// Returns the number of bytes zeroed.
430    pub fn zero(&self, dest_addr: usize, count: usize) -> usize {
431        // Assumption: The address 0 is invalid and cannot be mapped.  The error encoding scheme has
432        // a collision on the value 0 - it could mean that there was a fault at the address 0 or
433        // that there was no fault. We want to treat an attempt to copy to 0 as a fault always.
434        if dest_addr == 0 || !self.restricted_address_range.contains(&dest_addr) {
435            return 0;
436        }
437
438        #[allow(
439            clippy::undocumented_unsafe_blocks,
440            reason = "Force documented unsafe blocks in Starnix"
441        )]
442        let unset_address = unsafe { hermetic_zero(dest_addr as *mut u8, count) };
443        debug_assert!(
444            unset_address >= dest_addr,
445            "unset_address={:#x}, dest_addr={:#x}",
446            unset_address,
447            dest_addr,
448        );
449        let bytes_set = unset_address - dest_addr;
450        debug_assert!(
451            bytes_set <= count,
452            "bytes_set={}, count={}; unset_address={:#x}, dest_addr={:#x}",
453            bytes_set,
454            count,
455            unset_address,
456            dest_addr,
457        );
458        bytes_set
459    }
460
461    /// Copies data from `source` to the restricted address `dest_addr`.
462    ///
463    /// Returns the number of bytes copied.
464    pub fn copyout(&self, source: &[u8], dest_addr: usize) -> usize {
465        // Assumption: The address 0 is invalid and cannot be mapped.  The error encoding scheme has
466        // a collision on the value 0 - it could mean that there was a fault at the address 0 or
467        // that there was no fault. We want to treat an attempt to copy to 0 as a fault always.
468        if dest_addr == 0 || !self.restricted_address_range.contains(&dest_addr) {
469            return 0;
470        }
471
472        // SAFETY: `source` is a valid Starnix-owned buffer and `dest_addr` is the user-mode
473        // buffer.
474        unsafe {
475            do_hermetic_copy(hermetic_copy, dest_addr, source.as_ptr() as usize, source.len(), true)
476        }
477    }
478
479    /// Copies data from the restricted address `source_addr` to `dest`.
480    ///
481    /// Returns the read and unread bytes.
482    ///
483    /// The returned slices will always reference `dest`. Because of this, it is
484    /// guaranteed that that `dest` and the returned initialized slice will have
485    /// the same address.
486    pub fn copyin<'a>(
487        &self,
488        source_addr: usize,
489        dest: &'a mut [MaybeUninit<u8>],
490    ) -> (&'a mut [u8], &'a mut [MaybeUninit<u8>]) {
491        // Assumption: The address 0 is invalid and cannot be mapped.  The error encoding scheme has
492        // a collision on the value 0 - it could mean that there was a fault at the address 0 or
493        // that there was no fault. We want to treat an attempt to copy from 0 as a fault always.
494        let read_count =
495            if source_addr == 0 || !self.restricted_address_range.contains(&source_addr) {
496                0
497            } else {
498                // SAFETY: `dest` is a valid Starnix-owned buffer and `source_addr` is the user-mode
499                // buffer.
500                unsafe {
501                    do_hermetic_copy(
502                        hermetic_copy,
503                        dest.as_ptr() as usize,
504                        source_addr,
505                        dest.len(),
506                        false,
507                    )
508                }
509            };
510
511        // SAFETY: `dest`'s first `read_count` bytes are initialized.
512        unsafe { assume_initialized_until(dest, read_count) }
513    }
514
515    /// Copies data from the restricted address `source_addr` to `dest` until the
516    /// first null byte.
517    ///
518    /// Returns the read and unread bytes. The read bytes includes the null byte
519    /// if present.
520    ///
521    /// The returned slices will always reference `dest`. Because of this, it is
522    /// guaranteed that that `dest` and the returned initialized slice will have
523    /// the same address.
524    pub fn copyin_until_null_byte<'a>(
525        &self,
526        source_addr: usize,
527        dest: &'a mut [MaybeUninit<u8>],
528    ) -> (&'a mut [u8], &'a mut [MaybeUninit<u8>]) {
529        // Assumption: The address 0 is invalid and cannot be mapped.  The error encoding scheme has
530        // a collision on the value 0 - it could mean that there was a fault at the address 0 or
531        // that there was no fault. We want to treat an attempt to copy from 0 as a fault always.
532        let read_count =
533            if source_addr == 0 || !self.restricted_address_range.contains(&source_addr) {
534                0
535            } else {
536                // SAFETY: `dest` is a valid Starnix-owned buffer and `source_addr` is the user-mode
537                // buffer.
538                unsafe {
539                    do_hermetic_copy(
540                        hermetic_copy_until_null_byte,
541                        dest.as_ptr() as usize,
542                        source_addr,
543                        dest.len(),
544                        false,
545                    )
546                }
547            };
548
549        // SAFETY: `dest`'s first `read_count` bytes are initialized
550        unsafe { assume_initialized_until(dest, read_count) }
551    }
552
553    #[inline]
554    fn atomic_load_u32(
555        &self,
556        load_fn: unsafe extern "C" fn(usize) -> u64,
557        addr: usize,
558    ) -> Result<u32, ()> {
559        #[allow(
560            clippy::undocumented_unsafe_blocks,
561            reason = "Force documented unsafe blocks in Starnix"
562        )]
563        let value_or_error = unsafe { load_fn(addr) };
564        if value_or_error & ATOMIC_ERROR_MASK == 0 { Ok(value_or_error as u32) } else { Err(()) }
565    }
566
567    /// Performs an atomic load of a 32 bit value at `addr`.
568    /// `addr` must be aligned to 4 bytes.
569    pub fn atomic_load_u32_relaxed(&self, addr: usize) -> Result<u32, ()> {
570        self.atomic_load_u32(atomic_load_u32_relaxed, addr)
571    }
572
573    /// Performs an atomic load of a 32 bit value at `addr`.
574    /// `addr` must be aligned to 4 bytes.
575    pub fn atomic_load_u32_acquire(&self, addr: usize) -> Result<u32, ()> {
576        self.atomic_load_u32(atomic_load_u32_acquire, addr)
577    }
578
579    fn atomic_store_u32(
580        &self,
581        store_fn: unsafe extern "C" fn(usize, u32) -> u64,
582        addr: usize,
583        value: u32,
584    ) -> Result<(), ()> {
585        #[allow(
586            clippy::undocumented_unsafe_blocks,
587            reason = "Force documented unsafe blocks in Starnix"
588        )]
589        match unsafe { store_fn(addr, value) } {
590            0 => Ok(()),
591            _ => Err(()),
592        }
593    }
594
595    /// Performs an atomic store of a 32 bit value to `addr`.
596    /// `addr` must be aligned to 4 bytes.
597    pub fn atomic_store_u32_relaxed(&self, addr: usize, value: u32) -> Result<(), ()> {
598        self.atomic_store_u32(atomic_store_u32_relaxed, addr, value)
599    }
600
601    /// Performs an atomic store of a 32 bit value to `addr`.
602    /// `addr` must be aligned to 4 bytes.
603    pub fn atomic_store_u32_release(&self, addr: usize, value: u32) -> Result<(), ()> {
604        self.atomic_store_u32(atomic_store_u32_release, addr, value)
605    }
606
607    /// Performs an atomic compare and exchange of a 32 bit value at addr `addr`.
608    /// `addr` must be aligned to 4 bytes.
609    pub fn atomic_compare_exchange_u32_acq_rel(
610        &self,
611        addr: usize,
612        expected: u32,
613        desired: u32,
614    ) -> Result<Result<u32, u32>, ()> {
615        let mut expected = expected;
616        #[allow(
617            clippy::undocumented_unsafe_blocks,
618            reason = "Force documented unsafe blocks in Starnix"
619        )]
620        let value_or_error = unsafe {
621            atomic_compare_exchange_u32_acq_rel(addr, &mut expected as *mut u32, desired)
622        };
623        Self::parse_compare_exchange_result(expected, value_or_error)
624    }
625
626    /// Performs a weak atomic compare and exchange of a 32 bit value at addr `addr`.
627    /// `addr` must be aligned to 4 bytes.
628    pub fn atomic_compare_exchange_weak_u32_acq_rel(
629        &self,
630        addr: usize,
631        expected: u32,
632        desired: u32,
633    ) -> Result<Result<u32, u32>, ()> {
634        let mut expected = expected;
635        #[allow(
636            clippy::undocumented_unsafe_blocks,
637            reason = "Force documented unsafe blocks in Starnix"
638        )]
639        let value_or_error = unsafe {
640            atomic_compare_exchange_weak_u32_acq_rel(addr, &mut expected as *mut u32, desired)
641        };
642        Self::parse_compare_exchange_result(expected, value_or_error)
643    }
644
645    fn parse_compare_exchange_result(
646        expected: u32,
647        value_or_error: u64,
648    ) -> Result<Result<u32, u32>, ()> {
649        match value_or_error {
650            0 => Ok(Err(expected)),
651            1 => Ok(Ok(expected)),
652            _ => Err(()),
653        }
654    }
655}
656
657impl Drop for Usercopy {
658    fn drop(&mut self) {
659        self.shutdown_event.signal(zx::Signals::empty(), zx::Signals::USER_0).unwrap();
660        self.join_handle.take().unwrap().join().unwrap();
661    }
662}
663
664#[cfg(test)]
665mod test {
666    #![allow(
667        clippy::undocumented_unsafe_blocks,
668        reason = "Force documented unsafe blocks in Starnix"
669    )]
670    use super::*;
671
672    use test_case::test_case;
673
674    impl Usercopy {
675        fn new_for_test(restricted_address_range: Range<usize>) -> Self {
676            Self::new(restricted_address_range).unwrap()
677        }
678    }
679
680    #[test_case(0, 0)]
681    #[test_case(1, 1)]
682    #[test_case(7, 2)]
683    #[test_case(8, 3)]
684    #[test_case(9, 4)]
685    #[test_case(128, 5)]
686    #[test_case(zx::system_get_page_size() as usize - 1, 6)]
687    #[test_case(zx::system_get_page_size() as usize, 7)]
688    #[::fuchsia::test]
689    fn zero_no_fault(zero_len: usize, ch: u8) {
690        let page_size = zx::system_get_page_size() as usize;
691
692        let dest_vmo = zx::Vmo::create(page_size as u64).unwrap();
693
694        let root_vmar = fuchsia_runtime::vmar_root_self();
695
696        let mapped_addr = root_vmar
697            .map(0, &dest_vmo, 0, page_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
698            .unwrap();
699        let mapped_bytes =
700            unsafe { std::slice::from_raw_parts_mut(mapped_addr as *mut u8, page_size) };
701        mapped_bytes.fill(ch);
702
703        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size);
704
705        let result = usercopy.zero(mapped_addr, zero_len);
706        assert_eq!(result, zero_len);
707
708        assert_eq!(&mapped_bytes[..zero_len], &vec![0; zero_len]);
709        assert_eq!(&mapped_bytes[zero_len..], &vec![ch; page_size - zero_len]);
710    }
711
712    #[test_case(1, 2, 0)]
713    #[test_case(1, 4, 1)]
714    #[test_case(1, 8, 2)]
715    #[test_case(1, 16, 3)]
716    #[test_case(1, 32, 4)]
717    #[test_case(1, 64, 5)]
718    #[test_case(1, 128, 6)]
719    #[test_case(1, 256, 7)]
720    #[test_case(1, 512, 8)]
721    #[test_case(1, 1024, 9)]
722    #[test_case(32, 64, 10)]
723    #[test_case(32, 128, 11)]
724    #[test_case(32, 256, 12)]
725    #[test_case(32, 512, 13)]
726    #[test_case(32, 1024, 14)]
727    #[::fuchsia::test]
728    fn zero_fault(offset: usize, zero_len: usize, ch: u8) {
729        let page_size = zx::system_get_page_size() as usize;
730
731        let dest_vmo = zx::Vmo::create(page_size as u64).unwrap();
732
733        let root_vmar = fuchsia_runtime::vmar_root_self();
734
735        let mapped_addr = root_vmar
736            .map(
737                0,
738                &dest_vmo,
739                0,
740                page_size * 2,
741                zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
742            )
743            .unwrap();
744        let mapped_bytes =
745            unsafe { std::slice::from_raw_parts_mut(mapped_addr as *mut u8, page_size) };
746        mapped_bytes.fill(ch);
747
748        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size * 2);
749
750        let dest_addr = mapped_addr + page_size - offset;
751
752        let result = usercopy.zero(dest_addr, zero_len);
753        assert_eq!(result, offset);
754
755        assert_eq!(&mapped_bytes[page_size - offset..], &vec![0; offset][..]);
756        assert_eq!(&mapped_bytes[..page_size - offset], &vec![ch; page_size - offset][..]);
757    }
758
759    #[test_case(0)]
760    #[test_case(1)]
761    #[test_case(7)]
762    #[test_case(8)]
763    #[test_case(9)]
764    #[test_case(128)]
765    #[test_case(zx::system_get_page_size() as usize - 1)]
766    #[test_case(zx::system_get_page_size() as usize)]
767    #[::fuchsia::test]
768    fn copyout_no_fault(buf_len: usize) {
769        let page_size = zx::system_get_page_size() as usize;
770
771        let source = vec!['a' as u8; buf_len];
772
773        let dest_vmo = zx::Vmo::create(page_size as u64).unwrap();
774
775        let root_vmar = fuchsia_runtime::vmar_root_self();
776
777        let mapped_addr = root_vmar
778            .map(0, &dest_vmo, 0, page_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
779            .unwrap();
780
781        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size);
782
783        let result = usercopy.copyout(&source, mapped_addr);
784        assert_eq!(result, buf_len);
785
786        assert_eq!(
787            unsafe { std::slice::from_raw_parts(mapped_addr as *const u8, buf_len) },
788            &vec!['a' as u8; buf_len]
789        );
790    }
791
792    #[test_case(1, 2)]
793    #[test_case(1, 4)]
794    #[test_case(1, 8)]
795    #[test_case(1, 16)]
796    #[test_case(1, 32)]
797    #[test_case(1, 64)]
798    #[test_case(1, 128)]
799    #[test_case(1, 256)]
800    #[test_case(1, 512)]
801    #[test_case(1, 1024)]
802    #[test_case(32, 64)]
803    #[test_case(32, 128)]
804    #[test_case(32, 256)]
805    #[test_case(32, 512)]
806    #[test_case(32, 1024)]
807    #[::fuchsia::test]
808    fn copyout_fault(offset: usize, buf_len: usize) {
809        let page_size = zx::system_get_page_size() as usize;
810
811        let source = vec!['a' as u8; buf_len];
812
813        let dest_vmo = zx::Vmo::create(page_size as u64).unwrap();
814
815        let root_vmar = fuchsia_runtime::vmar_root_self();
816
817        let mapped_addr = root_vmar
818            .map(
819                0,
820                &dest_vmo,
821                0,
822                page_size * 2,
823                zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
824            )
825            .unwrap();
826
827        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size * 2);
828
829        let dest_addr = mapped_addr + page_size - offset;
830
831        let result = usercopy.copyout(&source, dest_addr);
832
833        assert_eq!(result, offset);
834
835        assert_eq!(
836            unsafe { std::slice::from_raw_parts(dest_addr as *const u8, offset) },
837            &vec!['a' as u8; offset][..],
838        );
839    }
840
841    #[test_case(0)]
842    #[test_case(1)]
843    #[test_case(7)]
844    #[test_case(8)]
845    #[test_case(9)]
846    #[test_case(128)]
847    #[test_case(zx::system_get_page_size() as usize - 1)]
848    #[test_case(zx::system_get_page_size() as usize)]
849    #[::fuchsia::test]
850    fn copyin_no_fault(buf_len: usize) {
851        let page_size = zx::system_get_page_size() as usize;
852
853        let mut dest = Vec::with_capacity(buf_len);
854
855        let source_vmo = zx::Vmo::create(page_size as u64).unwrap();
856
857        let root_vmar = fuchsia_runtime::vmar_root_self();
858
859        let mapped_addr = root_vmar
860            .map(0, &source_vmo, 0, page_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
861            .unwrap();
862
863        unsafe { std::slice::from_raw_parts_mut(mapped_addr as *mut u8, buf_len) }.fill('a' as u8);
864
865        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size);
866        let dest_as_mut_ptr = dest.as_mut_ptr();
867        let (read_bytes, unread_bytes) = usercopy.copyin(mapped_addr, dest.spare_capacity_mut());
868        let expected = vec!['a' as u8; buf_len];
869        assert_eq!(read_bytes, &expected);
870        assert_eq!(unread_bytes.len(), 0);
871        assert_eq!(read_bytes.as_mut_ptr(), dest_as_mut_ptr);
872
873        // SAFETY: OK because the copyin was successful.
874        unsafe { dest.set_len(buf_len) }
875        assert_eq!(dest, expected);
876    }
877
878    #[test_case(1, 2)]
879    #[test_case(1, 4)]
880    #[test_case(1, 8)]
881    #[test_case(1, 16)]
882    #[test_case(1, 32)]
883    #[test_case(1, 64)]
884    #[test_case(1, 128)]
885    #[test_case(1, 256)]
886    #[test_case(1, 512)]
887    #[test_case(1, 1024)]
888    #[test_case(32, 64)]
889    #[test_case(32, 128)]
890    #[test_case(32, 256)]
891    #[test_case(32, 512)]
892    #[test_case(32, 1024)]
893    #[::fuchsia::test]
894    fn copyin_fault(offset: usize, buf_len: usize) {
895        let page_size = zx::system_get_page_size() as usize;
896
897        let mut dest = vec![0u8; buf_len];
898
899        let source_vmo = zx::Vmo::create(page_size as u64).unwrap();
900
901        let root_vmar = fuchsia_runtime::vmar_root_self();
902
903        let mapped_addr = root_vmar
904            .map(
905                0,
906                &source_vmo,
907                0,
908                page_size * 2,
909                zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
910            )
911            .unwrap();
912
913        let source_addr = mapped_addr + page_size - offset;
914
915        unsafe { std::slice::from_raw_parts_mut(source_addr as *mut u8, offset) }.fill('a' as u8);
916
917        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size * 2);
918
919        let (read_bytes, unread_bytes) =
920            usercopy.copyin(source_addr, slice_to_maybe_uninit_mut(&mut dest));
921        let expected_copied = vec!['a' as u8; offset];
922        let expected_uncopied = vec![0 as u8; buf_len - offset];
923        assert_eq!(read_bytes, &expected_copied);
924        assert_eq!(unread_bytes.len(), expected_uncopied.len());
925
926        assert_eq!(&dest[0..offset], &expected_copied);
927        assert_eq!(&dest[offset..], &expected_uncopied);
928    }
929
930    #[test_case(0)]
931    #[test_case(1)]
932    #[test_case(7)]
933    #[test_case(8)]
934    #[test_case(9)]
935    #[test_case(128)]
936    #[test_case(zx::system_get_page_size() as usize - 1)]
937    #[test_case(zx::system_get_page_size() as usize)]
938    #[::fuchsia::test]
939    fn copyin_until_null_byte_no_fault(buf_len: usize) {
940        let page_size = zx::system_get_page_size() as usize;
941
942        let mut dest = Vec::with_capacity(buf_len);
943
944        let source_vmo = zx::Vmo::create(page_size as u64).unwrap();
945
946        let root_vmar = fuchsia_runtime::vmar_root_self();
947
948        let mapped_addr = root_vmar
949            .map(0, &source_vmo, 0, page_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
950            .unwrap();
951
952        unsafe { std::slice::from_raw_parts_mut(mapped_addr as *mut u8, buf_len) }.fill('a' as u8);
953
954        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size);
955
956        let dest_as_mut_ptr = dest.as_mut_ptr();
957        let (read_bytes, unread_bytes) =
958            usercopy.copyin_until_null_byte(mapped_addr, dest.spare_capacity_mut());
959        let expected = vec!['a' as u8; buf_len];
960        assert_eq!(read_bytes, &expected);
961        assert_eq!(unread_bytes.len(), 0);
962        assert_eq!(read_bytes.as_mut_ptr(), dest_as_mut_ptr);
963
964        // SAFETY: OK because the copyin_until_null_byte was successful.
965        unsafe { dest.set_len(dest.capacity()) }
966        assert_eq!(dest, expected);
967    }
968
969    #[test_case(1, 2)]
970    #[test_case(1, 4)]
971    #[test_case(1, 8)]
972    #[test_case(1, 16)]
973    #[test_case(1, 32)]
974    #[test_case(1, 64)]
975    #[test_case(1, 128)]
976    #[test_case(1, 256)]
977    #[test_case(1, 512)]
978    #[test_case(1, 1024)]
979    #[test_case(32, 64)]
980    #[test_case(32, 128)]
981    #[test_case(32, 256)]
982    #[test_case(32, 512)]
983    #[test_case(32, 1024)]
984    #[::fuchsia::test]
985    fn copyin_until_null_byte_fault(offset: usize, buf_len: usize) {
986        let page_size = zx::system_get_page_size() as usize;
987
988        let mut dest = vec![0u8; buf_len];
989
990        let source_vmo = zx::Vmo::create(page_size as u64).unwrap();
991
992        let root_vmar = fuchsia_runtime::vmar_root_self();
993
994        let mapped_addr = root_vmar
995            .map(
996                0,
997                &source_vmo,
998                0,
999                page_size * 2,
1000                zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE,
1001            )
1002            .unwrap();
1003
1004        let source_addr = mapped_addr + page_size - offset;
1005
1006        unsafe { std::slice::from_raw_parts_mut(source_addr as *mut u8, offset) }.fill('a' as u8);
1007
1008        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size * 2);
1009
1010        let (read_bytes, unread_bytes) =
1011            usercopy.copyin_until_null_byte(source_addr, slice_to_maybe_uninit_mut(&mut dest));
1012        let expected_copied = vec!['a' as u8; offset];
1013        let expected_uncopied = vec![0 as u8; buf_len - offset];
1014        assert_eq!(read_bytes, &expected_copied);
1015        assert_eq!(unread_bytes.len(), expected_uncopied.len());
1016
1017        assert_eq!(&dest[0..offset], &expected_copied);
1018        assert_eq!(&dest[offset..], &expected_uncopied);
1019    }
1020
1021    #[test_case(0)]
1022    #[test_case(1)]
1023    #[test_case(2)]
1024    #[test_case(126)]
1025    #[test_case(127)]
1026    #[::fuchsia::test]
1027    fn copyin_until_null_byte_no_fault_with_zero(zero_idx: usize) {
1028        const DEST_LEN: usize = 128;
1029
1030        let page_size = zx::system_get_page_size() as usize;
1031
1032        let mut dest = vec!['b' as u8; DEST_LEN];
1033
1034        let source_vmo = zx::Vmo::create(page_size as u64).unwrap();
1035
1036        let root_vmar = fuchsia_runtime::vmar_root_self();
1037
1038        let mapped_addr = root_vmar
1039            .map(0, &source_vmo, 0, page_size, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE)
1040            .unwrap();
1041
1042        {
1043            let slice =
1044                unsafe { std::slice::from_raw_parts_mut(mapped_addr as *mut u8, dest.len()) };
1045            slice.fill('a' as u8);
1046            slice[zero_idx] = 0;
1047        };
1048
1049        let usercopy = Usercopy::new_for_test(mapped_addr..mapped_addr + page_size);
1050
1051        let (read_bytes, unread_bytes) =
1052            usercopy.copyin_until_null_byte(mapped_addr, slice_to_maybe_uninit_mut(&mut dest));
1053        let expected_copied_non_zero_bytes = vec!['a' as u8; zero_idx];
1054        let expected_uncopied = vec!['b' as u8; DEST_LEN - zero_idx - 1];
1055        assert_eq!(&read_bytes[..zero_idx], &expected_copied_non_zero_bytes);
1056        assert_eq!(&read_bytes[zero_idx..], &[0]);
1057        assert_eq!(unread_bytes.len(), expected_uncopied.len());
1058
1059        assert_eq!(&dest[..zero_idx], &expected_copied_non_zero_bytes);
1060        assert_eq!(dest[zero_idx], 0);
1061        assert_eq!(&dest[zero_idx + 1..], &expected_uncopied);
1062    }
1063
1064    #[test_case(0..1, 0)]
1065    #[test_case(0..1, 1)]
1066    #[test_case(0..1, 2)]
1067    #[test_case(5..10, 0)]
1068    #[test_case(5..10, 1)]
1069    #[test_case(5..10, 2)]
1070    #[test_case(5..10, 5)]
1071    #[test_case(5..10, 7)]
1072    #[test_case(5..10, 10)]
1073    #[::fuchsia::test]
1074    fn starting_fault_address_copyin_until_null_byte(range: Range<usize>, addr: usize) {
1075        let usercopy = Usercopy::new_for_test(range);
1076
1077        let mut dest = vec![0u8];
1078
1079        let (read_bytes, unread_bytes) =
1080            usercopy.copyin_until_null_byte(addr, slice_to_maybe_uninit_mut(&mut dest));
1081        assert_eq!(read_bytes, &[] as &[u8]);
1082        assert_eq!(unread_bytes.len(), dest.len());
1083        assert_eq!(dest, [0]);
1084    }
1085
1086    #[test_case(0..1, 0)]
1087    #[test_case(0..1, 1)]
1088    #[test_case(0..1, 2)]
1089    #[test_case(5..10, 0)]
1090    #[test_case(5..10, 1)]
1091    #[test_case(5..10, 2)]
1092    #[test_case(5..10, 5)]
1093    #[test_case(5..10, 7)]
1094    #[test_case(5..10, 10)]
1095    #[::fuchsia::test]
1096    fn starting_fault_address_copyin(range: Range<usize>, addr: usize) {
1097        let usercopy = Usercopy::new_for_test(range);
1098
1099        let mut dest = vec![0u8];
1100
1101        let (read_bytes, unread_bytes) =
1102            usercopy.copyin(addr, slice_to_maybe_uninit_mut(&mut dest));
1103        assert_eq!(read_bytes, &[] as &[u8]);
1104        assert_eq!(unread_bytes.len(), dest.len());
1105        assert_eq!(dest, [0]);
1106    }
1107
1108    #[test_case(0..1, 0)]
1109    #[test_case(0..1, 1)]
1110    #[test_case(0..1, 2)]
1111    #[test_case(5..10, 0)]
1112    #[test_case(5..10, 1)]
1113    #[test_case(5..10, 2)]
1114    #[test_case(5..10, 5)]
1115    #[test_case(5..10, 7)]
1116    #[test_case(5..10, 10)]
1117    #[::fuchsia::test]
1118    fn starting_fault_address_copyout(range: Range<usize>, addr: usize) {
1119        let usercopy = Usercopy::new_for_test(range);
1120
1121        let source = vec![0u8];
1122
1123        let result = usercopy.copyout(&source, addr);
1124        assert_eq!(result, 0);
1125        assert_eq!(source, [0]);
1126    }
1127    struct MappedPageUsercopy {
1128        usercopy: Usercopy,
1129        addr: usize,
1130    }
1131
1132    impl MappedPageUsercopy {
1133        fn new(flags: zx::VmarFlags) -> Self {
1134            let page_size = zx::system_get_page_size() as usize;
1135
1136            let vmo = zx::Vmo::create(page_size as u64).unwrap();
1137
1138            let root_vmar = fuchsia_runtime::vmar_root_self();
1139
1140            let addr = root_vmar.map(0, &vmo, 0, page_size, flags).unwrap();
1141
1142            let usercopy = Usercopy::new_for_test(addr..addr + page_size);
1143            Self { usercopy, addr }
1144        }
1145    }
1146
1147    impl std::ops::Drop for MappedPageUsercopy {
1148        fn drop(&mut self) {
1149            let page_size = zx::system_get_page_size() as usize;
1150
1151            unsafe { fuchsia_runtime::vmar_root_self().unmap(self.addr, page_size) }.unwrap();
1152        }
1153    }
1154
1155    #[test_case(|usercopy, mapped_addr| usercopy.atomic_load_u32_relaxed(mapped_addr); "relaxed")]
1156    #[test_case(|usercopy, mapped_addr| usercopy.atomic_load_u32_acquire(mapped_addr); "acquire")]
1157    #[::fuchsia::test]
1158    fn atomic_load_u32_no_fault(load_fn: fn(&Usercopy, usize) -> Result<u32, ()>) {
1159        let m = MappedPageUsercopy::new(zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE);
1160
1161        unsafe { *(m.addr as *mut u32) = 0x12345678 };
1162
1163        let result = load_fn(&m.usercopy, m.addr);
1164
1165        assert_eq!(Ok(0x12345678), result);
1166    }
1167
1168    #[test_case(|usercopy, mapped_addr| usercopy.atomic_load_u32_relaxed(mapped_addr); "relaxed")]
1169    #[test_case(|usercopy, mapped_addr| usercopy.atomic_load_u32_acquire(mapped_addr); "acquire")]
1170    #[::fuchsia::test]
1171    fn atomic_load_u32_fault(load_fn: fn(&Usercopy, usize) -> Result<u32, ()>) {
1172        let m = MappedPageUsercopy::new(zx::VmarFlags::empty());
1173
1174        let result = load_fn(&m.usercopy, m.addr);
1175        assert_eq!(Err(()), result);
1176    }
1177
1178    #[test_case(|usercopy, mapped_addr, val| usercopy.atomic_store_u32_relaxed(mapped_addr, val); "relaxed")]
1179    #[test_case(|usercopy, mapped_addr, val| usercopy.atomic_store_u32_release(mapped_addr, val); "release")]
1180    #[::fuchsia::test]
1181    fn atomic_store_u32_no_fault(store_fn: fn(&Usercopy, usize, u32) -> Result<(), ()>) {
1182        let m = MappedPageUsercopy::new(zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE);
1183
1184        assert_eq!(store_fn(&m.usercopy, m.addr, 0x12345678), Ok(()));
1185
1186        assert_eq!(unsafe { *(m.addr as *mut u32) }, 0x12345678);
1187    }
1188
1189    #[test_case(|usercopy, mapped_addr, val| usercopy.atomic_store_u32_relaxed(mapped_addr, val); "relaxed")]
1190    #[test_case(|usercopy, mapped_addr, val| usercopy.atomic_store_u32_release(mapped_addr, val); "release")]
1191    #[::fuchsia::test]
1192    fn atomic_store_u32_fault(store_fn: fn(&Usercopy, usize, u32) -> Result<(), ()>) {
1193        let m = MappedPageUsercopy::new(zx::VmarFlags::empty());
1194
1195        let result = store_fn(&m.usercopy, m.addr, 0x12345678);
1196        assert_eq!(Err(()), result);
1197
1198        let page_size = zx::system_get_page_size() as usize;
1199        unsafe {
1200            fuchsia_runtime::vmar_root_self().protect(m.addr, page_size, zx::VmarFlags::PERM_READ)
1201        }
1202        .unwrap();
1203
1204        assert_ne!(unsafe { *(m.addr as *mut u32) }, 0x12345678);
1205    }
1206
1207    #[::fuchsia::test]
1208    fn atomic_compare_exchange_u32_acq_rel_no_fault() {
1209        let m = MappedPageUsercopy::new(zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE);
1210
1211        unsafe { *(m.addr as *mut u32) = 0x12345678 };
1212
1213        assert_eq!(
1214            m.usercopy.atomic_compare_exchange_u32_acq_rel(m.addr, 0x12345678, 0xffffffff),
1215            Ok(Ok(0x12345678))
1216        );
1217
1218        assert_eq!(unsafe { *(m.addr as *mut u32) }, 0xffffffff);
1219
1220        assert_eq!(
1221            m.usercopy.atomic_compare_exchange_u32_acq_rel(m.addr, 0x22222222, 0x11111111),
1222            Ok(Err(0xffffffff))
1223        );
1224
1225        assert_eq!(unsafe { *(m.addr as *mut u32) }, 0xffffffff);
1226    }
1227
1228    #[::fuchsia::test]
1229    fn atomic_compare_exchange_u32_acq_rel_fault() {
1230        let m = MappedPageUsercopy::new(zx::VmarFlags::empty());
1231
1232        let result = m.usercopy.atomic_compare_exchange_u32_acq_rel(m.addr, 0x00000000, 0x11111111);
1233        assert_eq!(Err(()), result);
1234
1235        let page_size = zx::system_get_page_size() as usize;
1236        unsafe {
1237            fuchsia_runtime::vmar_root_self().protect(m.addr, page_size, zx::VmarFlags::PERM_READ)
1238        }
1239        .unwrap();
1240
1241        assert_eq!(unsafe { *(m.addr as *mut u32) }, 0x00000000);
1242    }
1243}