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

[Merged by Bors] - [bevy_core/bytes] Fix UB with accessing memory with incorrect alignment #1966

Closed

Conversation

NathanSWard
Copy link
Contributor

After running bevy_core through miri, errors were reported surrounding incorrect memory accesses within the bytes test suit.

Specifically:

test bytes::tests::test_array_round_trip ... error: Undefined Behavior: accessing memory with alignment 1, but alignment 4 is required
   --> crates/bevy_core/src/bytes.rs:55:13
    |
55  |             (*ptr).clone()
    |             ^^^^^^ accessing memory with alignment 1, but alignment 4 is required
    |

and

test bytes::tests::test_vec_bytes_round_trip ... error: Undefined Behavior: accessing memory with alignment 2, but alignment 4 is required
   --> /home/nward/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/raw.rs:95:14
    |
95  |     unsafe { &*ptr::slice_from_raw_parts(data, len) }
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 2, but alignment 4 is required
    |

Solution:

The solution is to use slice::align_to method to ensure correct alignment.

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-Core Common functionality for all bevy apps P-High This is particularly urgent, and deserves immediate attention labels Apr 19, 2021
let len = bytes.len() / std::mem::size_of::<T>();
let slice = core::slice::from_raw_parts::<T>(byte_ptr, len);
slice.to_vec()
let (_, body, _) = bytes.align_to::<T>();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be any assertions that the head and tail of this tuple are empty slices?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is abaolutely no guarantee that the passed in slice is aligned. This should use ptr::read_unaligned instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think, like my other comment, that align_to is just the wrong approach here.

As a sketch, I think that perhaps it should be more like

assert_eq!(bytes.len() % std::mem::size_of::<T>(), 0);
let len = bytes.len() / std::mem::size_of::<T>();
let mut v = Vec::<T>::with_capacity(len);
ptr::copy_nonoverlapping(bytes.as_ptr(), v.as_mut_ptr() as *mut u8, len * std::mem::size_of::<T>());
v.set_len(len);
v

because it needs a 1-aligned copy to read from the slice correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For copy_nonoverlapping doesn't T have to be Copy?
Currently T is only bounded on Clone.

let ptr = byte_ptr as *const Self;
(*ptr).clone()
let ptr = bytes.as_ptr() as *const Self;
std::ptr::read_unaligned(ptr)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it. I still don't think this is right.
This only works if T is Copy, however, T is only bounded on Clone :/

Copy link
Contributor Author

@NathanSWard NathanSWard Apr 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So perhaps something like:

fn from_bytes(bytes: &[u8]) -> Self {
    let value = unsafe { bytes.as_ptr().cast::<Self>().read_unaligned() };
    let clone = value.clone();
    std::mem::forget(value);
    clone 
}

would work?

@NathanSWard NathanSWard force-pushed the nward/bevy_core-bytes-ub branch 2 times, most recently from d7425d3 to 746905f Compare April 19, 2021 23:52
@NathanSWard NathanSWard requested a review from bjorn3 April 19, 2021 23:53
@NathanSWard
Copy link
Contributor Author

I ended up changing the requirement on Byteable to enforce Copy instead of Clone.

@cart
Copy link
Member

cart commented Apr 20, 2021

This looks good to me. It would be nice to retain Clone (and it feels like it should be possible to do that in a non-ub way), but safety comes first and I'm clearly not an expert here 😄

I think I'll wait for @bjorn3 to weigh in before merging (unless we don't hear back in the next day or so).

Copy link
Contributor

@bjorn3 bjorn3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is sound.

@NathanSWard
Copy link
Contributor Author

This looks good to me. It would be nice to retain Clone (and it feels like it should be possible to do that in a non-ub way),

Yep, I agree that retaining Clone is nice. I can probably go ahead and open an issue for implementing the traits for Clone-able types. :)

@cart
Copy link
Member

cart commented Apr 20, 2021

bors r+

bors bot pushed a commit that referenced this pull request Apr 20, 2021
…nt (#1966)

After running `bevy_core` through `miri`, errors were reported surrounding incorrect memory accesses within the `bytes` test suit. 

Specifically:
```
test bytes::tests::test_array_round_trip ... error: Undefined Behavior: accessing memory with alignment 1, but alignment 4 is required
   --> crates/bevy_core/src/bytes.rs:55:13
    |
55  |             (*ptr).clone()
    |             ^^^^^^ accessing memory with alignment 1, but alignment 4 is required
    |
```

and 

```
test bytes::tests::test_vec_bytes_round_trip ... error: Undefined Behavior: accessing memory with alignment 2, but alignment 4 is required
   --> /home/nward/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/raw.rs:95:14
    |
95  |     unsafe { &*ptr::slice_from_raw_parts(data, len) }
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 2, but alignment 4 is required
    |
```

Solution:

The solution is to use `slice::align_to` method to ensure correct alignment.
@bjorn3
Copy link
Contributor

bjorn3 commented Apr 20, 2021

I don't think Clone should be used. Copy ensures that there is no drop glue and the conversion from a slice to an owned value semantically copies and not clones the value.

@bjorn3 bjorn3 closed this Apr 20, 2021
@bjorn3 bjorn3 reopened this Apr 20, 2021
@bjorn3
Copy link
Contributor

bjorn3 commented Apr 20, 2021

Oops

@bjorn3
Copy link
Contributor

bjorn3 commented Apr 20, 2021

@cart it may be necessary to bors r+ it again.

@cart
Copy link
Member

cart commented Apr 20, 2021

I don't think Clone should be used. Copy ensures that there is no drop glue and the conversion from a slice to an owned value semantically copies and not clones the value.

Cool. Lets just leave it as-is for now then. We really only use Byteable for writing data to the gpu and I expect that we will eventually encourage (1) defining separate types for the gpu data and (2) using a derive macro that properly lays out the memory according to gpu layout requirements (ex std140, crevice, or something custom). Then maybe we can remove Byteable / Bytes / AsBytes / FromBytes entirely.

@cart it may be necessary to bors r+ it again.

Cool Ill watch it and retry if it doesn't work out.

@bors bors bot changed the title [bevy_core/bytes] Fix UB with accessing memory with incorrect alignment [Merged by Bors] - [bevy_core/bytes] Fix UB with accessing memory with incorrect alignment Apr 20, 2021
@bors bors bot closed this Apr 20, 2021
@scottmcm
Copy link
Contributor

There's also work ongoing in the safe transmute project (see their recent MCP: rust-lang/compiler-team#411) to provide traits and APIs for this stuff in core, since the compiler can help with checking the safety conditions and (as seen here) these kind of things are pretty subtle.

ostwilkens pushed a commit to ostwilkens/bevy that referenced this pull request Jul 27, 2021
…nt (bevyengine#1966)

After running `bevy_core` through `miri`, errors were reported surrounding incorrect memory accesses within the `bytes` test suit. 

Specifically:
```
test bytes::tests::test_array_round_trip ... error: Undefined Behavior: accessing memory with alignment 1, but alignment 4 is required
   --> crates/bevy_core/src/bytes.rs:55:13
    |
55  |             (*ptr).clone()
    |             ^^^^^^ accessing memory with alignment 1, but alignment 4 is required
    |
```

and 

```
test bytes::tests::test_vec_bytes_round_trip ... error: Undefined Behavior: accessing memory with alignment 2, but alignment 4 is required
   --> /home/nward/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/slice/raw.rs:95:14
    |
95  |     unsafe { &*ptr::slice_from_raw_parts(data, len) }
    |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 2, but alignment 4 is required
    |
```

Solution:

The solution is to use `slice::align_to` method to ensure correct alignment.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Core Common functionality for all bevy apps C-Bug An unexpected or incorrect behavior P-High This is particularly urgent, and deserves immediate attention
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants