cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

fix/hero-release-note
goq / crates / sigil-host / src / input / acl.rs
acl.rs 6.9K
1#[cfg(any(target_os = "linux", test))]2use std::mem::size_of;34#[cfg(any(target_os = "linux", test))]5use anyhow::{Result, bail};67#[cfg(any(target_os = "linux", test))]8const POSIX_ACL_XATTR_VERSION: u32 = 0x0002;9#[cfg(any(target_os = "linux", test))]10const POSIX_ACL_ENTRY_BYTES: usize = 8;11#[cfg(any(target_os = "linux", test))]12const POSIX_ACL_REQUIRED_ENTRIES: usize = 5;13#[cfg(any(target_os = "linux", test))]14pub(crate) const POSIX_ACL_REQUIRED_BYTES: usize =15 size_of::<u32>() + POSIX_ACL_ENTRY_BYTES * POSIX_ACL_REQUIRED_ENTRIES;16#[cfg(any(target_os = "linux", test))]17const POSIX_ACL_UNDEFINED_ID: u32 = u32::MAX;18#[cfg(any(target_os = "linux", test))]19const POSIX_ACL_USER_OBJ: u16 = 0x01;20#[cfg(any(target_os = "linux", test))]21const POSIX_ACL_USER: u16 = 0x02;22#[cfg(any(target_os = "linux", test))]23const POSIX_ACL_GROUP_OBJ: u16 = 0x04;24#[cfg(any(target_os = "linux", test))]25const POSIX_ACL_MASK: u16 = 0x10;26#[cfg(any(target_os = "linux", test))]27const POSIX_ACL_OTHER: u16 = 0x20;28#[cfg(any(target_os = "linux", test))]29const POSIX_ACL_READ_WRITE: u16 = 0x06;3031/// Validate the only extended uinput ACL shape Sigil accepts.32///33/// Linux stores POSIX ACL xattrs as one little-endian version followed by34/// fixed-size entries. Requiring the canonical five-entry form prevents a35/// configured one-user exception from admitting extra users or groups.36#[cfg(any(target_os = "linux", test))]37pub(crate) fn validate_single_user_access_acl(38 bytes: &[u8],39 expected_uid: u32,40 expected_mode: u32,41) -> Result<()> {42 if bytes.len() != POSIX_ACL_REQUIRED_BYTES {43 bail!(44 "configured uinput access ACL must contain exactly one named user and no named groups"45 );46 }47 let version = u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);48 if version != POSIX_ACL_XATTR_VERSION {49 bail!("configured uinput access ACL has an unsupported Linux ACL version");50 }5152 let expected_entries = [53 (54 POSIX_ACL_USER_OBJ,55 ((expected_mode >> 6) & 0o7) as u16,56 POSIX_ACL_UNDEFINED_ID,57 ),58 (POSIX_ACL_USER, POSIX_ACL_READ_WRITE, expected_uid),59 (60 POSIX_ACL_GROUP_OBJ,61 ((expected_mode >> 3) & 0o7) as u16,62 POSIX_ACL_UNDEFINED_ID,63 ),64 (65 POSIX_ACL_MASK,66 ((expected_mode >> 3) & 0o7) as u16,67 POSIX_ACL_UNDEFINED_ID,68 ),69 (70 POSIX_ACL_OTHER,71 (expected_mode & 0o7) as u16,72 POSIX_ACL_UNDEFINED_ID,73 ),74 ];7576 for (index, (expected_tag, expected_permissions, expected_id)) in77 expected_entries.into_iter().enumerate()78 {79 let offset = size_of::<u32>() + index * POSIX_ACL_ENTRY_BYTES;80 let tag = u16::from_le_bytes([bytes[offset], bytes[offset + 1]]);81 let permissions = u16::from_le_bytes([bytes[offset + 2], bytes[offset + 3]]);82 let id = u32::from_le_bytes([83 bytes[offset + 4],84 bytes[offset + 5],85 bytes[offset + 6],86 bytes[offset + 7],87 ]);88 if permissions & !0o7 != 0 {89 bail!("configured uinput access ACL contains invalid permission bits");90 }91 if tag != expected_tag || permissions != expected_permissions || id != expected_id {92 bail!(93 "configured uinput access ACL does not match the exact one-user owner/group/mode contract"94 );95 }96 }97 Ok(())98}99100#[cfg(test)]101mod tests {102 use super::*;103104 fn acl_entry(tag: u16, permissions: u16, id: u32) -> [u8; POSIX_ACL_ENTRY_BYTES] {105 let mut bytes = [0_u8; POSIX_ACL_ENTRY_BYTES];106 bytes[0..2].copy_from_slice(&tag.to_le_bytes());107 bytes[2..4].copy_from_slice(&permissions.to_le_bytes());108 bytes[4..8].copy_from_slice(&id.to_le_bytes());109 bytes110 }111112 fn one_user_access_acl(uid: u32, mode: u32) -> Vec<u8> {113 let entries = [114 acl_entry(115 POSIX_ACL_USER_OBJ,116 ((mode >> 6) & 0o7) as u16,117 POSIX_ACL_UNDEFINED_ID,118 ),119 acl_entry(POSIX_ACL_USER, POSIX_ACL_READ_WRITE, uid),120 acl_entry(121 POSIX_ACL_GROUP_OBJ,122 ((mode >> 3) & 0o7) as u16,123 POSIX_ACL_UNDEFINED_ID,124 ),125 acl_entry(126 POSIX_ACL_MASK,127 ((mode >> 3) & 0o7) as u16,128 POSIX_ACL_UNDEFINED_ID,129 ),130 acl_entry(POSIX_ACL_OTHER, (mode & 0o7) as u16, POSIX_ACL_UNDEFINED_ID),131 ];132 let mut bytes = Vec::with_capacity(POSIX_ACL_REQUIRED_BYTES);133 bytes.extend_from_slice(&POSIX_ACL_XATTR_VERSION.to_le_bytes());134 for entry in entries {135 bytes.extend_from_slice(&entry);136 }137 bytes138 }139140 #[test]141 fn exact_one_user_access_acl_is_accepted() {142 let bytes = one_user_access_acl(1000, 0o660);143 assert_eq!(bytes.len(), POSIX_ACL_REQUIRED_BYTES);144 validate_single_user_access_acl(&bytes, 1000, 0o660).unwrap();145 }146147 #[test]148 fn one_user_access_acl_rejects_wrong_principal_and_permissions() {149 let bytes = one_user_access_acl(1000, 0o660);150 assert!(validate_single_user_access_acl(&bytes, 1001, 0o660).is_err());151152 for permission_offset in [6, 14, 22, 30, 38] {153 let mut changed = bytes.clone();154 changed[permission_offset] ^= 0x01;155 assert!(validate_single_user_access_acl(&changed, 1000, 0o660).is_err());156 }157158 let mut invalid_permissions = bytes;159 invalid_permissions[14] = 0x08;160 assert!(validate_single_user_access_acl(&invalid_permissions, 1000, 0o660).is_err());161 }162163 #[test]164 fn one_user_access_acl_rejects_extra_duplicate_and_named_group_entries() {165 let bytes = one_user_access_acl(1000, 0o660);166167 let mut extra = bytes.clone();168 extra.extend_from_slice(&acl_entry(POSIX_ACL_USER, POSIX_ACL_READ_WRITE, 1001));169 assert!(validate_single_user_access_acl(&extra, 1000, 0o660).is_err());170171 let mut duplicate_user = bytes.clone();172 duplicate_user[20..22].copy_from_slice(&POSIX_ACL_USER.to_le_bytes());173 assert!(validate_single_user_access_acl(&duplicate_user, 1000, 0o660).is_err());174175 let mut named_group = bytes;176 named_group[20..22].copy_from_slice(&0x08_u16.to_le_bytes());177 assert!(validate_single_user_access_acl(&named_group, 1000, 0o660).is_err());178 }179180 #[test]181 fn one_user_access_acl_rejects_bad_version_and_non_exact_lengths() {182 let bytes = one_user_access_acl(1000, 0o660);183184 let mut bad_version = bytes.clone();185 bad_version[0..4].copy_from_slice(&3_u32.to_le_bytes());186 assert!(validate_single_user_access_acl(&bad_version, 1000, 0o660).is_err());187 assert!(validate_single_user_access_acl(&bytes[..bytes.len() - 1], 1000, 0o660).is_err());188189 let mut oversized = bytes;190 oversized.push(0);191 assert!(validate_single_user_access_acl(&oversized, 1000, 0o660).is_err());192 }193}