diff --git a/src/shims/env.rs b/src/shims/env.rs index e049eec57a..ce24b23ca3 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -166,7 +166,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { // `buf_size` represents the size in characters. let buf_size = u64::from(this.read_scalar(size_op)?.to_u32()?); Scalar::from_u32(windows_check_buffer_size( - this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?, + this.write_os_str_to_wide_str( + &var, buf_ptr, buf_size, /*truncate*/ false, + )?, )) } None => { @@ -366,7 +368,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { match env::current_dir() { Ok(cwd) => return Ok(Scalar::from_u32(windows_check_buffer_size( - this.write_path_to_wide_str(&cwd, buf, size)?, + this.write_path_to_wide_str(&cwd, buf, size, /*truncate*/ false)?, ))), Err(e) => this.set_last_error_from_io_error(e.kind())?, } diff --git a/src/shims/os_str.rs b/src/shims/os_str.rs index 0375a228a2..f010d4251f 100644 --- a/src/shims/os_str.rs +++ b/src/shims/os_str.rs @@ -101,17 +101,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { self.eval_context_mut().write_c_str(bytes, ptr, size) } - /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what - /// the Windows APIs usually handle. This function returns `Ok((false, length))` without trying - /// to write if `size` is not large enough to fit the contents of `os_string` plus a null - /// terminator. It returns `Ok((true, length))` if the writing process was successful. The - /// string length returned does include the null terminator. Length is measured in units of - /// `u16.` + /// Helper function to write an OsStr as a 0x0000-terminated u16-sequence, which is what the + /// Windows APIs usually handle. + /// + /// If `truncate == false` (the usual mode of operation), this function returns `Ok((false, + /// length))` without trying to write if `size` is not large enough to fit the contents of + /// `os_string` plus a null terminator. It returns `Ok((true, length))` if the writing process + /// was successful. The string length returned does include the null terminator. Length is + /// measured in units of `u16.` + /// + /// If `truncate == true`, then in case `size` is not large enough it *will* write the first + /// `size.saturating_sub(1)` many items, followed by a null terminator (if `size > 0`). fn write_os_str_to_wide_str( &mut self, os_str: &OsStr, ptr: Pointer>, size: u64, + truncate: bool, ) -> InterpResult<'tcx, (bool, u64)> { #[cfg(windows)] fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec> { @@ -129,7 +135,15 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { } let u16_vec = os_str_to_u16vec(os_str)?; - self.eval_context_mut().write_wide_str(&u16_vec, ptr, size) + let (written, size_needed) = self.eval_context_mut().write_wide_str(&u16_vec, ptr, size)?; + if truncate && !written && size > 0 { + // Write the truncated part that fits. + let truncated_data = &u16_vec[..size.saturating_sub(1).try_into().unwrap()]; + let (written, written_len) = + self.eval_context_mut().write_wide_str(truncated_data, ptr, size)?; + assert!(written && written_len == size); + } + Ok((written, size_needed)) } /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes. @@ -143,7 +157,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let arg_type = this.tcx.mk_array(this.tcx.types.u8, size); let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?; - assert!(self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap().0); + let (written, _) = self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap(); + assert!(written); Ok(arg_place.ptr) } @@ -158,7 +173,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { let arg_type = this.tcx.mk_array(this.tcx.types.u16, size); let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), memkind)?; - assert!(self.write_os_str_to_wide_str(os_str, arg_place.ptr, size).unwrap().0); + let (written, _) = + self.write_os_str_to_wide_str(os_str, arg_place.ptr, size, /*truncate*/ false).unwrap(); + assert!(written); Ok(arg_place.ptr) } @@ -212,11 +229,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { path: &Path, ptr: Pointer>, size: u64, + truncate: bool, ) -> InterpResult<'tcx, (bool, u64)> { let this = self.eval_context_mut(); let os_str = this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); - this.write_os_str_to_wide_str(&os_str, ptr, size) + this.write_os_str_to_wide_str(&os_str, ptr, size, truncate) } /// Allocate enough memory to store a Path as a null-terminated sequence of bytes, @@ -232,6 +250,19 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.alloc_os_str_as_c_str(&os_str, memkind) } + /// Allocate enough memory to store a Path as a null-terminated sequence of `u16`s, + /// adjusting path separators if needed. + fn alloc_path_as_wide_str( + &mut self, + path: &Path, + memkind: MemoryKind, + ) -> InterpResult<'tcx, Pointer>> { + let this = self.eval_context_mut(); + let os_str = + this.convert_path(Cow::Borrowed(path.as_os_str()), PathConversion::HostToTarget); + this.alloc_os_str_as_wide_str(&os_str, memkind) + } + #[allow(clippy::get_first)] fn convert_path<'a>( &self, diff --git a/src/shims/windows/foreign_items.rs b/src/shims/windows/foreign_items.rs index 1da8f7c0e3..f310d16e86 100644 --- a/src/shims/windows/foreign_items.rs +++ b/src/shims/windows/foreign_items.rs @@ -381,6 +381,46 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> { this.write_scalar(Scalar::from_u32(1), dest)?; } + "GetModuleFileNameW" => { + let [handle, filename, size] = + this.check_shim(abi, Abi::System { unwind: false }, link_name, args)?; + this.check_no_isolation("`GetModuleFileNameW`")?; + + let handle = this.read_machine_usize(handle)?; + let filename = this.read_pointer(filename)?; + let size = this.read_scalar(size)?.to_u32()?; + + if handle != 0 { + throw_unsup_format!("`GetModuleFileNameW` only supports the NULL handle"); + } + + // Using the host current_exe is a bit off, but consistent with Linux + // (where stdlib reads /proc/self/exe). + // Unfortunately this Windows function has a crazy behavior so we can't just use + // `write_path_to_wide_str`... + let path = std::env::current_exe().unwrap(); + let (all_written, size_needed) = this.write_path_to_wide_str( + &path, + filename, + size.into(), + /*truncate*/ true, + )?; + + if all_written { + // If the function succeeds, the return value is the length of the string that + // is copied to the buffer, in characters, not including the terminating null + // character. + this.write_int(size_needed.checked_sub(1).unwrap(), dest)?; + } else { + // If the buffer is too small to hold the module name, the string is truncated + // to nSize characters including the terminating null character, the function + // returns nSize, and the function sets the last error to + // ERROR_INSUFFICIENT_BUFFER. + this.write_int(size, dest)?; + let insufficient_buffer = this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"); + this.set_last_error(insufficient_buffer)?; + } + } // Threading "CreateThread" => { diff --git a/tests/pass/shims/env/current_exe.rs b/tests/pass/shims/env/current_exe.rs index 3f1153d265..898a42b72d 100644 --- a/tests/pass/shims/env/current_exe.rs +++ b/tests/pass/shims/env/current_exe.rs @@ -1,4 +1,3 @@ -//@ignore-target-windows: current_exe not supported on Windows //@only-on-host: the Linux std implementation opens /proc/self/exe, which doesn't work cross-target //@compile-flags: -Zmiri-disable-isolation use std::env;