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

You have built an ACL and serialised it. Now the question that matters: can user X do operation Y on this file? This page covers the POSIX.1e access-check algorithm as implemented by PosixAcl::check_access, the role of the mask entry, and conversion between ACLs and Unix mode words.

The access-check algorithm

check_access implements POSIX.1e section 17.4. It walks the ACL entries in a fixed order and returns on the first match:

  1. Owner – if the user is the file owner, use the user:: permissions. No masking is applied.
  2. Named user – if a user:name: entry matches, return its permissions masked with the mask entry.
  3. Group – collect all matching group:name: entries (by checking the user’s group list) plus the group:: entry if the owning group is in the user’s groups. If any match, return the join (bitwise OR) of all matched entries’ masked permissions.
  4. Other – if nothing else matched, use other:: permissions. No masking is applied.
#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::R)
    .add_user("bob", PosixPerm::RW)
    .add_group("ops", PosixPerm::R)
    .build();

// 1. Owner match -- uses user:: directly, no masking
let r = acl.check_access("alice", &[] as &[&str], PosixPerm::RWX);
assert!(r.granted);

// 2. Named user match -- masked with mask entry
let r = acl.check_access("bob", &["staff"] as &[&str], PosixPerm::R);
assert!(r.granted);

// 3. Group match -- owning group via group list
let r = acl.check_access("charlie", &["devs"] as &[&str], PosixPerm::R);
assert!(r.granted);

// 3b. Named group match
let r = acl.check_access("dave", &["ops"] as &[&str], PosixPerm::R);
assert!(r.granted);

// 4. Other fallback
let r = acl.check_access("eve", &["guests"] as &[&str], PosixPerm::R);
assert!(r.granted);
}

AccessResult

check_access returns an AccessResult with three fields:

FieldTypeMeaning
grantedboolWhether the requested permissions are fully held
matched_tagAclTagWhich entry determined the result
effective_permsPosixPermThe actual permissions after masking

The matched_tag field tells you not just yes/no, but why – useful for auditing and debugging:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, AclTag, PosixPerm};

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::NONE)
    .add_user("bob", PosixPerm::RW)
    .build();

let r = acl.check_access("alice", &[] as &[&str], PosixPerm::RWX);
assert_eq!(r.matched_tag, AclTag::UserObj);
assert_eq!(r.effective_perms, PosixPerm::RWX);

let r = acl.check_access("bob", &[] as &[&str], PosixPerm::R);
assert!(matches!(r.matched_tag, AclTag::User(ref n) if n == "bob"));

let r = acl.check_access("eve", &[] as &[&str], PosixPerm::R);
assert_eq!(r.matched_tag, AclTag::Other);
assert!(!r.granted);
}

The mask’s role

The mask entry limits effective permissions for named users and named groups. It does not affect the owner (user::) or other (other::) entries. This design lets the file owner set an upper bound on everyone else’s access:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};

let mut acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::NONE)
    .other_perms(PosixPerm::NONE)
    .add_user("bob", PosixPerm::RWX)
    .build();

// Auto-computed mask is RWX -- bob gets full access
let r = acl.check_access("bob", &[] as &[&str], PosixPerm::W);
assert!(r.granted);

// Override mask to R only
acl.set_mask(PosixPerm::R);

// Now bob's effective permissions are RWX & R = R
let r = acl.check_access("bob", &[] as &[&str], PosixPerm::W);
assert!(!r.granted);
assert_eq!(r.effective_perms, PosixPerm::R);
}

The mask is auto-computed by AclBuilder as the bitwise OR of all named user, named group, and owning group entries. You can also compute it manually with acl.compute_mask() and set it with acl.set_mask(mask).

Group accumulation

When a user is a member of multiple groups that have ACL entries, the algorithm joins (ORs) all matching group permissions. This is a least-restrictive merge:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::R)
    .other_perms(PosixPerm::NONE)
    .add_group("ops", PosixPerm::W)
    .add_group("deploy", PosixPerm::X)
    .build();

// Charlie is in both ops and deploy -- effective = W | X = WX (after masking)
let r = acl.check_access(
    "charlie",
    &["ops", "deploy"] as &[&str],
    PosixPerm::WX,
);
assert!(r.granted);
}

Mode word conversion

POSIX ACLs and traditional Unix mode words are two views of the same permission model. PosixAcl::from_mode builds a minimal (non-extended) ACL from a 12-bit mode word, and PosixAcl::to_mode converts back:

#![allow(unused)]
fn main() {
use posix_acls::{PosixAcl, PosixPerm};

let acl = PosixAcl::from_mode("alice", "devs", 0o755);
assert_eq!(acl.entries()[0].perms, PosixPerm::RWX);  // user::rwx
assert_eq!(acl.entries()[1].perms, PosixPerm::RX);   // group::r-x
assert_eq!(acl.entries()[2].perms, PosixPerm::RX);   // other::r-x
assert_eq!(acl.to_mode(), 0o755);
}

Mode words include the special bits in the high 3 bits:

#![allow(unused)]
fn main() {
use posix_acls::PosixAcl;

// 0o2755 = SGID + rwxr-xr-x
let acl = PosixAcl::from_mode("alice", "devs", 0o2755);
assert!(acl.special_bits().sgid());
assert_eq!(acl.to_mode(), 0o2755);
}

For extended ACLs, to_mode follows POSIX.1e section 23.1.6: the group bits in the mode word reflect the mask entry, not the group:: permissions. This is what stat(2) and ls -l show:

#![allow(unused)]
fn main() {
use posix_acls::{AclBuilder, PosixPerm};

let acl = AclBuilder::new("alice", "devs")
    .owner_perms(PosixPerm::RWX)
    .owning_group_perms(PosixPerm::RX)
    .other_perms(PosixPerm::R)
    .add_user("bob", PosixPerm::RW)
    .build();

// mask = RW | RX = RWX → stat() shows group bits as rwx
let mode = acl.to_mode();
assert_eq!((mode >> 3) & 0o7, 0o7);
}

Minimal ACL mode-word round-trips are exhaustively tested for all 4096 values (9 permission bits plus 3 special bits).

Next steps

You know how permissions are checked and how ACLs map to mode words. When your organisation has many users and groups, managing ACLs manually becomes error-prone. The next page introduces the user/group model and policy system for declaring and validating ACLs at a higher level.

See User/Group Model and Policy.