Skip to main content

windows_namespace_request_sys/security/
sddl.rs

1// Copyright (c) Mike Grier.
2
3//! SDDL text to and from a [`SecurityDescriptor`].
4//!
5//! Both directions are public, so a caller can build a descriptor from the text form and read one
6//! back as text, and neither needs a raw pointer. The conversion is the documented
7//! `ConvertStringSecurityDescriptorToSecurityDescriptorW` and its reverse,
8//! `ConvertSecurityDescriptorToStringSecurityDescriptorW`; what they return is memory Windows
9//! allocated with `LocalAlloc`, which is copied into this crate's own owned type or string and then
10//! freed with `LocalFree`, so nothing the caller holds points at it.
11//!
12//! The text form is not canonical: two strings can describe the same descriptor, and a descriptor
13//! read back as text is Windows' rendering of it, not necessarily the string it was built from.
14
15use std::ops::BitOr;
16use std::ptr;
17use std::slice;
18
19use win_error_codes::Win32Error;
20use windows_sys::Win32::Foundation::{ERROR_INVALID_PARAMETER, FALSE, LocalFree};
21use windows_sys::Win32::Security::Authorization::{
22    ConvertSecurityDescriptorToStringSecurityDescriptorW,
23    ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1,
24};
25use windows_sys::Win32::Security::{
26    DACL_SECURITY_INFORMATION, GROUP_SECURITY_INFORMATION, LABEL_SECURITY_INFORMATION,
27    OBJECT_SECURITY_INFORMATION, OWNER_SECURITY_INFORMATION, PSECURITY_DESCRIPTOR,
28    SACL_SECURITY_INFORMATION,
29};
30use wtf_string::Wtf16String;
31
32use super::{SecurityCaptureError, SecurityCaptureFailure, SecurityDescriptor};
33
34#[cfg(test)]
35mod tests;
36
37/// Which parts of a descriptor to write as text.
38///
39/// A newtype over `OBJECT_SECURITY_INFORMATION`, because the value is a combination and Windows
40/// defines more bits than are named here. Nothing is defaulted on a caller's behalf: a mask that
41/// selects nothing is Windows' to answer.
42///
43/// # Example
44///
45/// ```
46/// use windows_namespace_request_sys::SecurityInformation;
47///
48/// let both = SecurityInformation::OWNER | SecurityInformation::DACL;
49/// assert!(both.contains(SecurityInformation::OWNER));
50/// assert!(both.contains(SecurityInformation::DACL));
51/// assert!(!both.contains(SecurityInformation::SACL));
52/// ```
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
54pub struct SecurityInformation(OBJECT_SECURITY_INFORMATION);
55
56impl SecurityInformation {
57    /// No part: what Windows makes of an empty selection is its to say.
58    pub const NONE: Self = Self(0);
59
60    /// `OWNER_SECURITY_INFORMATION`: the owner SID.
61    pub const OWNER: Self = Self(OWNER_SECURITY_INFORMATION);
62
63    /// `GROUP_SECURITY_INFORMATION`: the primary group SID.
64    pub const GROUP: Self = Self(GROUP_SECURITY_INFORMATION);
65
66    /// `DACL_SECURITY_INFORMATION`: the discretionary access-control list.
67    pub const DACL: Self = Self(DACL_SECURITY_INFORMATION);
68
69    /// `SACL_SECURITY_INFORMATION`: the system access-control list.
70    pub const SACL: Self = Self(SACL_SECURITY_INFORMATION);
71
72    /// `LABEL_SECURITY_INFORMATION`: the mandatory integrity label.
73    pub const LABEL: Self = Self(LABEL_SECURITY_INFORMATION);
74
75    /// A selection from its raw bits, for a part Windows defines that this crate has no name for.
76    #[must_use]
77    pub const fn from_bits(bits: OBJECT_SECURITY_INFORMATION) -> Self {
78        Self(bits)
79    }
80
81    /// The raw bits.
82    #[must_use]
83    pub const fn bits(self) -> OBJECT_SECURITY_INFORMATION {
84        self.0
85    }
86
87    /// Whether every bit of `other` is set. Everything contains [`NONE`](Self::NONE).
88    #[must_use]
89    pub const fn contains(self, other: Self) -> bool {
90        self.0 & other.0 == other.0
91    }
92}
93
94impl BitOr for SecurityInformation {
95    type Output = Self;
96
97    fn bitor(self, other: Self) -> Self {
98        Self(self.0 | other.0)
99    }
100}
101
102/// Memory Windows allocated with `LocalAlloc` and handed to this crate, freed when it goes out of
103/// scope, so no return path can leak it.
104struct LocalAllocation(*mut core::ffi::c_void);
105
106impl Drop for LocalAllocation {
107    fn drop(&mut self) {
108        if !self.0.is_null() {
109            // SAFETY: the pointer is one Windows allocated with `LocalAlloc` for this crate to
110            // free, and it is freed exactly once, here.
111            unsafe { LocalFree(self.0) };
112        }
113    }
114}
115
116impl SecurityDescriptor {
117    /// A descriptor from its SDDL text, such as `D:P(A;;FA;;;WD)`.
118    ///
119    /// The result is the self-relative form this type always holds, copied out of the memory
120    /// Windows allocated for it, which is then freed.
121    ///
122    /// The string is not interpreted here: Windows parses it, accepts what it defines and refuses
123    /// the rest, and a string with no parts, such as an empty one, is its to answer. A string with
124    /// an interior NUL is refused before the call, because Windows would read only up to it and
125    /// build a different descriptor than the string says.
126    ///
127    /// # Example
128    ///
129    /// A DACL granting everyone full access, read back through the descriptor:
130    ///
131    /// ```
132    /// use windows_namespace_request_sys::{AclState, SecurityDescriptor};
133    /// use wtf_string::Wtf16String;
134    ///
135    /// let descriptor = SecurityDescriptor::from_sddl(&Wtf16String::from("D:P(A;;GA;;;WD)"))?;
136    /// assert_eq!(descriptor.dacl()?, AclState::Populated(1));
137    /// # Ok::<(), Box<dyn std::error::Error>>(())
138    /// ```
139    ///
140    /// # Errors
141    ///
142    /// [`SecurityCaptureFailure::InvalidSddl`] with Windows' own code, unaltered, when it refuses
143    /// the string; [`SecurityCaptureFailure::InteriorNul`] for a string with an interior NUL; or
144    /// the failures of capturing the descriptor Windows returned, including
145    /// [`SecurityCaptureFailure::Allocate`] when the memory for the copy cannot be allocated.
146    pub fn from_sddl(sddl: &Wtf16String) -> Result<Self, SecurityCaptureError> {
147        if sddl.has_interior_nul() {
148            return Err(SecurityCaptureError::new(
149                SecurityCaptureFailure::InteriorNul,
150                Win32Error::from_code(ERROR_INVALID_PARAMETER).to_io_error(),
151            ));
152        }
153
154        let mut raw: PSECURITY_DESCRIPTOR = ptr::null_mut();
155        // SAFETY: the string is terminated and has no interior NUL, and is borrowed for the call;
156        // `raw` is a writable pointer for the descriptor, and a null size is accepted.
157        let converted = unsafe {
158            ConvertStringSecurityDescriptorToSecurityDescriptorW(
159                sddl.as_terminated_ptr(),
160                SDDL_REVISION_1,
161                &raw mut raw,
162                ptr::null_mut(),
163            )
164        };
165        if converted == FALSE {
166            return Err(SecurityCaptureError::last_os(
167                SecurityCaptureFailure::InvalidSddl,
168            ));
169        }
170
171        let allocation = LocalAllocation(raw);
172        // SAFETY: Windows returned a valid self-relative descriptor in memory that stays allocated
173        // until `allocation` is dropped, after the capture has copied out of it.
174        let captured = unsafe { Self::capture(allocation.0) };
175        drop(allocation);
176        captured
177    }
178
179    /// The descriptor's parts selected by `information`, as SDDL text.
180    ///
181    /// Windows' rendering of the descriptor, which is not necessarily the string it was built
182    /// from: the text form is not canonical.
183    ///
184    /// # Example
185    ///
186    /// ```
187    /// use windows_namespace_request_sys::{SecurityDescriptor, SecurityInformation};
188    /// use wtf_string::Wtf16String;
189    ///
190    /// let descriptor = SecurityDescriptor::from_sddl(&Wtf16String::from("D:P(A;;GA;;;WD)"))?;
191    /// let text = descriptor.to_sddl(SecurityInformation::DACL)?;
192    /// assert_eq!(text.to_string_lossy(), "D:P(A;;GA;;;WD)");
193    /// # Ok::<(), Box<dyn std::error::Error>>(())
194    /// ```
195    ///
196    /// The length Windows reports for the text is not used. It is documented to count the
197    /// terminator, and was measured to be the string's length plus one, plus two, and plus none,
198    /// depending on the string; the text is read up to the terminator the documentation promises.
199    ///
200    /// # Errors
201    ///
202    /// [`SecurityCaptureFailure::ToSddl`] with Windows' own code, unaltered, when it refuses.
203    pub fn to_sddl(
204        &self,
205        information: SecurityInformation,
206    ) -> Result<Wtf16String, SecurityCaptureError> {
207        let mut text: *mut u16 = ptr::null_mut();
208        // SAFETY: the descriptor validated at capture and is borrowed for the call; the text
209        // pointer is writable, and a null length is accepted.
210        let converted = unsafe {
211            ConvertSecurityDescriptorToStringSecurityDescriptorW(
212                self.as_ptr().cast_mut(),
213                SDDL_REVISION_1,
214                information.bits(),
215                &raw mut text,
216                ptr::null_mut(),
217            )
218        };
219        if converted == FALSE {
220            return Err(SecurityCaptureError::last_os(
221                SecurityCaptureFailure::ToSddl,
222            ));
223        }
224
225        let allocation = LocalAllocation(text.cast());
226        let mut count = 0_usize;
227        // SAFETY: Windows returned a NUL-terminated string, which stays allocated until
228        // `allocation` is dropped, after this has been copied; the read stops at the terminator.
229        while unsafe { *text.add(count) } != 0 {
230            count += 1;
231        }
232        // SAFETY: the `count` code units before the terminator were just read.
233        let units = unsafe { slice::from_raw_parts(text, count) };
234        let result = Wtf16String::from_units(units);
235        drop(allocation);
236        Ok(result)
237    }
238}