I broke it with 100% safe code!
#[derive(Debug, Copy, Clone)]
struct Reference<'a>(&'a i64);
fn add_local_variable(b: &mut Buffer) {
let value = 42_i64;
let reference = (Reference(&value));
b.push(&reference);
}
impl<'a> Field for Reference<'a> {}
#[test]
fn use_after_free() {
let mut b = Buffer::new();
add_local_variable(&mut b);
let got: Reference = b.pop();
assert_eq!(*got.0, 42);
}
Fails with:
thread 'tests::use_after_free' panicked at 'assertion failed: `(left == right)`
left: `140101426636416`,
right: `42`', src/lib.rs:68:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
(playground)
You need to add a 'static
bound to either T
or Field
.
Arguably, push()
and pop()
should be unsafe
functions because I need to "remember" which order things were pushed/popped in in order to maintain memory safety. If my pushes/pops are out of order then it needs to be safe to transmute between what type the memory originally had (or types, if I'm actually popping the first half of one object and the second half of another) and the type I'm popping off.
It shouldn't be possible for the developer to invoke UB by accidentally doing things in the wrong order.
I'm not sure what would happen if you pass in a trait object (i.e. T = dyn Foo
). What gets copied then?
Your push()
method also feels unnecessarily convoluted and we can skip an allocation.
pub fn push<T: Field>(&mut self, t: &T) {
unsafe {
let underlying_data = std::slice::from_raw_parts(
t as *const T as *const u8,
std::mem::size_of::<T>(),
);
self.data.extend_from_slice(underlying_data);
}
}
Miri also doesn't like your pointer transmute operation.
error: Undefined Behavior: accessing memory with alignment 1, but alignment 4 is required
--> src/main.rs:24:13
|
24 | *std::mem::transmute::<*mut u8, *mut T>(bytes.as_mut_ptr()) = *t;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ accessing memory with alignment 1, but alignment 4 is required
|
= 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
= note: inside `Buffer::push::<i32>` at src/main.rs:24:13
note: inside `test_00` at src/main.rs:40:5
--> src/main.rs:40:5
|
40 | b.push::<i32>(&1_i32);
| ^^^^^^^^^^^^^^^^^^^^^
note: inside `main` at src/main.rs:3:5
--> src/main.rs:3:5
|
3 | test_00();
| ^^^^^^^^^
= note: inside `<fn() as std::ops::FnOnce<()>>::call_once - shim(fn())` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
= note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::<fn(), ()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys_common/backtrace.rs:125:18
= note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:63:18
= note: inside `std::ops::function::impls::<impl std::ops::FnOnce<()> for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:259:13
= note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:401:40
= note: inside `std::panicking::r#try::<i32, &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:365:19
= note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:434:14
= note: inside closure at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:45:48
= note: inside `std::panicking::r#try::do_call::<[closure@std::rt::lang_start_internal::{closure#2}], isize>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:401:40
= note: inside `std::panicking::r#try::<isize, [closure@std::rt::lang_start_internal::{closure#2}]>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:365:19
= note: inside `std::panic::catch_unwind::<[closure@std::rt::lang_start_internal::{closure#2}], isize>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panic.rs:434:14
= note: inside `std::rt::lang_start_internal` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:45:20
= note: inside `std::rt::lang_start::<()>` at /playground/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/rt.rs:62:5
error: aborting due to previous error
(playground)