Skip to main content

CacheJitter

Struct CacheJitter 

Source
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>

Source

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.

Source

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.

Source

pub fn inner(&self) -> &I

Borrow the inner allocator.

Source

pub fn cache_line_size(&self) -> usize

Cache-line size in bytes (e.g. 64 on x86/ARM, 128 on Apple Silicon).

Source

pub fn associativity(&self) -> usize

Associativity window in cache lines — the jitter range.

Trait Implementations§

Source§

impl<I: Allocator> Allocator for CacheJitter<I>

Source§

fn allocate(&self, layout: NonZeroLayout) -> Result<NonNull<[u8]>, AllocError>

Allocate a block satisfying layout. The returned slice’s length is at least layout.size() but may be larger.
Source§

fn capacity_bytes(&self) -> Option<usize>

Total bytes this allocator can issue, if bounded. None for unbounded allocators like System. Used by Watermark to compute thresholds.
Source§

fn corruption_events(&self) -> u64

Detected freelist / metadata corruption events observed by this allocator since construction. Read more
Source§

fn allocate_zeroed( &self, layout: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>

Allocate a zero-initialized block.
Source§

unsafe fn grow( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>

Grow an allocation in place if possible, otherwise allocate-copy-free. Read more
Source§

unsafe fn shrink( &self, ptr: NonNull<u8>, old: NonZeroLayout, new: NonZeroLayout, ) -> Result<NonNull<[u8]>, AllocError>

Shrink an allocation in place if possible, otherwise allocate-copy-free. Read more
Source§

fn reset(&mut self) -> Result<(), AllocError>

Reclaim everything previously allocated. Default impl returns AllocError — only arena-style allocators implement a meaningful reset. Read more
Source§

unsafe fn usable_size( &self, _ptr: NonNull<u8>, _layout: NonZeroLayout, ) -> Option<usize>

Usable size of an existing allocation, if the allocator can report it. Defaults to None — implementors that track usable size override. Read more
Source§

impl<I: Allocator> Deallocator for CacheJitter<I>

Source§

unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: NonZeroLayout)

Release a previously allocated block. Read more
Source§

impl<I> Drop for CacheJitter<I>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more
Source§

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.

Source§

fn commit(&self, offset: usize, len: usize) -> Result<(), AllocError>

Pass-through forward so a commit-aware consumer reaches the inner backing when this wrapper sits over a lazy_commit MmapBacked.

Source§

fn base(&self) -> NonNull<u8>

First byte of the owned address range. Read more
Source§

fn size(&self) -> usize

Length in bytes of the owned address range. Read more
Source§

fn contains(&self, ptr: NonNull<u8>) -> bool

Whether ptr lies within [base, base + size). Read more

Auto Trait Implementations§

§

impl<I> !Freeze for CacheJitter<I>

§

impl<I> !RefUnwindSafe for CacheJitter<I>

§

impl<I> !Sync for CacheJitter<I>

§

impl<I> Send for CacheJitter<I>
where I: Send,

§

impl<I> Unpin for CacheJitter<I>
where I: Unpin,

§

impl<I> UnsafeUnpin for CacheJitter<I>
where I: UnsafeUnpin,

§

impl<I> UnwindSafe for CacheJitter<I>
where I: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.