Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Access Checks

The win-sd crate provides four access-check methods, from simple to full:

Simple check

check_access takes a slice of token SIDs and a desired access mask. Implements the basic MS-DTYP §2.5.3.2 algorithm:

  1. No DACL present → grant all
  2. Empty DACL → deny all (except owner implicit READ_CONTROL | WRITE_DAC)
  3. Walk ACEs in order, skip INHERIT_ONLY
  4. Deny ACEs block; allow ACEs accumulate
  5. All desired bits must be granted and none denied
#![allow(unused)]
fn main() {
use win_sd::{SecurityDescriptorBuilder, AccessMask, Sid};

let sd = SecurityDescriptorBuilder::new()
    .owner(Sid::local_system())
    .deny(Sid::anonymous(), AccessMask::FILE_ALL_ACCESS)
    .allow(Sid::administrators(), AccessMask::FILE_ALL_ACCESS)
    .build();

let result = sd.check_access(
    &[Sid::administrators(), Sid::everyone()],
    AccessMask::FILE_READ_DATA,
);
assert!(result.granted);
}

Object ACE check

check_access_object adds GUID filtering for Object ACEs:

#![allow(unused)]
fn main() {
use win_sd::{SecurityDescriptorBuilder, AccessMask, Sid, Ace, Guid, WinAcl};

let guid: Guid = "bf967aba-0de6-11d0-a285-00aa003049e2".parse().unwrap();
let ace = Ace::allow_object(
    Sid::administrators(),
    AccessMask::new(0x0010),
    Some(guid),
    None,
);

let mut sd = win_sd::SecurityDescriptor::new();
let mut dacl = WinAcl::new();
dacl.add_ace(ace);
sd.set_dacl(dacl);

// Matches only when the requested object type GUID matches
let result = sd.check_access_object(
    &[Sid::administrators()],
    AccessMask::new(0x0010),
    Some(&guid),
);
assert!(result.granted);
}

Full token-based check

check_access_full supports the complete Windows access-check algorithm:

  • Mandatory integrity check: scans SACL for SystemMandatoryLabel ACE, denies write/read/execute when token integrity is below object integrity.
  • Privilege handling: SeSecurityPrivilege grants ACCESS_SYSTEM_SECURITY; SeTakeOwnershipPrivilege grants WRITE_OWNER; SeBackupPrivilege grants FILE_GENERIC_READ, FILE_LIST_DIRECTORY, and FILE_TRAVERSE regardless of the DACL; SeRestorePrivilege grants FILE_GENERIC_WRITE, DELETE, WRITE_DAC, and WRITE_OWNER regardless of the DACL.
  • Generic rights expansion: expands GENERIC_READ/WRITE/EXECUTE/ALL via GenericMapping.
  • MAXIMUM_ALLOWED mode: when desired mask contains MAXIMUM_ALLOWED, computes the full set of grantable rights.
  • Callback ACE evaluation: evaluates conditional expressions via AttributeContext. Fail-secure: allow ACEs without a parseable condition are skipped; deny ACEs without a condition still deny.
  • Restricted tokens: second DACL walk against restricted SIDs, intersected with the primary result.
#![allow(unused)]
fn main() {
use win_sd::prelude::*;
use win_sd::token::{AccessToken, Privilege};

let sd = SecurityDescriptorBuilder::new()
    .owner(Sid::local_system())
    .allow(Sid::everyone(), AccessMask::FILE_ALL_ACCESS)
    .mandatory_label(IntegrityLevel::High, IntegrityPolicy::NO_WRITE_UP)
    .build();

// High-integrity token can write
let high = AccessToken::new(Sid::everyone())
    .with_integrity(IntegrityLevel::High);
let result = sd.check_access_full(&high, AccessMask::FILE_WRITE_DATA, None, None, None);
assert!(result.granted);

// Medium-integrity token cannot (NO_WRITE_UP)
let medium = AccessToken::new(Sid::everyone())
    .with_integrity(IntegrityLevel::Medium);
let result = sd.check_access_full(&medium, AccessMask::FILE_WRITE_DATA, None, None, None);
assert!(!result.granted);
}

Hierarchical object type list check

check_access_by_type_list implements the AccessCheckByTypeResultList algorithm from MS-DTYP §2.5.3.2: given an ObjectTypeEntry list (level 0 = the object itself, level 1 = property set, level 2 = property, …), it returns a separate grant/deny result for each entry. ACEs without an object_type GUID apply to every entry; ACEs with a GUID apply to the matching entry and its descendants:

#![allow(unused)]
fn main() {
use win_sd::{SecurityDescriptorBuilder, AccessMask, Sid, Ace, Guid};
use win_sd::object_type_list::ObjectTypeEntry;

let prop_set: Guid = "bf967aba-0de6-11d0-a285-00aa003049e2".parse().unwrap();
let ace = Ace::allow_object(Sid::everyone(), AccessMask::new(0x0010), Some(prop_set), None);

let mut sd = win_sd::SecurityDescriptor::new();
let mut dacl = win_sd::WinAcl::new();
dacl.add_ace(ace);
sd.set_dacl(dacl);

let list = vec![
    ObjectTypeEntry { level: 0, object_type: Guid::ZERO },
    ObjectTypeEntry { level: 1, object_type: prop_set },
];

let results = sd.check_access_by_type_list(
    &[Sid::everyone()],
    AccessMask::new(0x0010),
    &list,
);
assert!(!results[0].granted); // object itself: not covered by the object ACE
assert!(results[1].granted);  // property set: matches the ACE's GUID
}

Each ObjectTypeResult reports the checked entry, whether access was granted, the remaining (ungranted) bits, and the accumulated denied_mask.

AccessCheckResult

The first three methods return AccessCheckResult:

  • granted: bool — whether the full desired mask was satisfied
  • granted_mask: AccessMask — accumulated granted bits (after denials)
  • denied_mask: AccessMask — accumulated denied bits