CertGrid CertGrid
Hands-on Lab·Linux Foundation Certified System Administrator

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

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.

Both distributions, because the commands on this page differ between them. Every output is captured on the host it belongs to.
Server NameIP AddressOSRolesCPURAMHDD
LFCS-A01192.168.0.70Ubuntu 26.04 LTSPrimary host - most guides run only here2 Core4 GB50 GB
LFCS-C01192.168.0.72AlmaLinux 10.2The other distribution - dnf, firewalld and NetworkManager2 Core4 GB50 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.

Before you start

  1. 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 = 0775

    Expected resultumask 002 / u=rwx,g=rwx,o=rx; the file 664 and the directory 775; and the arithmetic 0666 & ~umask = 0664, 0777 & ~umask = 0775.

    Success conditionYou can predict the mode of a file you have not created yet.

  2. 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 551

    Expected result640 from the octal form and from u=rw,g=r,o=; then +w on group giving 660, a-w giving 440, and +x giving 551.

    Success conditionYou can set a mode either way.

  3. 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 resultr-x listing fine; r-- listing the names but cannot open file 'nox/inner'; --x refusing to list at all but still letting cat nox/inner/file through.

    Success conditionYou can explain the difference between r and x on a directory.

  4. So chmod -R with a file mode locks you out

    chmod -R 644 on 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 wanted

    Expected resultexit 0, then ls: cannot open file '.' and find: '.': Permission denied - and after putting 755 back on the directories, everything works again.

    Success conditionYou know the mistake before you make it.

  5. 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 well

    Expected resultDirectories becoming drwx--x--x while the files stay -rw-------; then a file that already had u+x getting -rwx--x--x.

    Success conditionYou can fix a tree's modes in one command.

  6. 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 it

    Expected result-rwsr-xr-x on /usr/bin/passwd, drwxrwxrwt on /tmp; then setgid drwxrwsr-x 2775 making a new file sysadmin:sudo where a normal directory gives sysadmin:sysadmin.

    Success conditionYou can read and set the special bits.

  7. Owner and group, and what changing the owner does not do

    chown in 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/f

    Expected resultroot:sysadmin, then root:sudo, then root:sysadmin again; and -R applying to the whole tree.

    Success conditionYou can change ownership precisely.

  8. 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 applies

    Expected resultAfter chown root: mode still -rw-rw-r--, group still sysadmin, and the write succeeds. After chown root:root: permission denied.

    Success conditionYou can reason about which of the three sets applies to you.

  9. 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 files

    Expected resultnot installed yet then the binaries after apt-get install acl; a plain getfacl showing three entries; then a user:nobody:rw- entry and a mask::rw-; and a default ACL inherited by a new file.

    Success conditionYou can grant one extra user access to one file.

  10. 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 + go

    Expected resultAfter -x: the named entry gone and ls -l still showing -rw-rw-r--+. After -b: the + finally gone.

    Success conditionYou can tell whether a file still has an ACL.

Troubleshooting

Official sources