miri/shims/windows/
handle.rs

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