Skip to content

std: fix stack buffer overflow in Windows junction_point#158147

Open
devnexen wants to merge 1 commit into
rust-lang:mainfrom
devnexen:windows_fs_oflow_fix
Open

std: fix stack buffer overflow in Windows junction_point#158147
devnexen wants to merge 1 commit into
rust-lang:mainfrom
devnexen:windows_fs_oflow_fix

Conversation

@devnexen

@devnexen devnexen commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

View all comments

The guard checked data_len > u16::MAX, allowing paths far larger than PathBuffer (a fixed 16384-element array), which the subsequent single copy_from then overflows. Bound against MAXIMUM_REPARSE_DATA_BUFFER_SIZE plus header instead, matching the kernel's limit.

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Jun 19, 2026
@rustbot

rustbot commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

r? @Darksonn

rustbot has assigned @Darksonn.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: @ChrisDenton, libs
  • @ChrisDenton, libs expanded to 12 candidates
  • Random selection from Darksonn, Mark-Simulacrum, clarfonthey, jhpratt

@Darksonn Darksonn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread library/std/src/sys/fs/windows.rs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It sounds like this should be changed to ptr[..abs_path.len()].copy_from_slice(abs_path) or similar so that we actually perform a bounds check here.

Also, it would be really nice with a test that'd catch this (may be easier to check that the test is failing if we insert a bounds check first).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, maybe the bounds check should be entirely rewritten to

ptr.get(..abs_path.len())
    .ok_or_else(|| io::const_error!(io::ErrorKind::InvalidInput, "`original` path is too long"))?
    .copy_from_slice(abs_path)

This way there's no risk that the two bounds checks are not kept in sync. For instance, why is there a + 8 in the previous check? I don't understand that part.

Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment on lines 1679 to 1683
PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],
}
let data_len = 12 + (abs_path.len() * 2);
if data_len > u16::MAX as usize {
if data_len + 8 > c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize {
return Err(io::const_error!(io::ErrorKind::InvalidInput, "`original` path is too long"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data_len counts the number of bytes, but MAXIMUM_REPARSE_DATA_BUFFER_SIZE counts the number of u16s. This doesn't sound right.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 19, 2026
@rustbot

rustbot commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@rust-log-analyzer

This comment has been minimized.

@devnexen

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 19, 2026

@Darksonn Darksonn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


#[test]
#[cfg(windows)]
fn junction_point_overlong_path() {

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed failure in #158201.

Comment thread library/std/src/sys/fs/windows.rs Outdated
Comment on lines +1694 to +1700
PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],
PathBuffer: [MaybeUninit::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize / 2],
};
// The path is followed by its null terminator and the (empty) print name's
// null terminator, so the buffer must hold two extra `u16`s. This single
// bounds check keeps the buffer, the copy, and `data_len` below in sync; if
// the path doesn't fit, fail rather than overflow the buffer.
let Some(ptr) = header.PathBuffer.get_mut(..abs_path.len() + 2) else {
return Err(io::const_error!(io::ErrorKind::InvalidInput, "`original` path is too long"));
};
ptr[..abs_path.len()].write_copy_of_slice(&abs_path);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This buffer is uninitialized. Don't you need to set path[abs_path.len()] and path[abs_path.len()+1] to zero?

@ChrisDenton ChrisDenton Jun 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The path has an explicit length so it doesn't need to be null terminated at all. I'm uncertain where that idea came from but it's probably just a misunderstanding? I mean, I guess there's no harm in writing a null but if so it shouldn't be part of the length.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 19, 2026
@ChrisDenton

Copy link
Copy Markdown
Member

To give some context here, this was originally a hacky function used only in tests. It's currently publicly exposed as a nightly-only API but it's not considered ready for stabilisation. E.g. 16kb is way too much for a stack buffer (though admittedly using a stack buffer for shorter paths would be useful).

PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize],
// `MAXIMUM_REPARSE_DATA_BUFFER_SIZE` is a size in bytes; halve it for a
// count of `u16`s (the `readlink` path uses it as a byte buffer).
PathBuffer: [MaybeUninit<u16>; c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize / 2],

@ChrisDenton ChrisDenton Jun 19, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't sound right at all. What makes you say MAXIMUM_REPARSE_DATA_BUFFER_SIZE is in bytes?

View changes since the review

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 MAXIMUM_REPARSE_DATA_BUFFER_SIZE in bytes of 16kb.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be changed to an u8 array as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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).

@devnexen devnexen force-pushed the windows_fs_oflow_fix branch from d842451 to 28748e5 Compare June 21, 2026 06:05
rust-bors Bot pushed a commit that referenced this pull request Jun 21, 2026
Temporary CI-verification test for #158147. Without the fix,
a >16 KiB junction target passes the old `> u16::MAX` length check yet
overflows the inline reparse stack buffer. This test must fail on master
and pass once the fix lands.
@Darksonn Darksonn added O-windows Operating system: Windows A-filesystem Area: `std::fs` labels Jun 21, 2026
@devnexen

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 21, 2026

@Darksonn Darksonn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread library/std/src/sys/fs/windows.rs Outdated
// Size of the reparse data after the 8-byte ReparseTag/ReparseDataLength/
// Reserved header: the four name offset/length `u16` fields (8 bytes) plus
// the path.
let data_len = 8 + abs_path.len() * 2;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it 16 bytes?

  • ReparseTag is 4 bytes
  • ReparseDataLength is 2 bytes
  • Reserved is 2 bytes
  • SubstituteNameOffset is 2 bytes
  • SubstituteNameLength is 2 bytes
  • PrintNameOffset is 2 bytes
  • PrintNameLength is 2 bytes

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it's 16 bytes. Switched to offset_of! so both lengths derive from the struct.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Jun 22, 2026
The guard checked `data_len > u16::MAX`, allowing paths far larger than
`PathBuffer` (a fixed 16384-element array), which the subsequent single
`copy_from` then overflows. Bound against `MAXIMUM_REPARSE_DATA_BUFFER_SIZE`
plus header instead, matching the kernel's limit.
@devnexen devnexen force-pushed the windows_fs_oflow_fix branch from 28748e5 to b7af0d1 Compare June 22, 2026 23:24
@devnexen

Copy link
Copy Markdown
Contributor Author

@rustbot ready

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Jun 23, 2026

@Darksonn Darksonn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks okay to me. @ChrisDenton do you have any comments before I merge it?

View changes since this review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-filesystem Area: `std::fs` O-windows Operating system: Windows S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-libs Relevant to the library team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants