DB-유

Linux File Permissions and umask Explained

· linux, permissions, security

Half the "Permission denied" questions I get aren't mysterious once you read ls -l properly. The other half come from files created with the wrong default permissions because nobody set the umask for the service or the deploy user. This is the explanation I wish someone had given me early on: the permission bits, the special bits, what umask really does, and how to keep a shared directory writable for a team.

Reading the permission string

ls -l /var/www/example.com/html/index.php
-rw-r--r--. 1 deploy www-data 2048 Nov  8 10:12 index.php

The first character is the type (- file, d directory, l symlink). The next nine are three groups of rwx: owner (deploy), group (www-data), and everyone else. Here the owner can read and write, the group can read, others can read. The trailing . on CentOS indicates an SELinux context is present; a + means an ACL is set.

For directories, the meaning shifts: r lets you list names, x lets you enter and access files whose names you know, and w lets you create or delete entries. A directory with rw- but no x is useless; --x alone is a valid way to let a service reach files without browsing.

chmod

I use the symbolic form most of the time because it changes only what I say:

chmod u+x deploy.sh          # owner can execute
chmod g+w shared/            # group can write
chmod o-rwx secrets.conf     # remove everything from others
chmod -R g+rX /var/www/site  # X: execute only on directories and already-executable files

Octal form sets everything at once. Each digit is r=4, w=2, x=1 summed:

chmod 644 index.php          # rw-r--r--
chmod 755 bin/run.sh         # rwxr-xr-x
chmod 600 ~/.ssh/id_rsa      # rw-------
chmod 750 /srv/app           # rwxr-x---

The capital X is the one trick worth memorising. chmod -R 755 makes every file executable, which is wrong; chmod -R u=rwX,go=rX gives directories 755 and files 644 in one pass. I got that wrong on a whole web root once.

The special bits

A fourth leading octal digit sets three special bits.

setuid (4) on an executable runs it as the file owner. /usr/bin/passwd is -rwsr-xr-x so an ordinary user can update /etc/shadow. Almost never set this yourself.

setgid (2) on a directory makes new files inherit the directory's group instead of the creator's primary group. This is the key to shared directories:

chgrp developers /srv/shared
chmod 2775 /srv/shared
ls -ld /srv/shared
drwxrwsr-x. 2 root developers 6 Nov  8 10:20 /srv/shared

sticky (1) on a directory means only a file's owner (or root) can delete it, even if the directory is world-writable. /tmp is drwxrwxrwt for this reason:

chmod 1777 /var/app/uploads

umask

umask doesn't set permissions, it removes them. A process creating a file asks for 666 (files) or 777 (directories) and the kernel clears any bit set in the umask. With the usual 022:

files:        666 - 022 = 644  (rw-r--r--)
directories:  777 - 022 = 755  (rwxr-xr-x)

With 027 the group keeps read and others get nothing: 640 and 750. With 002 the group gets write: 664 and 775. Subtraction is a mental shortcut; strictly it's a bitwise AND with the complement, which only matters for odd masks.

umask          # show current, e.g. 0022
umask -S       # symbolic: u=rwx,g=rx,o=rx
umask 027      # for this shell only

Setting it for users and services

For interactive users the default comes from /etc/profile and /etc/bashrc (CentOS) or /etc/login.defs via pam_umask (Ubuntu, UMASK 022). Override per user in ~/.bashrc, or system-wide with a file in /etc/profile.d/:

echo 'umask 027' | sudo tee /etc/profile.d/umask.sh

Services don't read shell profiles, which is the part people forget. For a systemd unit, use the [Service] section:

[Service]
UMask=0027

For a team sharing /srv/shared, combine setgid on the directory with a 002 umask for the members, so everything created there is group-writable and group-owned by developers.

Trying it out

umask
touch /tmp/t1 && ls -l /tmp/t1
mkdir /tmp/d1 && ls -ld /tmp/d1
sudo -u deploy sh -c 'umask; touch /srv/shared/probe' && ls -l /srv/shared/probe

The last line shows both the inherited group (setgid) and the deploy user's effective umask in a non-interactive shell, which is what cron and deploy scripts really get.

Common "Permission denied" causes

  • bash: ./script.sh: Permission denied on a file that's clearly there: no execute bit (chmod +x), or the filesystem is mounted noexec (mount | grep noexec).
  • Permission denied on a directory you own with rw-: no x on it, or on a parent. namei -l /path/to/file prints every component's permissions. This is my first command for these tickets.
  • Files created by a web app belong to apache (or www-data) and the deploy user can't edit them: the upload directory lacks setgid and the service umask is 022. Set 2775 on the directory and UMask=0002 in the unit, and put both users in one group.
  • chmod: changing permissions of 'x': Operation not permitted: you're not the owner or root, or the file is immutable (lsattr x; chattr -i x).
  • Everything looks right in ls -l but access is still denied on CentOS: SELinux, not the mode bits. ls -Z and ausearch -m avc -ts recent.