Skip to content

Commit 17ed06a

Browse files
authoredApr 6, 2023
Rollup merge of #109960 - thomcc:symlink-junction-buffer-overrun, r=ChrisDenton
Fix buffer overrun in bootstrap and (test-only) symlink_junction I don't think these can be hit in practice, due to their inputs being valid paths. It's also not security-sensitive code, but just... bad vibes. I think this is still not really the right way to do this (in terms of path correctness), but is no worse than it was. r? `@ChrisDenton`
2 parents e63586f + 42e38e8 commit 17ed06a

File tree

4 files changed

+40
-107
lines changed

4 files changed

+40
-107
lines changed
 

‎library/std/src/sys/windows/fs.rs

+25-9
Original file line numberDiff line numberDiff line change
@@ -1403,24 +1403,40 @@ fn symlink_junction_inner(original: &Path, junction: &Path) -> io::Result<()> {
14031403
opts.custom_flags(c::FILE_FLAG_OPEN_REPARSE_POINT | c::FILE_FLAG_BACKUP_SEMANTICS);
14041404
let f = File::open(junction, &opts)?;
14051405
let h = f.as_inner().as_raw_handle();
1406-
14071406
unsafe {
14081407
let mut data = Align8([MaybeUninit::<u8>::uninit(); c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE]);
14091408
let data_ptr = data.0.as_mut_ptr();
1409+
let data_end = data_ptr.add(c::MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
14101410
let db = data_ptr.cast::<c::REPARSE_MOUNTPOINT_DATA_BUFFER>();
14111411
// Zero the header to ensure it's fully initialized, including reserved parameters.
14121412
*db = mem::zeroed();
1413-
let buf = ptr::addr_of_mut!((*db).ReparseTarget).cast::<c::WCHAR>();
1414-
let mut i = 0;
1413+
let reparse_target_slice = {
1414+
let buf_start = ptr::addr_of_mut!((*db).ReparseTarget).cast::<c::WCHAR>();
1415+
// Compute offset in bytes and then divide so that we round down
1416+
// rather than hit any UB (admittedly this arithmetic should work
1417+
// out so that this isn't necessary)
1418+
let buf_len_bytes = usize::try_from(data_end.byte_offset_from(buf_start)).unwrap();
1419+
let buf_len_wchars = buf_len_bytes / core::mem::size_of::<c::WCHAR>();
1420+
core::slice::from_raw_parts_mut(buf_start, buf_len_wchars)
1421+
};
1422+
14151423
// FIXME: this conversion is very hacky
1416-
let v = br"\??\";
1417-
let v = v.iter().map(|x| *x as u16);
1418-
for c in v.chain(original.as_os_str().encode_wide()) {
1419-
*buf.add(i) = c;
1424+
let iter = br"\??\"
1425+
.iter()
1426+
.map(|x| *x as u16)
1427+
.chain(original.as_os_str().encode_wide())
1428+
.chain(core::iter::once(0));
1429+
let mut i = 0;
1430+
for c in iter {
1431+
if i >= reparse_target_slice.len() {
1432+
return Err(crate::io::const_io_error!(
1433+
crate::io::ErrorKind::InvalidFilename,
1434+
"Input filename is too long"
1435+
));
1436+
}
1437+
reparse_target_slice[i] = c;
14201438
i += 1;
14211439
}
1422-
*buf.add(i) = 0;
1423-
i += 1;
14241440
(*db).ReparseTag = c::IO_REPARSE_TAG_MOUNT_POINT;
14251441
(*db).ReparseTargetMaximumLength = (i * 2) as c::WORD;
14261442
(*db).ReparseTargetLength = ((i - 1) * 2) as c::WORD;

‎src/bootstrap/Cargo.lock

+11
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ dependencies = [
4545
"hex",
4646
"ignore",
4747
"is-terminal",
48+
"junction",
4849
"libc",
4950
"object",
5051
"once_cell",
@@ -349,6 +350,16 @@ version = "1.0.2"
349350
source = "registry+https://github.com/rust-lang/crates.io-index"
350351
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
351352

353+
[[package]]
354+
name = "junction"
355+
version = "1.0.0"
356+
source = "registry+https://github.com/rust-lang/crates.io-index"
357+
checksum = "ca39ef0d69b18e6a2fd14c2f0a1d593200f4a4ed949b240b5917ab51fac754cb"
358+
dependencies = [
359+
"scopeguard",
360+
"winapi",
361+
]
362+
352363
[[package]]
353364
name = "lazy_static"
354365
version = "1.4.0"

‎src/bootstrap/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ sysinfo = { version = "0.26.0", optional = true }
6161
[target.'cfg(not(target_os = "solaris"))'.dependencies]
6262
fd-lock = "3.0.8"
6363

64+
[target.'cfg(windows)'.dependencies.junction]
65+
version = "1.0.0"
66+
6467
[target.'cfg(windows)'.dependencies.windows]
6568
version = "0.46.0"
6669
features = [

‎src/bootstrap/util.rs

+1-98
Original file line numberDiff line numberDiff line change
@@ -146,106 +146,9 @@ pub fn symlink_dir(config: &Config, src: &Path, dest: &Path) -> io::Result<()> {
146146
fs::symlink(src, dest)
147147
}
148148

149-
// Creating a directory junction on windows involves dealing with reparse
150-
// points and the DeviceIoControl function, and this code is a skeleton of
151-
// what can be found here:
152-
//
153-
// http://www.flexhex.com/docs/articles/hard-links.phtml
154149
#[cfg(windows)]
155150
fn symlink_dir_inner(target: &Path, junction: &Path) -> io::Result<()> {
156-
use std::ffi::OsStr;
157-
use std::os::windows::ffi::OsStrExt;
158-
159-
use windows::{
160-
core::PCWSTR,
161-
Win32::Foundation::{CloseHandle, HANDLE},
162-
Win32::Storage::FileSystem::{
163-
CreateFileW, FILE_ACCESS_FLAGS, FILE_FLAG_BACKUP_SEMANTICS,
164-
FILE_FLAG_OPEN_REPARSE_POINT, FILE_SHARE_DELETE, FILE_SHARE_READ, FILE_SHARE_WRITE,
165-
MAXIMUM_REPARSE_DATA_BUFFER_SIZE, OPEN_EXISTING,
166-
},
167-
Win32::System::Ioctl::FSCTL_SET_REPARSE_POINT,
168-
Win32::System::SystemServices::{GENERIC_WRITE, IO_REPARSE_TAG_MOUNT_POINT},
169-
Win32::System::IO::DeviceIoControl,
170-
};
171-
172-
#[allow(non_snake_case)]
173-
#[repr(C)]
174-
struct REPARSE_MOUNTPOINT_DATA_BUFFER {
175-
ReparseTag: u32,
176-
ReparseDataLength: u32,
177-
Reserved: u16,
178-
ReparseTargetLength: u16,
179-
ReparseTargetMaximumLength: u16,
180-
Reserved1: u16,
181-
ReparseTarget: u16,
182-
}
183-
184-
fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
185-
Ok(s.as_ref().encode_wide().chain(Some(0)).collect())
186-
}
187-
188-
// We're using low-level APIs to create the junction, and these are more
189-
// picky about paths. For example, forward slashes cannot be used as a
190-
// path separator, so we should try to canonicalize the path first.
191-
let target = fs::canonicalize(target)?;
192-
193-
fs::create_dir(junction)?;
194-
195-
let path = to_u16s(junction)?;
196-
197-
let h = unsafe {
198-
CreateFileW(
199-
PCWSTR(path.as_ptr()),
200-
FILE_ACCESS_FLAGS(GENERIC_WRITE),
201-
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
202-
None,
203-
OPEN_EXISTING,
204-
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
205-
HANDLE::default(),
206-
)
207-
}
208-
.map_err(|_| io::Error::last_os_error())?;
209-
210-
unsafe {
211-
#[repr(C, align(8))]
212-
struct Align8<T>(T);
213-
let mut data = Align8([0u8; MAXIMUM_REPARSE_DATA_BUFFER_SIZE as usize]);
214-
let db = data.0.as_mut_ptr() as *mut REPARSE_MOUNTPOINT_DATA_BUFFER;
215-
let buf = core::ptr::addr_of_mut!((*db).ReparseTarget) as *mut u16;
216-
let mut i = 0;
217-
// FIXME: this conversion is very hacky
218-
let v = br"\??\";
219-
let v = v.iter().map(|x| *x as u16);
220-
for c in v.chain(target.as_os_str().encode_wide().skip(4)) {
221-
*buf.offset(i) = c;
222-
i += 1;
223-
}
224-
*buf.offset(i) = 0;
225-
i += 1;
226-
227-
(*db).ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
228-
(*db).ReparseTargetMaximumLength = (i * 2) as u16;
229-
(*db).ReparseTargetLength = ((i - 1) * 2) as u16;
230-
(*db).ReparseDataLength = ((*db).ReparseTargetLength + 12) as u32;
231-
232-
let mut ret = 0u32;
233-
DeviceIoControl(
234-
h,
235-
FSCTL_SET_REPARSE_POINT,
236-
Some(db.cast()),
237-
(*db).ReparseDataLength + 8,
238-
None,
239-
0,
240-
Some(&mut ret),
241-
None,
242-
)
243-
.ok()
244-
.map_err(|_| io::Error::last_os_error())?;
245-
}
246-
247-
unsafe { CloseHandle(h) };
248-
Ok(())
151+
junction::create(&target, &junction)
249152
}
250153
}
251154

0 commit comments

Comments
 (0)
Please sign in to comment.