Rollup merge of #108391 - sunfishcode:sunfishcode/is-terminal-file-length, r=ChrisDenton

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.
This commit is contained in:
Dylan DPC 2023-02-24 12:02:45 +05:30 committed by GitHub
commit 353827a044
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 21 additions and 19 deletions

View File

@ -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,

View File

@ -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)]
@ -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);