Skip to main content

miri/shims/windows/
handle.rs

1use std::mem::variant_count;
2
3use rustc_abi::HasDataLayout;
4
5use crate::shims::files::FdNum;
6use crate::*;
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum PseudoHandle {
10    CurrentThread,
11    CurrentProcess,
12}
13
14/// Miri representation of a Windows `HANDLE`
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
16pub enum Handle {
17    Null,
18    Pseudo(PseudoHandle),
19    Thread(ThreadId),
20    File(FdNum),
21    Invalid,
22}
23
24impl PseudoHandle {
25    const CURRENT_THREAD_VALUE: u32 = 0;
26    const CURRENT_PROCESS_VALUE: u32 = 1;
27
28    fn value(self) -> u32 {
29        match self {
30            Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
31            Self::CurrentProcess => Self::CURRENT_PROCESS_VALUE,
32        }
33    }
34
35    fn from_value(value: u32) -> Option<Self> {
36        match value {
37            Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
38            Self::CURRENT_PROCESS_VALUE => Some(Self::CurrentProcess),
39            _ => None,
40        }
41    }
42}
43
44/// Errors that can occur when constructing a [`Handle`] from a Scalar.
45pub enum HandleError {
46    /// There is no thread with the given ID.
47    ThreadNotFound,
48    /// Can't convert scalar to handle because it is structurally invalid.
49    InvalidHandle,
50}
51
52impl Handle {
53    const NULL_DISCRIMINANT: u32 = 0;
54    const PSEUDO_DISCRIMINANT: u32 = 1;
55    const THREAD_DISCRIMINANT: u32 = 2;
56    const FILE_DISCRIMINANT: u32 = 3;
57    // Chosen to ensure Handle::Invalid encodes to -1. Update this value if there are ever more than
58    // 8 discriminants.
59    const INVALID_DISCRIMINANT: u32 = 7;
60
61    fn discriminant(self) -> u32 {
62        match self {
63            Self::Null => Self::NULL_DISCRIMINANT,
64            Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
65            Self::Thread(_) => Self::THREAD_DISCRIMINANT,
66            Self::File(_) => Self::FILE_DISCRIMINANT,
67            Self::Invalid => Self::INVALID_DISCRIMINANT,
68        }
69    }
70
71    fn data(self) -> u32 {
72        match self {
73            Self::Null => 0,
74            Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
75            Self::Thread(thread) => thread.to_u32(),
76            Self::File(fd) => fd.cast_unsigned(),
77            // INVALID_HANDLE_VALUE is -1. This fact is explicitly declared or implied in several
78            // pages of Windows documentation.
79            // 1: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle?view=net-9.0
80            // 2: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170
81            Self::Invalid => 0x1FFFFFFF,
82        }
83    }
84
85    fn packed_disc_size() -> u32 {
86        // ceil(log2(x)) is how many bits it takes to store x numbers.
87        // We ensure that INVALID_HANDLE_VALUE (0xFFFFFFFF) decodes to Handle::Invalid.
88        // see https://devblogs.microsoft.com/oldnewthing/20230914-00/?p=108766 for more detail on
89        // INVALID_HANDLE_VALUE.
90        let variant_count = variant_count::<Self>();
91
92        // However, std's ilog2 is floor(log2(x)).
93        let floor_log2 = variant_count.ilog2();
94
95        // We need to add one for non powers of two to compensate for the difference.
96        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
97        if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
98    }
99
100    /// Converts a handle into its machine representation.
101    ///
102    /// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
103    /// The remaining bits are used for the variant's field.
104    ///
105    /// None of this layout is guaranteed to applications by Windows or Miri.
106    fn to_packed(self) -> u32 {
107        let disc_size = Self::packed_disc_size();
108        let data_size = u32::BITS.strict_sub(disc_size);
109
110        let discriminant = self.discriminant();
111        let data = self.data();
112
113        // make sure the discriminant fits into `disc_size` bits
114        assert!(discriminant < 2u32.pow(disc_size));
115
116        // make sure the data fits into `data_size` bits
117        assert!(data < 2u32.pow(data_size));
118
119        // packs the data into the lower `data_size` bits
120        // and packs the discriminant right above the data
121        (discriminant << data_size) | data
122    }
123
124    fn new(discriminant: u32, data: u32) -> Option<Self> {
125        match discriminant {
126            Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
127            Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
128            Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
129            Self::FILE_DISCRIMINANT => {
130                // This cast preserves all bits.
131                assert_eq!(size_of_val(&data), size_of::<FdNum>());
132                Some(Self::File(data.cast_signed()))
133            }
134            Self::INVALID_DISCRIMINANT => Some(Self::Invalid),
135            _ => None,
136        }
137    }
138
139    /// see docs for `to_packed`
140    fn from_packed(handle: u32) -> Option<Self> {
141        let disc_size = Self::packed_disc_size();
142        let data_size = u32::BITS.strict_sub(disc_size);
143
144        // the lower `data_size` bits of this mask are 1
145        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
146        let data_mask = 2u32.pow(data_size) - 1;
147
148        // the discriminant is stored right above the lower `data_size` bits
149        let discriminant = handle >> data_size;
150
151        // the data is stored in the lower `data_size` bits
152        let data = handle & data_mask;
153
154        Self::new(discriminant, data)
155    }
156
157    pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar {
158        // 64-bit handles are sign extended 32-bit handles
159        // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
160        let signed_handle = self.to_packed().cast_signed();
161        Scalar::from_target_isize(signed_handle.into(), cx)
162    }
163
164    /// Convert a scalar into a structured `Handle`.
165    /// Structurally invalid handles return [`HandleError::InvalidHandle`].
166    /// If the handle is structurally valid but semantically invalid, e.g. a for non-existent thread
167    /// ID, returns [`HandleError::ThreadNotFound`].
168    ///
169    /// This function is deliberately private; shims should always use `read_handle`.
170    /// That enforces handle validity even when Windows does not: for now, we argue invalid
171    /// handles are always a bug and programmers likely want to know about them.
172    fn try_from_scalar<'tcx>(
173        handle: Scalar,
174        cx: &MiriInterpCx<'tcx>,
175    ) -> InterpResult<'tcx, Result<Self, HandleError>> {
176        let sign_extended_handle = handle.to_target_isize(cx)?;
177
178        let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
179            signed_handle.cast_unsigned()
180        } else {
181            // if a handle doesn't fit in an i32, it isn't valid.
182            return interp_ok(Err(HandleError::InvalidHandle));
183        };
184
185        match Self::from_packed(handle) {
186            Some(Self::Thread(thread)) => {
187                // Validate the thread id. Windows handles remain valid even after thread
188                // termination.
189                use crate::concurrency::thread::ThreadLookupError;
190                match cx.machine.threads.thread_id_try_from(thread.to_u32()) {
191                    Ok(id) | Err(ThreadLookupError::Terminated(id)) =>
192                        interp_ok(Ok(Self::Thread(id))),
193                    Err(ThreadLookupError::InvalidId) =>
194                        interp_ok(Err(HandleError::ThreadNotFound)),
195                }
196            }
197            Some(handle) => interp_ok(Ok(handle)),
198            None => interp_ok(Err(HandleError::InvalidHandle)),
199        }
200    }
201}
202
203impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
204
205#[allow(non_snake_case)]
206pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
207    /// Convert a scalar into a structured `Handle`.
208    /// If the handle is invalid, or references a non-existent item, execution is aborted.
209    #[track_caller]
210    fn read_handle(&self, handle: &OpTy<'tcx>, function_name: &str) -> InterpResult<'tcx, Handle> {
211        let this = self.eval_context_ref();
212        let handle = this.read_scalar(handle)?;
213        match Handle::try_from_scalar(handle, this)? {
214            Ok(handle) => interp_ok(handle),
215            Err(HandleError::InvalidHandle) =>
216                throw_machine_stop!(TerminationInfo::Abort(format!(
217                    "invalid handle {} passed to {function_name}",
218                    handle.to_target_isize(this)?,
219                ))),
220            Err(HandleError::ThreadNotFound) =>
221                throw_machine_stop!(TerminationInfo::Abort(format!(
222                    "invalid thread ID {} passed to {function_name}",
223                    handle.to_target_isize(this)?,
224                ))),
225        }
226    }
227
228    fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
229        throw_machine_stop!(TerminationInfo::Abort(format!(
230            "invalid handle passed to `{function_name}`"
231        )))
232    }
233
234    fn GetStdHandle(&mut self, which: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
235        let this = self.eval_context_mut();
236        let which = this.read_scalar(which)?.to_i32()?;
237
238        let stdin = this.eval_windows("c", "STD_INPUT_HANDLE").to_i32()?;
239        let stdout = this.eval_windows("c", "STD_OUTPUT_HANDLE").to_i32()?;
240        let stderr = this.eval_windows("c", "STD_ERROR_HANDLE").to_i32()?;
241
242        // These values don't mean anything on Windows, but Miri unconditionally sets them up to the
243        // unix in/out/err descriptors. So we take advantage of that.
244        // Due to the `Handle` encoding, these values will not be directly exposed to the user.
245        let fd_num = if which == stdin {
246            0
247        } else if which == stdout {
248            1
249        } else if which == stderr {
250            2
251        } else {
252            throw_unsup_format!("Invalid argument to `GetStdHandle`: {which}")
253        };
254        let handle = Handle::File(fd_num);
255        interp_ok(handle.to_scalar(this))
256    }
257
258    fn DuplicateHandle(
259        &mut self,
260        src_proc: &OpTy<'tcx>,       // HANDLE
261        src_handle: &OpTy<'tcx>,     // HANDLE
262        target_proc: &OpTy<'tcx>,    // HANDLE
263        target_handle: &OpTy<'tcx>,  // LPHANDLE
264        desired_access: &OpTy<'tcx>, // DWORD
265        inherit: &OpTy<'tcx>,        // BOOL
266        options: &OpTy<'tcx>,        // DWORD
267    ) -> InterpResult<'tcx, Scalar> {
268        // ^ Returns BOOL (i32 on Windows)
269        let this = self.eval_context_mut();
270
271        let src_proc = this.read_handle(src_proc, "DuplicateHandle")?;
272        let src_handle = this.read_handle(src_handle, "DuplicateHandle")?;
273        let target_proc = this.read_handle(target_proc, "DuplicateHandle")?;
274        let target_handle_ptr = this.read_pointer(target_handle)?;
275        // Since we only support DUPLICATE_SAME_ACCESS, this value is ignored, but should be valid
276        let _ = this.read_scalar(desired_access)?.to_u32()?;
277        // We don't support the CreateProcess API, so inheritable or not means nothing.
278        // If we ever add CreateProcess support, this will need to be implemented.
279        let _ = this.read_scalar(inherit)?;
280        let options = this.read_scalar(options)?;
281
282        if src_proc != Handle::Pseudo(PseudoHandle::CurrentProcess) {
283            throw_unsup_format!(
284                "`DuplicateHandle` `hSourceProcessHandle` parameter is not the current process, which is unsupported"
285            );
286        }
287
288        if target_proc != Handle::Pseudo(PseudoHandle::CurrentProcess) {
289            throw_unsup_format!(
290                "`DuplicateHandle` `hSourceProcessHandle` parameter is not the current process, which is unsupported"
291            );
292        }
293
294        if this.ptr_is_null(target_handle_ptr)? {
295            throw_machine_stop!(TerminationInfo::Abort(
296                "`DuplicateHandle` `lpTargetHandle` parameter must not be null, as otherwise the \
297                newly created handle is leaked"
298                    .to_string()
299            ));
300        }
301
302        if options != this.eval_windows("c", "DUPLICATE_SAME_ACCESS") {
303            throw_unsup_format!(
304                "`DuplicateHandle` `dwOptions` parameter is not `DUPLICATE_SAME_ACCESS`, which is unsupported"
305            );
306        }
307
308        let new_handle = match src_handle {
309            Handle::File(old_fd_num) => {
310                let Some(fd) = this.machine.fds.get(old_fd_num) else {
311                    this.invalid_handle("DuplicateHandle")?
312                };
313                Handle::File(this.machine.fds.insert(fd))
314            }
315            Handle::Thread(_) => {
316                throw_unsup_format!(
317                    "`DuplicateHandle` called on a thread handle, which is unsupported"
318                );
319            }
320            Handle::Pseudo(_) => {
321                throw_unsup_format!(
322                    "`DuplicateHandle` called on a pseudo handle, which is unsupported"
323                );
324            }
325            Handle::Null | Handle::Invalid => this.invalid_handle("DuplicateHandle")?,
326        };
327
328        let target_place = this.deref_pointer_as(target_handle, this.machine.layouts.usize)?;
329        this.write_scalar(new_handle.to_scalar(this), &target_place)?;
330
331        interp_ok(this.eval_windows("c", "TRUE"))
332    }
333
334    fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
335        let this = self.eval_context_mut();
336
337        let handle = this.read_handle(handle_op, "CloseHandle")?;
338        let ret = match handle {
339            Handle::Thread(thread) => {
340                this.detach_thread(thread, /*allow_terminated_joined*/ true)?;
341                this.eval_windows("c", "TRUE")
342            }
343            Handle::File(fd_num) =>
344                if let Some(fd) = this.machine.fds.remove(fd_num) {
345                    let err = fd.close_ref(this.machine.communicate(), this)?;
346                    if let Err(e) = err {
347                        this.set_last_error(e)?;
348                        this.eval_windows("c", "FALSE")
349                    } else {
350                        this.eval_windows("c", "TRUE")
351                    }
352                } else {
353                    this.invalid_handle("CloseHandle")?
354                },
355            _ => this.invalid_handle("CloseHandle")?,
356        };
357
358        interp_ok(ret)
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_invalid_encoding() {
368        // Ensure the invalid handle encodes to `u32::MAX`/`INVALID_HANDLE_VALUE`.
369        assert_eq!(Handle::Invalid.to_packed(), u32::MAX)
370    }
371}