Skip to content

Commit c0c1925

Browse files
committed
Fix is_terminal's handling of long paths on Windows.
As reported in sunfishcode/is-terminal#18, there are situations where `GetFileInformationByHandleEx` can write a file name length that is longer than the provided buffer. To avoid deferencing memory past the end of the buffer, use a bounds-checked function to form a slice to the buffer and handle the out-of-bounds case. This ports the fix from sunfishcode/is-terminal#19 to std's `is_terminal` implementation.
1 parent 07c993e commit c0c1925

File tree

2 files changed

+21
-19
lines changed

2 files changed

+21
-19
lines changed

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

-8
Original file line numberDiff line numberDiff line change
@@ -539,14 +539,6 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
539539
pub PathBuffer: WCHAR,
540540
}
541541

542-
/// NB: Use carefully! In general using this as a reference is likely to get the
543-
/// provenance wrong for the `PathBuffer` field!
544-
#[repr(C)]
545-
pub struct FILE_NAME_INFO {
546-
pub FileNameLength: DWORD,
547-
pub FileName: [WCHAR; 1],
548-
}
549-
550542
#[repr(C)]
551543
pub struct MOUNT_POINT_REPARSE_BUFFER {
552544
pub SubstituteNameOffset: c_ushort,

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

+21-11
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use crate::marker::PhantomData;
22
use crate::mem::size_of;
33
use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
44
use crate::slice;
5-
use crate::sys::{c, Align8};
6-
use core;
5+
use crate::sys::c;
76
use libc;
87

98
#[derive(Copy, Clone)]
@@ -125,22 +124,33 @@ unsafe fn msys_tty_on(handle: c::HANDLE) -> bool {
125124
return false;
126125
}
127126

128-
const SIZE: usize = size_of::<c::FILE_NAME_INFO>() + c::MAX_PATH * size_of::<c::WCHAR>();
129-
let mut name_info_bytes = Align8([0u8; SIZE]);
127+
/// Mirrors [`FILE_NAME_INFO`], giving it a fixed length that we can stack
128+
/// allocate
129+
///
130+
/// [`FILE_NAME_INFO`]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_name_info
131+
#[repr(C)]
132+
#[allow(non_snake_case)]
133+
struct FILE_NAME_INFO {
134+
FileNameLength: u32,
135+
FileName: [u16; c::MAX_PATH as usize],
136+
}
137+
let mut name_info = FILE_NAME_INFO { FileNameLength: 0, FileName: [0; c::MAX_PATH as usize] };
138+
// Safety: buffer length is fixed.
130139
let res = c::GetFileInformationByHandleEx(
131140
handle,
132141
c::FileNameInfo,
133-
name_info_bytes.0.as_mut_ptr() as *mut libc::c_void,
134-
SIZE as u32,
142+
&mut name_info as *mut _ as *mut libc::c_void,
143+
size_of::<FILE_NAME_INFO>() as u32,
135144
);
136145
if res == 0 {
137146
return false;
138147
}
139-
let name_info: &c::FILE_NAME_INFO = &*(name_info_bytes.0.as_ptr() as *const c::FILE_NAME_INFO);
140-
let name_len = name_info.FileNameLength as usize / 2;
141-
// Offset to get the `FileName` field.
142-
let name_ptr = name_info_bytes.0.as_ptr().offset(size_of::<c::DWORD>() as isize).cast::<u16>();
143-
let s = core::slice::from_raw_parts(name_ptr, name_len);
148+
149+
// Use `get` because `FileNameLength` can be out of range.
150+
let s = match name_info.FileName.get(..name_info.FileNameLength as usize / 2) {
151+
None => return false,
152+
Some(s) => s,
153+
};
144154
let name = String::from_utf16_lossy(s);
145155
// Get the file name only.
146156
let name = name.rsplit('\\').next().unwrap_or(&name);

0 commit comments

Comments
 (0)