· security
chmod vs. setfacl: Where Linux File Permissions Live
chmod vs. setfacl: Where Linux File Permissions Live
chmod and setfacl both change a file’s permission metadata. The difference is where that metadata is stored and how expressive the rules can be.
In short:
chmodedits three fixed permission classes in the inode’s mode;setfacladds POSIX ACL entries in extended attributes, so you can grant access to specific extra users and groups.
Throughout this post, alice is the file owner, appuser is an additional service account, devs is an additional group, and shared/ is a placeholder directory. Substitute your own names.
chmod: Permission Bits in the Inode
Every file on Linux has an inode. Among other metadata, it records the owner (UID), the group (GID), and a mode field. chmod changes the permission bits in that mode:
-rw-r-----
│ │ └── other: everyone else
│ └───── group: members of the file's group
└──────── owner: the file's owner
Each class has three bits: r=4, w=2, and x=1. So chmod 640 file means the owner can read and write, the group can read, and everyone else has no access.
The kernel does not combine these classes. It selects exactly one class and checks only those bits:
- If the process’s effective (filesystem) UID equals the file owner, use the owner bits.
- Otherwise, if the file’s GID equals the process’s effective GID or one of its supplementary GIDs, use the group bits.
- Otherwise, use the other bits.
A privileged process can bypass these checks. On Linux, root’s power is split into capabilities: CAP_DAC_OVERRIDE overrides the permission checks, but it grants execute permission only if at least one of the file’s execute bits is set. CAP_DAC_READ_SEARCH grants read access to files and read/search access to directories.
The three classes are fixed. The mode alone cannot express a rule such as “also let appuser read this file” without changing the file’s owner or group, or opening access to other.
setfacl: ACLs in Extended Attributes
POSIX ACLs extend the mode. On Linux they are stored in an extended attribute (xattr) named system.posix_acl_access. The filesystem must support ACLs:
- ext4 supports POSIX ACLs through the
acl/noaclmount options. Many defaults come from the filesystem superblock (set withtune2fs). - XFS supports them when the kernel is built with
XFS_POSIX_ACL. - Btrfs enables the
aclmount option by default, if ACL support was compiled in.
Install the tools first. On Debian, the acl package provides getfacl and setfacl:
sudo apt install acl
Grant one extra user read access:
setfacl -m u:appuser:r shared/model.bin
getfacl shared/model.bin
A typical getfacl result with both a named user and a named group looks like this:
# file: shared/model.bin
# owner: alice
# group: alice
user::rw- # owner
user:appuser:r-- # named user
group::r-- # owning group
group:devs:r-x # named group
mask::r-x # upper limit for the group class
other::--- # everyone else
ACL Entry Types
| Entry | Meaning |
|---|---|
user:: |
The file owner |
user:NAME: |
A named user |
group:: |
The file’s owning group |
group:NAME: |
A named group |
mask:: |
Maximum permissions for named users, the owning group, and named groups |
other:: |
Everyone who matched no other entry |
An ACL with any named user or named group entry must contain exactly one mask entry.
The ACL Access Check
With an ACL, the kernel still picks one path and stops there:
- If the process is the owner, use
user::. The mask does not apply. - Otherwise, if the process matches a named user, use that
user:NAME:entry intersected withmask. - Otherwise, if the process’s effective GID or any supplementary GID matches the owning group or a named group, access is granted if any matching group entry, intersected with
mask, contains the requested permissions. If none does, access is denied. - Otherwise, use
other::.
Two details are easy to miss:
- A match ends the search. If
appuserhas a named-user entry with---, a more permissive group entry will not rescue it. - The mask is an upper limit, not a grant. For example:
user:appuser:rwx
mask::r--
appuser effectively gets only r--. getfacl shows this with a comment such as #effective:r--.
How ACLs and Mode Bits Stay in Sync
ACLs are a superset of the mode bits, and the kernel keeps the two synchronized:
| ACL entry | Corresponding mode bits |
|---|---|
user:: |
owner bits |
mask:: if present; otherwise group:: |
group bits |
other:: |
other bits |
Changing the mode bits changes the matching ACL entries, and changing those ACL entries changes the mode bits.
That is why, on a file with an extended ACL, the group bits shown by ls -l are the mask, not the group:: entry. GNU ls also prints + after the mode bits when an alternate access method, such as an ACL, applies:
-rw-r-x---+ 1 alice alice 4096 Sep 28 10:00 model.bin
This leads to a common trap:
chmod g-r shared/model.bin
You may expect this to affect only the owning group. On an ACL file, however, it removes r from the mask, so user:appuser:r-- and every group-class entry lose read access too. Likewise, chmod 600 sets the mask to ---.
The reverse also matters: by default, setfacl -m recalculates the mask as the union of the owning group, named users, and named groups, unless you give an explicit mask or use -n. A later setfacl -m can therefore restore permissions that an earlier chmod narrowed.
Default ACLs: Inheritance for New Files
An access ACL applies only to files that already exist. A directory can also have a default ACL, stored in the system.posix_acl_default xattr. The default ACL applies only to objects created later inside that directory:
# Existing tree: directories get x; files get x only if they are already executable
setfacl -R -m u:appuser:rX shared/
# Future files and subdirectories
setfacl -R -d -m u:appuser:rX shared/
Without -d, the first command updates only files and directories that exist now. Files added later will not get the appuser entry.
When a file is created in a directory that has a default ACL:
- The new object’s access ACL is copied from the directory’s default ACL.
- A new subdirectory also receives that default ACL, so inheritance continues down the tree.
- The
moderequested by the creating program still limits the result. The process umask is not applied in this case. If the program asks for mode0600, the inherited mask becomes---, so named entries become ineffective.
When the parent has no default ACL, the requested mode and the process umask determine the new file’s permissions, as usual.
To inspect or remove a default ACL:
getfacl -d shared/
setfacl -k shared/ # remove the default ACL
Who Can Change ACLs?
As with the mode, the file owner or a process with CAP_FOWNER can modify a file’s ACL. Extra ACL entries do not let a named user change permissions.
Summary
| Aspect | chmod | setfacl |
|---|---|---|
| Storage | Mode bits in the inode | xattrs system.posix_acl_access and system.posix_acl_default |
| Who can be targeted | Owner, group, other | Owner, group, other, plus any named users and groups |
| Upper limit | None | mask limits named users and all group entries |
| New-file permissions | Requested mode and umask |
Requested mode and the directory’s default ACL |
ls -l group column |
The group bits | The mask, with a trailing + |
| Best fit | Simple owner/group/other layouts | Granting access to a few specific users or groups |
Use chmod when the owner, group, and other classes are enough. Use setfacl when one extra account needs access and you do not want to change ownership or open the file to everyone. Add a default ACL with -d if new files must inherit that access. After using ACLs, remember that chmod’s group bits now control the mask.
References
- acl(5) — Linux manual page: ACL entry types, mask semantics, the access check algorithm, synchronization with mode bits, and default ACL behavior at object creation.
- setfacl(1) — Linux manual page:
-m,-d,-k,-R,-n, theXpermission, automatic mask recalculation, and who may modify ACLs. - path_resolution(7) — Linux manual page: Selection of owner/group/other permission bits and the
CAP_DAC_OVERRIDE/CAP_DAC_READ_SEARCHbypass rules. - chmod(1) — Linux manual page: Symbolic and octal mode syntax, including the 4/2/1 bit values.
- inode(7) — Linux manual page: The inode’s owner, group, and file mode metadata.
- xattr(7) — Linux manual page: Extended attribute namespaces and
system.posix_acl_accessas a system xattr. - linux/xattr.h: Kernel definitions of the
system.posix_acl_accessandsystem.posix_acl_defaultattribute names. - fs/posix_acl.c: Kernel
posix_acl_create(), which applies the umask only without a default ACL and gives new directories the parent’s default ACL. - ext4(5) — Linux manual page: ext4’s
acl/noaclmount options and superblock-derived defaults. - fs/xfs/Kconfig: The
XFS_POSIX_ACLbuild option. - BTRFS Mount Options: Btrfs
aclenabled by default and dependent on build-time support. - GNU Coreutils: What information is listed: The
+marker inls -loutput for alternate access methods such as ACLs. - Debian trixie package: acl: The Debian package that provides
getfaclandsetfacl.