-
-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix dereferencing of unaligned FILE_NAME_INFO #51
base: master
Are you sure you want to change the base?
Changes from 3 commits
e0f3e78
ba7ef89
844cc33
73762b1
1327972
b6322e5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -90,7 +90,7 @@ pub fn is(stream: Stream) -> bool { | |
|
||
// Otherwise, we fall back to a very strange msys hack to see if we can | ||
// sneakily detect the presence of a tty. | ||
unsafe { msys_tty_on(fd) } | ||
msys_tty_on(fd) | ||
} | ||
|
||
/// returns true if this is _not_ a tty | ||
|
@@ -115,34 +115,56 @@ unsafe fn console_on_any(fds: &[DWORD]) -> bool { | |
|
||
/// Returns true if there is an MSYS tty on the given handle. | ||
#[cfg(windows)] | ||
unsafe fn msys_tty_on(fd: DWORD) -> bool { | ||
fn msys_tty_on(fd: DWORD) -> bool { | ||
use std::{mem, slice}; | ||
|
||
use winapi::{ | ||
ctypes::c_void, | ||
shared::minwindef::MAX_PATH, | ||
um::{ | ||
fileapi::FILE_NAME_INFO, minwinbase::FileNameInfo, processenv::GetStdHandle, | ||
minwinbase::FileNameInfo, processenv::GetStdHandle, | ||
winbase::GetFileInformationByHandleEx, | ||
}, | ||
}; | ||
|
||
let size = mem::size_of::<FILE_NAME_INFO>(); | ||
let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()]; | ||
let res = GetFileInformationByHandleEx( | ||
GetStdHandle(fd), | ||
FileNameInfo, | ||
&mut *name_info_bytes as *mut _ as *mut c_void, | ||
name_info_bytes.len() as u32, | ||
); | ||
/// Mirrors winapi::um::fileapi::FILE_NAME_INFO, giving it a fixed length that | ||
/// we can stack allocate | ||
#[repr(C)] | ||
struct FILE_NAME_INFO { | ||
FileNameLength: DWORD, | ||
FileName: [mem::MaybeUninit<WCHAR>; MAX_PATH] | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you know why the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the way winapi normally models C's flexible array members, as it's typically the most flexible (heh) option. The actual max length of file name that |
||
let mut name_info = mem::MaybeUninit::<FILE_NAME_INFO>::uninit(); | ||
let handle = unsafe { | ||
// Safety: function has no invariants. an invalid handle id will cause | ||
// GetFileInformationByHandleEx to return an error | ||
GetStdHandle(fd) | ||
}; | ||
let res = unsafe { | ||
// Safety: handle is valid, buffer is large enough for expected data | ||
// and we have fully allocated it. | ||
GetFileInformationByHandleEx( | ||
handle, | ||
FileNameInfo, | ||
name_info.as_mut_ptr() as *mut c_void, | ||
mem::size_of_val(&name_info) as u32, | ||
) | ||
}; | ||
if res == 0 { | ||
return false; | ||
} | ||
let name_info: &FILE_NAME_INFO = &*(name_info_bytes.as_ptr() as *const FILE_NAME_INFO); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that a smaller change here would be to just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to use uninitialized memory here anyway - Edit: On second thought As for a let size = mem::size_of::<FILE_NAME_INFO>();
// Imo, this takes no less effort to grok
let mut name_info_bytes = vec![0u8; size + MAX_PATH * mem::size_of::<WCHAR>()]; The main problem with using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. w.r.t. to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be clear, I'm fine with fixing the heap alloc issue here. While it's not clear to me why fixing it is important, there is little cost to doing so I think. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That certainly wasn't the intention! I totally agree that it's good to keep the scope of these changes small, I just found that trying to align the vector properly was trickier than doing it on the stack, as I needed to intertwine the alignment with the generic type I was putting in the vector. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (Also, I know you're only skimming so I don't blame you for misreading the change, but the difference between a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah I see. Tricksy indeed. You're totally right. |
||
let s = slice::from_raw_parts( | ||
name_info.FileName.as_ptr(), | ||
name_info.FileNameLength as usize / 2, | ||
); | ||
let name_info = unsafe { | ||
// Safety: The API call succeeded, so name_info has been initialized | ||
// Note name_info.FileName is only partially initialized | ||
&*name_info.as_ptr() | ||
}; | ||
let s = unsafe { | ||
// Safety: the buffer is guaranteed to have FileNameLength initialized bytes | ||
slice::from_raw_parts( | ||
name_info.FileName.as_ptr() as *const WCHAR, | ||
name_info.FileNameLength as usize / 2, | ||
) | ||
}; | ||
let name = String::from_utf16_lossy(s); | ||
// This checks whether 'pty' exists in the file name, which indicates that | ||
// a pseudo-terminal is attached. To mitigate against false positives | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect this should be
unsafe
. It's taking a raw file descriptor without any guarantees about the validity of that file descriptor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
fd
isn't used as a file descriptor internally, just as an argument toGetStdHandle
which maps the handle id to the real runtime handle. I've documented why that's sound in its ownunsafe
block, which I'm fairly confident upholdswinapi
s contracts.Most
winapi
APIs are actually very forgiving with handles, and will simply return errors when they're invalild.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sunfishcode What do you think about this? Particularly with respect to your new I/O safety RFC, should a routine like this be marked as
unsafe
even though there is no possibility of UB for any value offd
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This... is a subtle one. It's true there's no possibility of UB here, or even corrupted I/O. On the other hand, the overall pattern here of using a pre-existing I/O handle without being given explicit access to it is the kind of thing I/O safety wants to avoid. On the first hand though, atty's own public API does this, by taking an enum instead of a handle; it's not
unsafe
, and this is doing essentially the same thing.So I think it's reasonable to leave this as a safe function for now. But I also think it makes sense to explore APIs where the user passes in a handle.