Skip to content

Commit 0ed046f

Browse files
authored
Rollup merge of #101171 - thomcc:fix-winfs-ub, r=ChrisDenton
Fix UB from misalignment and provenance widening in `std::sys::windows` This fixes two types of UB: 1. Reading past the end of a reference in types like `&c::REPARSE_DATA_BUFFER` (see rust-lang/unsafe-code-guidelines#256). This is fixed by using `addr_of!`. I think there are probably a couple more cases where we do this for other structures, and will look into it in a bit. 2. Failing to ensure that a `[u8; N]` on the stack is sufficiently aligned to convert to a `REPARSE_DATA_BUFFER`. ~~This was done by introducing a new `AlignedAs` struct that allows aligning one type to the alignment of another type. I expect there are other places where we have this issue too, or I wouldn't introduce this type, but will get to them after this lands.~~ ~~Worth noting, it *is* implemented in a way that can cause problems depending on how we fix #81996, but this would be caught by the test I added (and presumably if we decide to fix that in a way that would break this code, we'd also introduce a `#[repr(simple)]` or `#[repr(linear)]` as a replacement for this usage of `#[repr(C)]`).~~ Edit: None of that is still in the code, I just went with a `Align8` since that's all we'll need for almost everything we want to call. These are more or less "potential UB" since it's likely at the moment everything works fine, although the alignment not causing issues might just be down to luck (and x86 being forgiving). ~~NB: I've only ensured this check builds, but will run tests soon.~~ All tests pass, including stage2 compiler tests. r? ``@ChrisDenton``
2 parents b8b2f88 + c41f21b commit 0ed046f

File tree

3 files changed

+55
-28
lines changed

3 files changed

+55
-28
lines changed

library/std/src/sys/windows/c.rs

+6
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,8 @@ pub struct FILE_END_OF_FILE_INFO {
501501
pub EndOfFile: LARGE_INTEGER,
502502
}
503503

504+
/// NB: Use carefully! In general using this as a reference is likely to get the
505+
/// provenance wrong for the `rest` field!
504506
#[repr(C)]
505507
pub struct REPARSE_DATA_BUFFER {
506508
pub ReparseTag: c_uint,
@@ -509,6 +511,8 @@ pub struct REPARSE_DATA_BUFFER {
509511
pub rest: (),
510512
}
511513

514+
/// NB: Use carefully! In general using this as a reference is likely to get the
515+
/// provenance wrong for the `PathBuffer` field!
512516
#[repr(C)]
513517
pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
514518
pub SubstituteNameOffset: c_ushort,
@@ -519,6 +523,8 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
519523
pub PathBuffer: WCHAR,
520524
}
521525

526+
/// NB: Use carefully! In general using this as a reference is likely to get the
527+
/// provenance wrong for the `PathBuffer` field!
522528
#[repr(C)]
523529
pub struct MOUNT_POINT_REPARSE_BUFFER {
524530
pub SubstituteNameOffset: c_ushort,

library/std/src/sys/windows/fs.rs

+41-28
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::slice;
1111
use crate::sync::Arc;
1212
use crate::sys::handle::Handle;
1313
use crate::sys::time::SystemTime;
14-
use crate::sys::{c, cvt};
14+
use crate::sys::{c, cvt, Align8};
1515
use crate::sys_common::{AsInner, FromInner, IntoInner};
1616
use crate::thread;
1717

@@ -326,9 +326,9 @@ impl File {
326326
cvt(c::GetFileInformationByHandle(self.handle.as_raw_handle(), &mut info))?;
327327
let mut reparse_tag = 0;
328328
if info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
329-
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
329+
let mut b = Align8([0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
330330
if let Ok((_, buf)) = self.reparse_point(&mut b) {
331-
reparse_tag = buf.ReparseTag;
331+
reparse_tag = (*buf).ReparseTag;
332332
}
333333
}
334334
Ok(FileAttr {
@@ -389,9 +389,9 @@ impl File {
389389
attr.file_size = info.AllocationSize as u64;
390390
attr.number_of_links = Some(info.NumberOfLinks);
391391
if attr.file_type().is_reparse_point() {
392-
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
392+
let mut b = Align8([0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
393393
if let Ok((_, buf)) = self.reparse_point(&mut b) {
394-
attr.reparse_tag = buf.ReparseTag;
394+
attr.reparse_tag = (*buf).ReparseTag;
395395
}
396396
}
397397
Ok(attr)
@@ -458,48 +458,57 @@ impl File {
458458
Ok(Self { handle: self.handle.try_clone()? })
459459
}
460460

461-
fn reparse_point<'a>(
461+
// NB: returned pointer is derived from `space`, and has provenance to
462+
// match. A raw pointer is returned rather than a reference in order to
463+
// avoid narrowing provenance to the actual `REPARSE_DATA_BUFFER`.
464+
fn reparse_point(
462465
&self,
463-
space: &'a mut [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE],
464-
) -> io::Result<(c::DWORD, &'a c::REPARSE_DATA_BUFFER)> {
466+
space: &mut Align8<[u8]>,
467+
) -> io::Result<(c::DWORD, *const c::REPARSE_DATA_BUFFER)> {
465468
unsafe {
466469
let mut bytes = 0;
467470
cvt({
471+
// Grab this in advance to avoid it invalidating the pointer
472+
// we get from `space.0.as_mut_ptr()`.
473+
let len = space.0.len();
468474
c::DeviceIoControl(
469475
self.handle.as_raw_handle(),
470476
c::FSCTL_GET_REPARSE_POINT,
471477
ptr::null_mut(),
472478
0,
473-
space.as_mut_ptr() as *mut _,
474-
space.len() as c::DWORD,
479+
space.0.as_mut_ptr().cast(),
480+
len as c::DWORD,
475481
&mut bytes,
476482
ptr::null_mut(),
477483
)
478484
})?;
479-
Ok((bytes, &*(space.as_ptr() as *const c::REPARSE_DATA_BUFFER)))
485+
const _: () = assert!(core::mem::align_of::<c::REPARSE_DATA_BUFFER>() <= 8);
486+
Ok((bytes, space.0.as_ptr().cast::<c::REPARSE_DATA_BUFFER>()))
480487
}
481488
}
482489

483490
fn readlink(&self) -> io::Result<PathBuf> {
484-
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
491+
let mut space = Align8([0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
485492
let (_bytes, buf) = self.reparse_point(&mut space)?;
486493
unsafe {
487-
let (path_buffer, subst_off, subst_len, relative) = match buf.ReparseTag {
494+
let (path_buffer, subst_off, subst_len, relative) = match (*buf).ReparseTag {
488495
c::IO_REPARSE_TAG_SYMLINK => {
489496
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
490-
&buf.rest as *const _ as *const _;
497+
ptr::addr_of!((*buf).rest).cast();
498+
assert!(info.is_aligned());
491499
(
492-
&(*info).PathBuffer as *const _ as *const u16,
500+
ptr::addr_of!((*info).PathBuffer).cast::<u16>(),
493501
(*info).SubstituteNameOffset / 2,
494502
(*info).SubstituteNameLength / 2,
495503
(*info).Flags & c::SYMLINK_FLAG_RELATIVE != 0,
496504
)
497505
}
498506
c::IO_REPARSE_TAG_MOUNT_POINT => {
499507
let info: *const c::MOUNT_POINT_REPARSE_BUFFER =
500-
&buf.rest as *const _ as *const _;
508+
ptr::addr_of!((*buf).rest).cast();
509+
assert!(info.is_aligned());
501510
(
502-
&(*info).PathBuffer as *const _ as *const u16,
511+
ptr::addr_of!((*info).PathBuffer).cast::<u16>(),
503512
(*info).SubstituteNameOffset / 2,
504513
(*info).SubstituteNameLength / 2,
505514
false,
@@ -649,18 +658,18 @@ impl File {
649658

650659
/// A buffer for holding directory entries.
651660
struct DirBuff {
652-
buffer: Vec<u8>,
661+
buffer: Box<Align8<[u8; Self::BUFFER_SIZE]>>,
653662
}
654663
impl DirBuff {
664+
const BUFFER_SIZE: usize = 1024;
655665
fn new() -> Self {
656-
const BUFFER_SIZE: usize = 1024;
657-
Self { buffer: vec![0_u8; BUFFER_SIZE] }
666+
Self { buffer: Box::new(Align8([0u8; Self::BUFFER_SIZE])) }
658667
}
659668
fn capacity(&self) -> usize {
660-
self.buffer.len()
669+
self.buffer.0.len()
661670
}
662671
fn as_mut_ptr(&mut self) -> *mut u8 {
663-
self.buffer.as_mut_ptr().cast()
672+
self.buffer.0.as_mut_ptr().cast()
664673
}
665674
/// Returns a `DirBuffIter`.
666675
fn iter(&self) -> DirBuffIter<'_> {
@@ -669,7 +678,7 @@ impl DirBuff {
669678
}
670679
impl AsRef<[u8]> for DirBuff {
671680
fn as_ref(&self) -> &[u8] {
672-
&self.buffer
681+
&self.buffer.0
673682
}
674683
}
675684

@@ -697,9 +706,12 @@ impl<'a> Iterator for DirBuffIter<'a> {
697706
// used to get the file name slice.
698707
let (name, is_directory, next_entry) = unsafe {
699708
let info = buffer.as_ptr().cast::<c::FILE_ID_BOTH_DIR_INFO>();
709+
// Guaranteed to be aligned in documentation for
710+
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_id_both_dir_info
711+
assert!(info.is_aligned());
700712
let next_entry = (*info).NextEntryOffset as usize;
701713
let name = crate::slice::from_raw_parts(
702-
(*info).FileName.as_ptr().cast::<u16>(),
714+
ptr::addr_of!((*info).FileName).cast::<u16>(),
703715
(*info).FileNameLength as usize / size_of::<u16>(),
704716
);
705717
let is_directory = ((*info).FileAttributes & c::FILE_ATTRIBUTE_DIRECTORY) != 0;
@@ -1337,9 +1349,10 @@ fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> {
13371349
let h = f.as_inner().as_raw_handle();
13381350

13391351
unsafe {
1340-
let mut data = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
1341-
let db = data.as_mut_ptr() as *mut c::REPARSE_MOUNTPOINT_DATA_BUFFER;
1342-
let buf = &mut (*db).ReparseTarget as *mut c::WCHAR;
1352+
let mut data = Align8([0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
1353+
let data_ptr = data.0.as_mut_ptr();
1354+
let db = data_ptr.cast::<c::REPARSE_MOUNTPOINT_DATA_BUFFER>();
1355+
let buf = ptr::addr_of_mut!((*db).ReparseTarget).cast::<c::WCHAR>();
13431356
let mut i = 0;
13441357
// FIXME: this conversion is very hacky
13451358
let v = br"\??\";
@@ -1359,7 +1372,7 @@ fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> {
13591372
cvt(c::DeviceIoControl(
13601373
h as *mut _,
13611374
c::FSCTL_SET_REPARSE_POINT,
1362-
data.as_ptr() as *mut _,
1375+
data_ptr.cast(),
13631376
(*db).ReparseDataLength + 8,
13641377
ptr::null_mut(),
13651378
0,

library/std/src/sys/windows/mod.rs

+8
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,11 @@ pub fn abort_internal() -> ! {
329329
}
330330
crate::intrinsics::abort();
331331
}
332+
333+
/// Align the inner value to 8 bytes.
334+
///
335+
/// This is enough for almost all of the buffers we're likely to work with in
336+
/// the Windows APIs we use.
337+
#[repr(C, align(8))]
338+
#[derive(Copy, Clone)]
339+
pub(crate) struct Align8<T: ?Sized>(pub T);

0 commit comments

Comments
 (0)