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 helper alloc_os_str_as_c_str and use it in env_var emulation #1098

Closed
wants to merge 12 commits into from
23 changes: 19 additions & 4 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -480,31 +480,46 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
self.eval_context_mut().memory.write_bytes(scalar, bytes.iter().copied().chain(iter::once(0u8)))?;
Ok(true)
}

fn alloc_os_str_as_c_str(
&mut self,
os_str : &OsStr
) -> Pointer<Tag> {
let bytes = os_str_to_bytes(os_str).unwrap();
let size = bytes.len() as u64 + 1; // Make space for `0` terminator.

let this = self.eval_context_mut();

let arg_type = this.tcx.mk_array(this.tcx.types.u8, size);
let arg_place = this.allocate(this.layout_of(arg_type).unwrap(), MiriMemoryKind::Env.into());
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
self.write_os_str_to_c_str(os_str, arg_place.ptr, size).unwrap();
arg_place.ptr.assert_ptr()
}
}

#[cfg(target_os = "unix")]
fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
pub(crate) fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
std::os::unix::ffi::OsStringExt::into_bytes(os_str)
}

#[cfg(target_os = "unix")]
fn bytes_to_os_str<'tcx, 'a>(bytes: &'a[u8]) -> InterpResult<'tcx, &'a OsStr> {
pub(crate) fn bytes_to_os_str<'tcx, 'a>(bytes: &'a[u8]) -> InterpResult<'tcx, &'a OsStr> {
Ok(std::os::unix::ffi::OsStringExt::from_bytes(bytes))
}

// On non-unix platforms the best we can do to transform bytes from/to OS strings is to do the
// intermediate transformation into strings. Which invalidates non-utf8 paths that are actually
// valid.
#[cfg(not(target_os = "unix"))]
fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
pub(crate) fn os_str_to_bytes<'tcx, 'a>(os_str: &'a OsStr) -> InterpResult<'tcx, &'a [u8]> {
os_str
.to_str()
.map(|s| s.as_bytes())
.ok_or_else(|| err_unsup_format!("{:?} is not a valid utf-8 string", os_str).into())
}

#[cfg(not(target_os = "unix"))]
fn bytes_to_os_str<'tcx, 'a>(bytes: &'a [u8]) -> InterpResult<'tcx, &'a OsStr> {
pub(crate) fn bytes_to_os_str<'tcx, 'a>(bytes: &'a[u8]) -> InterpResult<'tcx, &'a OsStr> {
let s = std::str::from_utf8(bytes)
.map_err(|_| err_unsup_format!("{:?} is not a valid utf-8 string", bytes))?;
Ok(&OsStr::new(s))
Expand Down
31 changes: 16 additions & 15 deletions src/shims/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ use std::env;

use crate::stacked_borrows::Tag;
use crate::*;
use crate::helpers::{ os_str_to_bytes, bytes_to_os_str};

use rustc::ty::layout::Size;
use rustc_mir::interpret::{Memory, Pointer};
use rustc_mir::interpret::Pointer;

#[derive(Default)]
pub struct EnvVars {
/// Stores pointers to the environment variables. These variables must be stored as
/// null-terminated C strings with the `"{name}={value}"` format.
map: HashMap<Vec<u8>, Pointer<Tag>>,
map: HashMap<OsString, Pointer<Tag>>,
}

impl EnvVars {
Expand All @@ -27,24 +28,24 @@ impl EnvVars {
for (name, value) in env::vars() {
if !excluded_env_vars.contains(&name) {
let var_ptr =
alloc_env_var(name.as_bytes(), value.as_bytes(), &mut ecx.memory);
ecx.machine.env_vars.map.insert(name.into_bytes(), var_ptr);
alloc_env_var_as_c_str(name.as_bytes(), value.as_bytes(), ecx);
ecx.machine.env_vars.map.insert(OsString::from(name), var_ptr);
}
}
}
}
}

fn alloc_env_var<'mir, 'tcx>(
fn alloc_env_var_as_c_str<'mir, 'tcx>(
name: &[u8],
value: &[u8],
memory: &mut Memory<'mir, 'tcx, Evaluator<'tcx>>,
ecx: &mut InterpCx<'mir, 'tcx, Evaluator<'tcx>>
) -> Pointer<Tag> {
let mut bytes = name.to_vec();
bytes.push(b'=');
bytes.extend_from_slice(value);
bytes.push(0);
memory.allocate_static_bytes(bytes.as_slice(), MiriMemoryKind::Env.into())
ecx.alloc_os_str_as_c_str(bytes_to_os_str(bytes.as_slice()).unwrap())
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
}

impl<'mir, 'tcx> EvalContextExt<'mir, 'tcx> for crate::MiriEvalContext<'mir, 'tcx> {}
Expand All @@ -53,7 +54,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let this = self.eval_context_mut();

let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let name = this.memory.read_c_str(name_ptr)?;
let name = this.read_os_str_from_c_str(name_ptr)?;
Ok(match this.machine.env_vars.map.get(name) {
// The offset is used to strip the "{name}=" part of the string.
Some(var_ptr) => {
Expand All @@ -68,20 +69,20 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
name_op: OpTy<'tcx, Tag>,
value_op: OpTy<'tcx, Tag>,
) -> InterpResult<'tcx, i32> {
let this = self.eval_context_mut();
let mut this = self.eval_context_mut();

let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let value_ptr = this.read_scalar(value_op)?.not_undef()?;
let value = this.memory.read_c_str(value_ptr)?;
let value = this.read_os_str_from_c_str(value_ptr)?;
let mut new = None;
if !this.is_null(name_ptr)? {
let name = this.memory.read_c_str(name_ptr)?;
if !name.is_empty() && !name.contains(&b'=') {
let name = this.read_os_str_from_c_str(name_ptr)?;
if !name.is_empty() && !os_str_to_bytes(&name).unwrap().contains(&b'=') {
JOE1994 marked this conversation as resolved.
Show resolved Hide resolved
new = Some((name.to_owned(), value.to_owned()));
}
}
if let Some((name, value)) = new {
let var_ptr = alloc_env_var(&name, &value, &mut this.memory);
let var_ptr = alloc_env_var_as_c_str(os_str_to_bytes(&name).unwrap(), os_str_to_bytes(&value).unwrap(), &mut this);
if let Some(var) = this.machine.env_vars.map.insert(name.to_owned(), var_ptr) {
this.memory
.deallocate(var, None, MiriMemoryKind::Env.into())?;
Expand All @@ -98,8 +99,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
let name_ptr = this.read_scalar(name_op)?.not_undef()?;
let mut success = None;
if !this.is_null(name_ptr)? {
let name = this.memory.read_c_str(name_ptr)?.to_owned();
if !name.is_empty() && !name.contains(&b'=') {
let name = this.read_os_str_from_c_str(name_ptr)?.to_owned();
if !name.is_empty() && !os_str_to_bytes(&name).unwrap().contains(&b'=') {
success = Some(this.machine.env_vars.map.remove(&name));
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,10 +912,22 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
this.write_null(dest)?;
}
"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)?;
}
"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.

}
RalfJung marked this conversation as resolved.
Show resolved Hide resolved
"GetCommandLineW" => {
this.write_scalar(this.machine.cmd_line.expect("machine must be initialized"), dest)?;
}
Expand Down