Why is the lifetime important for slice::from_raw_parts?
Asked Answered
J

3

11

The docs for slice::from_raw_parts warn the programmer to annotate the slice with the correct lifetime. I assume that, given some lifetime 'a, I can perform this annotation with

let myslice: &'a mut [i32] = std::slice::from_raw_parts_mut(ptr, sz)

I also assume that

  • Since myslice is a reference, it has nothing to do with the allocation/deallocation of the underlying data pointed to by ptr. The lifetime annotation doesn't affect the memory management of the data.
  • There's nothing tricky about memory management for myslice itself (i.e. a struct containing a pointer and a size). It's just like any other struct or i32. If I put it in a Box, then the std::raw::slice struct will get deallocated when the Box dies. The data referred to by the slice will not get deallocated, of course. The lifetime doesn't affect the memory management for the slice.

Why is it important that I get the lifetime correct? Is use-after-free the only danger to fear when setting the slice lifetime?

Jeopardy answered 23/10, 2015 at 15:2 Comment(3)
Is use-after-free not enough of a danger? Many security bugs happen because of such.Anderlecht
(thanks; I fixed typo). It's plenty of danger :). I was just worried there was something else I had to be afraid of.Jeopardy
It's worth noting that use-after-free can lead to essentially any other memory safety problem, since it can cause arbitrary heap corruption (e.g. a UAF may change the memory representing the length of some array, and then subsequent accesses to that array could be out-of-bounds). (This is in fact true of most memory safety problems: each one of them can have knock-on effects that cause any other memory safety problem you can imagine.)Pictor
L
13

Use-after-free is not the only danger. With the wrong lifetime, one can cause mutable aliasing. Let's take this (contrived) function as an example:

fn duplicate_mut_slice<'a, T>(xs: &mut [T]) -> &'a mut [T] {
    let ptr = xs.as_mut_ptr(); // btw, this part is safe!
    unsafe { std::slice::from_raw_parts_mut(ptr, xs.len()) }
}

Because of how the lifetimes line up, calls like this will succeed:

fn alias_first_element<T>(xs: &mut [T]) -> (&mut T, &mut T) {
    let a = duplicate_mut_slice(xs);
    let b = duplicate_mut_slice(xs);
    (&mut a[0], &mut b[0])
}

Note that in this second function's signature, the lifetimes are correct and use-after-free is not an (immediate) danger. But mutable aliasing is very insidious. Basically everything relies on the guaranteed absence of mutable aliasing to prevent problems like race conditions, iterator invalidation, logic errors and indeed use-after-free (of something managed by the T). You can cause almost any problem imaginable with mutable aliasing.

Lough answered 24/10, 2015 at 14:49 Comment(0)
A
0

UPDATE: as delnan stated, mutable aliasing is a real problem that may arise from setting an incorrect lifetime. See his/her answer for more details.

Old answer

Use-after-free is indeed the only danger to fear when setting the slice lifetime. The compiler will trust you and assume the data pointed by the slice lives as long as the lifetime you specify. In case your annotated lifetime is longer than the real lifetime of the underlying data, you may end up with use-after-free bugs (you will be able to use the slice while the data has already been deallocated).

Regarding your assumptions, they are correct. Lifetime annotations don't have any effect on the memory management of the data.

Aircraftman answered 23/10, 2015 at 20:48 Comment(0)
M
0

Some perspective if someone is unsure about the borrow checker and the interplay with the from_raw_parts_mut function (like I still am, as a beginner).

The following code will compile without an error and probably run without doing anything unexpected, but is nonetheless invalid due to access of the same memory via different mutable references; see documentation for the specific wording. It is my understanding, that undefined behavior (UB) is a property of the semantics the language defines (or does not define, more accurately here) and the program loses all meaning, especially in the presence of a time traveling optimizer like LLVM.

This produces UB:

use std::mem::align_of;
fn main() {
    let mut data: [u8; 4] = [0x01, 0x00, 0x00, 0x02];
    let ptr = data.as_ptr() as *mut u32;
    assert!(ptr.align_offset(align_of::<u32>()) == 0);
    let my_ref: &mut [u32] = unsafe { std::slice::from_raw_parts_mut(ptr, data.len() / 4) };
    data[0] = 200; // Borrowed here.
    my_ref[0] = 5; // And independent borrow used here, but unseen -> UB
}

I'd like to imagine that the mutable reference my_ref could be passed to a new thread, while the main thread works on data directly and the rust borrow checker would let this happen since the lifetime and relationship of my_ref is opaque, at least I think that's happening here. But the program is invalid no matter if any race conditions would occur or not due to UB.

A fix as it is indicated in the documentation of from_raw_parts would be to make the lifetime relationship clear using a helper function to encapsulate the unsafe code.

The lifetime for the returned slice is inferred from its usage. To prevent accidental misuse, it’s suggested to tie the lifetime to whichever source lifetime is safe in the context, such as by providing a helper function taking the lifetime of a host value for the slice, or by explicit annotation.

Example fixed, in the sense that the compiler catches the UB:

use std::mem::align_of;
fn alias_u8_to_u32(some_ref: &mut [u8]) -> &mut [u32] {
    let ptr = some_ref.as_ptr() as *mut u32;
    assert!(ptr.align_offset(align_of::<u32>()) == 0);
    unsafe { std::slice::from_raw_parts_mut(ptr, some_ref.len() / 4) }
}
fn main() {
    let mut data: [u8; 4] = [0x01, 0x00, 0x00, 0x02];
    let my_ref = alias_u8_to_u32(&mut data);
    data[0] = 200;
    my_ref[0] = 5;
}

Note, that the function alias_u8_to_u32 does not even need explicit lifetime annotations since the defaults are sufficient. It helpfully identifies the error(s) now:

error[E0503]: cannot use `data` because it was mutably borrowed        
  --> src\main.rs:10:5
   |
9  |     let my_ref = alias_u8_to_u32(&mut data);
   |                                  --------- `data` is borrowed here
10 |     data[0] = 200;
   |     ^^^^^^^ use of borrowed `data`
11 |     my_ref[0] = 5;
   |     --------- borrow later used here

error[E0506]: cannot assign to `data[_]` because it is borrowed
  --> src\main.rs:10:5
   |
9  |     let my_ref = alias_u8_to_u32(&mut data);
   |                                  --------- `data[_]` is borrowed here
10 |     data[0] = 200;
   |     ^^^^^^^^^^^^^ `data[_]` is assigned to here but it was already borrowed
11 |     my_ref[0] = 5;
   |     --------- borrow later used here

Some errors have detailed explanations: E0503, E0506.
For more information about an error, try `rustc --explain E0503`.

Hopefully my understanding is accurate and this answer helps :)

Microbarograph answered 22/5, 2023 at 0:3 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.