diff --git a/README.md b/README.md index 59394830d7..03b995aa56 100644 --- a/README.md +++ b/README.md @@ -166,7 +166,7 @@ Several `-Z` flags are relevant for Miri: * `-Zmiri-disable-stacked-borrows` disables checking the experimental [Stacked Borrows] aliasing rules. This can make Miri run faster, but it also means no aliasing violations will be detected. -* `-Zmiri-disable-isolation` disables host host isolation. As a consequence, +* `-Zmiri-disable-isolation` disables host isolation. As a consequence, the program has access to host resources such as environment variables, file systems, and randomness. * `-Zmiri-ignore-leaks` disables the memory leak checker. diff --git a/src/shims/env.rs b/src/shims/env.rs index 79c386e894..ed689b1f42 100644 --- a/src/shims/env.rs +++ b/src/shims/env.rs @@ -23,10 +23,16 @@ pub struct EnvVars<'tcx> { impl<'tcx> EnvVars<'tcx> { pub(crate) fn init<'mir>( ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>, - excluded_env_vars: Vec, + mut excluded_env_vars: Vec, ) -> InterpResult<'tcx> { + let target_os = ecx.tcx.sess.target.target.target_os.as_str(); + if target_os == "windows" { + // Temporary hack: Exclude `TERM` var to avoid terminfo trying to open the termcap file. + // Can be removed once https://github.com/rust-lang/miri/issues/1013 is resolved. + excluded_env_vars.push("TERM".to_owned()); + } + if ecx.machine.communicate { - let target_os = ecx.tcx.sess.target.target.target_os.as_str(); for (name, value) in env::vars() { if !excluded_env_vars.contains(&name) { let var_ptr = match target_os { @@ -82,6 +88,82 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx }) } + #[allow(non_snake_case)] + fn GetEnvironmentVariableW( + &mut self, + name_op: OpTy<'tcx, Tag>, // LPCWSTR + buf_op: OpTy<'tcx, Tag>, // LPWSTR + size_op: OpTy<'tcx, Tag>, // DWORD + ) -> InterpResult<'tcx, u64> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentVariableW"); + + let name_ptr = this.read_scalar(name_op)?.not_undef()?; + let name = this.read_os_str_from_wide_str(name_ptr)?; + Ok(match this.machine.env_vars.map.get(&name) { + Some(var_ptr) => { + // The offset is used to strip the "{name}=" part of the string. + let name_offset_bytes = + u64::try_from(name.len()).unwrap().checked_add(1).unwrap().checked_mul(2).unwrap(); + let var_ptr = Scalar::from(var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?); + let var = this.read_os_str_from_wide_str(var_ptr)?; + + let buf_ptr = this.read_scalar(buf_op)?.not_undef()?; + // `buf_size` represents the size in characters. + let buf_size = u64::try_from(this.read_scalar(size_op)?.to_u32()?).unwrap(); + let (success, len) = this.write_os_str_to_wide_str(&var, buf_ptr, buf_size)?; + + if success { + // If the function succeeds, the return value is the number of characters stored in the buffer pointed to by lpBuffer, + // not including the terminating null character. + len + } else { + // If lpBuffer is 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 and the contents of lpBuffer are undefined. + len + 1 + } + } + None => { + let envvar_not_found = this.eval_path_scalar(&["std", "sys", "windows", "c", "ERROR_ENVVAR_NOT_FOUND"])?; + this.set_last_error(envvar_not_found.not_undef()?)?; + 0 // return zero upon failure + } + }) + } + + #[allow(non_snake_case)] + fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Scalar> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "GetEnvironmentStringsW"); + + // Info on layout of environment blocks in Windows: + // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables + let mut env_vars = std::ffi::OsString::new(); + for &item in this.machine.env_vars.map.values() { + let env_var = this.read_os_str_from_wide_str(Scalar::from(item))?; + env_vars.push(env_var); + env_vars.push("\0"); + } + // Allocate environment block & Store environment variables to environment block. + // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`. + // FIXME: MemoryKind should be `Machine`, blocked on https://github.com/rust-lang/rust/pull/70479. + let envblock_ptr = this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::WinHeap.into()); + // If the function succeeds, the return value is a pointer to the environment block of the current process. + Ok(envblock_ptr.into()) + } + + #[allow(non_snake_case)] + fn FreeEnvironmentStringsW(&mut self, env_block_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { + let this = self.eval_context_mut(); + this.assert_target_os("windows", "FreeEnvironmentStringsW"); + + let env_block_ptr = this.read_scalar(env_block_op)?.not_undef()?; + // FIXME: MemoryKind should be `Machine`, blocked on https://github.com/rust-lang/rust/pull/70479. + let result = this.memory.deallocate(this.force_ptr(env_block_ptr)?, None, MiriMemoryKind::WinHeap.into()); + // If the function succeeds, the return value is nonzero. + Ok(result.is_ok() as i32) + } + fn setenv( &mut self, name_op: OpTy<'tcx, Tag>, @@ -118,6 +200,47 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx } } + #[allow(non_snake_case)] + fn SetEnvironmentVariableW( + &mut self, + name_op: OpTy<'tcx, Tag>, // LPCWSTR + value_op: OpTy<'tcx, Tag>, // LPCWSTR + ) -> InterpResult<'tcx, i32> { + let mut this = self.eval_context_mut(); + this.assert_target_os("windows", "SetEnvironmentVariableW"); + + let name_ptr = this.read_scalar(name_op)?.not_undef()?; + let value_ptr = this.read_scalar(value_op)?.not_undef()?; + + if this.is_null(name_ptr)? { + // ERROR CODE is not clearly explained in docs.. For now, throw UB instead. + throw_ub_format!("pointer to environment variable name is NULL"); + } + + let name = this.read_os_str_from_wide_str(name_ptr)?; + if name.is_empty() { + throw_unsup_format!("environment variable name is an empty string"); + } else if name.to_string_lossy().contains('=') { + throw_unsup_format!("environment variable name contains '='"); + } else if this.is_null(value_ptr)? { + // Delete environment variable `{name}` + if let Some(var) = this.machine.env_vars.map.remove(&name) { + this.memory.deallocate(var, None, MiriMemoryKind::Machine.into())?; + this.update_environ()?; + } + Ok(1) // return non-zero on success + } else { + let value = this.read_os_str_from_wide_str(value_ptr)?; + let var_ptr = alloc_env_var_as_wide_str(&name, &value, &mut this)?; + if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) { + this.memory + .deallocate(var, None, MiriMemoryKind::Machine.into())?; + } + this.update_environ()?; + Ok(1) // return non-zero on success + } + } + fn unsetenv(&mut self, name_op: OpTy<'tcx, Tag>) -> InterpResult<'tcx, i32> { let this = self.eval_context_mut(); let target_os = &this.tcx.sess.target.target.target_os; diff --git a/src/shims/foreign_items/windows.rs b/src/shims/foreign_items/windows.rs index a35734573f..a64ef0f129 100644 --- a/src/shims/foreign_items/windows.rs +++ b/src/shims/foreign_items/windows.rs @@ -23,22 +23,23 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx // Environment related shims "GetEnvironmentVariableW" => { - // args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars) - // args[1] : LPWSTR lpBuffer (32-bit pointer to a string of 16-bit Unicode chars) - // lpBuffer : ptr to buffer that receives contents of the env_var as a null-terminated string. - // Return `# of chars` stored in the buffer pointed to by lpBuffer, excluding null-terminator. - // Return 0 upon failure. - - // This is not the env var you are looking for. - this.set_last_error(Scalar::from_u32(203))?; // ERROR_ENVVAR_NOT_FOUND - this.write_null(dest)?; + let result = this.GetEnvironmentVariableW(args[0], args[1], args[2])?; + this.write_scalar(Scalar::from_uint(result, dest.layout.size), dest)?; } "SetEnvironmentVariableW" => { - // args[0] : LPCWSTR lpName (32-bit ptr to a const string of 16-bit Unicode chars) - // args[1] : LPCWSTR lpValue (32-bit ptr to a const string of 16-bit Unicode chars) - // Return nonzero if success, else return 0. - throw_unsup_format!("can't set environment variable on Windows"); + let result = this.SetEnvironmentVariableW(args[0], args[1])?; + this.write_scalar(Scalar::from_int(result, dest.layout.size), dest)?; + } + + "GetEnvironmentStringsW" => { + let result = this.GetEnvironmentStringsW()?; + this.write_scalar(result, dest)?; + } + + "FreeEnvironmentStringsW" => { + let result = this.FreeEnvironmentStringsW(args[0])?; + this.write_scalar(Scalar::from_i32(result), dest)?; } // File related shims diff --git a/tests/compile-fail/environ-gets-deallocated.rs b/tests/compile-fail/environ-gets-deallocated.rs index dd00aef450..0f374a2e3f 100644 --- a/tests/compile-fail/environ-gets-deallocated.rs +++ b/tests/compile-fail/environ-gets-deallocated.rs @@ -1,4 +1,4 @@ -//ignore-windows: TODO env var emulation stubbed out on Windows +//ignore-windows: Windows does not have a global environ list that the program can access directly #[cfg(target_os="linux")] fn get_environ() -> *const *const u8 { diff --git a/tests/run-pass/env-exclude.rs b/tests/run-pass/env-exclude.rs index efcf7a7561..1e251084f0 100644 --- a/tests/run-pass/env-exclude.rs +++ b/tests/run-pass/env-exclude.rs @@ -1,4 +1,3 @@ -// ignore-windows: TODO env var emulation stubbed out on Windows // compile-flags: -Zmiri-disable-isolation -Zmiri-env-exclude=MIRI_ENV_VAR_TEST fn main() { diff --git a/tests/run-pass/env-without-isolation.rs b/tests/run-pass/env-without-isolation.rs index 537e0923d2..6384762098 100644 --- a/tests/run-pass/env-without-isolation.rs +++ b/tests/run-pass/env-without-isolation.rs @@ -1,4 +1,3 @@ -// ignore-windows: TODO env var emulation stubbed out on Windows // compile-flags: -Zmiri-disable-isolation fn main() { diff --git a/tests/run-pass/env.rs b/tests/run-pass/env.rs index c7506b23c1..23a3724ff7 100644 --- a/tests/run-pass/env.rs +++ b/tests/run-pass/env.rs @@ -1,5 +1,3 @@ -//ignore-windows: TODO env var emulation stubbed out on Windows - use std::env; fn main() {