Skip to content

Commit 08e409a

Browse files
committed
Split unsafe blocks and add safety comments
1 parent b9af8cb commit 08e409a

File tree

1 file changed

+51
-36
lines changed

1 file changed

+51
-36
lines changed

crates/devolutions-pedm/src/account.rs

Lines changed: 51 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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)]
214213
pub(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

Comments
 (0)