Skip to content

Commit 953cf27

Browse files
committed
Workaround for mem safety in third party dlls
1 parent d51b6f9 commit 953cf27

File tree

4 files changed

+91
-21
lines changed

4 files changed

+91
-21
lines changed

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use core::sync::atomic::{Atomic, AtomicU32, Ordering};
3333

3434
use super::{AsRawHandle, DirBuff, File, FromRawHandle};
3535
use crate::sys::c;
36-
use crate::sys::pal::api::WinError;
36+
use crate::sys::pal::api::{UnicodeStrRef, WinError, unicode_str};
3737
use crate::thread;
3838

3939
// The maximum number of times to spin when waiting for deletes to complete.
@@ -74,7 +74,7 @@ unsafe fn nt_open_file(
7474
/// `options` will be OR'd with `FILE_OPEN_REPARSE_POINT`.
7575
fn open_link_no_reparse(
7676
parent: &File,
77-
path: &[u16],
77+
path: UnicodeStrRef<'_>,
7878
access: u32,
7979
options: u32,
8080
) -> Result<Option<File>, WinError> {
@@ -90,9 +90,8 @@ fn open_link_no_reparse(
9090
static ATTRIBUTES: Atomic<u32> = AtomicU32::new(c::OBJ_DONT_REPARSE);
9191

9292
let result = unsafe {
93-
let mut path_str = c::UNICODE_STRING::from_ref(path);
9493
let mut object = c::OBJECT_ATTRIBUTES {
95-
ObjectName: &mut path_str,
94+
ObjectName: path.as_ptr(),
9695
RootDirectory: parent.as_raw_handle(),
9796
Attributes: ATTRIBUTES.load(Ordering::Relaxed),
9897
..c::OBJECT_ATTRIBUTES::with_length()
@@ -129,7 +128,7 @@ fn open_link_no_reparse(
129128
}
130129
}
131130

132-
fn open_dir(parent: &File, name: &[u16]) -> Result<Option<File>, WinError> {
131+
fn open_dir(parent: &File, name: UnicodeStrRef<'_>) -> Result<Option<File>, WinError> {
133132
// Open the directory for synchronous directory listing.
134133
open_link_no_reparse(
135134
parent,
@@ -140,7 +139,7 @@ fn open_dir(parent: &File, name: &[u16]) -> Result<Option<File>, WinError> {
140139
)
141140
}
142141

143-
fn delete(parent: &File, name: &[u16]) -> Result<(), WinError> {
142+
fn delete(parent: &File, name: UnicodeStrRef<'_>) -> Result<(), WinError> {
144143
// Note that the `delete` function consumes the opened file to ensure it's
145144
// dropped immediately. See module comments for why this is important.
146145
match open_link_no_reparse(parent, name, c::DELETE, 0) {
@@ -179,16 +178,17 @@ pub fn remove_dir_all_iterative(dir: File) -> Result<(), WinError> {
179178
'outer: while let Some(dir) = dirlist.pop() {
180179
let more_data = dir.fill_dir_buff(&mut buffer, restart)?;
181180
for (name, is_directory) in buffer.iter() {
181+
let name = unicode_str!(&name);
182182
if is_directory {
183-
let Some(subdir) = open_dir(&dir, &name)? else { continue };
183+
let Some(subdir) = open_dir(&dir, name)? else { continue };
184184
dirlist.push(dir);
185185
dirlist.push(subdir);
186186
continue 'outer;
187187
} else {
188188
// Attempt to delete, retrying on sharing violation errors as these
189189
// can often be very temporary. E.g. if something takes just a
190190
// bit longer than expected to release a file handle.
191-
retry(|| delete(&dir, &name), WinError::SHARING_VIOLATION)?;
191+
retry(|| delete(&dir, name), WinError::SHARING_VIOLATION)?;
192192
}
193193
}
194194
if more_data {
@@ -197,7 +197,8 @@ pub fn remove_dir_all_iterative(dir: File) -> Result<(), WinError> {
197197
} else {
198198
// Attempt to delete, retrying on not empty errors because we may
199199
// need to wait some time for files to be removed from the filesystem.
200-
retry(|| delete(&dir, &[]), WinError::DIR_NOT_EMPTY)?;
200+
let name = unicode_str!("");
201+
retry(|| delete(&dir, name), WinError::DIR_NOT_EMPTY)?;
201202
restart = true;
202203
}
203204
}

library/std/src/sys/pal/windows/api.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
//! should go in sys/pal/windows/mod.rs rather than here. See `IoResult` as an example.
3131
3232
use core::ffi::c_void;
33+
use core::marker::PhantomData;
3334

3435
use super::c;
3536

@@ -291,3 +292,75 @@ impl WinError {
291292
pub const TIMEOUT: Self = Self::new(c::ERROR_TIMEOUT);
292293
// tidy-alphabetical-end
293294
}
295+
296+
/// A wrapper around a UNICODE_STRING that is equivalent to `&[u16]`.
297+
///
298+
/// It is preferable to use the `unicode_str!` macro as that contains mitigations for #143078.
299+
///
300+
/// If the MaximumLength field of the underlying UNICODE_STRING is greater than
301+
/// the Length field then you can test if the string is null terminated by inspecting
302+
/// the u16 directly after the string. You cannot otherwise depend on nul termination.
303+
#[derive(Copy, Clone)]
304+
pub struct UnicodeStrRef<'a> {
305+
s: c::UNICODE_STRING,
306+
lifetime: PhantomData<&'a [u16]>,
307+
}
308+
309+
static EMPTY_STRING_NULL_TERMINATED: &[u16] = &[0];
310+
311+
impl UnicodeStrRef<'_> {
312+
const fn new(slice: &[u16], is_null_terminated: bool) -> Self {
313+
let (len, max_len, ptr) = if slice.is_empty() {
314+
(0, 2, EMPTY_STRING_NULL_TERMINATED.as_ptr().cast_mut())
315+
} else {
316+
let len = slice.len() - (is_null_terminated as usize);
317+
(len * 2, size_of_val(slice), slice.as_ptr().cast_mut())
318+
};
319+
Self {
320+
s: c::UNICODE_STRING { Length: len as _, MaximumLength: max_len as _, Buffer: ptr },
321+
lifetime: PhantomData,
322+
}
323+
}
324+
325+
pub const fn from_slice_with_nul(slice: &[u16]) -> Self {
326+
if !slice.is_empty() {
327+
debug_assert!(slice[slice.len() - 1] == 0);
328+
}
329+
Self::new(slice, true)
330+
}
331+
332+
pub const fn from_slice(slice: &[u16]) -> Self {
333+
Self::new(slice, false)
334+
}
335+
336+
/// Returns a pointer to the underlying UNICODE_STRING
337+
pub const fn as_ptr(&self) -> *const c::UNICODE_STRING {
338+
&self.s
339+
}
340+
}
341+
342+
/// Create a UnicodeStringRef from a literal str or a u16 array.
343+
///
344+
/// To mitigate #143078, when using a literal str the created UNICODE_STRING
345+
/// will be nul terminated. The MaximumLength field of the UNICODE_STRING will
346+
/// be set greater than the Length field to indicate that a nul may be present.
347+
///
348+
/// If using a u16 array, the array is used exactly as provided and you cannot
349+
/// count on the string being nul terminated.
350+
/// This should generally be used for strings that come from the OS.
351+
///
352+
/// **NOTE:** we lack a UNICODE_STRING builder type as we don't currently have
353+
/// a use for it. If needing to dynamically build a UNICODE_STRING, the builder
354+
/// should try to ensure there's a nul one past the end of the string.
355+
pub macro unicode_str {
356+
($str:literal) => {const {
357+
crate::sys::pal::windows::api::UnicodeStrRef::from_slice_with_nul(
358+
crate::sys::pal::windows::api::wide_str!($str),
359+
)
360+
}},
361+
($array:expr) => {
362+
crate::sys::pal::windows::api::UnicodeStrRef::from_slice(
363+
$array,
364+
)
365+
}
366+
}

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

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,6 @@ pub fn nt_success(status: NTSTATUS) -> bool {
3737
status >= 0
3838
}
3939

40-
impl UNICODE_STRING {
41-
pub fn from_ref(slice: &[u16]) -> Self {
42-
let len = size_of_val(slice);
43-
Self { Length: len as _, MaximumLength: len as _, Buffer: slice.as_ptr() as _ }
44-
}
45-
}
46-
4740
impl OBJECT_ATTRIBUTES {
4841
pub fn with_length() -> Self {
4942
Self {

library/std/src/sys/pal/windows/pipe.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut};
22
use crate::ops::Neg;
33
use crate::os::windows::prelude::*;
4-
use crate::sys::api::utf16;
5-
use crate::sys::c;
64
use crate::sys::handle::Handle;
5+
use crate::sys::{api, c};
76
use crate::sys_common::{FromInner, IntoInner};
87
use crate::{mem, ptr};
98

@@ -73,8 +72,8 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
7372
// Open a handle to the pipe filesystem (`\??\PIPE\`).
7473
// This will be used when creating a new annon pipe.
7574
let pipe_fs = {
76-
let path = c::UNICODE_STRING::from_ref(utf16!(r"\??\PIPE\"));
77-
object_attributes.ObjectName = &path;
75+
let path = api::unicode_str!(r"\??\PIPE\");
76+
object_attributes.ObjectName = path.as_ptr();
7877
let mut pipe_fs = ptr::null_mut();
7978
let status = c::NtOpenFile(
8079
&mut pipe_fs,
@@ -93,8 +92,12 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res
9392

9493
// From now on we're using handles instead of paths to create and open pipes.
9594
// So set the `ObjectName` to a zero length string.
95+
// As a (perhaps overzealous) mitigation for #143078, we use the null pointer
96+
// for empty.Buffer instead of unicode_str!("").
97+
// There's no difference to the OS itself but it's possible that third party
98+
// DLLs which hook in to processes could be relying on the exact form of this string.
9699
let empty = c::UNICODE_STRING::default();
97-
object_attributes.ObjectName = &empty;
100+
object_attributes.ObjectName = &raw const empty;
98101

99102
// Create our side of the pipe for async access.
100103
let ours = {

0 commit comments

Comments
 (0)