miri/shims/windows/
env.rs

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