Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix provenance UB and alignment UB (#27)
Fix provenance UB and alignment UB These issues were unlikely to cause any miscompilations today, but it's best to fix them right away. Thanks to miri for finding these issues! Alignment: In the default configuration (**not** gecko-ffi), types with alignment equal to the header size (16 on x64; 8 on x86) were incorrectly handled by an optimization. This could result in misaligned empty slices being produced when accessing a 0-capacity ThinVec (which is produced by ThinVec::new()). Such a slice of course can't be used to load or store any memory, but Rust requires references (including slices) to always be aligned even if they aren't ever used to load/store memory. The destructor for ThinVec creates a slice to its elements, so this dropping any ThinVec::new() with such a highly aligned type was technically UB. That said, it's quite unlikely it could have lead to miscompilations in practice, since the compiler would be unable to "see" the misalignment and Rust doesn't currently do any runtime optimizations based on alignment (such as Option-style enum layout optimizations). Types with higher and lower alignments were handled fine, it was just this *specific* alignment that was taking the wrong path. The specific issue is that 0-cap ThinVecs all point to statically allocated empty singleton. This singleton is designed to Do The Right Thing without additional branches for many operations, meaning we get non-allocating ThinVec::new() *without* needing to compromise the performance of most other operations. One such "just work" situation is the data pointer, which will generally just be one-past-the-end of the empty singleton, which is in-bounds for the singleton. But for highly-aligned elements, the data pointer needs to be "padded" and that would go beyond the static singleton's "allocation". So in general we actually check if cap==0 and return NonNull::dangling for the data pointer. The *optimization* was to identify the cases where no such padding was required and statically eliminate the cap == 0 path. Unfortunately "has no padding" isn't sufficient: in the specific case of align==header_size, there is no padding but the singleton is underaligned! In this case the NonNull::dangling path must be taken. The code has been fixed and the optimization now works correctly for all alignments. Provenance: Many places ran afoul of Stacked Borrows. The issues found were: A &Header cannot be used to get a useful pointer to data beyond it, because the pointer from the as-cast of the &Header only has provenance over the Header. After a set_len call that decreases the length, it is invalid to create a slice then try to get_unchecked into the region between the old and new length, because the reference in the slice that the ThinVec now Derefs to does not have provenance over that region. Alternatively, this is UB because the docs stipulate that you're not allowed to use `get_unchecked` to index out of bounds. I think the use of align_offset in tests is subtly wrong, align_offset seems to be for optimizations only. The docs say that a valid implementation may always return usize::MAX.
- Loading branch information