use std::ffi::c_void;
use std::ptr;
use windows_sys::Win32::System::Memory::{
MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_NOACCESS, PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
VirtualAlloc, VirtualFree, VirtualProtect,
};
use super::{AclState, SecurityDescriptor, SecurityInformation};
const SELF_RELATIVE: u16 = 0x8000;
const DACL_PRESENT: u16 = 0x0004;
const HEADER: usize = 20;
const OWNER_AT: usize = 4;
const GROUP_AT: usize = 8;
const SACL_AT: usize = 12;
const DACL_AT: usize = 16;
const EVERYONE: [u8; 12] = [1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0];
const EMPTY_ACL: [u8; 8] = [2, 0, 8, 0, 0, 0, 0, 0];
const PAGE: usize = 4096;
fn header(control: u16, owner: u32, group: u32, sacl: u32, dacl: u32) -> [u8; HEADER] {
let mut bytes = [0_u8; HEADER];
bytes[0] = 1;
bytes[2..4].copy_from_slice(&control.to_le_bytes());
bytes[OWNER_AT..OWNER_AT + 4].copy_from_slice(&owner.to_le_bytes());
bytes[GROUP_AT..GROUP_AT + 4].copy_from_slice(&group.to_le_bytes());
bytes[SACL_AT..SACL_AT + 4].copy_from_slice(&sacl.to_le_bytes());
bytes[DACL_AT..DACL_AT + 4].copy_from_slice(&dacl.to_le_bytes());
bytes
}
fn offset_in(bytes: &[u8], at: usize) -> usize {
u32::from_le_bytes(bytes[at..at + 4].try_into().expect("four bytes")) as usize
}
fn names_nothing_outside_itself(captured: &SecurityDescriptor) {
let bytes = captured.as_bytes();
for at in [OWNER_AT, GROUP_AT, SACL_AT, DACL_AT] {
let offset = offset_in(bytes, at);
assert!(
offset < bytes.len(),
"the part at header offset {at} is named at {offset}, past the copy's {} bytes",
bytes.len()
);
}
}
struct GuardedPage {
base: *mut c_void,
}
impl GuardedPage {
fn new() -> Self {
let base = unsafe {
VirtualAlloc(
ptr::null(),
2 * PAGE,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
)
};
assert!(!base.is_null(), "allocate two pages");
let mut previous: PAGE_PROTECTION_FLAGS = 0;
let protected = unsafe {
VirtualProtect(
base.cast::<u8>().wrapping_add(PAGE).cast(),
PAGE,
PAGE_NOACCESS,
&raw mut previous,
)
};
assert_ne!(protected, 0, "make the second page inaccessible");
Self { base }
}
fn place_at_end(&mut self, bytes: &[u8]) -> *const c_void {
let start = self.base.cast::<u8>().wrapping_add(PAGE - bytes.len());
unsafe { ptr::copy_nonoverlapping(bytes.as_ptr(), start, bytes.len()) };
start.cast_const().cast()
}
}
impl Drop for GuardedPage {
fn drop(&mut self) {
unsafe { VirtualFree(self.base, 0, MEM_RELEASE) };
}
}
#[test]
fn an_owner_and_group_that_share_one_sid_are_read_where_they_are() {
let mut bytes = header(SELF_RELATIVE, 20, 20, 0, 0).to_vec();
bytes.extend_from_slice(&EVERYONE);
let mut page = GuardedPage::new();
let source = page.place_at_end(&bytes);
let captured = unsafe { SecurityDescriptor::capture(source) }.expect("capture the descriptor");
names_nothing_outside_itself(&captured);
let text = captured
.to_sddl(SecurityInformation::OWNER | SecurityInformation::GROUP)
.expect("write the owner and group")
.to_string_lossy();
assert_eq!(text, "O:WDG:WD");
}
#[test]
fn a_part_after_a_gap_is_read_where_its_offset_says() {
const DACL: usize = 512;
let mut bytes = vec![0_u8; DACL + EMPTY_ACL.len()];
bytes[..HEADER].copy_from_slice(&header(SELF_RELATIVE | DACL_PRESENT, 0, 0, 0, 512));
bytes[DACL..].copy_from_slice(&EMPTY_ACL);
let mut page = GuardedPage::new();
let source = page.place_at_end(&bytes);
let captured = unsafe { SecurityDescriptor::capture(source) }.expect("capture the descriptor");
names_nothing_outside_itself(&captured);
assert_eq!(captured.dacl().expect("the DACL"), AclState::Empty);
assert!(
captured.len() < bytes.len(),
"the gap is not carried: {} bytes",
captured.len()
);
}
#[test]
fn a_contiguous_descriptor_is_rebuilt_to_the_same_parts() {
let descriptor = SecurityDescriptor::from_sddl(&wtf_string::Wtf16String::from(
"O:BAG:SYD:P(A;;FA;;;WD)(D;;FW;;;AU)",
))
.expect("a descriptor");
let all = SecurityInformation::OWNER | SecurityInformation::GROUP | SecurityInformation::DACL;
let captured = unsafe { SecurityDescriptor::capture(descriptor.as_ptr()) }.expect("capture");
names_nothing_outside_itself(&captured);
assert_eq!(
captured.to_sddl(all).expect("text").to_string_lossy(),
descriptor.to_sddl(all).expect("text").to_string_lossy()
);
}