Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shims for env var emulation in Windows #1225

Merged
merged 9 commits into from
Mar 27, 2020
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
131 changes: 129 additions & 2 deletions src/shims/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>,
mut excluded_env_vars: Vec<String>,
) -> 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 Issue#1013(Implement file system access for Windows) is resolved.
RalfJung marked this conversation as resolved.
Show resolved Hide 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 {
Expand Down Expand Up @@ -82,6 +88,86 @@ 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<Tag>> {
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 `MiMemoryKind::Machine`,
// but using it results in a Stacked Borrows error when running MIRI on 'tests/run-pass/env.rs'
// For now, use `MiriMemoryKind::WinHeap` instead.
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
let envblock_ptr = this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::WinHeap.into());
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
// 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 `MiMemoryKind::Machine`,
// but using it results in a Stacked Borrows error when running MIRI on 'tests/run-pass/env.rs'
// For now, use `MiriMemoryKind::WinHeap` instead.
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
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(
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
name_op: OpTy<'tcx, Tag>,
Expand Down Expand Up @@ -118,6 +204,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;
Expand Down
27 changes: 14 additions & 13 deletions src/shims/foreign_items/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" => {
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down
2 changes: 0 additions & 2 deletions tests/run-pass/env.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//ignore-windows: TODO env var emulation stubbed out on Windows

use std::env;

fn main() {
Expand Down