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}
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
27    fn value(self) -> u32 {
28        match self {
29            Self::CurrentThread => Self::CURRENT_THREAD_VALUE,
30        }
31    }
32
33    fn from_value(value: u32) -> Option<Self> {
34        match value {
35            Self::CURRENT_THREAD_VALUE => Some(Self::CurrentThread),
36            _ => None,
37        }
38    }
39}
40
41/// Errors that can occur when constructing a [`Handle`] from a Scalar.
42pub enum HandleError {
43    /// There is no thread with the given ID.
44    ThreadNotFound(ThreadNotFound),
45    /// Can't convert scalar to handle because it is structurally invalid.
46    InvalidHandle,
47}
48
49impl Handle {
50    const NULL_DISCRIMINANT: u32 = 0;
51    const PSEUDO_DISCRIMINANT: u32 = 1;
52    const THREAD_DISCRIMINANT: u32 = 2;
53    const FILE_DISCRIMINANT: u32 = 3;
54    // Chosen to ensure Handle::Invalid encodes to -1. Update this value if there are ever more than
55    // 8 discriminants.
56    const INVALID_DISCRIMINANT: u32 = 7;
57
58    fn discriminant(self) -> u32 {
59        match self {
60            Self::Null => Self::NULL_DISCRIMINANT,
61            Self::Pseudo(_) => Self::PSEUDO_DISCRIMINANT,
62            Self::Thread(_) => Self::THREAD_DISCRIMINANT,
63            Self::File(_) => Self::FILE_DISCRIMINANT,
64            Self::Invalid => Self::INVALID_DISCRIMINANT,
65        }
66    }
67
68    fn data(self) -> u32 {
69        match self {
70            Self::Null => 0,
71            Self::Pseudo(pseudo_handle) => pseudo_handle.value(),
72            Self::Thread(thread) => thread.to_u32(),
73            #[expect(clippy::cast_sign_loss)]
74            Self::File(fd) => fd as u32,
75            // INVALID_HANDLE_VALUE is -1. This fact is explicitly declared or implied in several
76            // pages of Windows documentation.
77            // 1: https://learn.microsoft.com/en-us/dotnet/api/microsoft.win32.safehandles.safefilehandle?view=net-9.0
78            // 2: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=msvc-170
79            Self::Invalid => 0x1FFFFFFF,
80        }
81    }
82
83    fn packed_disc_size() -> u32 {
84        // ceil(log2(x)) is how many bits it takes to store x numbers.
85        // We ensure that INVALID_HANDLE_VALUE (0xFFFFFFFF) decodes to Handle::Invalid.
86        // see https://devblogs.microsoft.com/oldnewthing/20230914-00/?p=108766 for more detail on
87        // INVALID_HANDLE_VALUE.
88        let variant_count = variant_count::<Self>();
89
90        // However, std's ilog2 is floor(log2(x)).
91        let floor_log2 = variant_count.ilog2();
92
93        // We need to add one for non powers of two to compensate for the difference.
94        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
95        if variant_count.is_power_of_two() { floor_log2 } else { floor_log2 + 1 }
96    }
97
98    /// Converts a handle into its machine representation.
99    ///
100    /// The upper [`Self::packed_disc_size()`] bits are used to store a discriminant corresponding to the handle variant.
101    /// The remaining bits are used for the variant's field.
102    ///
103    /// None of this layout is guaranteed to applications by Windows or Miri.
104    fn to_packed(self) -> u32 {
105        let disc_size = Self::packed_disc_size();
106        let data_size = u32::BITS.strict_sub(disc_size);
107
108        let discriminant = self.discriminant();
109        let data = self.data();
110
111        // make sure the discriminant fits into `disc_size` bits
112        assert!(discriminant < 2u32.pow(disc_size));
113
114        // make sure the data fits into `data_size` bits
115        assert!(data < 2u32.pow(data_size));
116
117        // packs the data into the lower `data_size` bits
118        // and packs the discriminant right above the data
119        (discriminant << data_size) | data
120    }
121
122    fn new(discriminant: u32, data: u32) -> Option<Self> {
123        match discriminant {
124            Self::NULL_DISCRIMINANT if data == 0 => Some(Self::Null),
125            Self::PSEUDO_DISCRIMINANT => Some(Self::Pseudo(PseudoHandle::from_value(data)?)),
126            Self::THREAD_DISCRIMINANT => Some(Self::Thread(ThreadId::new_unchecked(data))),
127            #[expect(clippy::cast_possible_wrap)]
128            Self::FILE_DISCRIMINANT => {
129                // This cast preserves all bits.
130                assert_eq!(size_of_val(&data), size_of::<FdNum>());
131                Some(Self::File(data as FdNum))
132            }
133            Self::INVALID_DISCRIMINANT => Some(Self::Invalid),
134            _ => None,
135        }
136    }
137
138    /// see docs for `to_packed`
139    fn from_packed(handle: u32) -> Option<Self> {
140        let disc_size = Self::packed_disc_size();
141        let data_size = u32::BITS.strict_sub(disc_size);
142
143        // the lower `data_size` bits of this mask are 1
144        #[expect(clippy::arithmetic_side_effects)] // cannot overflow
145        let data_mask = 2u32.pow(data_size) - 1;
146
147        // the discriminant is stored right above the lower `data_size` bits
148        let discriminant = handle >> data_size;
149
150        // the data is stored in the lower `data_size` bits
151        let data = handle & data_mask;
152
153        Self::new(discriminant, data)
154    }
155
156    pub fn to_scalar(self, cx: &impl HasDataLayout) -> Scalar {
157        // 64-bit handles are sign extended 32-bit handles
158        // see https://docs.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication
159        #[expect(clippy::cast_possible_wrap)] // we want it to wrap
160        let signed_handle = self.to_packed() as i32;
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    fn try_from_scalar<'tcx>(
169        handle: Scalar,
170        cx: &MiriInterpCx<'tcx>,
171    ) -> InterpResult<'tcx, Result<Self, HandleError>> {
172        let sign_extended_handle = handle.to_target_isize(cx)?;
173
174        #[expect(clippy::cast_sign_loss)] // we want to lose the sign
175        let handle = if let Ok(signed_handle) = i32::try_from(sign_extended_handle) {
176            signed_handle as u32
177        } else {
178            // if a handle doesn't fit in an i32, it isn't valid.
179            return interp_ok(Err(HandleError::InvalidHandle));
180        };
181
182        match Self::from_packed(handle) {
183            Some(Self::Thread(thread)) => {
184                // validate the thread id
185                match cx.machine.threads.thread_id_try_from(thread.to_u32()) {
186                    Ok(id) => interp_ok(Ok(Self::Thread(id))),
187                    Err(e) => interp_ok(Err(HandleError::ThreadNotFound(e))),
188                }
189            }
190            Some(handle) => interp_ok(Ok(handle)),
191            None => interp_ok(Err(HandleError::InvalidHandle)),
192        }
193    }
194}
195
196impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
197
198#[allow(non_snake_case)]
199pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
200    /// Convert a scalar into a structured `Handle`.
201    /// If the handle is invalid, or references a non-existent item, execution is aborted.
202    #[track_caller]
203    fn read_handle(&self, handle: &OpTy<'tcx>, function_name: &str) -> InterpResult<'tcx, Handle> {
204        let this = self.eval_context_ref();
205        let handle = this.read_scalar(handle)?;
206        match Handle::try_from_scalar(handle, this)? {
207            Ok(handle) => interp_ok(handle),
208            Err(HandleError::InvalidHandle) =>
209                throw_machine_stop!(TerminationInfo::Abort(format!(
210                    "invalid handle {} passed to {function_name}",
211                    handle.to_target_isize(this)?,
212                ))),
213            Err(HandleError::ThreadNotFound(_)) =>
214                throw_machine_stop!(TerminationInfo::Abort(format!(
215                    "invalid thread ID {} passed to {function_name}",
216                    handle.to_target_isize(this)?,
217                ))),
218        }
219    }
220
221    fn invalid_handle(&mut self, function_name: &str) -> InterpResult<'tcx, !> {
222        throw_machine_stop!(TerminationInfo::Abort(format!(
223            "invalid handle passed to `{function_name}`"
224        )))
225    }
226
227    fn CloseHandle(&mut self, handle_op: &OpTy<'tcx>) -> InterpResult<'tcx, Scalar> {
228        let this = self.eval_context_mut();
229
230        let handle = this.read_handle(handle_op, "CloseHandle")?;
231        let ret = match handle {
232            Handle::Thread(thread) => {
233                this.detach_thread(thread, /*allow_terminated_joined*/ true)?;
234                this.eval_windows("c", "TRUE")
235            }
236            Handle::File(fd_num) =>
237                if let Some(fd) = this.machine.fds.remove(fd_num) {
238                    let err = fd.close_ref(this.machine.communicate(), this)?;
239                    if let Err(e) = err {
240                        this.set_last_error(e)?;
241                        this.eval_windows("c", "FALSE")
242                    } else {
243                        this.eval_windows("c", "TRUE")
244                    }
245                } else {
246                    this.invalid_handle("CloseHandle")?
247                },
248            _ => this.invalid_handle("CloseHandle")?,
249        };
250
251        interp_ok(ret)
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    #[test]
260    fn test_invalid_encoding() {
261        // Ensure the invalid handle encodes to `u32::MAX`/`INVALID_HANDLE_VALUE`.
262        assert_eq!(Handle::Invalid.to_packed(), u32::MAX)
263    }
264}