Permissions and Users · about 40 min

Reading and changing permissions

Why this matters

"Permission denied" stops scripts, web servers and deployments every day. Sometimes it is protecting you (a private SSH key must not be readable by others, or ssh refuses to use it); sometimes it is a mistake (a script you forgot to make executable, a log directory the service cannot write to). Reading the nine permission letters fluently and changing them precisely is a core skill, and the rules the kernel applies are short enough to learn exactly.

The nine bits

Every inode stores, next to its owner (UID) and group (GID), a set of permission bits. ls -l shows them as nine letters after the type character:

 -  rwx  r-x  r--
 │  ───  ───  ───
 │   │    │    └── other: everyone else
 │   │    └─────── group: members of the file's group
 │   └──────────── user:  the file's owner
 └──────────────── type (- file, d directory, l link)

Each triplet is read, write, execute; a letter means granted, - means not. For a regular file:

BitAllows
rreading the content (cat, cp from it, opening it in an editor)
wchanging the content (writing, appending, truncating)
xrunning it as a program (./file)

Directories use the same bits with different meanings; that gets its own section in the next lesson, because it surprises everyone.

Note what w on a file does not allow: deleting or renaming it. Those change the directory (they remove or edit a name in it), so they depend on the directory's permissions.

How the kernel decides

When a process opens a file, the kernel picks exactly one triplet:

 is the process's UID the file's owner?          ── yes → use the user triplet
            │ no
 is the file's GID among the process's groups?   ── yes → use the group triplet
            │ no
 use the other triplet

then checks whether the requested access (read, write or execute) is in it. If not: Permission denied (the kernel's error code EACCES). The first matching class wins and the others are ignored, which leads to the odd but logical case in the second quiz: an owner with --- is locked out even if the group or others have access. (Root, UID 0, skips the check, as the previous lesson explained.)

Seeing permissions

Open the shell for this lesson:

$ cd perms
$ ls -l
-rw-r--r--. 1 learner learner 14 Nov  1 15:30 frozen.txt
-rw-r--r--. 1 learner learner 51 Nov  1 15:30 run.sh
-rw-r--r--. 1 learner learner 26 Nov  1 15:30 secret.txt
-rw-r--r--. 1 learner learner 11 Nov  1 15:30 shared.txt
$ stat -c '%A %U:%G %n' secret.txt
-rw-r--r-- learner:learner secret.txt

Right now anyone on the system could read secret.txt, and run.sh cannot be run:

$ ./run.sh
bash: ./run.sh: Permission denied
$ echo $?
126

Status 126 means "found, but could not execute", as you saw in module 1.

chmod: change mode

chmod changes the permission bits. Only the file's owner (or root) may do it. The symbolic form reads almost like English:

 chmod  [who][op][perms]  FILE...
         │    │   └─ r, w, x (any combination)
         │    └───── + add,  - remove,  = set exactly (others cleared)
         └────────── u user/owner, g group, o other, a all (default: all)
$ chmod u+x run.sh            # owner may execute
$ chmod go-rwx secret.txt     # group and other lose everything
$ chmod g+w shared.txt        # group may write
$ chmod a-w frozen.txt        # nobody may write
$ chmod u=rw,go=r notes.txt   # set exactly: rw-r--r--
$ ls -l
-r--r--r--. 1 learner learner 14 Nov  1 15:30 frozen.txt
-rwxr--r--. 1 learner learner 51 Nov  1 15:30 run.sh
-rw-------. 1 learner learner 26 Nov  1 15:30 secret.txt
-rw-rw-r--. 1 learner learner 11 Nov  1 15:30 shared.txt

Several changes can be joined with commas, and several files given at once. +x without a "who" applies to all classes (minus whatever your umask masks, next lesson). chmod -R applies the change recursively to a directory tree; be careful with it, because files and directories usually need different bits.

Write protection is not much protection from yourself

$ echo "change" >> frozen.txt
bash: frozen.txt: Permission denied
$ rm frozen.txt
rm: remove write-protected regular file 'frozen.txt'? n

The append fails, as it should. But rm only asks: deleting depends on the directory, which you own and may write. And as the owner you can always chmod u+w it back. Removing w from your own files protects against accidents, not against yourself.

The execute bit and scripts

To run a file as ./name, the kernel needs the x bit for your class. For a compiled program, the kernel loads the machine code. For a text file, the kernel looks at the first line: if it starts with #! (the shebang), the rest of the line names the interpreter to run it with:

 ./run.sh   →  kernel reads "#!/bin/bash"  →  runs  /bin/bash ./run.sh

Module 8 covers this in detail. Note that bash run.sh works even without the x bit: then bash is the program being executed, and it merely reads run.sh, which needs only r. That is why the code exercises in this course run as bash script.sh.

Changing owner and group

chown user file changes the owner, chgrp group file the group, and chown user:group file both. Giving a file away to another user requires root (otherwise you could create files that count against someone else's disk quota, or fake authorship). An ordinary user may change a file's group, but only to a group they belong to. In this sandbox you are only in the group learner, so there is nothing to change to; on a real system you would use it like this:

$ chgrp developers shared.txt           # you must be in "developers"
$ sudo chown www-data:www-data /srv/site/uploads

Common mistakes

Recap

Going further: run ls -l /usr/bin/passwd and notice an s where the owner's x should be. What could that mean, given that passwd must edit /etc/shadow, which only root can write? The next lesson answers it.

Exercises

Exercise 1 · quiz

Who can do what?

ls -l shows:

-rw-r----- 1 ana staff 812 Mar 3 09:00 budget.ods

bob is a member of the group staff; carol is not. Which statements are true? Select all that apply.

Answer
  • ana can read and write the file.

  • bob can read the file.

  • Nobody can execute it as a program (except root, if it had an x bit anywhere).

Split rw-r----- into three triplets: owner rw-, group r--, other ---. ana is the owner: read and write. bob is in the group: read only. carol falls into "other": nothing. No triplet has x, so nobody may execute it. (Root bypasses read and write checks, but even root cannot execute a file unless at least one x bit is set.)

Exercise 2 · quiz

The owner is locked out?

ana owns notes.txt, whose mode is ----rw-r-- (owner nothing, group read/write, other read). ana is also a member of the file's group. Can ana read the file?

Answer
  • No. The kernel uses only the first class that applies, and for the owner that is the owner triplet, which is empty.

The kernel checks classes in order and stops at the first one that fits: if you are the owner, only the owner bits count; else if you are in the group, only the group bits; else the other bits. The permissions are not added together. So the owner is locked out here, even though "everyone else" can read. Being the owner does give her one thing: she may chmod the file and give herself access back.

Exercise 3 · task in a shell

Set the right permissions

In /work/perms there are four files, all currently rw-r--r--. Use chmod with the symbolic notation (u, g, o, a with +, -, =) so that:

FileRequired permissionsls -l shows
secret.txtonly you can read and write; group and others nothing-rw-------
run.sheverybody may read and execute; only you may write-rwxr-xr-x
shared.txtyou and the group may read and write; others may read-rw-rw-r--
frozen.txteverybody may read, nobody may write-r--r--r--

Then run ./run.sh to see that it works now that it is executable.

Hint 1

chmod go-rwx FILE removes everything from group and others. a means all three classes.

Hint 2

secret.txt: go-rwx; run.sh: a+x; shared.txt: g+w; frozen.txt: a-w.

Reference solution
cd perms
chmod go-rwx secret.txt
chmod a+x run.sh
chmod g+w shared.txt
chmod a-w frozen.txt

Exercise 4 · write code

Make it runnable

The directory contains a small script hello.sh. Running it directly as ./hello.sh needs the execute bit. Write a script that:

  1. sets hello.sh to exactly rw-r--r-- using chmod u=rw,go=r hello.sh,
  2. prints the permission string with stat -c %A hello.sh,
  3. adds execute permission for the owner only,
  4. prints the permission string again,
  5. runs ./hello.sh.

Expected output:

-rw-r--r--
-rwxr--r--
hello from hello.sh

The starter tries to run hello.sh straight away; run it first and look at the error message and the exit status (126: found but not executable).

File: script.sh

Starting point

./hello.sh

Example

expected output

-rw-r--r--
-rwxr--r--
hello from hello.sh
Hint 1

Owner only: u+x. a+x or plain +x would give it to everyone.

Hint 2

chmod u+x hello.sh, then stat -c %A hello.sh, then ./hello.sh.

Reference solution
chmod u=rw,go=r hello.sh
stat -c %A hello.sh
chmod u+x hello.sh
stat -c %A hello.sh
./hello.sh