diff --git a/src/shims/env.rs b/src/shims/env.rs index db1ddf6291f..d333e78e524 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -18,11 +18,11 @@ fn windows_check_buffer_size((success, len): (bool, u64)) -> u32 { if success { // If the function succeeds, the return value is the number of characters stored in the target buffer, // not including the terminating null character. - u32::try_from(len).unwrap() + u32::try_from(len.checked_sub(1).unwrap()).unwrap() } else { // If the target buffer was not large enough to hold the data, the return value is the buffer size, in characters, // required to hold the string and its terminating null character. - u32::try_from(len.checked_add(1).unwrap()).unwrap() + u32::try_from(len).unwrap() } } diff --git a/src/shims/os_str.rs b/src/shims/os_str.rs index b9f3a435ea4..f99e2d174b5 100644 --- a/src/shims/os_str.rs +++ b/src/shims/os_str.rs @@ -92,7 +92,7 @@ pub fn u16vec_to_osstring<'tcx>(u16_vec: Vec) -> InterpResult<'tcx, OsStrin /// the Unix 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 not include the null terminator. + /// string length returned does include the null terminator. fn write_os_str_to_c_str( &mut self, os_str: &OsStr, @@ -103,7 +103,8 @@ fn write_os_str_to_c_str( // If `size` is smaller or equal than `bytes.len()`, writing `bytes` plus the required null // terminator to memory using the `ptr` pointer would cause an out-of-bounds access. let string_length = u64::try_from(bytes.len()).unwrap(); - if size <= string_length { + let string_length = string_length.checked_add(1).unwrap(); + if size < string_length { return Ok((false, string_length)); } self.eval_context_mut() @@ -115,7 +116,8 @@ fn write_os_str_to_c_str( /// 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 not include the null terminator. + /// string length returned does include the null terminator. Length is measured in units of + /// `u16.` fn write_os_str_to_wide_str( &mut self, os_str: &OsStr, @@ -157,7 +159,7 @@ fn os_str_to_u16vec<'tcx>(os_str: &OsStr) -> InterpResult<'tcx, Vec> { alloc .write_scalar(alloc_range(size2 * offset, size2), Scalar::from_u16(wchar).into())?; } - Ok((true, string_length - 1)) + Ok((true, string_length)) } /// Allocate enough memory to store the given `OsStr` as a null-terminated sequence of bytes. diff --git a/src/shims/unix/fs.rs b/src/shims/unix/fs.rs index 3dccdd5e74f..951ddae2c14 100644 --- a/src/shims/unix/fs.rs +++ b/src/shims/unix/fs.rs @@ -1380,11 +1380,12 @@ fn macos_readdir_r( let name_place = this.mplace_field(&entry_place, 5)?; let file_name = dir_entry.file_name(); // not a Path as there are no separators! - let (name_fits, file_name_len) = this.write_os_str_to_c_str( + let (name_fits, file_name_buf_len) = this.write_os_str_to_c_str( &file_name, name_place.ptr, name_place.layout.size.bytes(), )?; + let file_name_len = file_name_buf_len.checked_sub(1).unwrap(); if !name_fits { throw_unsup_format!( "a directory entry had a name too large to fit in libc::dirent" diff --git a/src/shims/unix/macos/foreign_items.rs b/src/shims/unix/macos/foreign_items.rs index fb545d8b584..35751d5818a 100644 --- a/src/shims/unix/macos/foreign_items.rs +++ b/src/shims/unix/macos/foreign_items.rs @@ -117,6 +117,33 @@ fn emulate_foreign_item_by_name( dest, )?; } + "_NSGetExecutablePath" => { + let [buf, bufsize] = + this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?; + this.check_no_isolation("`_NSGetExecutablePath`")?; + + let buf_ptr = this.read_pointer(buf)?; + let bufsize = this.deref_operand(bufsize)?; + + // Using the host current_exe is a bit off, but consistent with Linux + // (where stdlib reads /proc/self/exe). + let path = std::env::current_exe().unwrap(); + let (written, size_needed) = this.write_path_to_c_str( + &path, + buf_ptr, + this.read_scalar(&bufsize.into())?.to_u32()?.into(), + )?; + + if written { + this.write_null(dest)?; + } else { + this.write_scalar( + Scalar::from_u32(size_needed.try_into().unwrap()), + &bufsize.into(), + )?; + this.write_int(-1, dest)?; + } + } // Thread-local storage "_tlv_atexit" => { diff --git a/tests/pass/current_exe.rs b/tests/pass/current_exe.rs new file mode 100644 index 00000000000..64f62b230e4 --- /dev/null +++ b/tests/pass/current_exe.rs @@ -0,0 +1,8 @@ +//@ignore-target-windows +//@compile-flags: -Zmiri-disable-isolation +use std::env; + +fn main() { + // The actual value we get is a bit odd: we get the Miri binary that interprets us. + env::current_exe().unwrap(); +}