pub struct CacheJitter<I> { /* private fields */ }Expand description
CacheJitter wrapper.
cache_line_size and associativity are fixed at construction. The
xorshift64 state lives in an interior-mutable Cell so allocate can
take &self; this also makes the type !Sync.
§Cross-thread use
CacheJitter<I> is not thread-safe by itself — the Cell<u64>
rng state and the Cell on the per-instance MAC verification path
both prohibit &CacheJitter from being shared across threads.
Wrapping the inner allocator with Statistics or similar does
not help; the cell is inside CacheJitter and is what blocks
Sync.
For cross-thread use, pick one of:
- Per-thread instance — give each thread its own
CacheJitter<I>. Each instance has its own rng + MAC key, which actually improves the wrapper’s threat-model (the MAC key is thread-private). - External
Mutex<CacheJitter<I>>— serializes all access through the lock. Use only if a single shared instance is architecturally required; per-thread is faster.
§Randomness model
CacheJitter uses xorshift64, a fast non-cryptographic PRNG. The goal of the wrapper is to diversify cache-set occupancy across allocations so an attacker who controls allocation timing can’t deterministically evict a victim line. It is not designed to resist an attacker who can observe several user pointers and solve for the RNG state — xorshift64 is fully invertible from ~3 consecutive 64-bit outputs.
If your threat model includes that adversary, swap the RNG for a CSPRNG (e.g. ChaCha20) at the cost of ~10× per-allocation overhead. For the typical anti-spray use case, xorshift64 is appropriate.
§Composition
Layout requests with align > cache_line_size are forwarded to the
inner allocator without jitter — the jitter granularity is one cache
line, which can’t preserve a larger alignment. The vast majority of
requests have align <= 16, so jitter applies in the common case.
§Inner-allocator alignment requirement
The inner allocator MUST be able to satisfy alignment requests up
to cache_line_size (64 bytes on x86/ARM, 128 on Apple Silicon).
For jittered requests, this wrapper inflates the inner’s alignment
requirement up to cache_line_size so the user pointer (placed at
inner_ptr + cache_line_size + displacement) inherits the caller’s
requested alignment. Backings that cap alignment below
cache_line_size — notably InlineBacked,
whose MAX_ALIGN is 16 — will reject the inflated request and the
wrapped allocation will fail.
Practical implication: CacheJitter<MmapBacked> and
CacheJitter<BumpArena<MmapBacked>> work; CacheJitter<BumpArena< InlineBacked<N>>> compiles but cannot actually allocate jittered
blocks. The pattern is mainly useful for production heaps over the
OS allocator, not for stack-buffer arenas.
Implementations§
Source§impl<I> CacheJitter<I>
impl<I> CacheJitter<I>
Sourcepub fn with_params(
inner: I,
cache_line_size: usize,
associativity: usize,
seed: u64,
) -> Option<Self>
pub fn with_params( inner: I, cache_line_size: usize, associativity: usize, seed: u64, ) -> Option<Self>
Construct with explicit cache parameters and a caller-supplied seed.
Required for no_std builds.
cache_line_size must be a power of two and >= 8 so a u64
header fits within one line. associativity must be >= 1.
Returns None if either constraint is violated, or if
cache_line_size * associativity would overflow.
Sourcepub fn new(
inner: I,
cache_line_size: usize,
associativity: usize,
) -> Option<Self>
pub fn new( inner: I, cache_line_size: usize, associativity: usize, ) -> Option<Self>
Construct with OS-derived entropy. Available on std builds.
Uses the same entropy strategy as Canary::new — a single
RandomState-derived 64-bit value seeded by the OS RNG.
Sourcepub fn cache_line_size(&self) -> usize
pub fn cache_line_size(&self) -> usize
Cache-line size in bytes (e.g. 64 on x86/ARM, 128 on Apple Silicon).
Sourcepub fn associativity(&self) -> usize
pub fn associativity(&self) -> usize
Associativity window in cache lines — the jitter range.
Trait Implementations§
Source§impl<I: Allocator> Allocator for CacheJitter<I>
impl<I: Allocator> Allocator for CacheJitter<I>
Source§fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>
fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>
layout. The returned slice’s length is
at least layout.size() but may be larger.Source§fn capacity_bytes(&self) -> Option<usize>
fn capacity_bytes(&self) -> Option<usize>
None for unbounded
allocators like System. Used by Watermark to compute thresholds.Source§fn corruption_events(&self) -> u64
fn corruption_events(&self) -> u64
Source§fn allocate_zeroed(
&self,
layout: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
fn allocate_zeroed( &self, layout: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§unsafe fn grow(
&self,
ptr: NonNull<u8>,
old: NonZeroLayout,
new: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
unsafe fn grow( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§unsafe fn shrink(
&self,
ptr: NonNull<u8>,
old: NonZeroLayout,
new: NonZeroLayout,
) -> Result<NonNull<[u8]>, AllocError>
unsafe fn shrink( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>
Source§fn reset(&mut self) -> Result<(), AllocError>
fn reset(&mut self) -> Result<(), AllocError>
AllocError — only arena-style allocators implement a meaningful
reset. Read moreSource§unsafe fn usable_size(
&self,
_ptr: NonNull<u8>,
_layout: NonZeroLayout,
) -> Option<usize>
unsafe fn usable_size( &self, _ptr: NonNull<u8>, _layout: NonZeroLayout, ) -> Option<usize>
None — implementors that track usable size
override. Read moreSource§impl<I: Allocator> Deallocator for CacheJitter<I>
impl<I: Allocator> Deallocator for CacheJitter<I>
Source§unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout)
unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout)
Source§impl<I> Drop for CacheJitter<I>
impl<I> Drop for CacheJitter<I>
Source§impl<I: FixedRange> FixedRange for CacheJitter<I>
FixedRange passthrough so this wrapper composes over a lazy_commit
MmapBacked and similar backings.
impl<I: FixedRange> FixedRange for CacheJitter<I>
FixedRange passthrough so this wrapper composes over a lazy_commit
MmapBacked and similar backings.
Footgun: the displacement header is written and MAC-verified only in
this wrapper’s allocate/deallocate. If you nest it as a backing under
an arena — BumpArena<CacheJitter<..>> — the arena carves directly from
base()/size() and never calls CacheJitter::allocate/deallocate, so
no displacement is applied and no header is ever checked while the type
name still advertises the jitter. Keep the hardening wrapper outermost
(wrapping the allocator), never as the FixedRange an arena consumes.