miri/shims/windows/
env.rs

1use std::env;
2use std::ffi::{OsStr, OsString};
3use std::io::ErrorKind;
4
5use rustc_data_structures::fx::FxHashMap;
6
7use self::helpers::windows_check_buffer_size;
8use crate::*;
9
10#[derive(Default)]
11pub struct WindowsEnvVars {
12    /// Stores the environment variables.
13    map: FxHashMap<OsString, OsString>,
14}
15
16impl VisitProvenance for WindowsEnvVars {
17    fn visit_provenance(&self, _visit: &mut VisitWith<'_>) {
18        let WindowsEnvVars { map: _ } = self;
19    }
20}
21
22impl WindowsEnvVars {
23    pub(crate) fn new<'tcx>(
24        _ecx: &mut InterpCx<'tcx, MiriMachine<'tcx>>,
25        env_vars: FxHashMap<OsString, OsString>,
26    ) -> InterpResult<'tcx, Self> {
27        interp_ok(Self { map: env_vars })
28    }
29
30    /// Implementation detail for [`InterpCx::get_env_var`].
31    pub(crate) fn get<'tcx>(&self, name: &OsStr) -> InterpResult<'tcx, Option<OsString>> {
32        interp_ok(self.map.get(name).cloned())
33    }
34}
35
36impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
37pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
38    #[allow(non_snake_case)]
39    fn GetEnvironmentVariableW(
40        &mut self,
41        name_op: &OpTy<'tcx>, // LPCWSTR
42        buf_op: &OpTy<'tcx>,  // LPWSTR
43        size_op: &OpTy<'tcx>, // DWORD
44    ) -> InterpResult<'tcx, Scalar> {
45        // ^ Returns DWORD (u32 on Windows)
46
47        let this = self.eval_context_mut();
48        this.assert_target_os("windows", "GetEnvironmentVariableW");
49
50        let name_ptr = this.read_pointer(name_op)?;
51        let buf_ptr = this.read_pointer(buf_op)?;
52        let buf_size = this.read_scalar(size_op)?.to_u32()?; // in characters
53
54        let name = this.read_os_str_from_wide_str(name_ptr)?;
55        interp_ok(match this.machine.env_vars.windows().map.get(&name).cloned() {
56            Some(val) => {
57                Scalar::from_u32(windows_check_buffer_size(this.write_os_str_to_wide_str(
58                    &val,
59                    buf_ptr,
60                    buf_size.into(),
61                )?))
62                // This can in fact return 0. It is up to the caller to set last_error to 0
63                // beforehand and check it afterwards to exclude that case.
64            }
65            None => {
66                let envvar_not_found = this.eval_windows("c", "ERROR_ENVVAR_NOT_FOUND");
67                this.set_last_error(envvar_not_found)?;
68                Scalar::from_u32(0) // return zero upon failure
69            }
70        })
71    }
72
73    #[allow(non_snake_case)]
74    fn GetEnvironmentStringsW(&mut self) -> InterpResult<'tcx, Pointer> {
75        let this = self.eval_context_mut();
76        this.assert_target_os("windows", "GetEnvironmentStringsW");
77
78        // Info on layout of environment blocks in Windows:
79        // https://docs.microsoft.com/en-us/windows/win32/procthread/environment-variables
80        let mut env_vars = std::ffi::OsString::new();
81        for (name, value) in this.machine.env_vars.windows().map.iter() {
82            env_vars.push(name);
83            env_vars.push("=");
84            env_vars.push(value);
85            env_vars.push("\0");
86        }
87        // Allocate environment block & Store environment variables to environment block.
88        // Final null terminator(block terminator) is added by `alloc_os_str_to_wide_str`.
89        let envblock_ptr =
90            this.alloc_os_str_as_wide_str(&env_vars, MiriMemoryKind::Runtime.into())?;
91        // If the function succeeds, the return value is a pointer to the environment block of the current process.
92        interp_ok(envblock_ptr)
93    }
94
95    #[allow(non_snake_case)]
96    fn FreeEnvironmentStringsW(&mut self, env_block_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
97        let this = self.eval_context_mut();
98        this.assert_target_os("windows", "FreeEnvironmentStringsW");
99
100        let env_block_ptr = this.read_pointer(env_block_op)?;
101        this.deallocate_ptr(env_block_ptr, None, MiriMemoryKind::Runtime.into())?;
102        // If the function succeeds, the return value is nonzero.
103        interp_ok(Scalar::from_i32(1))
104    }
105
106    #[allow(non_snake_case)]
107    fn SetEnvironmentVariableW(
108        &mut self,
109        name_op: &OpTy<'tcx>,  // LPCWSTR
110        value_op: &OpTy<'tcx>, // LPCWSTR
111    ) -> InterpResult<'tcx, Scalar> {
112        let this = self.eval_context_mut();
113        this.assert_target_os("windows", "SetEnvironmentVariableW");
114
115        let name_ptr = this.read_pointer(name_op)?;
116        let value_ptr = this.read_pointer(value_op)?;
117
118        if this.ptr_is_null(name_ptr)? {
119            // ERROR CODE is not clearly explained in docs.. For now, throw UB instead.
120            throw_ub_format!("pointer to environment variable name is NULL");
121        }
122
123        let name = this.read_os_str_from_wide_str(name_ptr)?;
124        if name.is_empty() {
125            throw_unsup_format!("environment variable name is an empty string");
126        } else if name.to_string_lossy().contains('=') {
127            throw_unsup_format!("environment variable name contains '='");
128        } else if this.ptr_is_null(value_ptr)? {
129            // Delete environment variable `{name}` if it exists.
130            this.machine.env_vars.windows_mut().map.remove(&name);
131            interp_ok(this.eval_windows("c", "TRUE"))
132        } else {
133            let value = this.read_os_str_from_wide_str(value_ptr)?;
134            this.machine.env_vars.windows_mut().map.insert(name, value);
135            interp_ok(this.eval_windows("c", "TRUE"))
136        }
137    }
138
139    #[allow(non_snake_case)]
140    fn GetCurrentDirectoryW(
141        &mut self,
142        size_op: &OpTy<'tcx>, // DWORD
143        buf_op: &OpTy<'tcx>,  // LPTSTR
144    ) -> InterpResult<'tcx, Scalar> {
145        let this = self.eval_context_mut();
146        this.assert_target_os("windows", "GetCurrentDirectoryW");
147
148        let size = u64::from(this.read_scalar(size_op)?.to_u32()?);
149        let buf = this.read_pointer(buf_op)?;
150
151        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
152            this.reject_in_isolation("`GetCurrentDirectoryW`", reject_with)?;
153            this.set_last_error(ErrorKind::PermissionDenied)?;
154            return interp_ok(Scalar::from_u32(0));
155        }
156
157        // If we cannot get the current directory, we return 0
158        match env::current_dir() {
159            Ok(cwd) => {
160                // This can in fact return 0. It is up to the caller to set last_error to 0
161                // beforehand and check it afterwards to exclude that case.
162                return interp_ok(Scalar::from_u32(windows_check_buffer_size(
163                    this.write_path_to_wide_str(&cwd, buf, size)?,
164                )));
165            }
166            Err(e) => this.set_last_error(e)?,
167        }
168        interp_ok(Scalar::from_u32(0))
169    }
170
171    #[allow(non_snake_case)]
172    fn SetCurrentDirectoryW(
173        &mut self,
174        path_op: &OpTy<'tcx>, // LPCTSTR
175    ) -> InterpResult<'tcx, Scalar> {
176        // ^ Returns BOOL (i32 on Windows)
177
178        let this = self.eval_context_mut();
179        this.assert_target_os("windows", "SetCurrentDirectoryW");
180
181        let path = this.read_path_from_wide_str(this.read_pointer(path_op)?)?;
182
183        if let IsolatedOp::Reject(reject_with) = this.machine.isolated_op {
184            this.reject_in_isolation("`SetCurrentDirectoryW`", reject_with)?;
185            this.set_last_error(ErrorKind::PermissionDenied)?;
186
187            return interp_ok(this.eval_windows("c", "FALSE"));
188        }
189
190        match env::set_current_dir(path) {
191            Ok(()) => interp_ok(this.eval_windows("c", "TRUE")),
192            Err(e) => {
193                this.set_last_error(e)?;
194                interp_ok(this.eval_windows("c", "FALSE"))
195            }
196        }
197    }
198
199    #[allow(non_snake_case)]
200    fn GetCurrentProcessId(&mut self) -> InterpResult<'tcx, Scalar> {
201        let this = self.eval_context_mut();
202        this.assert_target_os("windows", "GetCurrentProcessId");
203
204        interp_ok(Scalar::from_u32(this.get_pid()))
205    }
206
207    #[allow(non_snake_case)]
208    fn GetUserProfileDirectoryW(
209        &mut self,
210        token: &OpTy<'tcx>, // HANDLE
211        buf: &OpTy<'tcx>,   // LPWSTR
212        size: &OpTy<'tcx>,  // LPDWORD
213    ) -> InterpResult<'tcx, Scalar> // returns BOOL
214    {
215        let this = self.eval_context_mut();
216        this.assert_target_os("windows", "GetUserProfileDirectoryW");
217        this.check_no_isolation("`GetUserProfileDirectoryW`")?;
218
219        let token = this.read_target_isize(token)?;
220        let buf = this.read_pointer(buf)?;
221        let size = this.deref_pointer_as(size, this.machine.layouts.u32)?;
222
223        if token != -4 {
224            throw_unsup_format!(
225                "GetUserProfileDirectoryW: only CURRENT_PROCESS_TOKEN is supported"
226            );
227        }
228
229        // See <https://learn.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-getuserprofiledirectoryw> for docs.
230        interp_ok(match directories::UserDirs::new() {
231            Some(dirs) => {
232                let home = dirs.home_dir();
233                let size_avail = if this.ptr_is_null(size.ptr())? {
234                    0 // if the buf pointer is null, we can't write to it; `size` will be updated to the required length
235                } else {
236                    this.read_scalar(&size)?.to_u32()?
237                };
238                // Of course we cannot use `windows_check_buffer_size` here since this uses
239                // a different method for dealing with a too-small buffer than the other functions...
240                let (success, len) = this.write_path_to_wide_str(home, buf, size_avail.into())?;
241                // The Windows docs just say that this is written on failure. But std
242                // seems to rely on it always being written.
243                this.write_scalar(Scalar::from_u32(len.try_into().unwrap()), &size)?;
244                if success {
245                    Scalar::from_i32(1) // return TRUE
246                } else {
247                    this.set_last_error(this.eval_windows("c", "ERROR_INSUFFICIENT_BUFFER"))?;
248                    Scalar::from_i32(0) // return FALSE
249                }
250            }
251            None => {
252                // We have to pick some error code.
253                this.set_last_error(this.eval_windows("c", "ERROR_BAD_USER_PROFILE"))?;
254                Scalar::from_i32(0) // return FALSE
255            }
256        })
257    }
258}