-
Notifications
You must be signed in to change notification settings - Fork 105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[derive] Fix AsBytes for #[repr(C, packed(N))]
#672
Conversation
`packed(N)` does not gaurantee no padding, but it doesn't prevent it either. This was previously supported.
Woah, great catch, and thanks for such a thorough PR! We'll review this soon. Out of curiosity, how did you find this issue? |
crosvm uses |
Okay cool, thanks! |
What happened here is subtle, so I'm attempting to figure it out and put it in writing: Parsing before #643In 7b98c7f (the commit before #643), the regression test provided by @maurer is accepted by zerocopy: #[derive(AsBytes)]
#[repr(C, packed(2))]
// The same caveats as for CPacked apply - we're assuming u64 is at least
// 4-byte aligned by default. Without packed(2), this should fail, as there
// would be padding between a/b assuming u64 is 4+ byte aligned.
struct CPacked2 {
a: u16,
b: u64,
}
static_assertions::assert_impl_all!(CPacked2: AsBytes); This was accepted possibly accidentally. To see why, let's trace the expansion of zerocopy/zerocopy-derive/src/lib.rs Lines 95 to 99 in 7b98c7f
Then enter zerocopy/zerocopy-derive/src/lib.rs Lines 270 to 273 in 7b98c7f
The definition of zerocopy/zerocopy-derive/src/lib.rs Lines 128 to 133 in 7b98c7f
So what happens when we call zerocopy/zerocopy-derive/src/repr.rs Lines 51 to 52 in 7b98c7f
...which calls zerocopy/zerocopy-derive/src/repr.rs Lines 131 to 136 in 7b98c7f
...which calls zerocopy/zerocopy-derive/src/repr.rs Lines 190 to 220 in 7b98c7f
Here, we find the bug. If the meta has arguments, it is unconditionally parsed as zerocopy/zerocopy-derive/src/repr.rs Lines 213 to 215 in 7b98c7f
zerocopy/zerocopy-derive/src/repr.rs Lines 68 to 71 in 7b98c7f
...so the apparent Fortunately, this is not a soundness bug. If the example is modified to use zerocopy/zerocopy-derive/src/lib.rs Lines 559 to 566 in 7b98c7f
Parsing after #643In #643, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR partially repairs a longstanding oddity in our repr
parsing: the special treatment of Align
. align
does not appear in our list of allowed combinations:
zerocopy/zerocopy-derive/src/lib.rs
Lines 128 to 133 in 7b98c7f
const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr]] = &[ | |
&[StructRepr::C], | |
&[StructRepr::Transparent], | |
&[StructRepr::Packed], | |
&[StructRepr::C, StructRepr::Packed], | |
]; |
...nor the invocation of
define_kind_specific_repr
: zerocopy/zerocopy-derive/src/repr.rs
Line 165 in 7b98c7f
define_kind_specific_repr!("a struct", StructRepr, C, Transparent, Packed); |
Rather, it is defined implicitly during macro expansion:
zerocopy/zerocopy-derive/src/repr.rs
Lines 111 to 114 in 7b98c7f
pub enum $repr_name { | |
$($repr_variant,)* | |
Align(u64), | |
} |
This is because, unlike the other modifiers, align
always takes an argument. This PR makes define_kind_specific_repr
a little less magical: It extends the macro with the ability to generate provided argument-taking modifiers.
However, the way it fixes the issue is still a little magical. Recall that validate_reprs
filters out the Align
repr (because it would be a pain to enumerate these in allowed_combinations
):
zerocopy/zerocopy-derive/src/repr.rs
Lines 68 to 71 in 7b98c7f
metas_reprs.into_iter().filter(|(_, repr)| !repr.is_align()).for_each(|(meta, repr)| { | |
metas.push(meta); | |
reprs.push(repr) | |
}); |
This PR leverages that behavior by having PackedN(n).is_align()
return true
:
zerocopy/zerocopy-derive/src/repr.rs
Lines 117 to 122 in 8d3c3fc
fn is_align(&self) -> bool { | |
match self { | |
$($repr_name::$repr_variant_aligned(_) => true,)* | |
_ => false, | |
} | |
} |
I'm not entirely comfortable with this. Not only is the name misleading now, but what happens when a new argument-consuming is added (particularly one that doesn't modify alignment)?
@joshlf, I think we need to reconsider our repr parsing framework, and reduce the the special handling of Align
and PackedN
. My understanding is we need this special handling because we can't write out argument-bearing modifiers in allowed_combinations
. Perhaps we can abstract the argument component so we can specify either concrete or pattern arguments; e.g.:
// ditto for `StructRepr`, etc.
enum ReprKind<A: ReprArgument> {
C,
Transparent,
Packed,
PackedN(A::PackedN),
Align(A::Align),
}
trait ReprArgument {
type PackedN;
type Align,
}
struct Pattern;
struct Concrete;
impl ReprArgument for Pattern {
type PackedN = u64;
type Align = u64;
}
impl ReprArgument for Concrete {
type PackedN = RangeFull;
type Align = RangeFull;
}
const STRUCT_UNION_ALLOWED_REPR_COMBINATIONS: &[&[StructRepr<Pattern>]] = &[
&[StructRepr::C],
&[StructRepr::Transparent],
&[StructRepr::Packed],
&[StructRepr::PackedN(..)],
&[StructRepr::Align],
&[StructRepr::C, StructRepr::Packed],
&[StructRepr::C, StructRepr::Packed(..)],
&[StructRepr::C, StructRepr::Align],
];
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jswrenn and I discussed this offline. We're going to follow up with the changes he suggested, but in the meantime we'll merge this and publish a new version. Thanks again for catching this and for submitting a fix!
Published as 0.7.29. |
Yanked 0.7.27 and 0.7.28, both of which contain the regression. |
packed(N)
does not gaurantee no padding, but it doesn't prevent it either. This was previously supported.This regressed in #643 (left out of the commit message because of the requirement not to link to review sites).