Advanced Features
Mandatory integrity labels
Windows integrity levels control write-up/read-down/execute-up policies. Each level maps to a SID under authority 16:
#![allow(unused)]
fn main() {
use win_sd::{Ace, IntegrityLevel, IntegrityPolicy};
assert!(IntegrityLevel::Low < IntegrityLevel::Medium);
assert!(IntegrityLevel::Medium < IntegrityLevel::High);
let sid = IntegrityLevel::Medium.to_sid();
assert_eq!(sid.to_string(), "S-1-16-8192");
let ace = Ace::mandatory_label(
IntegrityLevel::High,
IntegrityPolicy::NO_WRITE_UP | IntegrityPolicy::NO_READ_UP,
);
assert_eq!(ace.integrity_level(), Some(IntegrityLevel::High));
}
Conditional expressions
Callback ACEs (types XA, XD, ZA, ZD, XU, ZU) carry
conditional expressions in their application_data field, encoded in the
MS-DTYP binary postfix (RPN) format per §2.4.4.17. The crate provides
two interfaces: a text-based SDDL syntax and the underlying binary format.
SDDL text syntax
The sddl_conditional module parses and generates the human-readable
SDDL conditional expression syntax:
#![allow(unused)]
fn main() {
use win_sd::sddl_conditional::{parse_sddl_condition, generate_sddl_condition};
// Parse from text
let expr = parse_sddl_condition(r#"@User.dept == "Sales""#).unwrap();
// Generate text (round-trips with correct precedence)
let text = generate_sddl_condition(&expr);
assert_eq!(text, r#"@User.dept == "Sales""#);
// Complex expressions with logical operators
let expr = parse_sddl_condition(
r#"@User.dept == "Sales" && @Resource.public == 1"#
).unwrap();
// Membership operators
let expr = parse_sddl_condition("Member_of{SID(BA)}").unwrap();
// Composite literals
let expr = parse_sddl_condition(
r#"@User.groups Contains {"admin", "devs"}"#
).unwrap();
// Existence checks
let expr = parse_sddl_condition("Exists @User.clearance").unwrap();
}
Supported SDDL syntax:
| Element | Examples |
|---|---|
| Attribute references | @User.name, @Resource.name, @Device.name, @Local.name |
| Comparison | ==, !=, <, <=, >, >= |
| Set operators | Contains, Any_of |
| Logical | &&, ||, !(...) |
| Existence | Exists @User.attr |
| Membership | Member_of{SID(BA)}, Not_Member_of{...}, Member_of_Any{...}, Device_Member_of{...}, plus Not_Device_Member_of, Device_Member_of_Any, Not_Member_of_Any, Not_Device_Member_of_Any |
| Literals | integers, "strings", SID(BA), SID(S-1-5-...), {"a", "b"} |
The parser enforces a depth limit of 128 to prevent stack overflow from deeply nested input. Trailing unparsed input is rejected.
AST and value types
The ConditionalExpr enum has 22 variants:
| Category | Variants |
|---|---|
| Leaf nodes | Literal(ClaimValue), UserAttr, ResourceAttr, DeviceAttr, LocalAttr |
| Comparison | Equals, NotEquals, LessThan, LessEqual, GreaterThan, GreaterEqual |
| Set | Contains, AnyOf |
| Logical | And, Or, Not |
| Existence | Exists |
| Membership | MemberOf, DeviceMemberOf, NotMemberOf, NotDeviceMemberOf, MemberOfAny, DeviceMemberOfAny, NotMemberOfAny, NotDeviceMemberOfAny |
The ClaimValue enum carries typed data:
| Variant | Rust type | Description |
|---|---|---|
Int64 | i64 | 64-bit signed integer |
Uint64 | u64 | 64-bit unsigned integer |
String | String | Unicode string |
Sid | Sid | Security identifier |
Boolean | bool | Boolean value |
OctetString | Vec<u8> | Raw byte string |
Composite | Vec<ClaimValue> | Multi-valued set |
Building expressions programmatically
#![allow(unused)]
fn main() {
use win_sd::conditional::*;
let expr = ConditionalExpr::Equals(
Box::new(ConditionalExpr::UserAttr("department".to_string())),
Box::new(ConditionalExpr::Literal(ClaimValue::String("Engineering".to_string()))),
);
}
Binary format
The binary format uses the artx signature prefix (0x61 0x72 0x74 0x78):
#![allow(unused)]
fn main() {
use win_sd::conditional::*;
let expr = ConditionalExpr::Equals(
Box::new(ConditionalExpr::UserAttr("department".to_string())),
Box::new(ConditionalExpr::Literal(ClaimValue::String("Engineering".to_string()))),
);
// Write to binary
let binary = write_conditional(&expr);
assert!(is_conditional_ace_data(&binary));
// Parse back
let parsed = parse_conditional(&binary).unwrap();
assert_eq!(expr, parsed);
}
Extracting conditions from callback ACEs
#![allow(unused)]
fn main() {
use win_sd::Ace;
fn inspect_ace(ace: &Ace) {
if ace.ace_type().is_callback() {
if let Some(result) = ace.condition() {
match result {
Ok(expr) => println!("condition: {:?}", expr),
Err(e) => println!("parse error: {e}"),
}
}
}
}
}
Evaluation
Evaluation uses three-valued logic via ConditionalResult:
True— the expression matched; the ACE applies.False— the expression did not match; the ACE is skipped.Unknown— an attribute was missing or a type mismatch occurred. For allow ACEs,Unknownmeans “not granted.” For deny ACEs,Unknownmeans “denied” (fail-secure).
#![allow(unused)]
fn main() {
use win_sd::conditional::*;
struct MyCtx;
impl AttributeContext for MyCtx {
fn get_user_attr(&self, name: &str) -> Option<ClaimValue> {
if name == "department" {
Some(ClaimValue::String("Engineering".to_string()))
} else { None }
}
fn get_resource_attr(&self, _: &str) -> Option<ClaimValue> { None }
fn get_device_attr(&self, _: &str) -> Option<ClaimValue> { None }
fn get_local_attr(&self, _: &str) -> Option<ClaimValue> { None }
fn is_member_of(&self, _: &win_sd::Sid) -> bool { false }
fn is_device_member_of(&self, _: &win_sd::Sid) -> bool { false }
}
let expr = ConditionalExpr::Equals(
Box::new(ConditionalExpr::UserAttr("department".to_string())),
Box::new(ConditionalExpr::Literal(ClaimValue::String("Engineering".to_string()))),
);
assert_eq!(expr.evaluate(&MyCtx), ConditionalResult::True);
// Missing attribute returns Unknown
let missing = ConditionalExpr::Exists(
Box::new(ConditionalExpr::UserAttr("clearance".to_string())),
);
assert_eq!(missing.evaluate(&MyCtx), ConditionalResult::False);
}
The AttributeContext trait has six methods:
| Method | Purpose |
|---|---|
get_user_attr(name) | Look up a user claim attribute |
get_resource_attr(name) | Look up a resource attribute |
get_device_attr(name) | Look up a device claim attribute |
get_local_attr(name) | Look up a local attribute |
is_member_of(sid) | Check token SID membership |
is_device_member_of(sid) | Check device SID membership |
EmptyAttributeContext is a provided no-op implementation returning
None/false for all lookups.
The evaluator has a depth limit of 1024 to prevent stack overflow from crafted inputs.
Error handling
parse_conditional returns ConditionalParseError with these variants:
| Variant | Meaning |
|---|---|
InvalidSignature | Data doesn’t start with artx |
UnexpectedEnd | Truncated data |
UnknownToken(u8) | Unrecognized token byte |
StackUnderflow | Malformed RPN: not enough operands |
Binary serialization
Security descriptors can be serialized to/from the MS-DTYP self-relative binary format:
#![allow(unused)]
fn main() {
use win_sd::{SecurityDescriptorBuilder, SecurityDescriptor, AccessMask, Sid};
let sd = SecurityDescriptorBuilder::new()
.owner(Sid::local_system())
.allow(Sid::everyone(), AccessMask::FILE_GENERIC_READ)
.build();
// Serialize to binary
let bytes = sd.to_bytes();
// Parse back
let recovered = SecurityDescriptor::from_bytes(&bytes).unwrap();
assert_eq!(recovered.owner(), sd.owner());
}
The parser validates SID sub-authority counts, minimum ACE sizes, AclSize bounds, and rejects overlapping component offset regions.
ACL inheritance
The inheritance engine propagates ACEs from parent to child objects:
#![allow(unused)]
fn main() {
use win_sd::{SecurityDescriptorBuilder, AccessMask, Sid, AceFlags};
use win_sd::inheritance::create_child_sd;
let parent = SecurityDescriptorBuilder::new()
.owner(Sid::local_system())
.allow_with_flags(
Sid::administrators(),
AccessMask::FILE_ALL_ACCESS,
AceFlags::CONTAINER_INHERIT | AceFlags::OBJECT_INHERIT,
)
.build();
// Create a child directory SD
let child = create_child_sd(&parent, true);
assert!(child.dacl().is_some());
// Inherited ACEs have the INHERITED_ACE flag set
let ace = &child.dacl().unwrap().entries()[0];
assert!(ace.is_inherited());
// INHERIT_ONLY is cleared: the ACE applies to the child
assert!(!ace.is_inherit_only());
}
Inheritance rules:
- Containers: ACEs with
CONTAINER_INHERITare inherited. ACEs with onlyOBJECT_INHERITpass through asINHERIT_ONLY. - Non-containers: ACEs with
OBJECT_INHERITare inherited, with all inheritance flags cleared. NO_PROPAGATE_INHERITstops further propagation.INHERIT_ONLYon the parent means “skip the parent, apply to children” — it is cleared on the inherited copy.
Access tokens
The AccessToken consolidates all access-check inputs:
#![allow(unused)]
fn main() {
use win_sd::Sid;
use win_sd::token::{AccessToken, Privilege};
use win_sd::IntegrityLevel;
let token = AccessToken::new(Sid::from_authority(5, vec![21, 100, 200, 300, 1001]))
.with_groups([Sid::administrators(), Sid::everyone()])
.with_integrity(IntegrityLevel::High)
.with_privilege(Privilege::SeSecurityPrivilege);
assert_eq!(token.integrity_level(), IntegrityLevel::High);
assert!(token.has_privilege(Privilege::SeSecurityPrivilege));
// Restricted tokens limit the second DACL walk
let restricted = token.with_restricted_sids(vec![Sid::authenticated_users()]);
assert!(restricted.is_restricted());
}
Resource attribute claims
ClaimSecurityAttribute models the CLAIM_SECURITY_ATTRIBUTE_RELATIVE_V1
structure used in SYSTEM_RESOURCE_ATTRIBUTE_ACE:
#![allow(unused)]
fn main() {
use win_sd::claims::{ClaimSecurityAttribute, ClaimValueType};
use win_sd::conditional::ClaimValue;
let attr = ClaimSecurityAttribute::new(
"Department",
ClaimValueType::String,
vec![ClaimValue::String("Engineering".to_string())],
);
assert_eq!(attr.name(), "Department");
}