Getfacl: Auditing Granular POSIX Access Control Lists, Inspecting Extended Inode Permissions, and Enforcing Multi-Tenant Security Baselines in Production
When you log into the server to investigate, standard diagnostics offer cold comfort. Running a conventional directory listing displays what appears to be textbook access: the deployer account clearly owns the directory and holds full read, write, and execute permissions. Yet the operating system kernel steadfastly refuses to write so much as a temporary lockfile, throwing an unforgiving Permission denied error every time the script attempts to run.
In standard Unix lore, file permissions are simple: there is an owner, a group, and everyone else. But in modern production environments, that three-tiered model is merely the visible crust of authorization logic. Hidden beneath traditional file attributes lies the POSIX Access Control List (ACL) subsystemβa sophisticated, fine-grained mechanism capable of silently granting, constraining, or overriding the permissions displayed on your screen.
When standard tools like ls obscure the underlying reality, you need a diagnostic instrument that inspects the filesystem's true authorization geometry. The fastest way to cut through the confusion and inspect the exact permissions governing any file or directory is a direct invocation of getfacl(1):
getfacl /srv/storage
# file: srv/storage
# owner: root
# group: storage-admins
user::rwx
user:backup-daemon:r-x
group::r-x
group:auditors:r--
mask::r-x
other::---
default:user::rwx
default:group::r-x
default:group:auditors:r--
default:mask::r-x
default:other::---
In a dozen lines of clear output, getfacl exposes what traditional Unix commands conceal: an extended list of specific user and group privileges, a governing permission ceiling known as the "mask", and default inheritance rules that dictate how newly created files inside this folder will behave.
How POSIX ACLs Work: Peeling Back the Permission Layers
To understand why getfacl is indispensable, one must look at how Linux handles file security under the hood. Traditional Unix Discretionary Access Control (DAC) encodes permissions directly inside the 16-bit i_mode field of the filesystem inode. This compact field sets aside nine discrete bits for standard access flags: read (r), write (w), and execute (x) across the owning user, the owning group, and others.
| Bit Range | Inode Flag | Operational Meaning |
|---|---|---|
| Bits 15β12 | File Type | Encodes regular file, directory, symlink, socket, or block device |
| Bit 11 | SUID (u+s) |
Executes process with the permissions of the file owner |
| Bit 10 | SGID (g+s) |
Executes with group privileges or enforces directory group inheritance |
| Bit 9 | Sticky (+t) |
Restricts file deletion within directories to file owners |
| Bits 8β6 | User (u) |
Traditional read, write, execute permissions for file owner |
| Bits 5β3 | Group / Mask (g) |
Traditional group rights, or the governing ACL Mask ceiling |
| Bits 2β0 | Other (o) |
Permissions applied to all other unauthenticated system users |
This structure serves well until an administrator encounters a modern multi-tenant requirementβsuch as granting an automated monitoring daemon read access to an application log owned by root:root with mode 0600. Under standard DAC, you would be forced to either broaden group ownership or make the file globally readable.
The POSIX.1e specification, documented in the acl(5) manual and the historical POSIX.1e draft standard 17, solves this constraint by storing arbitrary lists of user and group permissions inside filesystem Extended Attributes (xattrs). Modern Linux filesystems like ext4, XFS, and Btrfs manage these via two dedicated system namespaces:
system.posix_acl_access: Governs immediate access rules on individual files and directories.system.posix_acl_default: Defines permission inheritance templates applied automatically to child files and folders.
Whenever an extended ACL is attached to an inode, the standard group bits in i_mode no longer reflect only the owning group. Instead, the kernel repurposes those bits to define the ACL Mask.
(UID, GID, Supplementary Groups)"] --> B{"Is Process UID == File Owner UID?"} B -- Yes --> C["Evaluate Standard Owner Rights
(ACL Mask is completely bypassed)"] B -- No --> D{"Does Process Match Named User ACL?
(e.g. user:deployer:rwx)"} D -- Yes --> E["Compute Effective User Rights
Granted Rights β© ACL Mask"] D -- No --> F{"Does Process Match Owning or Named Group?
(e.g. group:auditors:r--)"} F -- Yes --> G["Compute Effective Group Rights
(Union of Matching Groups) β© ACL Mask"] F -- No --> H["Evaluate World Rights
(other::rwx)"] C --> I{"Kernel Grants or Denies Access"} E --> I G --> I H --> I
The Governing Ceiling: Understanding the ACL Mask and Effective Rights
The introduction of multiple named users (user:deployer:rwx) and named groups (group:analytics:r-x) introduces a potential security risk: granting rights to secondary users could accidentally escalate privileges across shared files. To maintain safety, POSIX ACLs implement the ACL Mask (mask::rwx).
The mask acts as an absolute upper ceiling for any user or group other than the file owner and the "other" category:
- File Owner: Access is determined exclusively by the
user::entry. The mask is ignored entirely. - Named Users: Granted permissions are filtered through a bitwise intersection with the mask: $$\text{Effective Rights} = \text{ACL}{\text{user}} \land \text{ACL}{\text{mask}}$$
- Groups: If a user belongs to the owning group or any supplementary groups assigned via ACL entries, their permissions are combined into a union and then filtered through the mask: $$\text{Effective Rights} = \left( \bigcup_{g \in G} \text{ACL}{g} \right) \land \text{ACL}{\text{mask}}$$
- Other: If no user or group entries match, the kernel falls back to the
other::entry, evaluated independently of the mask.
This mathematical model explains why permission issues often baffle administrators. If an automated script or administrator runs chmod g-w file.txt on an ACL-enabled file, the command does not just alter the owning group's write permissionsβit rewrites the i_mode group bits, which directly mutates the ACL Mask. In a single stroke, every named user and group on that file has their effective write permissions revoked, regardless of what their individual ACL entries say.
Under the Hood: Inode Topography and Kernel Caching
To understand why getfacl behaves the way it does, it helps to look at where ACL data lives on disk. In native ext4 filesystems, small ACLs (typically up to four entries) are stored directly inside the unused space of the inode itself (i_extra_isize).
When an ACL grows larger, the filesystem allocates an external 4,096-byte Extended Attribute (EA) block on the disk.
(i_mode, i_uid, i_size, timestamps)"] IX["Inline xattr Space
(Stores ~4 compact ACL entries)"] BP["i_file_acl Block Pointer"] end subgraph External["External Filesystem Block"] EB["4096-Byte Shared EA Block
Array of posix_acl Entries
(Deduplicated and refcounted across inodes)"] end BP -->|"Allocated when entries exceed inline storage"| EB
At the kernel level:
- Inodes cached in memory maintain an RCU-protected pointer (inode->i_acl and inode->i_default_acl) to a compiled struct posix_acl in RAM.
- Uncached traversals over deep directory structures containing extensive ACLs can incur I/O overhead: the kernel must issue secondary block read requests to fetch external EA blocks, which can lead to disk cache churn on high-concurrency storage systems.
- When getfacl executes, it bypasses the conventional stat(2) system call in favor of explicit listxattr(2) and getxattr(2) calls, communicating directly with the kernel's extended attribute namespace.
For further architecture details, consult the Red Hat Enterprise Linux Storage Administration Guide.
Core Flags & Diagnostic Switches
The getfacl utility provides several flags designed to filter out noise, inspect inheritance, and produce machine-readable output:
| Flag | Long Option | Practical Purpose |
|---|---|---|
-a |
--access |
Displays only the immediate file access control list, suppressing default directory templates. |
-d |
--default |
Displays only default inheritance templates on directories, showing what child objects will receive. |
-c |
--omit-header |
Strips the three-line comment header (# file:, # owner:, # group:) for cleaner script parsing. |
-e |
--all-effective |
Forces output of inline #effective: comments beside every entry, making mask restrictions obvious. |
-E |
--no-effective |
Suppresses the calculation and display of #effective: comments. |
-s |
--skip-base |
Skips files that possess only standard Unix permissions, highlighting only ACL-customized files. |
-R |
--recursive |
Traverses directories recursively to audit entire storage hierarchies. |
-n |
--numeric |
Outputs numeric UIDs and GIDs directly without resolving textual usernames via system lookups. |
-p |
--absolute-names |
Preserves leading slashes in paths, creating reliable manifests for backup and restore operations. |
5 Production-Grade Real-World Use Cases
The following real-world scenarios demonstrate how systems engineers, security auditors, and site reliability engineers use getfacl to diagnose outages, verify security compliance, and safeguard storage systems.
Case 1: Recursive Security Auditing on Multi-Tenant Storage Trees
Scenario
A security auditor needs to review a multi-terabyte corporate NFS share at /mnt/tenant_storage/shared_data. Of the 500,000 files on disk, 99.8% adhere to standard base permissions (0755 for directories, 0644 for files). Over the years, however, previous administrators used setfacl to resolve ad-hoc access tickets. The auditor needs to locate every non-standard file carrying extended permissions without wading through millions of lines of standard output.
Command Execution
getfacl -R -s -p /mnt/tenant_storage/shared_data
Mock Terminal Output
# file: /mnt/tenant_storage/shared_data/finance/q4_projections.xlsx
# owner: cfo_user
# group: finance_dept
user::rw-
user:ext_consultant:r--
group::r--
mask::r--
other::---
# file: /mnt/tenant_storage/shared_data/engineering/firmware_signing.key
# owner: root
# group: secure_ops
user::rw-
user:ci_builder:rw-
group::---
mask::rw-
other::---
Line-by-Line Technical Analysis
getfacl -R -s -p: Recursively (-R) crawls the directory tree, retaining absolute path slashes (-p), while--skip-base(-s) filters out every file whose access rules match standard Unix permissions.# file: /mnt/tenant_storage/.../q4_projections.xlsx: Pinpoints the exact path of an anomalous file.user:ext_consultant:r--: An explicit ACL grant allowing an external consultant read access to financial data.mask::r--: Confirms the operating mask restricts named users and groups to read-only access.user:ci_builder:rw-: Highlights a custom rule granting a continuous integration service account read-write permissions on a private key owned byroot.
Administrator Next Steps
The auditor compiles these entries into an access review report. If the external consultant's contract has concluded, the administrator strips the custom rule using setfacl(1):
setfacl -x u:ext_consultant /mnt/tenant_storage/shared_data/finance/q4_projections.xlsx
Case 2: Auditing Inheritance Geometries for Autonomous Build Systems
Scenario
A nightly build system automatically provisions new artifact directories under /srv/shared/data. Newly created build outputs and database dumps consistently fail to grant write permissions to the jenkins group, causing automated archiving jobs to crash. The engineering team must inspect the parent directory's default inheritance blueprint to see why newly created files are missing group write rights.
Command Execution
getfacl -d /srv/shared/data
Mock Terminal Output
# file: srv/shared/data
# owner: buildmaster
# group: engineering
user::rwx
group::r-x
group:jenkins:rwx
group:db-backup:r-x
mask::r-x
other::---
Line-by-Line Technical Analysis
getfacl -d: Queries thesystem.posix_acl_defaultattribute on the directory, ignoring immediate access rules to display solely the template inherited by new children.user::rwx: Ensures newly created subdirectories grant full rights to their creator.group:jenkins:rwx: Intends to grant thejenkinsgroup full read, write, and traverse permissions on new children.group:db-backup:r-x: Grants the backup group read and traverse permissions.mask::r-x: The root cause. The default mask was set tor-x. Because the mask enforces a strict ceiling on all non-owner entries,group:jenkinsis constrained:rwxfiltered throughr-xresults in an effective permission ofr-x. The write bit is stripped at creation time.
Administrator Next Steps
The engineer corrects the default mask on the parent directory using setfacl:
setfacl -d -m m::rwx /srv/shared/data
All future directories and files generated inside /srv/shared/data will now grant full write access to group:jenkins:rwx.
Case 3: Diagnosing Permission Denials via Effective Rights and Mask Recalculation
Scenario
A deployment agent authenticating as deployer attempts to unpack an application release into /var/www/production/releases/20260819_build. The process fails with an EACCES: Permission denied error. A junior engineer checks the ACLs and notes that user:deployer:rwx is clearly listed. A senior engineer runs getfacl -e to show how the mask is silently blocking write privileges.
Command Execution
getfacl -e /var/www/production/releases/20260819_build
Mock Terminal Output
# file: var/www/production/releases/20260819_build
# owner: root
# group: root
user::rwx
user:deployer:rwx #effective:r--
group::r-x #effective:r--
group:webadmins:rwx #effective:r--
mask::r--
other::---
Line-by-Line Technical Analysis
getfacl -e: Instructsgetfaclto calculate effective permissions and display an inline#effective:comment beside each affected entry.user:deployer:rwx #effective:r--: While the rule explicitly specifiesrwx, evaluating it against the mask (r--) yields only read permissions. The user cannot write to the directory.group:webadmins:rwx #effective:r--: Similarly, thewebadminsgroup has write and execute capabilities stripped down to read-only.mask::r--: Confirms that the mask was reduced tor--, typically caused when an automated maintenance script executed a standardchmod 644orchmod g-wacross the release path.
Administrator Next Steps
The engineer restores the ACL mask without modifying base file ownership:
setfacl -m m::rwx /var/www/production/releases/20260819_build
Verifying with getfacl -e immediately confirms the fix:
user:deployer:rwx #effective:rwx
mask::rwx
Case 4: Disaster Recovery & Idempotent State Manifests
Scenario
A Linux infrastructure team is preparing to migrate an application data store from an older storage volume to a modern NVMe array. Standard migration tools like default tar or unflagged rsync strip extended attributes unless specific flags are provided. The team requires a complete, version-controlled backup manifest of all ACL permissions across /var/www/html to guarantee full disaster recovery.
Command Execution
getfacl -R -p /var/www/html > /backups/acl_manifest_var_www_html.bak
Manifest Verification
head -n 25 /backups/acl_manifest_var_www_html.bak
Mock Terminal Output
# file: /var/www/html
# owner: www-data
# group: www-data
user::rwx
user:deployer:rwx
group::r-x
group:auditors:r--
mask::rwx
other::---
default:user::rwx
default:user:deployer:rwx
default:group::r-x
default:mask::rwx
default:other::---
# file: /var/www/html/index.php
# owner: www-data
# group: www-data
user::rw-
user:deployer:rw-
group::r--
mask::rw-
other::---
Line-by-Line Technical Analysis
getfacl -R -p ... > file.bak: Recursively dumps all file and directory permissions while preserving absolute paths (-p). Absolute paths ensure that restoration scripts can run from any working directory without path ambiguity.- The output format matches the POSIX.1e standard syntax expected by
setfacl --restore. - Comments delineate file boundaries, followed by discrete user rules, the calculated mask, and default inheritance definitions.
Disaster Recovery Restoration Workflow
If a faulty deployment script strips permissions across the tree, the administrator can restore the entire permission structure across thousands of subdirectories with a single command:
setfacl --restore=/backups/acl_manifest_var_www_html.bak
This parses the manifest and re-applies the system.posix_acl_access and system.posix_acl_default attributes to their exact filesystem locations.
Case 5: Container Storage & Numeric UID/GID Audits in Rootless Namespaces
Scenario
A security engineer is inspecting a Kubernetes node running rootless Podman containers. Host storage under /var/lib/containers/storage/volumes/app_data/_data is bind-mounted directly into an isolated container namespace. The host system's user directory (/etc/passwd) does not recognize the container's mapped UIDs (/etc/subuid). Running getfacl normally displays confusing user mappings. The engineer needs to inspect raw numeric IDs to ensure container processes do not hold unexpected host privileges.
Command Execution
getfacl -n /var/lib/containers/storage/volumes/app_data/_data
Mock Terminal Output
# file: var/lib/containers/storage/volumes/app_data/_data
# owner: 100000
# group: 100000
user::rwx
user:100001:r-x
user:165536:rwx
group::r-x
group:100002:r--
mask::rwx
other::---
default:user::rwx
default:user:165536:rwx
default:group::r-x
default:mask::rwx
default:other::---
Line-by-Line Technical Analysis
getfacl -n: The--numericflag bypasses all name lookups, forcinggetfaclto print raw integer user and group IDs.# owner: 100000: Identifies the directory owner as subordinate UID100000(which maps to container rootUID 0).user:165536:rwx: Pinpoints an explicit grant to container UID165536.- Reviewing raw numbers confirms that no standard host users (such as host UID
0or1000) have been granted unintended access due to local username collisions.
Administrator Next Steps
If UID 165536 represents an unprivileged container process that should not hold write permissions, the administrator revokes the access entry using its numeric identifier:
setfacl -x u:165536 /var/lib/containers/storage/volumes/app_data/_data
What Can Go Wrong: Common Pitfalls and Mitigation
Working with POSIX ACLs introduces subtle operational behaviors that can catch even experienced administrators off guard.
| Operational Trap | Immediate Consequence | Safe Practice / Fix |
|---|---|---|
The chmod Mask Invalidation |
Running chmod g-w or chmod 755 silently changes the ACL mask, revoking write access for all named users and groups. |
Use setfacl -m for targeted changes, and verify with getfacl -e. |
| The Archival Blind Spot | Standard tar -czf and rsync -av omit extended attributes, stripping all ACLs during backup or transfer. |
Pass --acls --xattrs to tar, and -A -X to rsync. |
| The Network Traversal Bottleneck | Running getfacl -R on network storage issues thousands of synchronous getxattr requests across the network. |
Use getfacl -s to skip base files, or run audits during maintenance windows. |
1. The chmod Mask Invalidation Hazard (Silent Mask Downgrade)
The Problem: Administrators accustomed to standard Unix permissions often rely on chmod to quickly adjust directory access. When executed on a file that possesses an extended ACL, running chmod 755 file or chmod g-w file does not modify the group permissions in isolation. Instead, the Linux kernel updates the mask:: entry to match whatever bits were specified for the group class.
The Consequence: A named user who was explicitly granted user:developer:rwx will have their effective write permission stripped if an automated cron job or deployment script runs chmod 755 file.
Mitigation: Always check effective rights using getfacl -e <file> after running permission updates. In environments governed by ACLs, train operational teams to manage access using setfacl -m rather than chmod.
2. The Archival and Synchronization Blind Spot
The Problem: Standard backup and synchronization utilities do not capture extended filesystem attributes by default. Running tar -czf backup.tar.gz /srv/data or rsync -av /srv/data /backup/ copies only basic Unix mode bits.
The Consequence: Restoring data from these archives strips away all custom access entries and inheritance templates. Applications and automated services fail immediately upon recovery due to missing authorization records.
Mitigation:
- When creating archives with tar, include the --acls and --xattrs flags:
bash
tar --acls --xattrs -czvf backup.tar.gz /srv/data
- When synchronizing files with rsync, include the -A (--acls) and -X (--xattrs) flags:
bash
rsync -aAXv /srv/data/ /backup/data/
- Generate an independent text manifest with getfacl -R -p /srv/data > /backup/acl_manifest.bak alongside regular backups.
3. I/O Amplification on Network Filesystems
The Problem: Running getfacl -R across millions of files on high-latency network filesystems (such as NFSv4, CephFS, or GlusterFS) can place significant load on storage controllers.
The Consequence: Unlike ls -la, which retrieves basic metadata from directory read buffers, getfacl issues explicit getxattr(2) system calls for every individual inode. On a share containing 500,000 files, this generates 500,000 separate synchronous network round-trips.
Mitigation: Scope inspections to specific subdirectories, use -s (--skip-base) to avoid querying standard files, and schedule large audits during maintenance windows.
Technical Reference & Authoritative Documentation
For further reading on POSIX access control lists, extended attributes, and kernel filesystem security, refer to the following resources:
getfacl(1)Linux Manual Page β Official documentation covering command syntax, formatting switches, and path handling.acl(5)Access Control Lists Overview β Comprehensive overview of POSIX.1e data structures, evaluation logic, and mask calculation.setfacl(1)Linux Manual Page β Command-line reference for creating, modifying, and restoring access lists.attr(5)Extended Attributes System Manual β Technical specifications for extended attribute namespaces (system,user,security).- ArchWiki: Access Control Lists β Practical implementation guides, systemd considerations, and troubleshooting patterns.
Today's Takeaway
The presence of a subtle plus sign (+) at the end of the permission string in an ls -l listing (such as drwxr-xr-x+) is a clear signal that traditional Unix rules are no longer the sole authority governing that file. Open your terminal right now, navigate to an active workspace or project folder, and run getfacl -e .. Take a moment to examine the output: look for any named user or group entries, check the value of the mask:: line, and trace how your effective permissions are calculated in real time. Familiarizing yourself with this single command ensures that the next time an unexpected permission error strikes your deployment pipeline, you can diagnose the kernel's decisions in seconds.