Skip to content

Commit

Permalink
Auto merge of #1225 - JOE1994:rw_widestr, r=RalfJung
Browse files Browse the repository at this point in the history
Add shims for env var emulation in Windows

This PR attempts to implement the final step of the instructions laid out in #707 (comment) , and is yet a work in progress.

### STATUS
- [x] Add general **_target_** methods for **read_str/alloc_str** that dispatch to either **c_str** or **wide_str** variants
(**helpers.rs**)
- [x] Implement shims `fn getenvironmentvariablew`/`fn setenvironmentvariablew`
(`std::env::var()`, `std::env::set_var()`)
- [x] Implement shim `GetEnvironmentStringsW` (`std::env::vars()`)
- [x] Implement shim `FreeEnvironmentStringsW`

### ISSUES (updated on 03/21/2020)
- MIRI errors while running `std::env::remove_var()` in Windows.
    MIRI complaining about raw pointer usage in
Rust standard library [*src/libstd/sys/windows/os.rs*](#1225 (comment)).

### TODO (probably on a separate PR)
  - Move string helpers into a new file to avoid bloating **src/helpers.rs** too much. (**shims/os_str.rs**)
  • Loading branch information
bors committed Mar 27, 2020
2 parents e2a9c7b + 579b3c4 commit 01bc08a
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 21 deletions.
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
127 changes: 125 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 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 {
Expand Down Expand Up @@ -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<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 `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>,
Expand Down Expand Up @@ -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;
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" => {
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: 1 addition & 1 deletion tests/compile-fail/environ-gets-deallocated.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
1 change: 0 additions & 1 deletion tests/run-pass/env-exclude.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down
1 change: 0 additions & 1 deletion tests/run-pass/env-without-isolation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// ignore-windows: TODO env var emulation stubbed out on Windows
// compile-flags: -Zmiri-disable-isolation

fn main() {
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

0 comments on commit 01bc08a

Please sign in to comment.