summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/detect_win32k_conflicts/src/registry.rs
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre/detect_win32k_conflicts/src/registry.rs')
-rw-r--r--toolkit/xre/detect_win32k_conflicts/src/registry.rs360
1 files changed, 360 insertions, 0 deletions
diff --git a/toolkit/xre/detect_win32k_conflicts/src/registry.rs b/toolkit/xre/detect_win32k_conflicts/src/registry.rs
new file mode 100644
index 0000000000..f375a64284
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/src/registry.rs
@@ -0,0 +1,360 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module contains helpers for exploring the Windows Registry
+
+use super::error::DetectConflictError;
+
+use std::ffi::{OsStr, OsString};
+use std::os::windows::ffi::{OsStrExt, OsStringExt};
+
+use winapi::{
+ shared::{
+ minwindef::HKEY,
+ winerror::{ERROR_FILE_NOT_FOUND, ERROR_NO_MORE_ITEMS, ERROR_SUCCESS},
+ },
+ um::{
+ winnt::{KEY_READ, REG_BINARY, REG_DWORD, REG_SZ},
+ winreg::{
+ RegCloseKey, RegEnumKeyExW, RegGetValueW, RegOpenKeyExW, RegQueryInfoKeyW,
+ HKEY_LOCAL_MACHINE, RRF_RT_REG_BINARY, RRF_RT_REG_DWORD, RRF_RT_REG_SZ,
+ },
+ },
+};
+
+/// An open key in the Windows Registry
+///
+/// (Note that Windows ref-counts keys internally, so we don't need subkeys to "hang on" to
+/// their parents)
+#[derive(Debug)]
+pub struct RegKey {
+ /// The Win32 handle of the open key
+ handle: HKEY,
+}
+
+impl RegKey {
+ /// Open the global HKEY_LOCAL_MACHINE handle
+ pub fn root_local_machine() -> RegKey {
+ RegKey {
+ handle: HKEY_LOCAL_MACHINE,
+ }
+ }
+ /// Try to open a subkey relative to this key with read-only access rights
+ ///
+ /// Returns `None` if the subkey doesn't exist
+ pub fn try_open_subkey(
+ &self,
+ subkey: impl AsRef<OsStr>,
+ ) -> Result<Option<RegKey>, DetectConflictError> {
+ let win32_subkey = to_win32_string(subkey.as_ref());
+
+ let mut subkey_handle = std::ptr::null_mut();
+ let rv = unsafe {
+ RegOpenKeyExW(
+ self.handle,
+ win32_subkey.as_ptr(),
+ 0, // options
+ KEY_READ,
+ &mut subkey_handle,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ match rv {
+ ERROR_SUCCESS => {
+ assert!(!subkey_handle.is_null());
+
+ Ok(Some(RegKey {
+ handle: subkey_handle,
+ }))
+ }
+ ERROR_FILE_NOT_FOUND => Ok(None),
+ _ => Err(DetectConflictError::RegOpenKeyFailed(rv)),
+ }
+ }
+ /// Try to get a value with the given name
+ ///
+ /// Returns `None` if there is no value with that name
+ ///
+ /// Note that you can pass the empty string to get the default value
+ pub fn try_get_value(
+ &self,
+ value_name: impl AsRef<OsStr>,
+ ) -> Result<Option<RegValue>, DetectConflictError> {
+ let win32_value_name = to_win32_string(value_name.as_ref());
+
+ // First we query the value's length and type so we know how to create the data buffer
+ let mut value_type = 0;
+ let mut value_len = 0;
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_BINARY | RRF_RT_REG_DWORD | RRF_RT_REG_SZ,
+ &mut value_type,
+ std::ptr::null_mut(), // no data ptr, just query length & type
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv == ERROR_FILE_NOT_FOUND {
+ return Ok(None);
+ }
+
+ if rv != ERROR_SUCCESS || value_len == 0 {
+ return Err(DetectConflictError::RegGetValueLenFailed(rv));
+ }
+
+ if value_type == REG_SZ {
+ // buffer length is in wide characters
+ let buffer_len = value_len / 2;
+
+ assert_eq!(buffer_len * 2, value_len);
+
+ let mut buffer = vec![0u16; buffer_len.try_into().unwrap()];
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_SZ,
+ std::ptr::null_mut(), // no need to query type again
+ buffer.as_mut_ptr().cast(),
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegGetValueFailed(rv));
+ }
+
+ Ok(Some(RegValue::String(from_win32_string(&buffer))))
+ } else if value_type == REG_BINARY {
+ let mut buffer = vec![0u8; value_len.try_into().unwrap()];
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_BINARY,
+ std::ptr::null_mut(), // no need to query type again
+ buffer.as_mut_ptr().cast(),
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegGetValueFailed(rv));
+ }
+
+ Ok(Some(RegValue::Binary(buffer)))
+ } else if value_type == REG_DWORD {
+ assert_eq!(value_len, 4);
+
+ let mut buffer = 0u32;
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_DWORD,
+ std::ptr::null_mut(), // no need to query type again
+ (&mut buffer as *mut u32).cast(),
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegGetValueFailed(rv));
+ }
+
+ Ok(Some(RegValue::Dword(buffer)))
+ } else {
+ Err(DetectConflictError::UnsupportedValueType(value_type))
+ }
+ }
+ /// Get an iterator that returns the names of all the subkeys of this key
+ pub fn subkey_names(&self) -> SubkeyNames<'_> {
+ SubkeyNames::new(self)
+ }
+}
+
+impl Drop for RegKey {
+ fn drop(&mut self) {
+ assert!(!self.handle.is_null());
+
+ if self.handle == HKEY_LOCAL_MACHINE {
+ return;
+ }
+
+ let rv: u32 = unsafe { RegCloseKey(self.handle) }.try_into().unwrap();
+
+ if rv != ERROR_SUCCESS {
+ log::warn!("failed to close opened registry key");
+ }
+ }
+}
+
+/// An iterator through the subkeys of a key
+///
+/// Returns items of type `Result<OsString, DetectConflictError>`. If an error occurs, calling `next()` again
+/// will likely just continue to result in errors, so it's best to just abandon ship at that
+/// point
+#[derive(Debug)]
+pub struct SubkeyNames<'a> {
+ /// The key that is being iterated
+ key: &'a RegKey,
+ /// The current index of the subkey -- Incremented each successful call to `next()`
+ index: u32,
+ /// A buffer that is large enough to hold the longest subkey name
+ buffer: Option<Vec<u16>>,
+}
+
+impl<'a> SubkeyNames<'a> {
+ /// Create a new subkey name iterator
+ fn new(key: &'a RegKey) -> SubkeyNames<'a> {
+ SubkeyNames {
+ key,
+ index: 0,
+ buffer: None,
+ }
+ }
+ /// Initialize `self.buffer` with a vector that's long enough to hold the longest subkey name
+ /// (if it isn't already)
+ fn create_buffer_if_needed(&mut self) -> Result<(), DetectConflictError> {
+ if self.buffer.is_some() {
+ return Ok(());
+ }
+
+ let mut max_key_length = 0;
+
+ let rv = unsafe {
+ RegQueryInfoKeyW(
+ self.key.handle,
+ std::ptr::null_mut(), // class ptr
+ std::ptr::null_mut(), // class len ptr
+ std::ptr::null_mut(), // reserved
+ std::ptr::null_mut(), // num of subkeys ptr
+ &mut max_key_length,
+ std::ptr::null_mut(), // max class length ptr
+ std::ptr::null_mut(), // number of values ptr
+ std::ptr::null_mut(), // longest value name ptr
+ std::ptr::null_mut(), // longest value data ptr
+ std::ptr::null_mut(), // DACL ptr
+ std::ptr::null_mut(), // last write time ptr
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegQueryInfoKeyFailed(rv));
+ }
+
+ // +1 for the null terminator
+ let max_key_length = usize::try_from(max_key_length).unwrap() + 1;
+
+ self.buffer = Some(vec![0u16; max_key_length]);
+
+ Ok(())
+ }
+}
+
+impl<'a> Iterator for SubkeyNames<'a> {
+ type Item = Result<OsString, DetectConflictError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Err(e) = self.create_buffer_if_needed() {
+ return Some(Err(e));
+ }
+
+ let buffer = self.buffer.as_mut().unwrap();
+ let mut name_len = u32::try_from(buffer.len()).unwrap();
+
+ let rv = unsafe {
+ RegEnumKeyExW(
+ self.key.handle,
+ self.index,
+ buffer.as_mut_ptr(),
+ &mut name_len,
+ std::ptr::null_mut(), // reserved
+ std::ptr::null_mut(), // class ptr
+ std::ptr::null_mut(), // class len ptr
+ std::ptr::null_mut(), // last write time ptr
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv == ERROR_NO_MORE_ITEMS {
+ return None;
+ }
+
+ if rv != ERROR_SUCCESS {
+ return Some(Err(DetectConflictError::RegEnumKeyFailed(rv)));
+ }
+
+ self.index += 1;
+
+ // +1 for the null terminator
+ let name_len = usize::try_from(name_len).unwrap() + 1;
+
+ Some(Ok(from_win32_string(&buffer[0..name_len])))
+ }
+}
+
+/// A single value in the registry
+#[derive(Clone, Debug, PartialEq)]
+pub enum RegValue {
+ /// A REG_BINARY value
+ Binary(Vec<u8>),
+ /// A REG_DWORD value
+ Dword(u32),
+ /// A REG_SZ value
+ String(OsString),
+}
+
+/// Convert an OsStr to a null-terminated wide character string
+///
+/// The input string must not contain any interior null values, and the resulting wide
+/// character array will have a null terminator appended
+fn to_win32_string(s: &OsStr) -> Vec<u16> {
+ // +1 for the null terminator
+ let mut result = Vec::with_capacity(s.len() + 1);
+
+ for wc in s.encode_wide() {
+ assert_ne!(wc, 0);
+ result.push(wc);
+ }
+
+ result.push(0);
+ result
+}
+
+/// Convert a null-terminated wide character string into an OsString
+///
+/// The passed-in slice must have exactly one wide null character as the last element
+fn from_win32_string(s: &[u16]) -> OsString {
+ for (idx, wc) in s.iter().enumerate() {
+ if *wc == 0 {
+ assert_eq!(idx, s.len() - 1);
+ return OsString::from_wide(&s[0..idx]);
+ }
+ }
+ panic!("missing null terminator at end of win32 string");
+}