-
-
Notifications
You must be signed in to change notification settings - Fork 15.4k
std: fix stack buffer overflow in Windows junction_point #158147
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 |
|---|---|---|
|
|
@@ -1676,33 +1676,40 @@ pub fn junction_point(original: &Path, link: &Path) -> io::Result<()> { | |
| SubstituteNameLength: u16, | ||
| PrintNameOffset: u16, | ||
| PrintNameLength: u16, | ||
| PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize], | ||
| // `MAXIMUM_REPARSE_DATA_BUFFER_SIZE` is a size in bytes, but this is a | ||
| // buffer of `u16`s, so it holds half as many elements. | ||
| PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize / 2], | ||
|
Member
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 doesn't sound right at all. What makes you say
Member
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. Huh, apparently it is. The protocol docs make no mention of a maximum size, only that it's a 16 bit unsigned integer (hence my surprise at 16kb being a limit). But the public headers for the kernel API do have
Member
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. Should it be changed to an u8 array as well?
Contributor
Author
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 would say no. It's the mount-point WCHAR path, and u16 keeps the copy a clean write_copy_of_slice(&abs_path). |
||
| } | ||
| let data_len = 12 + (abs_path.len() * 2); | ||
| if data_len > u16::MAX as usize { | ||
| return Err(io::const_error!(io::ErrorKind::InvalidInput, "`original` path is too long")); | ||
| } | ||
| let data_len = data_len as u16; | ||
| let mut header = MountPointBuffer { | ||
| ReparseTag: c::IO_REPARSE_TAG_MOUNT_POINT, | ||
| ReparseDataLength: data_len, | ||
| ReparseDataLength: 0, // filled in below | ||
| Reserved: 0, | ||
| SubstituteNameOffset: 0, | ||
| SubstituteNameLength: (abs_path.len() * 2) as u16, | ||
| PrintNameOffset: ((abs_path.len() + 1) * 2) as u16, | ||
| PrintNameOffset: (abs_path.len() * 2) as u16, | ||
| PrintNameLength: 0, | ||
| PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize], | ||
| PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize / 2], | ||
| }; | ||
| // The substitute name has an explicit length, so it needs no null | ||
| // terminator. Bounds-check and copy in a single step so an over-long path | ||
| // fails cleanly instead of overflowing the buffer. | ||
| let Some(ptr) = header.PathBuffer.get_mut(..abs_path.len()) else { | ||
| return Err(io::const_error!(io::ErrorKind::InvalidInput, "`original` path is too long")); | ||
| }; | ||
| ptr.write_copy_of_slice(&abs_path); | ||
| // Total size of the structure: the fixed header fields followed by the path. | ||
| let total_len = offset_of!(MountPointBuffer, PathBuffer) + abs_path.len() * 2; | ||
| // `ReparseDataLength` counts only the bytes after the 8-byte common header | ||
| // (`ReparseTag`, `ReparseDataLength`, `Reserved`). | ||
| header.ReparseDataLength = | ||
| (total_len - offset_of!(MountPointBuffer, SubstituteNameOffset)) as u16; | ||
| unsafe { | ||
| let ptr = header.PathBuffer.as_mut_ptr(); | ||
| ptr.copy_from(abs_path.as_ptr().cast_uninit(), abs_path.len()); | ||
|
|
||
| let mut ret = 0; | ||
| cvt(c::DeviceIoControl( | ||
| d.as_raw_handle(), | ||
| c::FSCTL_SET_REPARSE_POINT, | ||
| (&raw const header).cast::<c_void>(), | ||
| data_len as u32 + 8, | ||
| total_len as u32, | ||
| ptr::null_mut(), | ||
| 0, | ||
| &mut ret, | ||
|
|
||
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'd like to see this regression test fail without the change. Do you mind opening a second temporary PR containing just the test so that we can run it through CI? We can close it again when we've confirmed the regression test catches the bug.
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.
Confirmed failure in #158201.