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

Slices that cover the last byte of the address space are invalid #83996

Open
joboet opened this issue Apr 8, 2021 · 13 comments
Open

Slices that cover the last byte of the address space are invalid #83996

joboet opened this issue Apr 8, 2021 · 13 comments
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools A-iterators Area: Iterators T-libs Relevant to the library team, which will review and decide on the PR/issue.

Comments

@joboet
Copy link
Member

joboet commented Apr 8, 2021

The current implementation uses pointer::add to compute the end pointer for the bounds check:

pub(super) fn new(slice: &'a [T]) -> Self {
let ptr = slice.as_ptr();
// SAFETY: Similar to `IterMut::new`.
unsafe {
assume(!ptr.is_null());
let end = if mem::size_of::<T>() == 0 {
(ptr as *const u8).wrapping_add(slice.len()) as *const T
} else {
ptr.add(slice.len())
};
Self { ptr: NonNull::new_unchecked(ptr as *mut T), end, _marker: PhantomData }
}
}

The method requires that the calculation will not overflow a usize, however that is not always the case. For instance, an allocator might return the last available page (0xfffff000 on x86) and correctly return a slice of u8 (with size 4096 on x86). If a program now iterates over the slice, the end pointer will overflow, wrapping around the address space and thus creating UB.

This behaviour is extremely unlikely and only occurs with no_std as most kernels reserve the higher half of the address space anyway.

Solutions

  • Use wrapping_add instead, which avoids the UB, but might disable some optimizations
  • Update the requirements for allocators so they aren't allowed to return the last bit of memory
@the8472
Copy link
Member

the8472 commented Apr 8, 2021

vec::IntoIter is constructed in a similar fashion

fn into_iter(self) -> IntoIter<T, A> {
unsafe {
let mut me = ManuallyDrop::new(self);
let alloc = ptr::read(me.allocator());
let begin = me.as_mut_ptr();
let end = if mem::size_of::<T>() == 0 {
arith_offset(begin as *const i8, me.len() as isize) as *const T
} else {
begin.add(me.len()) as *const T
};
let cap = me.buf.capacity();

array::IntoIter on the other hand uses a different idiom

data: [MaybeUninit<T>; N],
/// The elements in `data` that have not been yielded yet.
///
/// Invariants:
/// - `alive.start <= alive.end`
/// - `alive.end <= N`
alive: Range<usize>,

@joboet
Copy link
Member Author

joboet commented Apr 9, 2021

@rustbot label A-iterators C-bug

@rustbot rustbot added A-iterators Area: Iterators C-bug Category: This is a bug. labels Apr 9, 2021
@est31
Copy link
Member

est31 commented Apr 9, 2021

@rustbot label I-unsound 💥

@rustbot
Copy link
Collaborator

rustbot commented Apr 9, 2021

Error: Label 💥 can only be set by Rust team members

Please let @rust-lang/release know if you're having trouble with this bot.

@Aaron1011 Aaron1011 added the I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness label Apr 9, 2021
@rustbot rustbot added the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Apr 9, 2021
@jyn514 jyn514 added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Apr 9, 2021
@RalfJung
Copy link
Member

The method requires that the calculation will not overflow a usize, however that is not always the case. For instance, an allocator might return the last available page (0xfffff000 on x86) and correctly return a slice of u8 (with size 4096 on x86). If a program now iterates over the slice, the end pointer will overflow, wrapping around the address space and thus creating UB.

An allocator must ensure that the one-past-the-end address of an allocation does not overflow. In other words, 0xffffffff can never be inside an allocation, it can only be one-past-the-end of an allocation. I think this rules out your example, doesn't it?

@joboet
Copy link
Member Author

joboet commented Apr 10, 2021

Yes, that would solve the issue! I can't seem to find it in the documentation, however. Should I change the labeling of this issue to make it a doc-bug?

@RalfJung
Copy link
Member

Yeah this could certainly be documented better.

@scottmcm
Copy link
Member

I think this has always been part of the implicit invariant for slices, but it would definitely be good to mention it explicitly in things like the documentation for https://doc.rust-lang.org/std/slice/fn.from_raw_parts.html#safety (The last bullet there is coming from the expectation that data.offset(len) needs to be valid, it doesn't seem to be directly mentioned.)

@RalfJung
Copy link
Member

This is not really specific to slices though, it is a property of all "allocated objects" in Rust and the allocator must comply by it.

@bluss bluss added A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools and removed I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness labels Apr 12, 2021
@m-ou-se
Copy link
Member

m-ou-se commented Apr 14, 2021

We assume in lots of places that slice.as_ptr().add(slice.len()) is safe. E.g.

pub const fn as_ptr_range(&self) -> Range<*const T> {
let start = self.as_ptr();
// SAFETY: The `add` here is safe, because:
//
// - Both pointers are part of the same object, as pointing directly
// past the object also counts.
//
// - The size of the slice is never larger than isize::MAX bytes, as
// noted here:
// - https://github.com/rust-lang/unsafe-code-guidelines/issues/102#issuecomment-473340447
// - https://doc.rust-lang.org/reference/behavior-considered-undefined.html
// - https://doc.rust-lang.org/core/slice/fn.from_raw_parts.html#safety
// (This doesn't seem normative yet, but the very same assumption is
// made in many places, including the Index implementation of slices.)
//
// - There is no wrapping around involved, as slices do not wrap past
// the end of the address space.
//
// See the documentation of pointer::add.
let end = unsafe { start.add(self.len()) };
start..end
}

The documentation for pointer::add does mention that vec.as_ptr().add(vec.len()) is always safe, but doesn't state that clearly for slices in general:

/// and `Box` ensure they never allocate more than `isize::MAX` bytes, so
/// `vec.as_ptr().add(vec.len())` is always safe.

The implementation of Index for slices also assumes this is always safe:

// SAFETY: the caller guarantees that `slice` is not dangling, so it
// cannot be longer than `isize::MAX`. They also guarantee that
// `self` is in bounds of `slice` so `self` cannot overflow an `isize`,
// so the call to `add` is safe.
unsafe { slice.as_ptr().add(self) }

@m-ou-se m-ou-se removed the C-bug Category: This is a bug. label Apr 14, 2021
@the8472
Copy link
Member

the8472 commented Apr 14, 2021

Should that part of the allocator API contracts?

@apiraino
Copy link
Contributor

Removing the I-prioritize as it looks like more relevant to documentation
(but in case apply a priority if it helps a follow-up)

@rustbot label -I-prioritize

@rustbot rustbot removed the I-prioritize Issue: Indicates that prioritization has been requested for this issue. label Apr 14, 2021
@RalfJung
Copy link
Member

Should that part of the allocator API contracts?

Yes, this effectively already is part of the allocator API contract.

@RalfJung RalfJung changed the title core::slice::Iter::new is unsound for non-ZSTs Slices that cover the last byte of the address space are invalid Jun 16, 2023
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jun 23, 2023
…Denton

slice::from_raw_parts: mention no-wrap-around condition

Cc rust-lang#83996. This probably needs to be mentioned in more places, so I am not closing that issue, but this here should help at least.
Dylan-DPC added a commit to Dylan-DPC/rust that referenced this issue Jun 23, 2023
…Denton

slice::from_raw_parts: mention no-wrap-around condition

Cc rust-lang#83996. This probably needs to be mentioned in more places, so I am not closing that issue, but this here should help at least.
matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue Jun 23, 2023
…Denton

slice::from_raw_parts: mention no-wrap-around condition

Cc rust-lang#83996. This probably needs to be mentioned in more places, so I am not closing that issue, but this here should help at least.
thomcc pushed a commit to tcdi/postgrestd that referenced this issue Aug 24, 2023
slice::from_raw_parts: mention no-wrap-around condition

Cc rust-lang/rust#83996. This probably needs to be mentioned in more places, so I am not closing that issue, but this here should help at least.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-docs Area: documentation for any part of the project, including the compiler, standard library, and tools A-iterators Area: Iterators T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests