-
Notifications
You must be signed in to change notification settings - Fork 1
Description
First off, kudos for carefully considering each of the stated safety requirements for each unsafe operation used and documenting these so clearly. However, I believe I found a subtle issue that can lead to UB. It relates to a requirement that is unfortunately not explicitly noted (unless I missed something), but follows from how slices and str
work. For simplicity I'll demonstrate with u8 slices and the currently-internal concat_slice
function, but of course str
is just as affected.
To set the stage, note that it is possible for two separate allocations (for example, two String
s, or two u8 arrays on the stack) to be adjacent in memory. When that happens, this crate will happily detect that they're adjacent and concatenate them. I don't think there's any way to avoid that, and while one can't rely on two allocations being adjacent, maybe someone wants to make use of it when the stars align (or they simply made a mistake when passing the pointers).
The problem arises after the concatenated slice is constructed: indexing into it or slicing it (and likely other operations) calculates addresses in ways that assume the pointer doesn't cross allocation boundaries, otherwise it's UB. For example, given bytes: &[u8]
, the expression bytes[i]
boils down to something involving the pointer bytes.as_ptr().add(i)
. That can cross from the first allocation into the second in the scenario we're considering, but <*const T>::add
states among other safety preconditions:
Both the starting and resulting pointer must be either in bounds or one byte past the end of the same allocated object.
So the following code will have UB in the cases where concat_slice
returns Ok(..)
:
let buf1 = [0; 16];
let buf2 = [0; 16];
if let Ok(combined) = concat_slice(&buf1, &buf2) {
let x = combined[20];
}
In effect, slices and str
s and other safe references can't span allocation boundaries. Unfortunately, since I also don't know a way to ensure that two references come from the same allocation, I believe this makes it impossible to soundly provide the operations this crate exists to provide 😭
NOTE: Theoretically it's an implementation detail that
add
-- or any of its cousins with the same precondition -- is used here. However, this requirement is pretty important for performance and thus unlikely to change, which is why I'm raising it here first. Certainly should be documented, though. I filed rust-lang/rust#62765 for that.