@@ -209,7 +209,6 @@ impl PartialEq<Account> for AccountWithId {
209209///
210210/// `LookupAccountNameW` must be called to enable `ConvertSidToStringSidW` to work.
211211#[ cfg( target_os = "windows" ) ]
212- #[ allow( clippy:: multiple_unsafe_ops_per_block) ]
213212#[ allow( clippy:: cast_possible_truncation) ]
214213pub ( crate ) unsafe fn list_accounts ( ) -> Result < Vec < Account > , ListAccountsError > {
215214 use windows:: core:: PWSTR ;
@@ -220,13 +219,16 @@ pub(crate) unsafe fn list_accounts() -> Result<Vec<Account>, ListAccountsError>
220219 use windows:: Win32 :: Security :: { LookupAccountNameW , PSID , SECURITY_MAX_SID_SIZE , SID_NAME_USE } ;
221220
222221 // SAFETY: uses `NetUserEnum` and `LookupAccountNameW` from `windows`
223- unsafe {
224- let mut buf: * mut u8 = std:: ptr:: null_mut ( ) ;
225- let mut entries_read = 0 ;
226- let mut total_entries = 0 ;
227222
223+ let mut buf: * mut u8 = std:: ptr:: null_mut ( ) ;
224+ let mut entries_read = 0 ;
225+ let mut total_entries = 0 ;
226+
227+ // SAFETY: `buf` is a null-initialized out-pointer that NetUserEnum will allocate.
228+ // `entries_read` and `total_entries` are valid pointers to receive output counts.
229+ let status = unsafe {
228230 // Get the list of user accounts.
229- let status = NetUserEnum (
231+ NetUserEnum (
230232 None ,
231233 0 ,
232234 FILTER_NORMAL_ACCOUNT ,
@@ -235,48 +237,61 @@ pub(crate) unsafe fn list_accounts() -> Result<Vec<Account>, ListAccountsError>
235237 & mut entries_read,
236238 & mut total_entries,
237239 None ,
238- ) ;
239-
240- if status != NERR_Success {
241- return Err ( ListAccountsError :: NetUserEnumFail ( status) ) ;
242- }
243-
244- #[ allow( clippy:: cast_ptr_alignment) ]
245- let users = std:: slice:: from_raw_parts ( buf as * const USER_INFO_0 , entries_read as usize ) ;
246-
247- let mut accounts = Vec :: with_capacity ( users. len ( ) ) ;
248- for user in users {
249- let username = user. usri0_name . display ( ) ;
250- let mut sid = [ 0u8 ; SECURITY_MAX_SID_SIZE as usize ] ;
251- let mut sid_size = sid. len ( ) as u32 ;
252- let mut domain_name = [ 0u16 ; 256 ] ;
253- let mut domain_size = domain_name. len ( ) as u32 ;
254- let mut sid_type = SID_NAME_USE ( 0 ) ;
255- let sid = PSID ( sid. as_mut_ptr ( ) . cast ( ) ) ;
240+ )
241+ } ;
256242
243+ if status != NERR_Success {
244+ return Err ( ListAccountsError :: NetUserEnumFail ( status) ) ;
245+ }
246+
247+ #[ expect( clippy:: cast_ptr_alignment) ]
248+ // SAFETY: `buf` is guaranteed by `NetUserEnum` to point to an array of `USER_INFO_0` structs.
249+ // We cast it and build a slice with `entries_read` elements, which was returned alongside `buf`.
250+ // We expect the alignment to be correct because `USER_INFO_0` is a `#[repr(C)]` struct with a single field, so it is identical to the alignment of PWSTR.
251+ let users = unsafe { std:: slice:: from_raw_parts ( buf as * const USER_INFO_0 , entries_read as usize ) } ;
252+
253+ let mut accounts = Vec :: with_capacity ( users. len ( ) ) ;
254+ for user in users {
255+ // SAFETY: `user.usri0_name` is a valid string.
256+ let name = unsafe { user. usri0_name . display ( ) } . to_string ( ) ;
257+ let mut sid = [ 0u8 ; SECURITY_MAX_SID_SIZE as usize ] ;
258+ let mut sid_size = sid. len ( ) as u32 ;
259+ let mut domain_name = [ 0u16 ; 256 ] ;
260+ let mut domain_size = domain_name. len ( ) as u32 ;
261+ let domain_name = PWSTR ( domain_name. as_mut_ptr ( ) ) ;
262+ let mut sid_type = SID_NAME_USE ( 0 ) ;
263+ let sid = PSID ( sid. as_mut_ptr ( ) . cast ( ) ) ;
264+
265+ // SAFETY: `user.usri0_name` is a valid string.
266+ // `sid` and `domain_name` buffers are correctly sized and initialized.
267+ // `sid_size` and `domain_size` are set to their respective buffer lengths.
268+ // All pointers are valid for writes.
269+ unsafe {
257270 LookupAccountNameW (
258271 None ,
259272 user. usri0_name ,
260273 Some ( sid) ,
261274 & mut sid_size,
262- Some ( PWSTR ( domain_name. as_mut_ptr ( ) ) ) ,
275+ Some ( domain_name) ,
263276 & mut domain_size,
264277 & mut sid_type,
265- ) ?;
278+ )
279+ } ?;
266280
267- let mut sid_str: PWSTR = PWSTR :: null ( ) ;
268- ConvertSidToStringSidW ( sid, & mut sid_str) ?;
281+ let mut sid_str: PWSTR = PWSTR :: null ( ) ;
282+ // SAFETY: `sid` is a valid buffer previously populated by `LookupAccountNameW`.
283+ unsafe { ConvertSidToStringSidW ( sid, & mut sid_str) } ?;
284+ // SAFETY: `sid_str` is a valid string.
285+ let s = unsafe { sid_str. to_string ( ) } ?;
286+ let sid = Sid :: from_str ( & s) ?;
287+ accounts. push ( Account { name, sid } )
288+ }
269289
270- // convert to string
271- let s = sid_str. to_string ( ) ?;
272- accounts. push ( Account {
273- name : username. to_string ( ) ,
274- sid : Sid :: from_str ( & s) ?,
275- } )
276- }
290+ // SAFETY: `buf` was allocated by `NetUserEnum` and must be freed.
291+ unsafe {
277292 NetApiBufferFree ( Some ( buf as * mut _ ) ) ;
278- Ok ( accounts)
279293 }
294+ Ok ( accounts)
280295}
281296
282297/// A diff of changes between two lists of accounts.
0 commit comments