Linux chmod Command Deep Dive: A Complete Guide from Permission Bits to Symbolic Mode#

As a Linux system administrator, chmod is a command I use daily. The seemingly simple permission management actually has a rigorous design logic behind it. Today let’s talk about the implementation principles of chmod and some easily overlooked details.

The Essence of Permissions: Three Groups of Three Bits#

Linux file permissions are represented by 9 bits, divided into three groups:

Owner  Group  Other
rwx    rwx    rwx
111    101    100
7      5      4
  • Owner: File creator
  • Group: Members of the file’s group
  • Other: All other users

Each group has three bits representing:

  • r (read): Read permission, value 4
  • w (write): Write permission, value 2
  • x (execute): Execute permission, value 1

This is why we commonly use commands like chmod 755 file — 7=4+2+1 (rwx), 5=4+1 (r-x), 5=4+1 (r-x).

Implementation of Numeric Mode#

The essence of permission calculation is bit operations. Assuming current permission is 755 (binary 111101101), we want to remove owner’s write permission:

# Current permission: 111101101 (755)
# Target permission: 101101101 (555)
# Operation: AND operation

chmod 555 file

At the kernel level, the permission check pseudo-code:

// fs/namei.c - Linux kernel permission check
int inode_permission(struct inode *inode, int mask)
{
    umode_t mode = inode->i_mode;
    int retval;

    // Check permission bits
    if (mask & MAY_READ) {
        if (!(mode & S_IRUGO))  // No read permission
            return -EACCES;
    }

    if (mask & MAY_WRITE) {
        if (!(mode & S_IWUGO))  // No write permission
            return -EACCES;
    }

    if (mask & MAY_EXEC) {
        if (!(mode & S_IXUGO))  // No execute permission
            return -EACCES;
    }

    return 0;  // Permission check passed
}

Symbolic Mode: More Intuitive Permission Modification#

While numeric mode is concise, it’s not intuitive. Symbolic mode allows precise control of permission changes:

# Add execute permission for owner
chmod u+x script.sh

# Remove write permission for group
chmod g-w config.txt

# Set read-only permission for others
chmod o=r file.txt

# Add read permission for everyone
chmod a+r README.md

The advantage of symbolic mode: only modify needed permission bits, without affecting other bits.

Symbolic mode implementation logic:

def apply_symbolic_permission(current_mode, who, operator, permission):
    """
    who: u/g/o/a
    operator: +/-
    permission: r/w/x
    """
    # Permission bit mapping
    perm_bits = {'r': 4, 'w': 2, 'x': 1}

    # User group offset
    who_shift = {'u': 6, 'g': 3, 'o': 0}

    # Calculate target bits
    target_bits = perm_bits[permission] << who_shift[who]

    if operator == '+':
        # Add permission: OR operation
        return current_mode | target_bits
    elif operator == '-':
        # Remove permission: AND + NOT operation
        return current_mode & ~target_bits
    elif operator == '=':
        # Set permission: clear first, then set
        mask = 7 << who_shift[who]  # Clear all bits of target group
        cleared = current_mode & ~mask
        return cleared | (perm_bits[permission] << who_shift[who])

# Example: current permission 644, add write permission for group
current = 0o644  # 110100100
new_mode = apply_symbolic_permission(current, 'g', '+', 'w')
# Result: 0o664 = 110110100

Special Permission Bits: SUID/SGID/Sticky Bit#

Besides the 9 regular permission bits, there are 3 special permission bits:

SUID (Set User ID, value 4)#

chmod u+s /usr/bin/passwd
  • When executing the file, the process’s effective user ID becomes the file owner
  • Typical use: passwd command (normal users need to write /etc/shadow to change passwords)
  • Security risk: SUID program vulnerabilities may lead to privilege escalation

SGID (Set Group ID, value 2)#

chmod g+s /shared/project/

For directories: newly created files inherit the directory’s group For files: when executed, process’s effective group ID becomes the file’s group

Sticky Bit (value 1)#

chmod +t /tmp
  • Used for directories: only file owners can delete their own files
  • Typical use: /tmp directory (prevents users from deleting others’ temporary files)

Complete special permission settings:

# SUID + regular permission 755
chmod 4755 file

# SGID + regular permission 775
chmod 2775 directory

# Sticky Bit + regular permission 777
chmod 1777 /tmp

Common Pitfalls and Best Practices#

Pitfall 1: Recursive Permission Modification#

# Wrong: modifies both files and directories
chmod -R 755 /var/www

# Correct: handle files and directories separately
find /var/www -type d -exec chmod 755 {} \;
find /var/www -type f -exec chmod 644 {} \;

Files typically don’t need execute permission. Recursively setting 755 makes all files executable, creating security risks.

# Symbolic link permissions are always 777
# chmod modifies target file's permissions, not the link itself
ln -s target.txt link.txt
chmod 600 link.txt  # Actually modifies target.txt's permissions

Pitfall 3: umask Impact#

# Current umask is 022
# Default permission for files: 666 - 022 = 644
# Default permission for directories: 777 - 022 = 755

umask 077  # Stricter permission setting
touch newfile.txt  # Permission becomes 600

Web Implementation: Permission Calculator#

In JsonKit’s Chmod Calculator tool, we implemented a visual permission calculator:

interface PermissionState {
  owner: { read: boolean; write: boolean; execute: boolean };
  group: { read: boolean; write: boolean; execute: boolean };
  other: { read: boolean; write: boolean; execute: boolean };
  suid: boolean;
  sgid: boolean;
  sticky: boolean;
}

function calculatePermission(perm: PermissionState): number {
  let mode = 0;

  // Owner permissions (bits 6-8)
  if (perm.owner.read) mode |= 0o400;
  if (perm.owner.write) mode |= 0o200;
  if (perm.owner.execute) mode |= 0o100;

  // Group permissions (bits 3-5)
  if (perm.group.read) mode |= 0o040;
  if (perm.group.write) mode |= 0o020;
  if (perm.group.execute) mode |= 0o010;

  // Other permissions (bits 0-2)
  if (perm.other.read) mode |= 0o004;
  if (perm.other.write) mode |= 0o002;
  if (perm.other.execute) mode |= 0o001;

  // Special permissions (bits 9-11)
  if (perm.suid) mode |= 0o4000;
  if (perm.sgid) mode |= 0o2000;
  if (perm.sticky) mode |= 0o1000;

  return mode;
}

// Example: calculate 755 permission
const result = calculatePermission({
  owner: { read: true, write: true, execute: true },
  group: { read: true, write: false, execute: true },
  other: { read: true, write: false, execute: true },
  suid: false,
  sgid: false,
  sticky: false
});
console.log(result.toString(8));  // "755"

Summary#

While the chmod command is simple, the permission management mechanism behind it involves the core of Linux security model. Understanding the binary representation of permission bits, the operation logic of symbolic mode, and the application scenarios of special permissions can help us better manage system security.

Next time using chmod, think one step further: what is the current permission state? Which bits need precise modification? Are special permissions needed? This can avoid many security risks.