Permissions, ownership and the bits above them
Nine bits, three more above them, and an ACL layer on top. The parts that get examined are the ones that are not obvious: what execute means on a directory, why chmod -R 755 is almost always wrong, what setgid does to a shared directory, and the fact that removing an ACL entry does not remove the ACL.
Essential Commands Guide 9 of 38 Intermediate
- PlatformsUbuntu 26.04 LTS + AlmaLinux 10.2
- LVM2.03.31 (Ubuntu) / 2.03.36 (AlmaLinux)
- nftables1.1.6 (Ubuntu) / 1.1.5 (AlmaLinux)
- TimeAbout 22 min
getfacl and setfacl come from the acl package, which is installed on RHEL 10 by default and is NOT installed on Ubuntu 26.04 - the capture installs it. The umask on both lab hosts is 002.
- Firewallufw 0.36.2 enabled but reporting inactive / firewalld active
- Network confignetplan + systemd-networkd / NetworkManager 1.56.0
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| LFCS-A01 | 192.168.0.70 | Ubuntu 26.04 LTS | Primary host - most guides run only here | 2 Core | 4 GB | 50 GB |
| LFCS-C01 | 192.168.0.72 | AlmaLinux 10.2 | The other distribution - dnf, firewalld and NetworkManager | 2 Core | 4 GB | 50 GB |
This guide includes
Use this for the permissions marks. This matters because chmod -R 644 on a tree locks you out of your own directories - execute means something different there, and capital X exists for exactly that case.
- finding the umask that decided a new file's mode, and using chmod in both spellings
- learning what execute means on a directory, and how
chmod -Rwith a file mode locks you out - using capital
X, which exists for that case - setting the three bits above the nine, and changing owner and group
- adding ACLs for when nine bits are not enough, including the entry that stays after you remove it
Before you start
- guide 5 -
statand the mode string.
-
The mode a new file gets, and the umask that decided it
No chmod anywhere - just a new file and a new directory.
bash Example session mkdir -p ~/ess/perm && cd ~/ess/perm && umask; echo "--- and symbolically:"; umask -S002--- and symbolically:u=rwx,g=rwx,o=rxcd ~/ess/perm && touch newfile && mkdir newdir && stat -c '%A %a %n' newfile newdir-rw-rw-r-- 664 newfiledrwxrwxr-x 775 newdircd ~/ess/perm && bash -c 'um=$(umask); echo "umask $um"; printf "file 0666 & ~umask = 0%o\n" $((0666 & ~0$um)); printf "dir 0777 & ~umask = 0%o\n" $((0777 & ~0$um))'umask 0002file 0666 & ~umask = 0664dir 0777 & ~umask = 0775Expected resultumask
002/u=rwx,g=rwx,o=rx; the file 664 and the directory 775; and the arithmetic0666 & ~umask = 0664,0777 & ~umask = 0775.Success conditionYou can predict the mode of a file you have not created yet.
-
chmod, both spellings
Octal and symbolic, on the same file.
bash Example session cd ~/ess/perm && chmod 640 newfile && stat -c '%A %a' newfile && chmod u=rw,g=r,o= newfile && stat -c '%A %a' newfile-rw-r----- 640-rw-r----- 640cd ~/ess/perm && chmod g+w newfile && stat -c '%A %a' newfile; chmod a-w newfile && stat -c '%A %a' newfile; chmod +x newfile && stat -c '%A %a' newfile-rw-rw---- 660-r--r----- 440-r-xr-x--x 551Expected result
640from the octal form and fromu=rw,g=r,o=; then+won group giving 660,a-wgiving 440, and+xgiving 551.Success conditionYou can set a mode either way.
-
What execute means on a directory
Three modes on the same directory, and what each one lets you do.
bash Example session cd ~/ess/perm && mkdir -p nox/inner && touch nox/inner/file && chmod 500 nox && ls nox && echo "--- r-x: listing works"; chmod 400 nox && ls nox 2>&1 | tail -1; echo "--- r--: listing works, but:"; ls nox/inner 2>&1 | tail -1inner--- r-x: listing worksinner--- r--: listing works, but:ls: cannot open file 'nox/inner': Permission deniedcd ~/ess/perm && chmod 100 nox && ls nox 2>&1 | tail -1; echo "--- --x: cannot list, but CAN traverse:"; cat nox/inner/file 2>&1 | tail -1; echo "(empty file, no error = traversal worked)"; chmod 700 noxls: cannot open directory 'nox': Permission denied--- --x: cannot list, but CAN traverse:(empty file, no error = traversal worked)Expected result
r-xlisting fine;r--listing the names butcannot open file 'nox/inner';--xrefusing to list at all but still lettingcat nox/inner/filethrough.Success conditionYou can explain the difference between r and x on a directory.
-
So chmod -R with a file mode locks you out
chmod -R 644on a directory tree. It exits 0.bash Example session cd ~/ess/x && chmod -R 644 . ; echo "exit $?"; ls 2>&1 | tail -1; find . 2>&1 | tail -2exit 0ls: cannot open file '.': Permission deniedfind: ‘.’: Permission deniedfind: Failed to restore initial working directory: /home/sysadmin/ess/x: Permission deniedcd ~/ess && chmod 755 x x/sub && cd x && find . | sort; echo "--- put the directories' x back and it works again"../f./sub./sub/g--- put the directories' x back and it works againcd ~/ess/x && chmod 644 f sub/g && stat -c '%A %n' . sub f sub/g; echo "--- files 644, directories 755: the state you actually wanted"drwxr-xr-x .drwxr-xr-x sub-rw-r--r-- f-rw-r--r-- sub/g--- files 644, directories 755: the state you actually wantedExpected result
exit 0, thenls: cannot open file '.'andfind: '.': Permission denied- and after putting 755 back on the directories, everything works again.Success conditionYou know the mistake before you make it.
-
Capital X, which exists for this
A tree where nothing has execute, then
a+X.bash Example session cd ~/ess/x && chmod -R a-x . 2>/dev/null; sudo chmod 700 . sub; chmod 600 f sub/g; stat -c '%A %n' . sub f sub/gdrwx------ .drwx------ sub-rw------- f-rw------- sub/gcd ~/ess/x && chmod -R a+X . && stat -c '%A %n' . sub f sub/g; echo "--- +X gave x to the DIRECTORIES and left the files alone"drwx--x--x .drwx--x--x sub-rw------- f-rw------- sub/g--- +X gave x to the DIRECTORIES and left the files alonecd ~/ess/x && chmod u+x f && chmod -R a+X . && stat -c '%A %n' f; echo "--- but f already had one x, so +X handed it the other two as well"-rwx--x--x f--- but f already had one x, so +X handed it the other two as wellExpected resultDirectories becoming
drwx--x--xwhile the files stay-rw-------; then a file that already hadu+xgetting-rwx--x--x.Success conditionYou can fix a tree's modes in one command.
-
The three bits above the nine
setuid on a real binary, setgid on a shared directory, and the sticky bit on
/tmp.bash Example session cd ~/ess/perm && ls -l /usr/bin/passwd /usr/bin/wall /tmp -d 2>/dev/null | awk '{print $1, $3, $4, $NF}'drwxrwxrwt root root /tmp-rwsr-xr-x root root /usr/bin/passwd-rwxr-xr-x root root /usr/bin/wallcd ~/ess/perm && mkdir -p shared && sudo chgrp sudo shared && sudo chmod 2775 shared && stat -c '%A %a %U:%G %n' shared; echo "--- setgid: new files inherit the directory group"drwxrwsr-x 2775 sysadmin:sudo shared--- setgid: new files inherit the directory groupcd ~/ess/perm && touch shared/mine && stat -c '%U:%G %n' shared/mine; echo "--- compare a normal directory:"; touch newdir/mine && stat -c '%U:%G %n' newdir/minesysadmin:sudo shared/mine--- compare a normal directory:sysadmin:sysadmin newdir/minecd ~/ess/perm && mkdir -p stick && chmod 1777 stick && stat -c '%A %a %n' stick; echo "--- the t is the sticky bit: only the owner of a file may delete it"drwxrwxrwt 1777 stick--- the t is the sticky bit: only the owner of a file may delete itExpected result
-rwsr-xr-xon/usr/bin/passwd,drwxrwxrwton/tmp; then setgiddrwxrwsr-x 2775making a new filesysadmin:sudowhere a normal directory givessysadmin:sysadmin.Success conditionYou can read and set the special bits.
-
Owner and group, and what changing the owner does not do
chownin its three forms - and then a write that should have failed.bash Example session cd ~/ess/perm && sudo chown root newfile && stat -c '%U:%G %n' newfile; sudo chown root:sudo newfile && stat -c '%U:%G %n' newfile; sudo chgrp sysadmin newfile && stat -c '%U:%G %n' newfileroot:sysadmin newfileroot:sudo newfileroot:sysadmin newfilecd ~/ess/perm && sudo chown -R sysadmin:sysadmin tree && find tree -exec stat -c '%U:%G %n' {} \;sysadmin:sysadmin treesysadmin:sysadmin tree/subsysadmin:sysadmin tree/sub/gsysadmin:sysadmin tree/fExpected result
root:sysadmin, thenroot:sudo, thenroot:sysadminagain; and-Rapplying to the whole tree.Success conditionYou can change ownership precisely.
-
And changing the owner does not take the group's access away
A file handed to root, still written by a normal user.
zsh Example session cd ~/ess/own && sudo chown root f && stat -c '%A %U:%G %n' f; echo "test" > f && echo "the write SUCCEEDED - group sysadmin still has w, and I am in it"; stat -c '%s bytes' f-rw-rw-r-- root:sysadmin fthe write SUCCEEDED - group sysadmin still has w, and I am in it5 bytescd ~/ess/own && sudo chown root:root f && stat -c '%A %U:%G %n' f; echo "test" > f 2>&1 | tail -1; echo "--- now the group is root too, and only o=r applies"-rw-rw-r-- root:root fzsh:2: permission denied: f--- now the group is root too, and only o=r appliesExpected resultAfter
chown root: mode still-rw-rw-r--, group stillsysadmin, and the write succeeds. Afterchown root:root:permission denied.Success conditionYou can reason about which of the three sets applies to you.
-
ACLs, for when nine bits are not enough
The tools are not installed on Ubuntu, so first install them.
bash Example session command -v getfacl setfacl 2>/dev/null || echo "not installed yet"; sudo apt-get install -y -qq acl 2>&1 | tail -2; command -v getfacl setfaclnot installed yet No VM guests are running outdated hypervisor (qemu) binaries on this host./usr/bin/getfacl/usr/bin/setfaclmkdir -p ~/ess/acl && cd ~/ess/acl && touch f && getfacl f 2>&1# file: f# owner: sysadmin# group: sysadminuser::rw-group::rw-other::r--cd ~/ess/acl && setfacl -m u:nobody:rw f && getfacl f 2>&1 | tail -7# group: sysadminuser::rw-user:nobody:rw-group::rw-mask::rw-other::r--cd ~/ess/acl && mkdir d && setfacl -d -m g:sudo:rwx d && touch d/inherited && getfacl d/inherited 2>&1 | tail -6; echo "--- a DEFAULT acl is inherited by new files"user::rw-group::rwx #effective:rw-group:sudo:rwx #effective:rw-mask::rw-other::r-- --- a DEFAULT acl is inherited by new filesExpected result
not installed yetthen the binaries afterapt-get install acl; a plaingetfaclshowing three entries; then auser:nobody:rw-entry and amask::rw-; and a default ACL inherited by a new file.Success conditionYou can grant one extra user access to one file.
-
And the ACL that stays after you remove it
Removing the entry, and then removing the ACL.
bash Example session cd ~/ess/acl && setfacl -x u:nobody f && ls -l f | awk '{print $1}'; getfacl f 2>/dev/null | tail -5; echo "--- the named entry is gone and the + is STILL THERE - a mask entry remains"-rw-rw-r--+user::rw-group::rw-mask::rw-other::r-- --- the named entry is gone and the + is STILL THERE - a mask entry remainscd ~/ess/acl && setfacl -b f && ls -l f | awk '{print $1}'; getfacl f 2>/dev/null | tail -4; echo "--- -b removes the whole ACL, and only then does the + go"-rw-rw-r--user::rw-group::rw-other::r-- --- -b removes the whole ACL, and only then does the + goExpected resultAfter
-x: the named entry gone andls -lstill showing-rw-rw-r--+. After-b: the+finally gone.Success conditionYou can tell whether a file still has an ACL.
Troubleshooting
chmod -R 755or-R 644broke a directory tree.Why: A file mode applied to directories removes their execute bit.
Fix:
chmod -R u=rwX,go=rX, or twofind -typepasses. Put 755 back on the directories first.lsworks in a directory butls -lfails on every entry.Why: The directory has
rbut notx, so names are readable and cannot be stat'ed.Fix:Add the execute bit:
chmod +x thedir.New files in a shared directory get the wrong group.
Why: The directory is not setgid, so files take the creator's primary group.
Fix:
chmod 2775 thedirand fix existing files withchgrp -R.A permission problem that the mode string does not explain.
Why: There is an ACL. Look for the
+inls -l.Fix:
getfacl file.setfacl -bto clear it.getfacl: command not found.Why: The
aclpackage is not installed - the default on Ubuntu 26.04.Fix:
apt-get install acl. RHEL has it already.A user can still write a file after you chowned it to root.
Why: The group did not change and the group has write.
Fix:
chown root:rootandchmod 600.A capital
SorTin the mode.Why: The special bit is set without the underlying execute bit.
Fix:Almost always a mistake - add the execute bit or clear the special bit.