host_env: bound symlink probe; add Win32 times (#8733)

nt::symlink's directory probe now uses two WCHAR[MAX_PATH]
buffers and treats an overflowing join as not a directory.
winapi::version_ex_triple exposes GetVersionExW. nt::set_file_times
opens with FILE_WRITE_ATTRIBUTES and writes wrapping FILETIMEs.

Assisted-by: Claude
This commit is contained in:
Jeong, YunWon authored and GitHub committed 2026-09-19 00:44:17 +09:00
1 parent 300d8bdc02
commit faa5a60638
2 files changed
+105 -17

No files matched your search

+89 -17
View File
@@ -31,7 +31,7 @@ use windows_sys::{
ERROR_FILENAME_EXCED_RANGE, ERROR_INSUFFICIENT_BUFFER, ERROR_INVALID_FUNCTION,
ERROR_INVALID_HANDLE, ERROR_INVALID_NAME, ERROR_INVALID_PARAMETER, ERROR_MORE_DATA,
ERROR_NOT_READY, ERROR_NOT_SUPPORTED, ERROR_PATH_NOT_FOUND, ERROR_SHARING_VIOLATION,
GENERIC_READ, GENERIC_WRITE, GetHandleInformation, GetLastError, HANDLE,
FILETIME, GENERIC_READ, GENERIC_WRITE, GetHandleInformation, GetLastError, HANDLE,
HANDLE_FLAG_INHERIT, INVALID_HANDLE_VALUE, MAX_PATH, SetHandleInformation,
},
Globalization::{CP_UTF8, MultiByteToWideChar, WideCharToMultiByte},
@@ -48,7 +48,7 @@ use windows_sys::{
GetVolumePathNameW, GetVolumePathNamesForVolumeNameW, INVALID_FILE_ATTRIBUTES,
OPEN_EXISTING, RemoveDirectoryW, SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,
SYMBOLIC_LINK_FLAG_DIRECTORY, SetFileAttributesW, SetFileInformationByHandle,
WIN32_FILE_ATTRIBUTE_DATA, WIN32_FIND_DATAW,
SetFileTime, WIN32_FILE_ATTRIBUTE_DATA, WIN32_FIND_DATAW,
},
System::{
Console,
@@ -288,33 +288,58 @@ pub fn supports_virtual_terminal() -> bool {
}
pub fn symlink(
src: &Path,
dst: &Path,
_src: &Path,
_dst: &Path,
src_wide: &widestring::WideCStr,
dst_wide: &widestring::WideCStr,
target_is_directory: bool,
) -> io::Result<()> {
static HAS_UNPRIVILEGED_FLAG: AtomicBool = AtomicBool::new(true);
fn check_dir(src: &Path, dst: &Path) -> bool {
fn check_dir(src: &widestring::WideCStr, dst: &widestring::WideCStr) -> bool {
use windows_sys::Win32::Storage::FileSystem::GetFileExInfoStandard;
let Some(dst_parent) = dst.parent() else {
// `_check_dirW`: two WCHAR[MAX_PATH] buffers; a join that does not
// fit is "not a directory".
let src = src.as_slice();
let dst = dst.as_slice();
let max_path = MAX_PATH_USIZE;
if dst.len() >= max_path {
return false;
};
let resolved = if src.is_absolute() {
src.to_path_buf()
}
let parent_len = dst
.iter()
.rposition(|&unit| unit == b'\\' as u16 || unit == b'/' as u16)
.unwrap_or(0);
let parent = &dst[..parent_len];
let absolute = src
.first()
.is_some_and(|&unit| unit == b'\\' as u16 || unit == b'/' as u16)
|| (src.first().is_some_and(|&unit| unit != 0) && src.get(1) == Some(&(b':' as u16)));
let mut resolved = Vec::with_capacity(max_path);
if absolute {
if src.len() >= max_path {
return false;
}
resolved.extend_from_slice(src);
} else {
dst_parent.join(src)
};
let wide = match widestring::WideCString::from_os_str(&resolved) {
Ok(wide) => wide,
Err(_) => return false,
};
let separator_len = usize::from(!parent.is_empty());
if parent.len() + separator_len + src.len() >= max_path {
return false;
}
resolved.extend_from_slice(parent);
if !parent.is_empty() {
resolved.push(b'\\' as u16);
}
resolved.extend_from_slice(src);
}
resolved.push(0);
let mut info: WIN32_FILE_ATTRIBUTE_DATA = unsafe { core::mem::zeroed() };
let ok = unsafe {
GetFileAttributesExW(
wide.as_ptr(),
resolved.as_ptr(),
GetFileExInfoStandard,
(&mut info as *mut WIN32_FILE_ATTRIBUTE_DATA).cast(),
)
@@ -326,7 +351,7 @@ pub fn symlink(
if HAS_UNPRIVILEGED_FLAG.load(Ordering::Relaxed) {
flags |= SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE;
}
if target_is_directory || check_dir(src, dst) {
if target_is_directory || check_dir(src_wide, dst_wide) {
flags |= SYMBOLIC_LINK_FLAG_DIRECTORY;
}
@@ -349,6 +374,53 @@ pub fn symlink(
}
}
/// `time_t_to_FILE_TIME` plus `SetFileTime`. A FILETIME counts 100ns ticks
/// from 1601-01-01, so the epoch shift makes a second before 1970 an ordinary
/// positive tick count. The arithmetic wraps: a second this filesystem cannot
/// hold writes the bits the multiplication leaves.
pub fn set_file_times(
path: &widestring::WideCStr,
atime_sec: i64,
atime_nsec: i64,
mtime_sec: i64,
mtime_nsec: i64,
) -> io::Result<()> {
const EPOCH_DIFF: i64 = 11_644_473_600;
let to_filetime = |sec: i64, nsec: i64| -> FILETIME {
let ticks = sec
.wrapping_add(EPOCH_DIFF)
.wrapping_mul(10_000_000)
.wrapping_add(nsec / 100) as u64;
FILETIME {
dwLowDateTime: ticks as u32,
dwHighDateTime: (ticks >> 32) as u32,
}
};
let atime = to_filetime(atime_sec, atime_nsec);
let mtime = to_filetime(mtime_sec, mtime_nsec);
let handle = unsafe {
CreateFileW(
path.as_ptr(),
FILE_WRITE_ATTRIBUTES,
0,
core::ptr::null(),
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
core::ptr::null_mut(),
)
};
if handle == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error());
}
let wrote = unsafe { SetFileTime(handle, core::ptr::null(), &atime, &mtime) };
let error = (wrote == 0).then(io::Error::last_os_error);
unsafe { CloseHandle(handle) };
match error {
Some(error) => Err(error),
None => Ok(()),
}
}
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn win32_hchmod(handle: HANDLE, mode: u32, write_bit: u32) -> io::Result<()> {
let mut info: FILE_BASIC_INFO = unsafe { core::mem::zeroed() };
+16
View File
@@ -790,6 +790,22 @@ pub fn get_version() -> u32 {
unsafe { windows_sys::Win32::System::SystemInformation::GetVersion() }
}
/// `GetVersionExW` major/minor/build. Not `get_version()`: that DWORD is
/// `GetVersion()`, and `get_windows_version()` overwrites the triple with
/// kernel32's file version.
#[must_use]
pub fn version_ex_triple() -> Option<(u32, u32, u32)> {
use windows_sys::Win32::System::SystemInformation::{GetVersionExW, OSVERSIONINFOW};
let mut info: OSVERSIONINFOW = unsafe { core::mem::zeroed() };
info.dwOSVersionInfoSize = core::mem::size_of::<OSVERSIONINFOW>() as u32;
(unsafe { GetVersionExW(&mut info) } != 0).then_some((
info.dwMajorVersion,
info.dwMinorVersion,
info.dwBuildNumber,
))
}
pub fn create_job_object_w(name: Option<&widestring::WideCStr>) -> io::Result<HANDLE> {
let name_ptr = name.map_or(core::ptr::null(), |n| n.as_ptr());
unsafe { windows_sys::Win32::System::JobObjects::CreateJobObjectW(core::ptr::null(), name_ptr) }