google / google/zerocopy

Retrieving prefix bytes of types with trailing padding

Open
#2,668 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
2.6k
Forks
179
Avg merge
1d 19h
Merged PRs (30d)
29

Description

Currently, `IntoBytes` cannot be implemented for types with trailing padding because the derived `as_bytes` implementation would expose the uninitialized padding bytes, which is unsound. This means there’s no safe way to access only the initialized prefix bytes of such a type.
For example:
```rs
#[derive(FromBytes, KnownLayout, Immutable)]
#[derive(IntoBytes)] // this should and does fail to compile
#[repr(C)]
struct StructWithPadding {
x: u16,
y: u8,
}

let s = StructWithPadding { x: 1, y: 2 };
assert_eq!(s.as_bytes(), &[1, 0, 2]); // fails, because even if `IntoBytes` was implemented, it would also expose the padding byte
```

## Proposed Solution: Trait for the initialized prefix

A new trait, that works similar to `IntoBytes`, but returns the longest prefix byte slice that does only contain initialized bytes. For types that implement `IntoBytes`, these traits would function identically, but for types, that may have trailing padding and no other padding, it returns what `IntoBytes` would return, but without the (uninitialized) padding.

```rs
pub unsafe trait IntoInitPrefixBytes {
fn as_init_prefix_bytes(&self) -> &[u8]
where Self: Immutable;
}

// # Examples

#[derive(IntoInitPrefixBytes)]
#[repr(C)]
struct StructWithPadding { x: u16, y: u8 }

assert_eq!((StructWithPadding { x: 1, y: 2 }).as_init_prefix_bytes(), &[1, 0, 2]);

// For this type, `self.as_init_prefix_bytes()` would effectively return `&self.0`.
#[derive(FromBytes, KnownLayout, Immutable, SplitAt, IntoInitPrefixBytes)]
#[repr(C, align(4))]
struct AlignedBytes([u8]);

// It is currently impossible to access the memory representation of this struct.
// This struct allows:
// * Well aligned accesses to e.g. `u32`s in the payload with only one bound check.
// * Accessing the payload as a byte slice (for e.g. strings) with a size that doesn't have to be aligned, without requiring any checks.
#[derive(FromBytes, KnownLayout, Immutable, SplitAt, IntoInitPrefixBytes)]
#[repr(C)]
struct NetlinkAttr {
nlmsg_len: u16,
nlmsg_type: u16,
payload: AlignedBytes,
}

let attr_data: [u16; 3] = [6, 1, 2];
let attr: &NetlinkAttr = NetlinkAttr::ref_from_bytes(attr_data.as_bytes()).unwrap();
assert_eq!(&attr.payload.0, &[2, 0]); // accessing as byte slice with only length 2
netlink_socket.send(attr.as_init_prefix_bytes());
```

Open question: What to do with types with non-trailing padding, as here:
```rs
#[derive(FromBytes, IntoInitPrefixBytes)]
#[repr(C)]
struct AnotherStructWithPadding {
a: u8,
b: u16,
c: u8,
}

// should we
// a) return a slice of length 1, because this is the largest init prefix, or
// b) throw a compile error when the trailing bytes are not exclusively padding
assert_eq!(AnotherStructWithPadding::new_zeroed().as_init_prefix_bytes().len(), 1);
```

## Problems, that this would solve

* For socket-`send()`ing types, that have align requirements as well as trailing padding, we need to access the underlying bytes of that type
* As a workaround for working with DSTs that currently fail padding analysis (#1112)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.