Challenge 7: Safety of Methods for Atomic Types & Atomic Intrinsics
- Status: Open
- Tracking Issue: #83
- Start date: 2024/10/30
- End date: 2025/04/10
- Reward: 10,000 USD
Goal
core::sync::atomic provides methods that operate on atomic types.
For example, AtomicBool::store(&self, val: bool, order: Ordering) stores val in the atomic boolean referenced by self according to the specified atomic memory ordering.
The goal of this challenge is to verify that these methods are safe.1
Success Criteria
Part 1: Unsafe Methods
First, verify that the unsafe from_ptr methods are safe, given that their safety preconditions are met.
Write safety contracts for each of the from_ptr methods:
AtomicBool::from_ptrAtomicPtr::from_ptrAtomicI8::from_ptrAtomicU8::from_ptrAtomicI16::from_ptrAtomicU16::from_ptrAtomicI32::from_ptrAtomicU32::from_ptrAtomicI64::from_ptrAtomicU64::from_ptrAtomicI128::from_ptrAtomicU128::from_ptr
Specifically, encode the conditions about ptr's alignment and validity (marked #Safety in the methods' documentation) as preconditions.
Then, verify that the methods are safe for all possible values for the type that ptr points to, given that ptr satisfies those preconditions.
For example, AtomicI8::from_ptr is defined as:
/// `ptr` must be [valid] ... (comment continues; omitted for brevity)
pub const unsafe fn from_ptr<'a>(ptr: *mut i8) -> &'a AtomicI8 {
// SAFETY: guaranteed by the caller
unsafe { &*ptr.cast() }
}
To verify this method, first encode the safety comments (e.g., about pointer validity) as preconditions, then verify the absence of undefined behavior for all possible i8 values.
For the AtomicPtr case only, we do not require that you verify safety for all possible types.
Concretely, below is the type signature for AtomicPtr::from_ptr:
pub const unsafe fn from_ptr<'a>(ptr: *mut *mut T) -> &'a AtomicPtr<T>
The type pointed to is a *mut T.
Verify that for any arbitrary value for this *mut T (i.e., any arbitrary memory address), the method is safe.
However, you need not verify the method for all possible instantiations of T.
It suffices to verify this method for T of byte sizes 0, 1, 2, 4, and at least one non-power of two.
Part 2: Safe Abstractions
The atomic types expose safe abstractions for atomic operations. In this part, you will work toward verifying that these abstractions are indeed safe by writing and verifying safety contracts for the unsafe code in their bodies.
For example, AtomicBool::store is the (public) safe method that atomically updates the boolean's value.
This method invokes the unsafe function atomic_store, which in turn calls intrinsics::atomic_store_relaxed, intrinsics::atomic_store_release, or intrinsics::atomic_store_seqcst, depending on the provided ordering.
Write and verify safety contracts for the unsafe functions:
atomic_storeatomic_loadatomic_swapatomic_addatomic_subatomic_compare_exchangeatomic_compare_exchange_weakatomic_andatomic_nandatomic_oratomic_xoratomic_maxatomic_umaxatomic_umin
Panicking (Optional)
Then, for each of the safe abstractions that invoke the unsafe functions listed above, write contracts that ensure that they are not invoked with orders that would cause panics.
For example, atomic_store panics if invoked with Acquire or AcqRel ordering.
In this case, you would write contracts on the safe store methods that enforce that they are not called with either of those orders.
This section is not required to complete the challenge, since panicking is not undefined behavior. However, it would be incorrect for someone to call these functions with the wrong arguments, so we encourage providing these specifications.
Part 3: Atomic Intrinsics
Write safety contracts for the intrinsics invoked by the unsafe functions from Part 2 (in core::intrinsics):
| Operations | Functions |
|---|---|
| Store | atomic_store_relaxed, atomic_store_release, atomic_store_seqcst |
| Load | atomic_load_relaxed, atomic_load_acquire, atomic_load_seqcst |
| Swap | atomic_xchg_relaxed, atomic_xchg_acquire, atomic_xchg_release, atomic_xchg_acqrel, atomic_xchg_seqcst |
| Add | atomic_xadd_relaxed, atomic_xadd_acquire, atomic_xadd_release, atomic_xadd_acqrel, atomic_xadd_seqcst |
| Sub | atomic_xsub_relaxed, atomic_xsub_acquire, atomic_xsub_release, atomic_xsub_acqrel, atomic_xsub_seqcst |
| Compare Exchange | atomic_cxchg_relaxed_relaxed, atomic_cxchg_relaxed_acquire, atomic_cxchg_relaxed_seqcst, atomic_cxchg_acquire_relaxed, atomic_cxchg_acquire_acquire, atomic_cxchg_acquire_seqcst, atomic_cxchg_release_relaxed, atomic_cxchg_release_acquire, atomic_cxchg_release_seqcst, atomic_cxchg_acqrel_relaxed, atomic_cxchg_acqrel_acquire, atomic_cxchg_acqrel_seqcst, atomic_cxchg_seqcst_relaxed, atomic_cxchg_seqcst_acquire, atomic_cxchg_seqcst_seqcst |
| Compare Exchange Weak | atomic_cxchgweak_relaxed_relaxed, atomic_cxchgweak_relaxed_acquire, atomic_cxchgweak_relaxed_seqcst, atomic_cxchgweak_acquire_relaxed, atomic_cxchgweak_acquire_acquire, atomic_cxchgweak_acquire_seqcst, atomic_cxchgweak_release_relaxed, atomic_cxchgweak_release_acquire, atomic_cxchgweak_release_seqcst, atomic_cxchgweak_acqrel_relaxed, atomic_cxchgweak_acqrel_acquire, atomic_cxchgweak_acqrel_seqcst, atomic_cxchgweak_seqcst_relaxed, atomic_cxchgweak_seqcst_acquire, atomic_cxchgweak_seqcst_seqcst |
| And | atomic_and_relaxed, atomic_and_acquire, atomic_and_release, atomic_and_acqrel, atomic_and_seqcst |
| Nand | atomic_nand_relaxed, atomic_nand_acquire, atomic_nand_release, atomic_nand_acqrel, atomic_nand_seqcst |
| Or | atomic_or_seqcst, atomic_or_acquire, atomic_or_release, atomic_or_acqrel, atomic_or_relaxed |
| Xor | atomic_xor_seqcst, atomic_xor_acquire, atomic_xor_release, atomic_xor_acqrel, atomic_xor_relaxed |
| Max | atomic_max_relaxed, atomic_max_acquire, atomic_max_release, atomic_max_acqrel, atomic_max_seqcst |
| Min | atomic_min_relaxed, atomic_min_acquire, atomic_min_release, atomic_min_acqrel, atomic_min_seqcst |
| Umax | atomic_umax_relaxed, atomic_umax_acquire, atomic_umax_release, atomic_umax_acqrel, atomic_umax_seqcst |
| Umin | atomic_umin_relaxed, atomic_umin_acquire, atomic_umin_release, atomic_umin_acqrel, atomic_umin_seqcst |
List of UBs
In addition to any safety properties mentioned in the API documentation, all proofs must automatically ensure the absence of the following undefined behaviors:
- Data races.
- Accessing (loading from or storing to) a place that is dangling or based on a misaligned pointer.
- Reading from uninitialized memory.
- Invoking undefined behavior via compiler intrinsics.
- Producing an invalid value.
Note: All solutions to verification challenges need to satisfy the criteria established in the challenge book in addition to the ones listed above.
-
Throughout this challenge, when we say "safe", it is identical to saying "does not exhibit undefined behavior". ↩