Skip to content

Commit

Permalink
Fix is_terminal's handling of long paths on Windows.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sunfishcode committed Feb 23, 2023
1 parent 47a64b8 commit 5bbc9aa
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 19 deletions.
8 changes: 0 additions & 8 deletions std/src/sys/windows/c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -539,14 +539,6 @@ pub struct SYMBOLIC_LINK_REPARSE_BUFFER {
pub PathBuffer: WCHAR,
}

/// NB: Use carefully! In general using this as a reference is likely to get the
/// provenance wrong for the `PathBuffer` field!
#[repr(C)]
pub struct FILE_NAME_INFO {
pub FileNameLength: DWORD,
pub FileName: [WCHAR; 1],
}

#[repr(C)]
pub struct MOUNT_POINT_REPARSE_BUFFER {
pub SubstituteNameOffset: c_ushort,
Expand Down
32 changes: 21 additions & 11 deletions std/src/sys/windows/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ use crate::marker::PhantomData;
use crate::mem::size_of;
use crate::os::windows::io::{AsHandle, AsRawHandle, BorrowedHandle};
use crate::slice;
use crate::sys::{c, Align8};
use core;
use crate::sys::c;
use libc;

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

const SIZE: usize = size_of::<c::FILE_NAME_INFO>() + c::MAX_PATH * size_of::<c::WCHAR>();
let mut name_info_bytes = Align8([0u8; SIZE]);
/// Mirrors [`FILE_NAME_INFO`], giving it a fixed length that we can stack
/// allocate
///
/// [`FILE_NAME_INFO`]: https://learn.microsoft.com/en-us/windows/win32/api/winbase/ns-winbase-file_name_info
#[repr(C)]
#[allow(non_snake_case)]
struct FILE_NAME_INFO {
FileNameLength: u32,
FileName: [u16; c::MAX_PATH as usize],
}
let mut name_info = FILE_NAME_INFO { FileNameLength: 0, FileName: [0; c::MAX_PATH as usize] };
// Safety: buffer length is fixed.
let res = c::GetFileInformationByHandleEx(
handle,
c::FileNameInfo,
name_info_bytes.0.as_mut_ptr() as *mut libc::c_void,
SIZE as u32,
&mut name_info as *mut _ as *mut libc::c_void,
size_of::<FILE_NAME_INFO>() as u32,
);
if res == 0 {
return false;
}
let name_info: &c::FILE_NAME_INFO = &*(name_info_bytes.0.as_ptr() as *const c::FILE_NAME_INFO);
let name_len = name_info.FileNameLength as usize / 2;
// Offset to get the `FileName` field.
let name_ptr = name_info_bytes.0.as_ptr().offset(size_of::<c::DWORD>() as isize).cast::<u16>();
let s = core::slice::from_raw_parts(name_ptr, name_len);

// Use `get` because `FileNameLength` can be out of range.
let s = match name_info.FileName.get(..name_info.FileNameLength as usize / 2) {
None => return false,
Some(s) => s,
};
let name = String::from_utf16_lossy(s);
// Get the file name only.
let name = name.rsplit('\\').next().unwrap_or(&name);
Expand Down

0 comments on commit 5bbc9aa

Please sign in to comment.