Skip to content

Instantly share code, notes, and snippets.

@MaulingMonkey
Created September 28, 2022 06:54
Show Gist options
  • Save MaulingMonkey/40a91f0c4e217f74509280ec29e951c4 to your computer and use it in GitHub Desktop.
Save MaulingMonkey/40a91f0c4e217f74509280ec29e951c4 to your computer and use it in GitHub Desktop.
# ...
[dependencies]
windows.version = "0.41"
# ...
#![forbid(unsafe_op_in_unsafe_fn)]
use windows::{
Win32::Foundation::ERROR_MORE_DATA,
Win32::System::Services::*,
Win32::Security::*,
};
use core::mem::align_of;
fn main() {
let sc_manager = unsafe { OpenSCManagerW(None, None, SC_MANAGER_ENUMERATE_SERVICE) }.expect("unable to open services for enumeration");
let mut services = Vec::new();
let services = unsafe { get_services_status(sc_manager, SERVICE_WIN32, SERVICE_STATE_ALL, &mut services) }.expect("unable to enumerate services");
for service in services {
let service = service.into_inner();
let service = (
unsafe { service.lpServiceName.to_string() }.unwrap(),
unsafe { service.lpDisplayName.to_string() }.unwrap(),
service.ServiceStatus
);
dbg!(service);
}
}
/// \[[microsoft.com](https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-enumservicesstatusw) EnumServicesStatusW
///
/// ### Safety
/// * May be UB if `sc_manager` isn't a valid service manager handle
/// * May be UB if `sc_manager` is simultaniously accessed from another thread
/// * May be UB if `service_type` or `service_state` aren't valid enumeration values
///
/// It's also a bad idea to copy the returned ENUM_SERVICE_STATUSW s, as lpServiceName etc. doesn't properly borrow `buffer`.
/// However, it's `unsafe` to dereference those already.
unsafe fn get_services_status<'b>(
sc_manager: SC_HANDLE,
service_type: ENUM_SERVICE_TYPE,
service_state: ENUM_SERVICE_STATE,
buffer: &'b mut Vec<u8>,
) -> windows::core::Result<&'b [Unaligned<ENUM_SERVICE_STATUSW>]> {
buffer.clear();
let mut bytes = 0;
let mut services = 0;
match unsafe { EnumServicesStatusW(sc_manager, service_type, service_state, None, 0, &mut bytes, &mut services, None) }.ok() {
Ok(()) => {}, // OK
Err(e) if e == ERROR_MORE_DATA.into() => {}, // OK
Err(e) => return Err(e),
}
buffer.reserve(bytes as _);
assert!(buffer.as_ptr() as usize % align_of::<ENUM_SERVICE_STATUSW>() == 0, "bug: vec buffer insufficiently aligned to read service status");
// TODO: instead either:
// 1. overallocate buffer and then round ptr up to alignment
// 2. use wio::VariableSizedBox https://github.com/retep998/wio-rs/blob/9bf021178b2d02485f1bd35e6cff41bf52d4a9a2/src/vsb.rs
// 3. use firehazard::alloc::CBox::new_oversized? https://docs.rs/firehazard/*/firehazard/alloc/struct.CBox.html#method.new_oversized
// Only UB if passing a misaligned buffer to EnumServicesStatusW is UB
// Unaligned<T> prevents UB on the Rust side of things, although that's rather pointless thanks to this assert
unsafe { EnumServicesStatusW(sc_manager, service_type, service_state, Some(buffer.as_mut_ptr().cast()), bytes, &mut bytes, &mut services, None) }.ok()?;
unsafe { buffer.set_len(bytes as _) };
Ok(unsafe { core::slice::from_raw_parts(buffer.as_ptr().cast(), services.try_into().unwrap_or(!0)) })
}
#[repr(packed)] struct Unaligned<T>(T);
impl<T: Copy> Clone for Unaligned<T> { fn clone(&self) -> Self { *self }} // TODO: fancy manuallydrop shenannigans to weaken constraint to T: Clone
impl<T: Copy> Copy for Unaligned<T> {}
#[allow(dead_code)] impl<T> Unaligned<T> {
pub fn new(value: T) -> Self { Self(value) }
pub fn into_inner(self) -> T { let Self(value) = self; value }
}
const _ : () = assert!(core::mem::align_of::<Unaligned<ENUM_SERVICE_STATUSW>>() == 1, "Unaligned<T> failed to unalign T");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment