Files
Sprimo/skills/unsafe-checker/rules/ptr-04-alignment.md
2026-02-12 22:58:33 +08:00

3.1 KiB

id, original_id, level, impact, clippy
id original_id level impact clippy
ptr-04 G.UNS.PTR.01 G HIGH cast_ptr_alignment

Do Not Dereference Pointers Cast to Misaligned Types

Summary

When casting a pointer to a different type, ensure the resulting pointer is properly aligned for the target type.

Rationale

Misaligned pointer dereferences are undefined behavior on most architectures. Even on architectures that support unaligned access, it may cause performance penalties or subtle bugs.

Bad Example

// DON'T: Cast without checking alignment
fn bad_cast(bytes: &[u8]) -> u32 {
    // BAD: bytes might not be aligned for u32
    let ptr = bytes.as_ptr() as *const u32;
    unsafe { *ptr }  // UB if misaligned!
}

// DON'T: Assume struct layout
#[repr(C)]
struct Header {
    flags: u8,
    value: u32,  // Aligned at offset 4 in the struct
}

fn bad_field_access(bytes: &[u8]) -> u32 {
    let header = bytes.as_ptr() as *const Header;
    // Even if bytes is 4-byte aligned, this might fail
    // if Header has different alignment than expected
    unsafe { (*header).value }
}

Good Example

// DO: Use read_unaligned for potentially misaligned data
fn good_cast(bytes: &[u8]) -> u32 {
    assert!(bytes.len() >= 4);
    let ptr = bytes.as_ptr() as *const u32;
    // SAFETY: We're reading 4 bytes, alignment doesn't matter for read_unaligned
    unsafe { ptr.read_unaligned() }
}

// DO: Check alignment before cast
fn good_aligned_cast(bytes: &[u8]) -> Option<&u32> {
    if bytes.len() >= 4 && bytes.as_ptr() as usize % std::mem::align_of::<u32>() == 0 {
        // SAFETY: Checked length and alignment
        Some(unsafe { &*(bytes.as_ptr() as *const u32) })
    } else {
        None
    }
}

// DO: Use from_ne_bytes for portable byte conversion
fn good_from_bytes(bytes: &[u8]) -> u32 {
    u32::from_ne_bytes(bytes[..4].try_into().unwrap())
}

// DO: Use bytemuck for safe transmutation
// use bytemuck::{Pod, Zeroable};
// let value: u32 = bytemuck::pod_read_unaligned(bytes);

// DO: Use align_to for splitting at alignment boundaries
fn process_aligned(bytes: &[u8]) {
    let (prefix, aligned, suffix) = unsafe { bytes.align_to::<u32>() };
    // prefix and suffix are unaligned portions
    // aligned is a &[u32] that's properly aligned
}

Alignment Check Helpers

fn is_aligned<T>(ptr: *const u8) -> bool {
    ptr as usize % std::mem::align_of::<T>() == 0
}

/// Align a pointer up to the next aligned address
fn align_up<T>(ptr: *const u8) -> *const u8 {
    let align = std::mem::align_of::<T>();
    let addr = ptr as usize;
    let aligned = (addr + align - 1) & !(align - 1);
    aligned as *const u8
}

Architecture Notes

Arch Misaligned Access
x86/x64 Works but slower
ARM UB, may trap or give wrong results
RISC-V UB, may trap
WASM UB

Checklist

  • Is my pointer cast changing alignment requirements?
  • Is the source pointer guaranteed to be aligned?
  • Should I use read_unaligned instead?
  • Can I use safe conversion methods (from_ne_bytes)?
  • mem-01: Choose appropriate data layout
  • ffi-13: Ensure consistent data layout