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

Undefined behaviour in slice::fill specialization. #87891

Closed
m-ou-se opened this issue Aug 9, 2021 · 9 comments · Fixed by #87892 or #88507
Closed

Undefined behaviour in slice::fill specialization. #87891

m-ou-se opened this issue Aug 9, 2021 · 9 comments · Fixed by #87892 or #88507
Labels
A-raw-pointers Area: raw pointers, MaybeUninit, NonNull A-slice Area: `[T]` E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@m-ou-se
Copy link
Member

m-ou-se commented Aug 9, 2021

This was reported by @the8472 on Zulip: https://rust-lang.zulipchat.com/#narrow/stream/219381-t-libs/topic/potential.20UB.20in.20slice.3A.3Afill/near/248871405

I'm wondering if the optimization here is correct:

if size_of::<T>() == 1 {
// SAFETY: The size_of check above ensures that values are 1 byte wide, as required
// for the transmute and write_bytes
unsafe {
let value: u8 = transmute_copy(&value);
write_bytes(self.as_mut_ptr(), value, self.len());
}

Specifically if T is MaybeUninit<u8>. It momentarily transmutes that to an u8 before passing it to write_bytes.

Demo:

use std::mem::MaybeUninit;

fn main() {
    let mut a = [MaybeUninit::<u8>::uninit(); 10];
    a.fill(MaybeUninit::uninit());
}
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
    --> /home/mara/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/intrinsics.rs:2191:14
     |
2191 |     unsafe { write_bytes(dst, val, count) }
     |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
     |
     = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
     = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
@m-ou-se m-ou-se added I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness I-prioritize Issue: Indicates that prioritization has been requested for this issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. A-slice Area: `[T]` A-raw-pointers Area: raw pointers, MaybeUninit, NonNull labels Aug 9, 2021
@Aaron1011
Copy link
Member

Aaron1011 commented Aug 9, 2021

I think the fix should be as simple as using MaybeUninit<u8> instead of u8 as the type of value - that will work regardless of whether or not the memory is initialized.

@m-ou-se
Copy link
Member Author

m-ou-se commented Aug 9, 2021

The other option is just removing the if size_of::<T>() == 1 case entirely. Llvm is smart enough to turn that loop in the else branch into a memset.

@the8472
Copy link
Member

the8472 commented Aug 9, 2021

It does help the gcc codgen backend at least as it can't manage to produce a memset for u64 so I assume it would fail to do the same for u8 if it weren't for that specialization.

https://rust.godbolt.org/z/hrMYvWz9n

@m-ou-se
Copy link
Member Author

m-ou-se commented Aug 9, 2021

@the8472: It also fails for the u8 case: https://rust.godbolt.org/z/Wjx5Mnj5K

@the8472
Copy link
Member

the8472 commented Aug 9, 2021

It does call memset if you don't pass an array. https://rust.godbolt.org/z/x4KT99heT

@RalfJung
Copy link
Member

I'd think the write_bytes intrinsic could easily have its type changed to take a MaybeUninit<u8>, which would let one solve this problem. The stable function of course cannot be changed, though...

@bors bors closed this as completed in 362e0f5 Aug 11, 2021
@RalfJung RalfJung added the E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. label Aug 11, 2021
@RalfJung
Copy link
Member

Reopening to track adding a test. (That's the standard procedure for this, isn't it?)

@RalfJung RalfJung reopened this Aug 11, 2021
@RalfJung RalfJung added E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. and removed I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Aug 12, 2021
@atsuzaki
Copy link
Contributor

@RalfJung I would like to try working on adding the test, though this will be my first contribution so I'd need some guidance. I'm assuming this would be a miri test as it's related to correctness--may I DM you on Zulip? (or would someone else be more suitable?)

@RalfJung
Copy link
Member

@atsuzaki great. :)

Miri runs the libcore test suite via https://github.com/rust-lang/miri-test-libstd. So adding it as a test in libcore makes most sense, IMO.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-raw-pointers Area: raw pointers, MaybeUninit, NonNull A-slice Area: `[T]` E-easy Call for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue. E-needs-test Call for participation: An issue has been fixed and does not reproduce, but no test has been added. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
5 participants