use bitflags::bitflags;
pub mod rights;
pub mod sid;
pub use sid::{Guid, Sid};
pub fn sid_to_bytes(sid: &Sid) -> Vec<u8> {
sid.to_bytes()
}
pub fn build_rbcd_sd(trustee: &Sid) -> Vec<u8> {
let owner = Sid {
revision: 1,
identifier_authority: 5,
sub_authorities: vec![32, 544],
};
let ownerb = owner.to_bytes();
let trusteeb = trustee.to_bytes();
let ace_size = (4 + 4 + trusteeb.len()) as u16;
let mut ace = vec![0x00u8, 0x00];
ace.extend_from_slice(&ace_size.to_le_bytes());
ace.extend_from_slice(&0x000F_01FFu32.to_le_bytes());
ace.extend_from_slice(&trusteeb);
let dacl_size = (8 + ace.len()) as u16;
let mut dacl = vec![0x02u8, 0x00];
dacl.extend_from_slice(&dacl_size.to_le_bytes());
dacl.extend_from_slice(&1u16.to_le_bytes());
dacl.extend_from_slice(&0u16.to_le_bytes());
dacl.extend_from_slice(&ace);
let owner_off = 20u32;
let group_off = 20 + ownerb.len() as u32;
let dacl_off = group_off + ownerb.len() as u32;
let mut sd = vec![1u8, 0];
sd.extend_from_slice(&0x8004u16.to_le_bytes()); sd.extend_from_slice(&owner_off.to_le_bytes());
sd.extend_from_slice(&group_off.to_le_bytes());
sd.extend_from_slice(&0u32.to_le_bytes()); sd.extend_from_slice(&dacl_off.to_le_bytes());
sd.extend_from_slice(&ownerb); sd.extend_from_slice(&ownerb); sd.extend_from_slice(&dacl);
sd
}
#[derive(Debug, thiserror::Error)]
pub enum SddlError {
#[error("buffer too short at {0}")]
Truncated(&'static str),
#[error("bad ACE sid")]
BadSid,
}
type Result<T> = std::result::Result<T, SddlError>;
bitflags! {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AccessMask: u32 {
const CREATE_CHILD = 0x0000_0001;
const DELETE_CHILD = 0x0000_0002;
const LIST_CHILDREN = 0x0000_0004;
const SELF = 0x0000_0008; const READ_PROP = 0x0000_0010; const WRITE_PROP = 0x0000_0020; const DELETE_TREE = 0x0000_0040;
const LIST_OBJECT = 0x0000_0080;
const CONTROL_ACCESS = 0x0000_0100; const DELETE = 0x0001_0000;
const READ_CONTROL = 0x0002_0000;
const WRITE_DAC = 0x0004_0000;
const WRITE_OWNER = 0x0008_0000;
const SYNCHRONIZE = 0x0010_0000;
const ACCESS_SYSTEM_SECURITY = 0x0100_0000;
const GENERIC_ALL = 0x1000_0000;
const GENERIC_EXECUTE = 0x2000_0000;
const GENERIC_WRITE = 0x4000_0000;
const GENERIC_READ = 0x8000_0000;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AceType {
AccessAllowed,
AccessDenied,
AccessAllowedObject,
AccessDeniedObject,
Other(u8),
}
#[derive(Clone, Debug)]
pub struct Ace {
pub ace_type: AceType,
pub flags: u8,
pub mask: AccessMask,
pub trustee: Sid,
pub object_type: Option<Guid>,
pub inherited_object_type: Option<Guid>,
}
impl Ace {
pub fn is_allow(&self) -> bool {
matches!(
self.ace_type,
AceType::AccessAllowed | AceType::AccessAllowedObject
)
}
}
#[derive(Clone, Debug, Default)]
pub struct Acl {
pub aces: Vec<Ace>,
}
#[derive(Clone, Debug, Default)]
pub struct SecurityDescriptor {
pub owner: Option<Sid>,
pub group: Option<Sid>,
pub dacl: Option<Acl>,
}
fn u16le(b: &[u8], o: usize) -> Result<u16> {
Ok(u16::from_le_bytes(
b.get(o..o + 2)
.ok_or(SddlError::Truncated("u16"))?
.try_into()
.unwrap(),
))
}
fn u32le(b: &[u8], o: usize) -> Result<u32> {
Ok(u32::from_le_bytes(
b.get(o..o + 4)
.ok_or(SddlError::Truncated("u32"))?
.try_into()
.unwrap(),
))
}
fn sid_at(b: &[u8], o: usize) -> Result<Sid> {
let count = *b.get(o + 1).ok_or(SddlError::Truncated("sid"))? as usize;
let end = o + 8 + count * 4;
Sid::from_bytes(b.get(o..end).ok_or(SddlError::Truncated("sid"))?).ok_or(SddlError::BadSid)
}
pub fn parse(b: &[u8]) -> Result<SecurityDescriptor> {
if b.len() < 20 {
return Err(SddlError::Truncated("sd header"));
}
let owner_off = u32le(b, 4)? as usize;
let group_off = u32le(b, 8)? as usize;
let dacl_off = u32le(b, 16)? as usize;
let owner = (owner_off != 0).then(|| sid_at(b, owner_off)).transpose()?;
let group = (group_off != 0).then(|| sid_at(b, group_off)).transpose()?;
let dacl = (dacl_off != 0)
.then(|| parse_acl(b, dacl_off))
.transpose()?;
Ok(SecurityDescriptor { owner, group, dacl })
}
fn parse_acl(b: &[u8], off: usize) -> Result<Acl> {
let ace_count = u16le(b, off + 4)? as usize;
let mut cur = off + 8;
let mut aces = Vec::with_capacity(ace_count);
for _ in 0..ace_count {
let ace_type_byte = *b.get(cur).ok_or(SddlError::Truncated("ace type"))?;
let flags = *b.get(cur + 1).ok_or(SddlError::Truncated("ace flags"))?;
let size = u16le(b, cur + 2)? as usize;
let ace_type = match ace_type_byte {
0x00 => AceType::AccessAllowed,
0x01 => AceType::AccessDenied,
0x05 => AceType::AccessAllowedObject,
0x06 => AceType::AccessDeniedObject,
x => AceType::Other(x),
};
let mask = AccessMask::from_bits_truncate(u32le(b, cur + 4)?);
let (object_type, inherited_object_type, sid_off) = match ace_type {
AceType::AccessAllowedObject | AceType::AccessDeniedObject => {
let obj_flags = u32le(b, cur + 8)?;
let mut p = cur + 12;
let mut ot = None;
let mut iot = None;
if obj_flags & 0x1 != 0 {
ot = b.get(p..p + 16).and_then(Guid::from_bytes);
p += 16;
}
if obj_flags & 0x2 != 0 {
iot = b.get(p..p + 16).and_then(Guid::from_bytes);
p += 16;
}
(ot, iot, p)
}
_ => (None, None, cur + 8),
};
let trustee = sid_at(b, sid_off)?;
aces.push(Ace {
ace_type,
flags,
mask,
trustee,
object_type,
inherited_object_type,
});
if size == 0 {
break;
}
cur += size;
}
Ok(Acl { aces })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rbcd_sd_roundtrips_through_parser() {
let sid = Sid::parse("S-1-5-21-1-2-3-1104").unwrap();
let sd = build_rbcd_sd(&sid);
let parsed = parse(&sd).expect("parse our own SD");
let aces = &parsed.dacl.expect("dacl").aces;
assert_eq!(aces.len(), 1);
assert!(aces[0].is_allow());
assert_eq!(aces[0].trustee, sid);
assert!(aces[0].mask.contains(AccessMask::WRITE_DAC));
}
#[test]
fn truncated_object_ace_does_not_panic() {
let mut sd = vec![1, 0, 0, 0];
sd.extend_from_slice(&0u32.to_le_bytes()); sd.extend_from_slice(&0u32.to_le_bytes()); sd.extend_from_slice(&0u32.to_le_bytes()); sd.extend_from_slice(&20u32.to_le_bytes()); sd.extend_from_slice(&[2, 0, 0x30, 0, 1, 0, 0, 0]); sd.extend_from_slice(&[0x05, 0, 0x20, 0]); sd.extend_from_slice(&0u32.to_le_bytes()); sd.extend_from_slice(&1u32.to_le_bytes()); let _ = parse(&sd); }
#[test]
fn fuzz_parse_never_panics() {
let mut seed = vec![1, 0, 0, 0];
seed.extend_from_slice(&0u32.to_le_bytes());
seed.extend_from_slice(&0u32.to_le_bytes());
seed.extend_from_slice(&0u32.to_le_bytes());
seed.extend_from_slice(&20u32.to_le_bytes());
seed.extend_from_slice(&[2, 0, 0x30, 0, 1, 0, 0, 0]);
seed.extend_from_slice(&[0x05, 0, 0x2c, 0]);
seed.extend_from_slice(&[0u8; 40]);
let mut s: u64 = 0xDEAD_BEEF_CAFE_F00D;
let mut rng = || {
s ^= s >> 12;
s ^= s << 25;
s ^= s >> 27;
s.wrapping_mul(0x2545_F491_4F6C_DD1D)
};
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let mut fail = None;
for _ in 0..200_000 {
let mut buf = if rng() & 1 == 0 {
seed.clone()
} else {
let n = (rng() as usize) % 80;
(0..n).map(|_| rng() as u8).collect::<Vec<u8>>()
};
for _ in 0..(rng() as usize % 6) {
if !buf.is_empty() {
let i = (rng() as usize) % buf.len();
buf[i] = rng() as u8;
}
}
let b = buf.clone();
if std::panic::catch_unwind(|| {
let _ = parse(&b);
})
.is_err()
{
fail = Some(buf);
break;
}
}
std::panic::set_hook(prev);
if let Some(buf) = fail {
panic!(
"parse panicked on {} bytes: {}",
buf.len(),
buf.iter().map(|x| format!("{x:02x}")).collect::<String>()
);
}
}
}