Skip to content

Add an optional zerocopy feature deriving byte-conversion traits on wire-format types#190

Open
dflemstr wants to merge 2 commits into
sunfishcode:mainfrom
modal-labs:zerocopy-derives
Open

Add an optional zerocopy feature deriving byte-conversion traits on wire-format types#190
dflemstr wants to merge 2 commits into
sunfishcode:mainfrom
modal-labs:zerocopy-derives

Conversation

@dflemstr

@dflemstr dflemstr commented Jul 8, 2026

Copy link
Copy Markdown

Programs that pass stat/statx/timespec-style structs across a byte
boundary — sandboxes and supervisors writing into another process's memory,
container runtimes, serializers, anything doing process_vm_writev or
ptrace — all end up hand-writing the same unsafe transmutes-to-bytes
around these types, along with a hand-audited "no padding, no invalid bit
patterns" claim. The zerocopy crate exists to replace exactly that pattern
with compile-time-verified derives, but its traits can't be implemented
downstream (orphan rule), so users today either maintain newtype wrappers or
carry the unsafe themselves.

This adds an off-by-default zerocopy feature that derives
FromBytes, IntoBytes, KnownLayout, and Immutable on a curated
allowlist of fixed-layout wire-format types (ZEROCOPY_TYPES in
gen/src/main.rs): the stat/statx/statfs family, rusage,
rlimit/rlimit64, the timespec/timeval/itimerspec family,
epoll_event, pollfd, new_utsname, and winsize.

Design notes:

  • The derives are the proof. zerocopy's derives verify "no padding, no
    pointers, valid for any bit pattern" per target at compile time, so this
    feature adds no unsafe and no auditing burden here: a type that doesn't
    qualify on some architecture fails that architecture's build instead of
    compiling into unsoundness. A blanket derive over all generated structs is
    not possible for exactly this reason (padded structs like flock,
    union-typed fields, __IncompleteArrayField tails), hence the explicit
    allowlist. The list is easy to extend: add a name, regenerate, and let the
    target matrix judge it.
  • Per-architecture exceptions. Padding is a per-ABI fact:
    epoll_event is __EPOLL_PACKED only on the x86 family, stat has
    tail padding on powerpc/sparc, x32's timespec pads after its 32-bit
    tv_nsec, and sparc64's timeval pads after its 32-bit tv_usec.
    ZEROCOPY_ARCH_EXCEPTIONS omits the derives exactly there (transitively:
    rusage/itimerval follow their field types). A missing derive is never
    unsound — it's just unavailable on that architecture.
  • Generator-driven. The attribute is emitted by a
    ParseCallbacks::add_attributes callback in gen, as
    #[cfg_attr(feature = "zerocopy", derive(...))] on struct types in the
    allowlist, and the zerocopy = ["dep:zerocopy"] feature line is emitted
    into the generated [features] section. The generated sources in this PR
    come from a run of gen (the diff against the previous generation is the
    attribute lines only).
  • Dependency footprint. zerocopy is optional and off by default with
    default-features = false, features = ["derive"]; nothing changes for
    existing users. When enabled it adds one proc-macro dependency chain at
    build time and a #![no_std]-compatible trait crate at runtime.
  • MSRV. The crate's MSRV (1.63) is unchanged for all existing feature
    combinations. Enabling zerocopy requires zerocopy 0.8's own MSRV
    (currently 1.65); the new CI job runs on stable only, and the 1.63 leg of
    the check job doesn't enable the feature.
  • CI. A new zerocopy job cargo checks the feature across a width-
    and endianness-diverse target set (x86_64 gnu/musl/x32, i686, riscv64,
    aarch64, ppc64le, armv5te, loongarch64, s390x), since the padding proof
    is per-target. Locally verified with cargo check --features zerocopy
    (via -Zbuild-std=core where no prebuilt core exists) on all 20
    architecture directories except csky (crate doesn't currently build there
    at all: c_char missing from ctypes, also on main), m68k (rustc
    SIGSEGV building core), and mips32r6 (rustc SIGILL building core) — those
    three carry conservative exceptions.

Happy to adjust the trait set (e.g. TryFromBytes instead of FromBytes),
the allowlist, or to gate each trait separately if you'd prefer.

dflemstr added 2 commits July 8, 2026 18:15
Derives zerocopy's FromBytes, IntoBytes, KnownLayout, and Immutable on a
curated allowlist of fixed-layout wire-format types (ZEROCOPY_TYPES in
gen/src/main.rs) behind an off-by-default 'zerocopy' feature, so users
converting stat/statx/timespec-family structs to and from byte buffers
don't need hand-written unsafe or orphan-rule newtype wrappers. The
derives verify no-padding/no-pointers per target at compile time, so the
allowlist is self-policing: an unsound addition fails the build. CI gains
a job checking the feature across a width- and endianness-diverse target
set.
Output of gen with the ZEROCOPY_TYPES callback: cfg_attr'd derive lines on
the allowlisted wire-format structs, per the architecture exceptions, plus
the generated 'zerocopy' feature line. Verified with
'cargo check --features zerocopy' (via -Zbuild-std=core where no prebuilt
core exists) for every architecture except csky, m68k, and mips32r6, whose
toolchains currently can't build the crate at all (csky) or core itself
(m68k SIGSEGV, mips32r6 SIGILL); those three received conservative
exceptions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant