Auto merge of #3517 - RalfJung:env-vars, r=RalfJung
env: split up Windows and Unix environment variable handling On Windows, manage them entirely outside the AM state; this also means we no longer report any data races for environment variable memory. Fixes https://github.com/rust-lang/rust/issues/124411
This commit is contained in:
commit
3a74fae72d
@ -31,7 +31,7 @@
|
||||
|
||||
use crate::{
|
||||
concurrency::{data_race, weak_memory},
|
||||
shims::unix::FdTable,
|
||||
shims::unix,
|
||||
*,
|
||||
};
|
||||
|
||||
@ -439,8 +439,7 @@ pub struct MiriMachine<'mir, 'tcx> {
|
||||
/// Ptr-int-cast module global data.
|
||||
pub alloc_addresses: alloc_addresses::GlobalState,
|
||||
|
||||
/// Environment variables set by `setenv`.
|
||||
/// Miri does not expose env vars from the host to the emulated program.
|
||||
/// Environment variables.
|
||||
pub(crate) env_vars: EnvVars<'tcx>,
|
||||
|
||||
/// Return place of the main function.
|
||||
@ -465,9 +464,9 @@ pub struct MiriMachine<'mir, 'tcx> {
|
||||
pub(crate) validate: bool,
|
||||
|
||||
/// The table of file descriptors.
|
||||
pub(crate) fds: shims::unix::FdTable,
|
||||
pub(crate) fds: unix::FdTable,
|
||||
/// The table of directory descriptors.
|
||||
pub(crate) dirs: shims::unix::DirTable,
|
||||
pub(crate) dirs: unix::DirTable,
|
||||
|
||||
/// This machine's monotone clock.
|
||||
pub(crate) clock: Clock,
|
||||
@ -642,7 +641,7 @@ pub(crate) fn new(config: &MiriConfig, layout_cx: LayoutCx<'tcx, TyCtxt<'tcx>>)
|
||||
tls: TlsData::default(),
|
||||
isolated_op: config.isolated_op,
|
||||
validate: config.validate,
|
||||
fds: FdTable::new(config.mute_stdout_stderr),
|
||||
fds: unix::FdTable::new(config.mute_stdout_stderr),
|
||||
dirs: Default::default(),
|
||||
layouts,
|
||||
threads: ThreadManager::default(),
|
||||
|
@ -1,33 +1,24 @@
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::ErrorKind;
|
||||
use std::mem;
|
||||
use std::ffi::OsString;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_target::abi::Size;
|
||||
|
||||
use crate::*;
|
||||
use helpers::windows_check_buffer_size;
|
||||
use shims::{unix::UnixEnvVars, windows::WindowsEnvVars};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EnvVars<'tcx> {
|
||||
/// Stores pointers to the environment variables. These variables must be stored as
|
||||
/// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format.
|
||||
map: FxHashMap<OsString, Pointer<Option<Provenance>>>,
|
||||
|
||||
/// Place where the `environ` static is stored. Lazily initialized, but then never changes.
|
||||
pub(crate) environ: Option<MPlaceTy<'tcx, Provenance>>,
|
||||
pub enum EnvVars<'tcx> {
|
||||
#[default]
|
||||
Uninit,
|
||||
Unix(UnixEnvVars<'tcx>),
|
||||
Windows(WindowsEnvVars),
|
||||
}
|
||||
|
||||
impl VisitProvenance for EnvVars<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let EnvVars { map, environ } = self;
|
||||
|
||||
environ.visit_provenance(visit);
|
||||
for ptr in map.values() {
|
||||
ptr.visit_provenance(visit);
|
||||
match self {
|
||||
EnvVars::Uninit => {}
|
||||
EnvVars::Unix(env) => env.visit_provenance(visit),
|
||||
EnvVars::Windows(env) => env.visit_provenance(visit),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -39,517 +30,73 @@ pub(crate) fn init<'mir>(
|
||||
) -> InterpResult<'tcx> {
|
||||
// Initialize the `env_vars` map.
|
||||
// Skip the loop entirely if we don't want to forward anything.
|
||||
let mut env_vars = FxHashMap::default();
|
||||
if ecx.machine.communicate() || !config.forwarded_env_vars.is_empty() {
|
||||
for (name, value) in &config.env {
|
||||
let forward = ecx.machine.communicate()
|
||||
|| config.forwarded_env_vars.iter().any(|v| **v == *name);
|
||||
if forward {
|
||||
add_env_var(ecx, name, value)?;
|
||||
env_vars.insert(OsString::from(name), OsString::from(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (name, value) in &config.set_env_vars {
|
||||
add_env_var(ecx, OsStr::new(name), OsStr::new(value))?;
|
||||
env_vars.insert(OsString::from(name), OsString::from(value));
|
||||
}
|
||||
|
||||
// Initialize the `environ` pointer when needed.
|
||||
if ecx.target_os_is_unix() {
|
||||
// This is memory backing an extern static, hence `ExternStatic`, not `Env`.
|
||||
let layout = ecx.machine.layouts.mut_raw_ptr;
|
||||
let place = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
|
||||
ecx.write_null(&place)?;
|
||||
ecx.machine.env_vars.environ = Some(place);
|
||||
ecx.update_environ()?;
|
||||
}
|
||||
let env_vars = if ecx.target_os_is_unix() {
|
||||
EnvVars::Unix(UnixEnvVars::new(ecx, env_vars)?)
|
||||
} else if ecx.tcx.sess.target.os == "windows" {
|
||||
EnvVars::Windows(WindowsEnvVars::new(ecx, env_vars)?)
|
||||
} else {
|
||||
// Used e.g. for wasi
|
||||
EnvVars::Uninit
|
||||
};
|
||||
ecx.machine.env_vars = env_vars;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup<'mir>(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Deallocate individual env vars.
|
||||
let env_vars = mem::take(&mut ecx.machine.env_vars.map);
|
||||
for (_name, ptr) in env_vars {
|
||||
ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
let this = ecx.eval_context_mut();
|
||||
match this.machine.env_vars {
|
||||
EnvVars::Unix(_) => UnixEnvVars::cleanup(this),
|
||||
EnvVars::Windows(_) => Ok(()), // no cleanup needed
|
||||
EnvVars::Uninit => Ok(()),
|
||||
}
|
||||
// Deallocate environ var list.
|
||||
if ecx.target_os_is_unix() {
|
||||
let environ = ecx.machine.env_vars.environ.as_ref().unwrap();
|
||||
let old_vars_ptr = ecx.read_pointer(environ)?;
|
||||
ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn add_env_var<'mir, 'tcx>(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
name: &OsStr,
|
||||
value: &OsStr,
|
||||
) -> InterpResult<'tcx, ()> {
|
||||
let var_ptr = match ecx.tcx.sess.target.os.as_ref() {
|
||||
_ if ecx.target_os_is_unix() => alloc_env_var_as_c_str(name, value, ecx)?,
|
||||
"windows" => alloc_env_var_as_wide_str(name, value, ecx)?,
|
||||
unsupported =>
|
||||
throw_unsup_format!(
|
||||
"environment support for target OS `{}` not yet available",
|
||||
unsupported
|
||||
),
|
||||
};
|
||||
ecx.machine.env_vars.map.insert(name.to_os_string(), var_ptr);
|
||||
Ok(())
|
||||
}
|
||||
pub(crate) fn unix(&self) -> &UnixEnvVars<'tcx> {
|
||||
match self {
|
||||
EnvVars::Unix(env) => env,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_env_var_as_c_str<'mir, 'tcx>(
|
||||
name: &OsStr,
|
||||
value: &OsStr,
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let mut name_osstring = name.to_os_string();
|
||||
name_osstring.push("=");
|
||||
name_osstring.push(value);
|
||||
ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
|
||||
}
|
||||
pub(crate) fn unix_mut(&mut self) -> &mut UnixEnvVars<'tcx> {
|
||||
match self {
|
||||
EnvVars::Unix(env) => env,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_env_var_as_wide_str<'mir, 'tcx>(
|
||||
name: &OsStr,
|
||||
value: &OsStr,
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let mut name_osstring = name.to_os_string();
|
||||
name_osstring.push("=");
|
||||
name_osstring.push(value);
|
||||
ecx.alloc_os_str_as_wide_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
|
||||
pub(crate) fn windows(&self) -> &WindowsEnvVars {
|
||||
match self {
|
||||
EnvVars::Windows(env) => env,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn windows_mut(&mut self) -> &mut WindowsEnvVars {
|
||||
match self {
|
||||
EnvVars::Windows(env) => env,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
fn getenv(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("getenv");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let name = this.read_os_str_from_c_str(name_ptr)?;
|
||||
this.read_environ()?;
|
||||
Ok(match this.machine.env_vars.map.get(name) {
|
||||
Some(var_ptr) => {
|
||||
// The offset is used to strip the "{name}=" part of the string.
|
||||
var_ptr.offset(
|
||||
Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()),
|
||||
this,
|
||||
)?
|
||||
}
|
||||
None => Pointer::null(),
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetEnvironmentVariableW(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
|
||||
buf_op: &OpTy<'tcx, Provenance>, // LPWSTR
|
||||
size_op: &OpTy<'tcx, Provenance>, // DWORD
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
// ^ Returns DWORD (u32 on Windows)
|
||||
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetEnvironmentVariableW");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let buf_ptr = this.read_pointer(buf_op)?;
|
||||
let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
|
||||
|
||||
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.
|
||||
#[rustfmt::skip]
|
||||
let name_offset_bytes = u64::try_from(name.len()).unwrap()
|
||||
.checked_add(1).unwrap()
|
||||
.checked_mul(2).unwrap();
|
||||
let var_ptr = var_ptr.offset(Size::from_bytes(name_offset_bytes), this)?;
|
||||
let var = this.read_os_str_from_wide_str(var_ptr)?;
|
||||
|
||||
Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
|
||||
&var,
|
||||
buf_ptr,
|
||||
buf_size.into(),
|
||||
)?))
|
||||
// This can in fact return 0. It is up to the caller to set last_error to 0
|
||||
// beforehand and check it afterwards to exclude that case.
|
||||
}
|
||||
None => {
|
||||
let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
|
||||
this.set_last_error(envvar_not_found)?;
|
||||
Scalar::from_u32(0) // return zero upon failure
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
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(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`.
|
||||
let envblock_ptr =
|
||||
this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?;
|
||||
// If the function succeeds, the return value is a pointer to the environment block of the current process.
|
||||
Ok(envblock_ptr)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn FreeEnvironmentStringsW(
|
||||
&mut self,
|
||||
env_block_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "FreeEnvironmentStringsW");
|
||||
|
||||
let env_block_ptr = this.read_pointer(env_block_op)?;
|
||||
let result = this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into());
|
||||
// If the function succeeds, the return value is nonzero.
|
||||
Ok(Scalar::from_i32(i32::from(result.is_ok())))
|
||||
}
|
||||
|
||||
fn setenv(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>,
|
||||
value_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("setenv");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let value_ptr = this.read_pointer(value_op)?;
|
||||
|
||||
let mut new = None;
|
||||
if !this.ptr_is_null(name_ptr)? {
|
||||
let name = this.read_os_str_from_c_str(name_ptr)?;
|
||||
if !name.is_empty() && !name.to_string_lossy().contains('=') {
|
||||
let value = this.read_os_str_from_c_str(value_ptr)?;
|
||||
new = Some((name.to_owned(), value.to_owned()));
|
||||
}
|
||||
}
|
||||
if let Some((name, value)) = new {
|
||||
let var_ptr = alloc_env_var_as_c_str(&name, &value, this)?;
|
||||
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
this.update_environ()?;
|
||||
Ok(0) // return zero on success
|
||||
} else {
|
||||
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
|
||||
let einval = this.eval_libc("EINVAL");
|
||||
this.set_last_error(einval)?;
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn SetEnvironmentVariableW(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
|
||||
value_op: &OpTy<'tcx, Provenance>, // LPCWSTR
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "SetEnvironmentVariableW");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let value_ptr = this.read_pointer(value_op)?;
|
||||
|
||||
if this.ptr_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.ptr_is_null(value_ptr)? {
|
||||
// Delete environment variable `{name}`
|
||||
if let Some(var) = this.machine.env_vars.map.remove(&name) {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
Ok(this.eval_windows("c", "TRUE"))
|
||||
} else {
|
||||
let value = this.read_os_str_from_wide_str(value_ptr)?;
|
||||
let var_ptr = alloc_env_var_as_wide_str(&name, &value, this)?;
|
||||
if let Some(var) = this.machine.env_vars.map.insert(name, var_ptr) {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
Ok(this.eval_windows("c", "TRUE"))
|
||||
}
|
||||
}
|
||||
|
||||
fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("unsetenv");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let mut success = None;
|
||||
if !this.ptr_is_null(name_ptr)? {
|
||||
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
|
||||
if !name.is_empty() && !name.to_string_lossy().contains('=') {
|
||||
success = Some(this.machine.env_vars.map.remove(&name));
|
||||
}
|
||||
}
|
||||
if let Some(old) = success {
|
||||
if let Some(var) = old {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
this.update_environ()?;
|
||||
Ok(0)
|
||||
} else {
|
||||
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
|
||||
let einval = this.eval_libc("EINVAL");
|
||||
this.set_last_error(einval)?;
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
|
||||
fn getcwd(
|
||||
&mut self,
|
||||
buf_op: &OpTy<'tcx, Provenance>,
|
||||
size_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("getcwd");
|
||||
|
||||
let buf = this.read_pointer(buf_op)?;
|
||||
let size = this.read_target_usize(size_op)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`getcwd`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
return Ok(Pointer::null());
|
||||
}
|
||||
|
||||
// If we cannot get the current directory, we return null
|
||||
match env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
if this.write_path_to_c_str(&cwd, buf, size)?.0 {
|
||||
return Ok(buf);
|
||||
}
|
||||
let erange = this.eval_libc("ERANGE");
|
||||
this.set_last_error(erange)?;
|
||||
}
|
||||
Err(e) => this.set_last_error_from_io_error(e.kind())?,
|
||||
}
|
||||
|
||||
Ok(Pointer::null())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetCurrentDirectoryW(
|
||||
&mut self,
|
||||
size_op: &OpTy<'tcx, Provenance>, // DWORD
|
||||
buf_op: &OpTy<'tcx, Provenance>, // LPTSTR
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetCurrentDirectoryW");
|
||||
|
||||
let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
|
||||
let buf = this.read_pointer(buf_op)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
return Ok(Scalar::from_u32(0));
|
||||
}
|
||||
|
||||
// If we cannot get the current directory, we return 0
|
||||
match env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
// This can in fact return 0. It is up to the caller to set last_error to 0
|
||||
// beforehand and check it afterwards to exclude that case.
|
||||
return Ok(Scalar::from_u32(windows_check_buffer_size(
|
||||
this.write_path_to_wide_str(&cwd, buf, size)?,
|
||||
)));
|
||||
}
|
||||
Err(e) => this.set_last_error_from_io_error(e.kind())?,
|
||||
}
|
||||
Ok(Scalar::from_u32(0))
|
||||
}
|
||||
|
||||
fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("chdir");
|
||||
|
||||
let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`chdir`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
match env::set_current_dir(path) {
|
||||
Ok(()) => Ok(0),
|
||||
Err(e) => {
|
||||
this.set_last_error_from_io_error(e.kind())?;
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn SetCurrentDirectoryW(
|
||||
&mut self,
|
||||
path_op: &OpTy<'tcx, Provenance>, // LPCTSTR
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
// ^ Returns BOOL (i32 on Windows)
|
||||
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "SetCurrentDirectoryW");
|
||||
|
||||
let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
|
||||
return Ok(this.eval_windows("c", "FALSE"));
|
||||
}
|
||||
|
||||
match env::set_current_dir(path) {
|
||||
Ok(()) => Ok(this.eval_windows("c", "TRUE")),
|
||||
Err(e) => {
|
||||
this.set_last_error_from_io_error(e.kind())?;
|
||||
Ok(this.eval_windows("c", "FALSE"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the `environ` static.
|
||||
/// The first time it gets called, also initializes `extra.environ`.
|
||||
fn update_environ(&mut self) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
// Deallocate the old environ list, if any.
|
||||
let environ = this.machine.env_vars.environ.as_ref().unwrap().clone();
|
||||
let old_vars_ptr = this.read_pointer(&environ)?;
|
||||
if !this.ptr_is_null(old_vars_ptr)? {
|
||||
this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
|
||||
// Collect all the pointers to each variable in a vector.
|
||||
let mut vars: Vec<Pointer<Option<Provenance>>> =
|
||||
this.machine.env_vars.map.values().copied().collect();
|
||||
// Add the trailing null pointer.
|
||||
vars.push(Pointer::null());
|
||||
// Make an array with all these pointers inside Miri.
|
||||
let tcx = this.tcx;
|
||||
let vars_layout = this.layout_of(Ty::new_array(
|
||||
tcx.tcx,
|
||||
this.machine.layouts.mut_raw_ptr.ty,
|
||||
u64::try_from(vars.len()).unwrap(),
|
||||
))?;
|
||||
let vars_place = this.allocate(vars_layout, MiriMemoryKind::Runtime.into())?;
|
||||
for (idx, var) in vars.into_iter().enumerate() {
|
||||
let place = this.project_field(&vars_place, idx)?;
|
||||
this.write_pointer(var, &place)?;
|
||||
}
|
||||
this.write_pointer(vars_place.ptr(), &environ)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reads from the `environ` static.
|
||||
/// We don't actually care about the result, but we care about this potentially causing a data race.
|
||||
fn read_environ(&self) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_ref();
|
||||
let environ = this.machine.env_vars.environ.as_ref().unwrap();
|
||||
let _vars_ptr = this.read_pointer(environ)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn getpid(&mut self) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("getpid");
|
||||
|
||||
this.check_no_isolation("`getpid`")?;
|
||||
|
||||
// The reason we need to do this wacky of a conversion is because
|
||||
// `libc::getpid` returns an i32, however, `std::process::id()` return an u32.
|
||||
// So we un-do the conversion that stdlib does and turn it back into an i32.
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
Ok(std::process::id() as i32)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetCurrentProcessId");
|
||||
this.check_no_isolation("`GetCurrentProcessId`")?;
|
||||
|
||||
Ok(std::process::id())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetUserProfileDirectoryW(
|
||||
&mut self,
|
||||
token: &OpTy<'tcx, Provenance>, // HANDLE
|
||||
buf: &OpTy<'tcx, Provenance>, // LPWSTR
|
||||
size: &OpTy<'tcx, Provenance>, // LPDWORD
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> // returns BOOL
|
||||
{
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetUserProfileDirectoryW");
|
||||
this.check_no_isolation("`GetUserProfileDirectoryW`")?;
|
||||
|
||||
let token = this.read_target_isize(token)?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let size = this.deref_pointer(size)?;
|
||||
|
||||
if token != -4 {
|
||||
throw_unsup_format!(
|
||||
"GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
|
||||
);
|
||||
}
|
||||
|
||||
// See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
|
||||
Ok(match directories::UserDirs::new() {
|
||||
Some(dirs) => {
|
||||
let home = dirs.home_dir();
|
||||
let size_avail = if this.ptr_is_null(size.ptr())? {
|
||||
0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
|
||||
} else {
|
||||
this.read_scalar(&size)?.to_u32()?
|
||||
};
|
||||
// Of course we cannot use `windows_check_buffer_size` here since this uses
|
||||
// a different method for dealing with a too-small buffer than the other functions...
|
||||
let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
|
||||
// The Windows docs just say that this is written on failure. But std
|
||||
// seems to rely on it always being written.
|
||||
this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
|
||||
if success {
|
||||
Scalar::from_i32(1) // return TRUE
|
||||
} else {
|
||||
this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
|
||||
Scalar::from_i32(0) // return FALSE
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// We have to pick some error code.
|
||||
this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
|
||||
Scalar::from_i32(0) // return FALSE
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {}
|
||||
|
@ -47,20 +47,14 @@ pub fn init_extern_statics(this: &mut MiriInterpCx<'mir, 'tcx>) -> InterpResult<
|
||||
&["__cxa_thread_atexit_impl", "getrandom", "statx", "__clock_gettime64"],
|
||||
)?;
|
||||
// "environ"
|
||||
Self::add_extern_static(
|
||||
this,
|
||||
"environ",
|
||||
this.machine.env_vars.environ.as_ref().unwrap().ptr(),
|
||||
);
|
||||
let environ = this.machine.env_vars.unix().environ();
|
||||
Self::add_extern_static(this, "environ", environ);
|
||||
}
|
||||
"freebsd" => {
|
||||
Self::null_ptr_extern_statics(this, &["__cxa_thread_atexit_impl"])?;
|
||||
// "environ"
|
||||
Self::add_extern_static(
|
||||
this,
|
||||
"environ",
|
||||
this.machine.env_vars.environ.as_ref().unwrap().ptr(),
|
||||
);
|
||||
let environ = this.machine.env_vars.unix().environ();
|
||||
Self::add_extern_static(this, "environ", environ);
|
||||
}
|
||||
"android" => {
|
||||
Self::null_ptr_extern_statics(this, &["bsd_signal"])?;
|
||||
|
276
src/tools/miri/src/shims/unix/env.rs
Normal file
276
src/tools/miri/src/shims/unix/env.rs
Normal file
@ -0,0 +1,276 @@
|
||||
use std::env;
|
||||
use std::ffi::{OsStr, OsString};
|
||||
use std::io::ErrorKind;
|
||||
use std::mem;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
use rustc_middle::ty::layout::LayoutOf;
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_target::abi::Size;
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub struct UnixEnvVars<'tcx> {
|
||||
/// Stores pointers to the environment variables. These variables must be stored as
|
||||
/// null-terminated target strings (c_str or wide_str) with the `"{name}={value}"` format.
|
||||
map: FxHashMap<OsString, Pointer<Option<Provenance>>>,
|
||||
|
||||
/// Place where the `environ` static is stored. Lazily initialized, but then never changes.
|
||||
environ: MPlaceTy<'tcx, Provenance>,
|
||||
}
|
||||
|
||||
impl VisitProvenance for UnixEnvVars<'_> {
|
||||
fn visit_provenance(&self, visit: &mut VisitWith<'_>) {
|
||||
let UnixEnvVars { map, environ } = self;
|
||||
|
||||
environ.visit_provenance(visit);
|
||||
for ptr in map.values() {
|
||||
ptr.visit_provenance(visit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> UnixEnvVars<'tcx> {
|
||||
pub(crate) fn new<'mir>(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
env_vars: FxHashMap<OsString, OsString>,
|
||||
) -> InterpResult<'tcx, Self> {
|
||||
// Allocate memory for all these env vars.
|
||||
let mut env_vars_machine = FxHashMap::default();
|
||||
for (name, val) in env_vars.into_iter() {
|
||||
let ptr = alloc_env_var(ecx, &name, &val)?;
|
||||
env_vars_machine.insert(name, ptr);
|
||||
}
|
||||
|
||||
// This is memory backing an extern static, hence `ExternStatic`, not `Env`.
|
||||
let layout = ecx.machine.layouts.mut_raw_ptr;
|
||||
let environ = ecx.allocate(layout, MiriMemoryKind::ExternStatic.into())?;
|
||||
let environ_block = alloc_environ_block(ecx, env_vars_machine.values().copied().collect())?;
|
||||
ecx.write_pointer(environ_block, &environ)?;
|
||||
|
||||
Ok(UnixEnvVars { map: env_vars_machine, environ })
|
||||
}
|
||||
|
||||
pub(crate) fn cleanup<'mir>(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
) -> InterpResult<'tcx> {
|
||||
// Deallocate individual env vars.
|
||||
let env_vars = mem::take(&mut ecx.machine.env_vars.unix_mut().map);
|
||||
for (_name, ptr) in env_vars {
|
||||
ecx.deallocate_ptr(ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
// Deallocate environ var list.
|
||||
let environ = &ecx.machine.env_vars.unix().environ;
|
||||
let old_vars_ptr = ecx.read_pointer(environ)?;
|
||||
ecx.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn environ(&self) -> Pointer<Option<Provenance>> {
|
||||
self.environ.ptr()
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_env_var<'mir, 'tcx>(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
name: &OsStr,
|
||||
value: &OsStr,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let mut name_osstring = name.to_os_string();
|
||||
name_osstring.push("=");
|
||||
name_osstring.push(value);
|
||||
ecx.alloc_os_str_as_c_str(name_osstring.as_os_str(), MiriMemoryKind::Runtime.into())
|
||||
}
|
||||
|
||||
/// Allocates an `environ` block with the given list of pointers.
|
||||
fn alloc_environ_block<'mir, 'tcx>(
|
||||
ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
mut vars: Vec<Pointer<Option<Provenance>>>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
// Add trailing null.
|
||||
vars.push(Pointer::null());
|
||||
// Make an array with all these pointers inside Miri.
|
||||
let vars_layout = ecx.layout_of(Ty::new_array(
|
||||
*ecx.tcx,
|
||||
ecx.machine.layouts.mut_raw_ptr.ty,
|
||||
u64::try_from(vars.len()).unwrap(),
|
||||
))?;
|
||||
let vars_place = ecx.allocate(vars_layout, MiriMemoryKind::Runtime.into())?;
|
||||
for (idx, var) in vars.into_iter().enumerate() {
|
||||
let place = ecx.project_field(&vars_place, idx)?;
|
||||
ecx.write_pointer(var, &place)?;
|
||||
}
|
||||
Ok(vars_place.ptr())
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
fn getenv(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("getenv");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let name = this.read_os_str_from_c_str(name_ptr)?;
|
||||
|
||||
// We don't care about the value as we have the `map` to keep track of everything,
|
||||
// but we do want to do this read so it shows up as a data race.
|
||||
let _vars_ptr = this.read_pointer(&this.machine.env_vars.unix().environ)?;
|
||||
Ok(match this.machine.env_vars.unix().map.get(name) {
|
||||
Some(var_ptr) => {
|
||||
// The offset is used to strip the "{name}=" part of the string.
|
||||
var_ptr.offset(
|
||||
Size::from_bytes(u64::try_from(name.len()).unwrap().checked_add(1).unwrap()),
|
||||
this,
|
||||
)?
|
||||
}
|
||||
None => Pointer::null(),
|
||||
})
|
||||
}
|
||||
|
||||
fn setenv(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>,
|
||||
value_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("setenv");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let value_ptr = this.read_pointer(value_op)?;
|
||||
|
||||
let mut new = None;
|
||||
if !this.ptr_is_null(name_ptr)? {
|
||||
let name = this.read_os_str_from_c_str(name_ptr)?;
|
||||
if !name.is_empty() && !name.to_string_lossy().contains('=') {
|
||||
let value = this.read_os_str_from_c_str(value_ptr)?;
|
||||
new = Some((name.to_owned(), value.to_owned()));
|
||||
}
|
||||
}
|
||||
if let Some((name, value)) = new {
|
||||
let var_ptr = alloc_env_var(this, &name, &value)?;
|
||||
if let Some(var) = this.machine.env_vars.unix_mut().map.insert(name, var_ptr) {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
this.update_environ()?;
|
||||
Ok(0) // return zero on success
|
||||
} else {
|
||||
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
|
||||
let einval = this.eval_libc("EINVAL");
|
||||
this.set_last_error(einval)?;
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
|
||||
fn unsetenv(&mut self, name_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("unsetenv");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let mut success = None;
|
||||
if !this.ptr_is_null(name_ptr)? {
|
||||
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
|
||||
if !name.is_empty() && !name.to_string_lossy().contains('=') {
|
||||
success = Some(this.machine.env_vars.unix_mut().map.remove(&name));
|
||||
}
|
||||
}
|
||||
if let Some(old) = success {
|
||||
if let Some(var) = old {
|
||||
this.deallocate_ptr(var, None, MiriMemoryKind::Runtime.into())?;
|
||||
}
|
||||
this.update_environ()?;
|
||||
Ok(0)
|
||||
} else {
|
||||
// name argument is a null pointer, points to an empty string, or points to a string containing an '=' character.
|
||||
let einval = this.eval_libc("EINVAL");
|
||||
this.set_last_error(einval)?;
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
|
||||
fn getcwd(
|
||||
&mut self,
|
||||
buf_op: &OpTy<'tcx, Provenance>,
|
||||
size_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("getcwd");
|
||||
|
||||
let buf = this.read_pointer(buf_op)?;
|
||||
let size = this.read_target_usize(size_op)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`getcwd`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
return Ok(Pointer::null());
|
||||
}
|
||||
|
||||
// If we cannot get the current directory, we return null
|
||||
match env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
if this.write_path_to_c_str(&cwd, buf, size)?.0 {
|
||||
return Ok(buf);
|
||||
}
|
||||
let erange = this.eval_libc("ERANGE");
|
||||
this.set_last_error(erange)?;
|
||||
}
|
||||
Err(e) => this.set_last_error_from_io_error(e.kind())?,
|
||||
}
|
||||
|
||||
Ok(Pointer::null())
|
||||
}
|
||||
|
||||
fn chdir(&mut self, path_op: &OpTy<'tcx, Provenance>) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("chdir");
|
||||
|
||||
let path = this.read_path_from_c_str(this.read_pointer(path_op)?)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`chdir`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
|
||||
return Ok(-1);
|
||||
}
|
||||
|
||||
match env::set_current_dir(path) {
|
||||
Ok(()) => Ok(0),
|
||||
Err(e) => {
|
||||
this.set_last_error_from_io_error(e.kind())?;
|
||||
Ok(-1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Updates the `environ` static.
|
||||
fn update_environ(&mut self) -> InterpResult<'tcx> {
|
||||
let this = self.eval_context_mut();
|
||||
// Deallocate the old environ list.
|
||||
let environ = this.machine.env_vars.unix().environ.clone();
|
||||
let old_vars_ptr = this.read_pointer(&environ)?;
|
||||
this.deallocate_ptr(old_vars_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
|
||||
// Write the new list.
|
||||
let vals = this.machine.env_vars.unix().map.values().copied().collect();
|
||||
let environ_block = alloc_environ_block(this, vals)?;
|
||||
this.write_pointer(environ_block, &environ)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn getpid(&mut self) -> InterpResult<'tcx, i32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os_is_unix("getpid");
|
||||
|
||||
this.check_no_isolation("`getpid`")?;
|
||||
|
||||
// The reason we need to do this wacky of a conversion is because
|
||||
// `libc::getpid` returns an i32, however, `std::process::id()` return an u32.
|
||||
// So we un-do the conversion that stdlib does and turn it back into an i32.
|
||||
#[allow(clippy::cast_possible_wrap)]
|
||||
Ok(std::process::id() as i32)
|
||||
}
|
||||
}
|
@ -74,15 +74,8 @@ fn emulate_foreign_item_inner(
|
||||
// Environment related shims
|
||||
"_NSGetEnviron" => {
|
||||
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
this.write_pointer(
|
||||
this.machine
|
||||
.env_vars
|
||||
.environ
|
||||
.as_ref()
|
||||
.expect("machine must be initialized")
|
||||
.ptr(),
|
||||
dest,
|
||||
)?;
|
||||
let environ = this.machine.env_vars.unix().environ();
|
||||
this.write_pointer(environ, dest)?;
|
||||
}
|
||||
|
||||
// Time related shims
|
||||
|
@ -1,5 +1,6 @@
|
||||
pub mod foreign_items;
|
||||
|
||||
mod env;
|
||||
mod fd;
|
||||
mod fs;
|
||||
mod mem;
|
||||
@ -11,9 +12,11 @@
|
||||
mod linux;
|
||||
mod macos;
|
||||
|
||||
pub use env::UnixEnvVars;
|
||||
pub use fd::{FdTable, FileDescriptor};
|
||||
pub use fs::DirTable;
|
||||
// All the unix-specific extension traits
|
||||
// All the Unix-specific extension traits
|
||||
pub use env::EvalContextExt as _;
|
||||
pub use fd::EvalContextExt as _;
|
||||
pub use fs::EvalContextExt as _;
|
||||
pub use mem::EvalContextExt as _;
|
||||
|
257
src/tools/miri/src/shims/windows/env.rs
Normal file
257
src/tools/miri/src/shims/windows/env.rs
Normal file
@ -0,0 +1,257 @@
|
||||
use std::env;
|
||||
use std::ffi::OsString;
|
||||
use std::io::ErrorKind;
|
||||
|
||||
use rustc_data_structures::fx::FxHashMap;
|
||||
|
||||
use crate::*;
|
||||
use helpers::windows_check_buffer_size;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct WindowsEnvVars {
|
||||
/// Stores the environment varialbles.
|
||||
map: FxHashMap<OsString, OsString>,
|
||||
}
|
||||
|
||||
impl VisitProvenance for WindowsEnvVars {
|
||||
fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
|
||||
let WindowsEnvVars { map: _ } = self;
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowsEnvVars {
|
||||
pub(crate) fn new<'mir, 'tcx>(
|
||||
_ecx: &mut InterpCx<'mir, 'tcx, MiriMachine<'mir, 'tcx>>,
|
||||
env_vars: FxHashMap<OsString, OsString>,
|
||||
) -> InterpResult<'tcx, Self> {
|
||||
Ok(Self { map: env_vars })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'mir, 'tcx: 'mir> EvalContextExt<'mir, 'tcx> for crate::MiriInterpCx<'mir, 'tcx> {}
|
||||
pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
|
||||
#[allow(non_snake_case)]
|
||||
fn GetEnvironmentVariableW(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
|
||||
buf_op: &OpTy<'tcx, Provenance>, // LPWSTR
|
||||
size_op: &OpTy<'tcx, Provenance>, // DWORD
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
// ^ Returns DWORD (u32 on Windows)
|
||||
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetEnvironmentVariableW");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let buf_ptr = this.read_pointer(buf_op)?;
|
||||
let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
|
||||
|
||||
let name = this.read_os_str_from_wide_str(name_ptr)?;
|
||||
Ok(match this.machine.env_vars.windows().map.get(&name).cloned() {
|
||||
Some(val) => {
|
||||
Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
|
||||
&val,
|
||||
buf_ptr,
|
||||
buf_size.into(),
|
||||
)?))
|
||||
// This can in fact return 0. It is up to the caller to set last_error to 0
|
||||
// beforehand and check it afterwards to exclude that case.
|
||||
}
|
||||
None => {
|
||||
let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
|
||||
this.set_last_error(envvar_not_found)?;
|
||||
Scalar::from_u32(0) // return zero upon failure
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer<Option<Provenance>>> {
|
||||
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 (name, value) in this.machine.env_vars.windows().map.iter() {
|
||||
env_vars.push(name);
|
||||
env_vars.push("=");
|
||||
env_vars.push(value);
|
||||
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`.
|
||||
let envblock_ptr =
|
||||
this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?;
|
||||
// If the function succeeds, the return value is a pointer to the environment block of the current process.
|
||||
Ok(envblock_ptr)
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn FreeEnvironmentStringsW(
|
||||
&mut self,
|
||||
env_block_op: &OpTy<'tcx, Provenance>,
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "FreeEnvironmentStringsW");
|
||||
|
||||
let env_block_ptr = this.read_pointer(env_block_op)?;
|
||||
this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into())?;
|
||||
// If the function succeeds, the return value is nonzero.
|
||||
Ok(Scalar::from_i32(1))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn SetEnvironmentVariableW(
|
||||
&mut self,
|
||||
name_op: &OpTy<'tcx, Provenance>, // LPCWSTR
|
||||
value_op: &OpTy<'tcx, Provenance>, // LPCWSTR
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "SetEnvironmentVariableW");
|
||||
|
||||
let name_ptr = this.read_pointer(name_op)?;
|
||||
let value_ptr = this.read_pointer(value_op)?;
|
||||
|
||||
if this.ptr_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.ptr_is_null(value_ptr)? {
|
||||
// Delete environment variable `{name}` if it exists.
|
||||
this.machine.env_vars.windows_mut().map.remove(&name);
|
||||
Ok(this.eval_windows("c", "TRUE"))
|
||||
} else {
|
||||
let value = this.read_os_str_from_wide_str(value_ptr)?;
|
||||
this.machine.env_vars.windows_mut().map.insert(name, value);
|
||||
Ok(this.eval_windows("c", "TRUE"))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetCurrentDirectoryW(
|
||||
&mut self,
|
||||
size_op: &OpTy<'tcx, Provenance>, // DWORD
|
||||
buf_op: &OpTy<'tcx, Provenance>, // LPTSTR
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetCurrentDirectoryW");
|
||||
|
||||
let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
|
||||
let buf = this.read_pointer(buf_op)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
return Ok(Scalar::from_u32(0));
|
||||
}
|
||||
|
||||
// If we cannot get the current directory, we return 0
|
||||
match env::current_dir() {
|
||||
Ok(cwd) => {
|
||||
// This can in fact return 0. It is up to the caller to set last_error to 0
|
||||
// beforehand and check it afterwards to exclude that case.
|
||||
return Ok(Scalar::from_u32(windows_check_buffer_size(
|
||||
this.write_path_to_wide_str(&cwd, buf, size)?,
|
||||
)));
|
||||
}
|
||||
Err(e) => this.set_last_error_from_io_error(e.kind())?,
|
||||
}
|
||||
Ok(Scalar::from_u32(0))
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn SetCurrentDirectoryW(
|
||||
&mut self,
|
||||
path_op: &OpTy<'tcx, Provenance>, // LPCTSTR
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> {
|
||||
// ^ Returns BOOL (i32 on Windows)
|
||||
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "SetCurrentDirectoryW");
|
||||
|
||||
let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?;
|
||||
|
||||
if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
|
||||
this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
|
||||
this.set_last_error_from_io_error(ErrorKind::PermissionDenied)?;
|
||||
|
||||
return Ok(this.eval_windows("c", "FALSE"));
|
||||
}
|
||||
|
||||
match env::set_current_dir(path) {
|
||||
Ok(()) => Ok(this.eval_windows("c", "TRUE")),
|
||||
Err(e) => {
|
||||
this.set_last_error_from_io_error(e.kind())?;
|
||||
Ok(this.eval_windows("c", "FALSE"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, u32> {
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetCurrentProcessId");
|
||||
this.check_no_isolation("`GetCurrentProcessId`")?;
|
||||
|
||||
Ok(std::process::id())
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn GetUserProfileDirectoryW(
|
||||
&mut self,
|
||||
token: &OpTy<'tcx, Provenance>, // HANDLE
|
||||
buf: &OpTy<'tcx, Provenance>, // LPWSTR
|
||||
size: &OpTy<'tcx, Provenance>, // LPDWORD
|
||||
) -> InterpResult<'tcx, Scalar<Provenance>> // returns BOOL
|
||||
{
|
||||
let this = self.eval_context_mut();
|
||||
this.assert_target_os("windows", "GetUserProfileDirectoryW");
|
||||
this.check_no_isolation("`GetUserProfileDirectoryW`")?;
|
||||
|
||||
let token = this.read_target_isize(token)?;
|
||||
let buf = this.read_pointer(buf)?;
|
||||
let size = this.deref_pointer(size)?;
|
||||
|
||||
if token != -4 {
|
||||
throw_unsup_format!(
|
||||
"GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
|
||||
);
|
||||
}
|
||||
|
||||
// See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
|
||||
Ok(match directories::UserDirs::new() {
|
||||
Some(dirs) => {
|
||||
let home = dirs.home_dir();
|
||||
let size_avail = if this.ptr_is_null(size.ptr())? {
|
||||
0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
|
||||
} else {
|
||||
this.read_scalar(&size)?.to_u32()?
|
||||
};
|
||||
// Of course we cannot use `windows_check_buffer_size` here since this uses
|
||||
// a different method for dealing with a too-small buffer than the other functions...
|
||||
let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
|
||||
// The Windows docs just say that this is written on failure. But std
|
||||
// seems to rely on it always being written.
|
||||
this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
|
||||
if success {
|
||||
Scalar::from_i32(1) // return TRUE
|
||||
} else {
|
||||
this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
|
||||
Scalar::from_i32(0) // return FALSE
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// We have to pick some error code.
|
||||
this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
|
||||
Scalar::from_i32(0) // return FALSE
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -10,11 +10,10 @@
|
||||
|
||||
use crate::shims::alloc::EvalContextExt as _;
|
||||
use crate::shims::os_str::bytes_to_os_str;
|
||||
use crate::shims::windows::*;
|
||||
use crate::*;
|
||||
use shims::foreign_items::EmulateForeignItemResult;
|
||||
use shims::windows::handle::{EvalContextExt as _, Handle, PseudoHandle};
|
||||
use shims::windows::sync::EvalContextExt as _;
|
||||
use shims::windows::thread::EvalContextExt as _;
|
||||
use shims::windows::handle::{Handle, PseudoHandle};
|
||||
|
||||
fn is_dyn_sym(name: &str) -> bool {
|
||||
// std does dynamic detection for these symbols
|
||||
|
@ -1,5 +1,13 @@
|
||||
pub mod foreign_items;
|
||||
|
||||
mod env;
|
||||
mod handle;
|
||||
mod sync;
|
||||
mod thread;
|
||||
|
||||
pub use env::WindowsEnvVars;
|
||||
// All the Windows-specific extension traits
|
||||
pub use env::EvalContextExt as _;
|
||||
pub use handle::EvalContextExt as _;
|
||||
pub use sync::EvalContextExt as _;
|
||||
pub use thread::EvalContextExt as _;
|
||||
|
8
src/tools/miri/tests/pass/shims/env/var.rs
vendored
8
src/tools/miri/tests/pass/shims/env/var.rs
vendored
@ -1,4 +1,5 @@
|
||||
use std::env;
|
||||
use std::thread;
|
||||
|
||||
fn main() {
|
||||
// Test that miri environment is isolated when communication is disabled.
|
||||
@ -23,4 +24,11 @@ fn main() {
|
||||
env::remove_var("MIRI_TEST");
|
||||
assert_eq!(env::var("MIRI_TEST"), Err(env::VarError::NotPresent));
|
||||
println!("{:#?}", env::vars().collect::<Vec<_>>());
|
||||
|
||||
// Do things concurrently, to make sure there's no data race.
|
||||
let t = thread::spawn(|| {
|
||||
env::set_var("MIRI_TEST", "42");
|
||||
});
|
||||
env::set_var("MIRI_TEST", "42");
|
||||
t.join().unwrap();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user