Skip to content

Commit 1b8025a

Browse files
committed
Fix some possible UB in std::sys::windows
1 parent fcc2bdd commit 1b8025a

File tree

4 files changed

+71
-16
lines changed

4 files changed

+71
-16
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,8 @@ pub struct FILE_END_OF_FILE_INFO {
503503
pub EndOfFile: LARGE_INTEGER,
504504
}
505505

506+
/// NB: Use carefully! In general using this as a reference is likely to get the
507+
/// provenance wrong for the `rest` field!
506508
#[repr(C)]
507509
pub struct REPARSE_DATA_BUFFER {
508510
pub ReparseTag: c_uint,
@@ -511,6 +513,8 @@ pub struct REPARSE_DATA_BUFFER {
511513
pub rest: (),
512514
}
513515

516+
/// NB: Use carefully! In general using this as a reference is likely to get the
517+
/// provenance wrong for the `PathBuffer` field!
514518
#[repr(C)]
515519
pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
516520
pub SubstituteNameOffset: c_ushort,
@@ -521,6 +525,8 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
521525
pub PathBuffer: WCHAR,
522526
}
523527

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

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

Lines changed: 24 additions & 16 deletions
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, AlignedAs};
1515
use crate::sys_common::{AsInner, FromInner, IntoInner};
1616
use crate::thread;
1717

@@ -47,6 +47,9 @@ pub struct ReadDir {
4747
first: Option<c::WIN32_FIND_DATAW>,
4848
}
4949

50+
type AlignedReparseBuf =
51+
AlignedAs<c::REPARSE_DATA_BUFFER, [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]>;
52+
5053
struct FindNextFileHandle(c::HANDLE);
5154

5255
unsafe impl Send for FindNextFileHandle {}
@@ -326,9 +329,9 @@ impl File {
326329
cvt(c::GetFileInformationByHandle(self.handle.as_raw_handle(), &mut info))?;
327330
let mut reparse_tag = 0;
328331
if info.dwFileAttributes & c::FILE_ATTRIBUTE_REPARSE_POINT != 0 {
329-
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
332+
let mut b = AlignedReparseBuf::new([0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
330333
if let Ok((_, buf)) = self.reparse_point(&mut b) {
331-
reparse_tag = buf.ReparseTag;
334+
reparse_tag = (*buf).ReparseTag;
332335
}
333336
}
334337
Ok(FileAttr {
@@ -389,7 +392,7 @@ impl File {
389392
attr.file_size = info.AllocationSize as u64;
390393
attr.number_of_links = Some(info.NumberOfLinks);
391394
if attr.file_type().is_reparse_point() {
392-
let mut b = [0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
395+
let mut b = AlignedReparseBuf::new([0; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
393396
if let Ok((_, buf)) = self.reparse_point(&mut b) {
394397
attr.reparse_tag = buf.ReparseTag;
395398
}
@@ -458,10 +461,13 @@ impl File {
458461
Ok(Self { handle: self.handle.try_clone()? })
459462
}
460463

461-
fn reparse_point<'a>(
464+
// NB: returned pointer is derived from `space`, and has provenance to
465+
// match. A raw pointer is returned rather than a reference in order to
466+
// avoid narrowing provenance to the actual `REPARSE_DATA_BUFFER`.
467+
fn reparse_point(
462468
&self,
463-
space: &'a mut [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE],
464-
) -> io::Result<(c::DWORD, &'a c::REPARSE_DATA_BUFFER)> {
469+
space: &mut AlignedReparseBuf,
470+
) -> io::Result<(c::DWORD, *const c::REPARSE_DATA_BUFFER)> {
465471
unsafe {
466472
let mut bytes = 0;
467473
cvt({
@@ -470,36 +476,38 @@ impl File {
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.value.as_mut_ptr() as *mut _,
480+
space.value.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+
Ok((bytes, space.value.as_ptr().cast::<c::REPARSE_DATA_BUFFER>()))
480486
}
481487
}
482488

483489
fn readlink(&self) -> io::Result<PathBuf> {
484-
let mut space = [0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
490+
let mut space = AlignedReparseBuf::new([0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
485491
let (_bytes, buf) = self.reparse_point(&mut space)?;
486492
unsafe {
487-
let (path_buffer, subst_off, subst_len, relative) = match buf.ReparseTag {
493+
let (path_buffer, subst_off, subst_len, relative) = match (*buf).ReparseTag {
488494
c::IO_REPARSE_TAG_SYMLINK => {
489495
let info: *const c::SYMBOLIC_LINK_REPARSE_BUFFER =
490-
&buf.rest as *const _ as *const _;
496+
ptr::addr_of!((*buf).rest).cast();
497+
assert!(info.is_aligned());
491498
(
492-
&(*info).PathBuffer as *const _ as *const u16,
499+
ptr::addr_of!((*info).PathBuffer).cast::<u16>(),
493500
(*info).SubstituteNameOffset / 2,
494501
(*info).SubstituteNameLength / 2,
495502
(*info).Flags & c::SYMLINK_FLAG_RELATIVE != 0,
496503
)
497504
}
498505
c::IO_REPARSE_TAG_MOUNT_POINT => {
499506
let info: *const c::MOUNT_POINT_REPARSE_BUFFER =
500-
&buf.rest as *const _ as *const _;
507+
ptr::addr_of!((*buf).rest).cast();
508+
assert!(info.is_aligned());
501509
(
502-
&(*info).PathBuffer as *const _ as *const u16,
510+
ptr::addr_of!((*info).PathBuffer).cast::<u16>(),
503511
(*info).SubstituteNameOffset / 2,
504512
(*info).SubstituteNameLength / 2,
505513
false,

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,3 +329,26 @@ pub fn abort_internal() -> ! {
329329
}
330330
crate::intrinsics::abort();
331331
}
332+
333+
/// Used for some win32 buffers which are stack allocated, for example:
334+
/// `AlignedAs<c::REPARSE_DATA_BUFFER, [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]>`
335+
#[repr(C)]
336+
#[derive(Copy, Clone)]
337+
pub struct AlignedAs<Aligner, Alignee: ?Sized> {
338+
/// Use `[Aligner; 0]` as a sort of `PhantomAlignNextField<Aligner>`. This
339+
/// is a bit of a hack, and could break (in a way that's caught by tests) if
340+
/// #81996 is fixed.
341+
aligner: [Aligner; 0],
342+
/// The aligned value. Public rather than exposed via accessors so that if
343+
/// needed it can be used with `addr_of` and such (also, this is less code).
344+
pub value: Alignee,
345+
}
346+
347+
impl<Aligner, Alignee> AlignedAs<Aligner, Alignee> {
348+
// This is frequently used with large stack buffers, so force-inline to
349+
// try and avoid using 2x as much stack space in debug builds.
350+
#[inline(always)]
351+
pub const fn new(value: Alignee) -> Self {
352+
Self { aligner: [], value }
353+
}
354+
}

library/std/src/sys/windows/os/tests.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,21 @@ fn ntstatus_error() {
1111
.contains("FormatMessageW() returned error")
1212
);
1313
}
14+
15+
#[test]
16+
fn smoketest_aligned_as() {
17+
use crate::{
18+
mem::{align_of, size_of},
19+
ptr::addr_of,
20+
sys::{c, AlignedAs},
21+
};
22+
type AlignedReparseBuf =
23+
AlignedAs<c::REPARSE_DATA_BUFFER, [u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]>;
24+
assert!(size_of::<AlignedReparseBuf>() >= c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
25+
assert_eq!(align_of::<AlignedReparseBuf>(), align_of::<c::REPARSE_DATA_BUFFER>());
26+
let a = AlignedReparseBuf::new([0u8; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
27+
// Quick and dirty offsetof check.
28+
assert_eq!(addr_of!(a).cast::<u8>(), addr_of!(a.value).cast::<u8>());
29+
// Smoke check that it's actually aligned.
30+
assert!(addr_of!(a.value).is_aligned_to(align_of::<c::REPARSE_DATA_BUFFER>()));
31+
}

0 commit comments

Comments
 (0)