Skip to content
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

Invalid PartialEq implementation for unions #2816

Open
asomers opened this issue Jun 3, 2022 · 7 comments
Open

Invalid PartialEq implementation for unions #2816

asomers opened this issue Jun 3, 2022 · 7 comments
Labels
C-bug Category: bug I-unsound 💥 A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness S-waiting-on-decision
Milestone

Comments

@asomers
Copy link
Contributor

asomers commented Jun 3, 2022

union semun, in unix/bsd/apple/mod.rs, has three fields. On LP64, these fields will have unequal sizes. But its PartialEq and Hash implementations only check the smallest field. So the following code should fail (untested, because I don't have access to a Mac):

use libc::semun;

#[test]
fn semun_eq() {
    let x = semun{ val: 0x44556677 };
    let y = semun{ array: 0x0011223344556677 };
    assert!(x != y);
}

A similar situation holds on Linux for __c_anonymous_ptrace_syscall_info_data. That union has three members of different sizes, and its PartialEq implementation will return turn as long as any pair of fields compare equal. Effectively, that means it's only comparing the smallest field.

There are other unions in libc, but I haven't audited them all.

@asomers asomers added the C-bug Category: bug label Jun 3, 2022
@RalfJung
Copy link
Member

RalfJung commented Jun 3, 2022

Note that an implementation that compares the larger, ptr-sized fields would be unsound, since semun { val: 0 } leaves those fields uninitialized -- so reading them (at integer or raw ptr type) is UB.

@asomers
Copy link
Contributor Author

asomers commented Jun 3, 2022

Crap, I never thought of that. Are you saying that it's impossible to correctly implement PartialEq for a union type then? In that case, would the only way to do it be to create a newtype that always zero-initializes the entire union before initializing any field?

@RalfJung
Copy link
Member

RalfJung commented Jun 3, 2022

Are you saying that it's impossible to correctly implement PartialEq for a union type then?

Depends on what you mean by 'correctly'... if you mean it should compare all the bytes, then yes.

Probably the type should just not implement PartialEq since the expected semantics cannot be provided. But it might be too late for that due to backwards compatibility constraints.

@notgull
Copy link
Contributor

notgull commented Jun 4, 2022

But it might be too late for that due to backwards compatibility constraints.

Is there some way to indicate that the PartialEq implementation is deprecated? I think that would be useful if we wanted to move in the direction of removing it.

@asomers
Copy link
Contributor Author

asomers commented Jun 4, 2022

I don't think so. AFAIK you still can't deprecate a trait impl rust-lang/rust#39935 .

asomers added a commit to asomers/libc that referenced this issue Jun 24, 2022
sigevent's Debug, PartialEq, and Hash trait impls might read union
fields that could be potentially uninitialized by a standard
initializer.  Those trait impls shouldn't be present (see
rust-lang#2816), but can't easily be
removed.  Until they get removed, the constructor must be `unsafe` to
force the user to zero all fields.

The same issue applies to the Deref<Target=sigevent_0_2_126> trait impl,
which exists only for backwards compatibility.
asomers added a commit to asomers/libc that referenced this issue Aug 11, 2023
sigevent's Debug, PartialEq, and Hash trait impls might read union
fields that could be potentially uninitialized by a standard
initializer.  Those trait impls shouldn't be present (see
rust-lang#2816), but can't easily be
removed.  Until they get removed, the constructor must be `unsafe` to
force the user to zero all fields.

The same issue applies to the Deref<Target=sigevent_0_2_126> trait impl,
which exists only for backwards compatibility.
asomers added a commit to asomers/libc that referenced this issue Aug 19, 2023
sigevent's Debug, PartialEq, and Hash trait impls might read union
fields that could be potentially uninitialized by a standard
initializer.  Those trait impls shouldn't be present (see
rust-lang#2816), but can't easily be
removed.  Until they get removed, the constructor must be `unsafe` to
force the user to zero all fields.

The same issue applies to the Deref<Target=sigevent_0_2_126> trait impl,
which exists only for backwards compatibility.
asomers added a commit to asomers/libc that referenced this issue Dec 20, 2023
sigevent's Debug, PartialEq, and Hash trait impls might read union
fields that could be potentially uninitialized by a standard
initializer.  Those trait impls shouldn't be present (see
rust-lang#2816), but can't easily be
removed.  Until they get removed, the constructor must be `unsafe` to
force the user to zero all fields.

The same issue applies to the Deref<Target=sigevent_0_2_126> trait impl,
which exists only for backwards compatibility.
@Ralith
Copy link
Contributor

Ralith commented Apr 5, 2024

New instance of this unsoundness are still being introduced, e.g.:

impl PartialEq for __c_anonymous_cpu_topology_info_data {
fn eq(&self, other: &__c_anonymous_cpu_topology_info_data) -> bool {
unsafe {
self.root == other.root
|| self.package == other.package
|| self.core == other.core
}
}
}
impl Eq for __c_anonymous_cpu_topology_info_data {}

@tgross35
Copy link
Contributor

To be discussed as part of #3880

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C-bug Category: bug I-unsound 💥 A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness S-waiting-on-decision
Projects
None yet
Development

No branches or pull requests

5 participants