Research notes / Recovery metadata
Restoring Linux ACLs: identical bytes, different permissions
Published / Controlled metadata observations
Our local rsync copy preserved every byte and the file mode 0660, yet changed the owning-group entry from no permissions to read/write. Adding ACL preservation prevented that change. A successful content comparison can therefore miss a consequential difference in recovery metadata.
On this page8 sections
The same 0660 can describe different access rules
The source fixture had owner read/write, a named-user entry granting read/write, no owning-group permissions, a read/write mask and no permissions for others. Its numeric mode was 0660. After rsync -a into a fresh destination without inherited ACLs, the named-user entry and mask were absent. The remaining owning-group entry was read/write. The mode stayed 0660 and all 448 payload bytes were identical.
This follows from how Linux maps an extended POSIX ACL to mode bits: when a mask exists, the middle mode digit represents that mask, not the owning-group entry alone. Without the extended ACL, those same bits represent the owning group directly. [1] Thus losing ACL metadata can broaden one entry while removing another, not merely make the restored file more restrictive.
Rsync documents that archive mode does not imply ACL preservation. The -A option preserves ACLs; -X handles extended attributes separately. Our -aX control still lost the ACL, while -aA retained it exactly. These options have distinct contracts even where a filesystem internally stores ACLs as attributes. Compatible ACL semantics and sufficient permissions remain prerequisites. [2]
Eleven controls, two mount types
We repeated each scenario three times on ext4 and tmpfs: 66 case executions on one host, running Linux 6.8.0-146-generic, rsync version 3.2.7 protocol version 31 and setfacl 2.3.2. Every repetition produced the outcomes below. This is a repeatability check, not 66 independent systems or an estimate of incident frequency.
| Operation | Resulting mode | UID 12345 entry, effective | Owning-group entry, effective |
|---|---|---|---|
| Copy: rsync -a | 0660 | No entry | rw- |
| Copy: rsync -aX | 0660 | No entry | rw- |
| Copy: rsync -aA | 0660 | rw- | --- |
| chmod 0660 on masked ACL | 0660 | rw- | --- |
| Add second user; automatic mask | 0660 | rw- | --- |
| Add second user; retain mask (-n) | 0640 | r-- | --- |
| Set ACL, then chmod 0600 | 0600 | --- | --- |
| chmod 0600, then set ACL | 0640 | r-- | --- |
| Create 0666, no default ACL | 0600 | No entry | --- |
| Create 0666, with default ACL | 0660 | rw- | --- |
| Create 0600, with default ACL | 0600 | --- | --- |
Copy cases start from mask rw-. The chmod, editing and restore-order cases start from a named-user entry rw- restricted by mask r--. Creation controls use umask 0077 and either no default ACL or a default ACL with named-user and mask permissions rwx. Each scenario uses fresh fixtures; rows are not successive edits to one file.
A later chmod can reactivate a stored permission
Changing the masked fixture from mode 0640 to 0660 left its named-user entry at rw- and widened the mask. The entry's effective permissions changed from r-- to rw-, although no command explicitly edited that user. The owning-group entry remained ---. The Linux v6.8 implementation makes the distinction explicit: __posix_acl_chmod_masq() changes the mask when present, otherwise the owning-group entry. [5]
There is a second path to the same outcome. Adding a read-only entry for UID 12346 using ordinary setfacl -m recalculated the mask and re-enabled UID 12345's stored write permission. With setfacl -n -m, the original read-only mask survived. The utility documents this recalculation; it is not a kernel defect. [3] Review the whole ACL after an edit, including permissions that existed but were previously masked.
The restore-order controls applied deliberately conflicting targets: an ACL equivalent to mode 0640 and a chmod target of 0600. Applying chmod last suppressed the named entry; applying the ACL last restored its effective read permission and mode 0640. No ordering rule can satisfy two contradictory targets simultaneously. Decide which complete policy is authoritative, then verify the final state.
A restrictive umask does not override a default ACL
Under umask 0077, our plain-directory creation with requested mode 0666 yielded mode 0600. The same call inside the default-ACL directory yielded 0660 with effective read/write for the named entry. Requesting mode 0600 in that directory retained the named entry but masked its permissions to zero.
This distinguishes the process umask from the ACL mask. Linux documents default-ACL inheritance as an alternative to applying the umask, while the requested creation mode still limits the resulting permissions. [4] A program that explicitly requests 0600 therefore differs from one requesting 0666 under umask 0077. The probe uses os.open with explicit arguments to avoid hiding that distinction behind a copying tool.
The overlooked recovery problem has a long history
Andreas Gruenbacher's 2003 FREENIX paper explains why extended ACLs map the group class to a mask, and separately examines backup and restore in section 13. Read together, these sections connect compatibility with old permission tools to a recovery obligation: file content and coarse modes do not encode the full policy. [6]
The paper describes implementations and tool limitations of its period. Its statements about archive-tool support must not be used as a current product matrix. Our contribution is a small, current, downloadable set of cross-operation controls that connects this design history to an actual rsync 3.2.7 run. It reproduces documented behavior rather than claiming a new vulnerability.
Include the permission policy in recovery acceptance
Access checks select entries in order; they do not combine every permission in the ACL. A matching named user whose masked entry denies the requested operation does not fall through to a permissive other entry. The owner is checked before named users. This documented selection rule is another reason our per-entry table is not an end-to-end access test. [5]
For a real restore, our recommendation is to compare content and metadata separately. Check numeric owner/group identities, complete access ACLs, default ACLs on directories and final mode bits after all post-processing. Confirm the intended identity mapping on the destination; matching an integer alone does not prove it represents the same person.
Then test representative operations as the intended identities, including directory traversal and creation of new children. This probe does not do that: entry-and-mask intersections are not a complete authorization decision. Parent-directory permissions, identity/group membership, capabilities and other security mechanisms can change the outcome. Windows and NFSv4 ACL translation also require their own checks.
This extends our backup verification analysis: identical payload hashes are useful evidence, but not a substitute for the application's recovery contract. For change discovery before capture, see the inotify experiment.
Reproduce and inspect the full ACLs
Download the MIT-licensed Python probe and CC0 observation JSON. Requirements: Linux, Python 3, getfacl, setfacl, rsync with ACL support, findmnt and writable ACL-capable test directories.
python3 acl-restore.py --directory . --directory /dev/shm --repetitions 3 --output observations.jsonThe probe runs unprivileged, creates private temporary directories, removes inherited ACLs on its test roots and deletes only its own fixtures. It records full numeric access ACLs, modes, exact-byte checks, hashes, versions and its own SHA-256. Unsupported operations fail the run rather than silently become successful cases. The observations contain no user data.
Both mounts were on one host. There were no concurrent writers, cross-host restores, arbitrary ownership changes, impersonated access checks or power interruptions. The result establishes these metadata transformations in this environment; it does not certify a backup product or the durability of a recovery.
Primary sources
- Linux ACL project: acl(5), permission-bit correspondence and object creation. Accessed 2026-10-19.
- Rsync manual: --archive, --acls and --xattrs. Accessed 2026-10-19.
- Linux ACL project: setfacl(1), mask recalculation. Accessed 2026-10-19.
- Linux man-pages: umask(2), interaction with default ACLs. Accessed 2026-10-19.
- Linux v6.8: fs/posix_acl.c, __posix_acl_chmod_masq and posix_acl_create. Accessed 2026-10-19.
- Andreas Gruenbacher: POSIX Access Control Lists on Linux, USENIX FREENIX 2003, pp. 259-272. Accessed 2026-10-19.