HomeCurriculumSystem AdministrationUser Identity, Permissions & Access Control
User Identity, Permissions & Access Control

User Identity, Permissions & Access Control

Master Linux identity storage, SHA-512 crypt hashing in /etc/shadow, Pluggable Authentication Modules (PAM) stack control flags, and POSIX ACL permissions.

DT
Authored by DevThesis Systems Editorial Team
Reviewed for Technical Accuracy & Code Determinism

Operating systems are fundamentally multi-tenant computational environments. The operating system kernel must ensure that multiple concurrent human users, system daemons, automated cron jobs, and background service threads execute without interfering with one another's memory spaces, data stores, or computational allocations.

In Unix-like operating systems, security and access governance are built upon the foundational pillars of Identity, Discretionary Access Control (DAC), Extended Attributes, and Pluggable Authentication Modules (PAM). From the low-level representation of User Identifiers (UIDs) and process credential inheritance in the Linux kernel to the complex cryptographic hashing of passwords in /etc/shadow and the multi-layered evaluation rules of the PAM subsystem, system administrators must understand the exact mechanics governing authorization. A single misconfigured permission bit, an overly permissive sudoers entry, or an unhardened SUID binary can compromise an entire enterprise infrastructure.


1. Unix User & Group Identity Architecture#

In the Linux kernel, humans, service accounts, and automated daemons do not exist as text strings (such as "alice" or "nginx"). The kernel operates strictly on numeric values: User Identifiers (UIDs) and Group Identifiers (GIDs).

text
+----------------------------------------------------------------------------------------------------+
|                                  LINUX USER IDENTITY ARCHITECTURE                                  |
|                                                                                                    |
|  /etc/passwd (World-Readable: 0644)                                                                |
|  +----------------------------------------------------------------------------------------------+  |
|  | username:password:UID:GID:GECOS:home_directory:login_shell                                   |  |
|  | alice:x:1001:1001:Alice Developer:/home/alice:/bin/bash                                      |  |
|  +----------------------------------------------------------------------------------------------+  |
|             | 'x' indicates password hash is stored in shadow file                                 |
|             v                                                                                      |
|  /etc/shadow (Root-Only: 0640 or 0000)                                                             |
|  +----------------------------------------------------------------------------------------------+  |
|  | username:password_hash:last_change:min_age:max_age:warn_period:inactive:expire:reserved       |  |
|  | alice:6saltStringcryptographicHash...:19842:0:90:7:::                                     |  |
|  +----------------------------------------------------------------------------------------------+  |
+----------------------------------------------------------------------------------------------------+

Anatomical Breakdown of `/etc/passwd`#

The /etc/passwd file is a colon-delimited database containing one record per system user:

  1. Username: The alphanumeric login name (e.g., alice).
  2. Password Placeholder: Historically contained the DES password hash. Today, it contains the character x, indicating that the actual password hash is segregated into the protected /etc/shadow file to prevent offline dictionary and brute-force cracking by unprivileged users.
  3. User Identifier (UID): The unique numeric ID:

- UID 0: The superuser (root), possessing absolute kernel privileges.

- UID 1 - 999: System users and service accounts (e.g., bin, daemon, systemd-resolve, nginx, postgres). These accounts possess no interactive login shell (/sbin/nologin or /bin/false) and exist solely to isolate daemon execution.

- UID 1000+: Regular human interactive users.

  1. Primary Group Identifier (GID): The primary group ID associated with the user.
  2. GECOS Field: General Electric Comprehensive Operating Supervisor metadata (full name, room number, telephone).
  3. Home Directory: The user's initial working directory upon login (e.g., /home/alice).
  4. Login Shell: The binary executable spawned upon login (e.g., /bin/bash, /usr/bin/zsh, or /sbin/nologin).

Anatomical Breakdown of `/etc/shadow`#

The /etc/shadow file is readable only by the superuser (root) or members of the shadow group. The password hash field uses a modular format:

text
idsaltencrypted_hash

The id identifier specifies the cryptographic hashing algorithm:

  • 1: MD5 (Legacy, cryptographically broken).
  • 2a / 2y: Blowfish (bcrypt).
  • 5: SHA-256 (32-byte digest, 5,000 default rounds).
  • 6: SHA-512 (64-byte digest, 5,000 default rounds). Standard in modern Linux.
  • y: Yescrypt (Modern memory-hard password hashing function, resistant to GPU/ASIC offline attacks).

The Salt is a pseudo-random alphanumeric string (up to 16 characters). Salting prevents precomputed dictionary attacks (Rainbow Tables) by ensuring that two users sharing the identical password generate completely unique password hashes.

Process Credential Inheritance: RUID, EUID, SUID, and FSUID#

Every process executing in the Linux kernel carries four distinct user credentials in its task descriptor (task_struct):

text
+----------------------------------------------------------------------------------------------------+
|                                    PROCESS CREDENTIAL TAXONOMY                                     |
+----------------------------------------------------------------------------------------------------+
| Credential | Functional Scope                                                                      |
+----------------------------------------------------------------------------------------------------+
| RUID       | Real User ID: Identifies the real user who launched the process.                     |
|            | Determines who has permission to send signals (e.g., SIGTERM, SIGKILL) to the process.|
| EUID       | Effective User ID: Determines process access rights for filesystem operations,        |
|            | system calls, and privilege checks. Changed by SUID binaries or setuid(2).           |
| SUID       | Saved User ID: Saves the previous EUID when the process temporarily drops privileges, |
|            | allowing the process to switch back and forth between unprivileged and privileged.   |
| FSUID      | Filesystem User ID: Linux-specific credential used exclusively for filesystem access  |
|            | checks (NFS server threads); mirrors EUID unless explicitly decoupled.                |
+----------------------------------------------------------------------------------------------------+

2. Traditional Discretionary Access Control (DAC) & Special Permission Bits#

Discretionary Access Control (DAC) is the fundamental security model of Unix systems: the owner of an object (file or directory) has the discretion to grant or deny access permissions to other users.

text
+----------------------------------------------------------------------------------------------------+
|                                 POSIX FILE PERMISSION ARCHITECTURE                                 |
|                                                                                                    |
|    - r w x r - x r - -   1   alice   developers   4096   Oct 30 10:00   deploy.sh                  |
|    | ----- ----- -----                                                                             |
|    |   |     |     |                                                                               |
|    |   |     |     +──> Others Permissions (Read-only: r--) ──> Octal 4                            |
|    |   |     +────────> Group Permissions (Read & Execute: r-x) ──> Octal 5                        |
|    |   +──────────────> Owner Permissions (Read, Write, Execute: rwx) ──> Octal 7                  |
|    +──────────────────> File Type Indicator (- regular file, d directory, l symlink, c/b device)   |
+----------------------------------------------------------------------------------------------------+

The Binary Representation of Permission Bits#

Permissions are represented internally as a 12-bit integer:

  • The lower 9 bits govern standard permissions across three triads: Owner (`u`), Group (`g`), and Others (`o`).
  • Each triad possesses three permission bits:

- Read (`r`): Weight 4 (2^2 = 100_2). On files, permits reading contents; on directories, permits listing file names (ls).

- Write (`w`): Weight 2 (2^1 = 010_2). On files, permits modifying or truncating contents; on directories, permits creating, renaming, or deleting files within the directory.

- Execute (`x`): Weight 1 (2^0 = 001_2). On files, permits executing the binary or script; on directories, permits traversing into the directory (cd) and accessing file metadata (stat).

The octal value of a triad is computed by summing the active weights:

Mode = Read(4) + Write(2) + Execute(1)Example: rwxr-xr-- translates to:

Owner: 4 + 2 + 1 = 7 | | | Group: 4 + 0 + 1 = 5 | | | Others: 4 + 0 + 0 = 4 ──> 0754

The User Creation Mask (`umask`) Mathematics#

When a process creates a new file or directory via open(..., O_CREAT) or mkdir(2), it requests a baseline creation mode:

  • Default requested mode for files: 0666 (rw-rw-rw-, execution is never granted by default for safety).
  • Default requested mode for directories: 0777 (rwxrwxrwx).

The Linux kernel filters this requested mode against the process's `umask` (User Creation Mask) using bitwise logic:

Final Mode = Requested Mode & (~umask)Example: If umask = 0027:

  • For a new file (0666):

Requested: 110 110 110_2 | | | umask: 000 010 111_2 | | | ~umask: 111 101 000_2

Final Mode = 110 110 110_2 & 111 101 000_2 = 110 100 000_2 = 0640 (rw-r-----)

  • For a new directory (0777):

Final Mode = 0777 & (~ 0027) = 0750 (rwxr-x---)### Special Permission Bits: SUID, SGID, and Sticky Bit

The upper 3 bits of the 12-bit mode field govern special architectural behaviors:

text
+----------------------------------------------------------------------------------------------------+
|                                    SPECIAL PERMISSION BITS                                         |
+----------------------------------------------------------------------------------------------------+
| Bit Name   | Octal | Symbolic | Behavior on Executable Files         | Behavior on Directories     |
+----------------------------------------------------------------------------------------------------+
| SUID       | 4000  | u+s      | Process executes with the EUID of the| No effect on standard Linux.|
| (Setuid)   |       |          | file owner rather than caller.       |                             |
| SGID       | 2000  | g+s      | Process executes with the EGID of the| Newly created files inherit |
| (Setgid)   |       |          | file group rather than caller group. | the directory's group ID!   |
| Sticky Bit | 1000  | +t       | Historically cached text in memory;  | Only file owner, dir owner, |
|            |       |          | unused on modern Linux executables.  | or root can unlink/rename!  |
+----------------------------------------------------------------------------------------------------+
  1. SetUID (`4000` / `u+s`):

- Classic example: /usr/bin/passwd. An unprivileged user (UID 1000) needs to update their own password in /etc/shadow. Because /etc/shadow is owned by root:root with permissions 0640, standard users cannot write to it. The /usr/bin/passwd binary is owned by root and marked with SUID (-rwsr-xr-x). When Alice executes it, her process Effective User ID becomes EUID = 0, allowing the binary to write to /etc/shadow.

  1. SetGID on Directories (`2000` / `g+s`):

- Critical for multi-user collaboration directories (e.g., /data/shared/). Normally, when Alice creates a file, the file's group ownership is set to Alice's primary group (alice). When Bob attempts to edit the file, he is blocked. If SGID is set on /data/shared/ (chmod g+s /data/shared/), all files newly created inside inherit the group ownership of the directory (developers), enabling collaboration.

  1. Sticky Bit on Directories (`1000` / `+t`):

- Classic example: /tmp (drwxrwxrwt). Any user can create temporary files in /tmp. Without the sticky bit, because /tmp has write permission w for Others, User Bob could execute rm /tmp/alice-file, deleting Alice's files! The sticky bit restricts deletion: a user can only delete or rename files they personally own.


3. POSIX Access Control Lists (ACLs) & Extended Attributes#

Traditional DAC suffers from a structural limitation: it only supports three distinct identities (one Owner, one Group, and Others). If an administrator wishes to grant read access to User Carol without granting access to other group members or making the file world-readable, DAC fails.

POSIX Access Control Lists (ACLs)#

POSIX ACLs extend file metadata, allowing arbitrary numbers of named users and groups to be assigned granular permissions.

text
+----------------------------------------------------------------------------------------------------+
|                                    POSIX ACL METADATA BREAKDOWN                                    |
|                                                                                                    |
|   getfacl /var/reports/finance.xlsx                                                               |
|  # file: var/reports/finance.xlsx                                                                  |
|  # owner: alice                                                                                    |
|  # group: finance                                                                                  |
|  user::rw-                           <── Standard Owner Permissions (Alice: rw-)                   |
|  user:carol:r--                      <── Named User ACL (Carol has read-only access)               |
|  user:david:rw-                      <── Named User ACL (David has read-write access)              |
|  group::r--                          <── Standard Primary Group Permissions (finance: r--)         |
|  group:auditors:r--                  <── Named Group ACL (auditors have read-only access)          |
|  mask::rw-                           <── ACL MASK: The maximum effective permissions ceiling!      |
|  other::---                          <── Others Permissions (Zero access)                          |
+----------------------------------------------------------------------------------------------------+

The ACL Mask: The Permissions Ceiling#

When an ACL contains named users or named groups, the Mask field defines the maximum possible permissions that can be exercised by any named user, named group, or primary group.

The effective permission granted to a named user is calculated as:

Effective Permission = Granted ACL Permission & ACL Mask

Example: If User David is granted user:david:rwx, but the mask is set to mask::r--:

Effective = rwx & r-- = r-- (Read-Only!)

The mask acts as a dynamic circuit breaker, allowing administrators to revoke write or execute permissions across all non-owner collaborators simultaneously with a single command (setfacl -m m::r--).

Extended File Attributes (`chattr` and `lsattr`)#

Linux filesystems (ext4, XFS) support extended filesystem flags that override standard POSIX permissions, even for the root superuser:

  • Immutable (`+i`): The file cannot be modified, deleted, overwritten, renamed, or linked to. Even root cannot delete an immutable file without first stripping the flag (chattr -i). Crucial for securing /etc/resolv.conf or critical system binaries against tampering.
  • Append-Only (`+a`): The file can only be opened in append mode (O_APPEND). Data cannot be overwritten, truncated, or deleted. Standard for audit log files.

4. Pluggable Authentication Modules (PAM) Architecture#

Prior to 1995, every Unix application that required authentication (login, su, passwd, ftpd) embedded its own proprietary password-checking routines compiled directly into the binary. If an enterprise wanted to migrate from local passwords to Kerberos, LDAP, or smart cards, every single login utility on the system had to be rewritten and recompiled from source code.

Pluggable Authentication Modules (PAM) decouples authentication policies from application logic. Applications link against libpam.so, and system administrators define authentication rules dynamically in configuration files located in /etc/pam.d/.

text
+----------------------------------------------------------------------------------------------------+
|                                    PAM ARCHITECTURAL FLOW                                          |
|                                                                                                    |
|  CLIENT APPLICATION (sshd, sudo, login, su)                                                        |
|  * Calls pam_start("sshd", "alice", &conv, &pamh)                                                 |
|  * Calls pam_authenticate(pamh, 0)                                                                |
|                                     |                                                              |
|                                     v Transfers control to PAM framework                           |
|                                                                                                    |
|  PAM CONFIGURATION PARSER (/etc/pam.d/sshd)                                                        |
|  * Evaluates the four module management groups in sequential order:                                |
|                                                                                                    |
|   1. auth        Verify identity (Passwords, TOTP 2FA, Kerberos tickets, biometric)                |
|   2. account     Verify account validity (Password expired? Login permitted at this hour?)         |
|   3. password    Update authentication credentials (Enforce password complexity, history)         |
|   4. session     Configure environment (Mount home dir, set ulimits, initialize systemd --user)    |
|                                     |                                                              |
|                                     v Evaluates stack using control flags                          |
|                                                                                                    |
|  PAM MODULE STACK EVALUATION (pam_unix.so, pam_faillock.so, pam_google_authenticator.so)            |
|  * Combines module return values based on: required, requisite, sufficient, optional              |
+----------------------------------------------------------------------------------------------------+

The Four PAM Management Groups#

  1. `auth`: Handles identity authentication. Validates credentials (prompting for passwords, scanning hardware security keys) and establishes process credentials (setting supplementary groups).
  2. `account`: Determines whether an authenticated user is legally authorized to access the system (e.g., verifying that the account has not expired, checking that the user is not logging in outside allowed business hours via pam_time.so, or verifying shell authorization via pam_shells.so).
  3. `password`: Governs password updates. Enforces complexity rules (via pam_pwquality.so), prevents password reuse (via pam_pwhistory.so), and updates /etc/shadow or LDAP backends.
  4. `session`: Manages tasks required prior to granting userspace execution and after logout (e.g., allocating PAM environment variables via pam_env.so, mounting home directories via pam_mkhomedir.so, or registering the user session with systemd-logind via pam_systemd.so).

The Four Traditional Control Flags#

The execution flow through a PAM stack is governed by control flags assigned to each module:

text
+----------------------------------------------------------------------------------------------------+
|                                    PAM CONTROL FLAG BEHAVIOR MATRIX                                 |
+----------------------------------------------------------------------------------------------------+
| Flag       | On Success                               | On Failure                                 |
+----------------------------------------------------------------------------------------------------+
| required   | Continue evaluating stack; remember      | Continue evaluating stack; remember failure|
|            | success. (Must succeed for overall pass).| and return failure at end of stack.         |
| requisite  | Continue evaluating stack; remember      | ABORT STACK IMMEDIATELY! Return failure    |
|            | success.                                 | to application without evaluating further. |
| sufficient | If no prior required module failed,      | Failure is ignored; continue evaluating    |
|            | ABORT STACK IMMEDIATELY and return PASS! | remaining stack modules.                   |
| optional   | Module result is ignored unless it is the| Module result is ignored unless it is the  |
|            | only module in the stack.                | only module in the stack.                  |
+----------------------------------------------------------------------------------------------------+

The Account Lockout Shield: `pam_faillock`#

To protect enterprise servers against brute-force password spraying attacks, modern Linux systems deploy pam_faillock.so:

  • Tracks consecutive failed authentication attempts in transient files under /run/faillock/.
  • If an attacker exceeds a defined failure threshold (e.g., deny = 5 within fail_interval = 900 seconds), the account is locked for unlock_time = 900 seconds.
  • Even if the attacker subsequently guesses the correct password, pam_faillock refuses authentication, logging the lockout event to journald.

5. Privilege Escalation Control: Sudo Architecture & Sudoers Syntax#

In enterprise system administration, granting full root shell access (su -) violates the principle of least privilege and destroys non-repudiation auditing. Sudo (Superuser Do) allows an administrator to delegate granular authority to specific users to execute designated commands as root while preserving the caller's identity in audit logs.

text
+----------------------------------------------------------------------------------------------------+
|                                      SUDOERS GRAMMAR ANATOMY                                       |
|                                                                                                    |
|    dbadmin    prod-db-01 = (postgres : postgres)   NOPASSWD:   /usr/bin/pg_dump, /bin/systemctl       |
|    -------    ----------   ----------------------   ---------   ----------------------------       |
|       |           |                  |                  |                     |                    |
|       |           |                  |                  |                     +──> Allowed Commands|
|       |           |                  |                  +──> Execution Tag (No password prompted)   |
|       |           |                  +──> Runas User & Group Alias (Executes as postgres:postgres) |
|       |           +─────────────────────> Host Alias (Valid only on host prod-db-01)               |
|       +─────────────────────────────────> User Specification (Applies to user dbadmin)             |
+----------------------------------------------------------------------------------------------------+

Sudo Security Directives#

  1. `NOPASSWD:`: Bypasses the password prompt. Standard for automated service accounts (e.g., monitoring probes or deployment agents).
  2. `NOEXEC:`: Prevents shell escaping. When a user runs a permitted command (e.g., /usr/bin/less), they can execute shell commands by typing !/bin/bash. NOEXEC injects a custom shared library via LD_PRELOAD that intercepts execve(2), blocking the user from spawning sub-shells.
  3. `visudo`: Always edit /etc/sudoers or /etc/sudoers.d/ files using visudo. It locks the file against concurrent edits and validates syntax before writing to disk, preventing syntax errors from locking all administrators out of root access.

6. Industrial Implementation: Complete POSIX Permission & PAM Evaluation Engine#

To internalize how the Linux kernel evaluates owner, group, and other permissions, interprets the ACL mask, and traverses multi-module PAM authentication stacks, we present a complete, fully typed TypeScript implementation of a POSIX DAC and PAM Evaluation Engine.

typescript
// dac-pam-engine.ts
// Production-grade implementation of POSIX File Permissions, ACL Masks,
// and Pluggable Authentication Module (PAM) Stack Evaluation.

// ============================================================================
// PART 1: POSIX DISCRETIONARY ACCESS CONTROL (DAC) & ACL ENGINE
// ============================================================================

export interface UserContext 
  uid: number;
  username: string;
  gid: number;
  groups: number[]; // Supplementary GIDs


export interface AclEntry 
  type: 'named_user' | 'named_group';
  id: number; // UID or GID
  read: boolean;
  write: boolean;
  execute: boolean;


export interface Inode 
  uid: number; // File Owner
  gid: number; // File Group
  mode: number; // Standard 12-bit mode (e.g., 0755)
  aclEntries: AclEntry[];
  aclMask?:  read: boolean; write: boolean; execute: boolean ;


export class DacEvaluator 
  // Evaluates access: Read (4), Write (2), Execute (1)
  public static checkAccess(
    inode: Inode,
    user: UserContext,
    requestedAccess:  read?: boolean; write?: boolean; execute?: boolean 
  ): boolean 
    // 1. Root (UID 0) bypasses all read/write checks; requires at least one execute bit for execution
    if (user.uid === 0) 
      if (requestedAccess.execute) 
        const anyExecute = (inode.mode & 0o111) !== 0;
        return anyExecute;
      
      return true;
    

    // 2. Check Owner Triad
    if (user.uid === inode.uid) 
      const ownerBits = (inode.mode >> 6) & 0o7;
      if (requestedAccess.read && !(ownerBits & 4)) return false;
      if (requestedAccess.write && !(ownerBits & 2)) return false;
      if (requestedAccess.execute && !(ownerBits & 1)) return false;
      return true;
    

    // 3. Check Named User ACLs (POSIX ACL check precedes group check)
    const userAcl = inode.aclEntries.find((e) => e.type === 'named_user' && e.id === user.uid);
    if (userAcl) 
      const mask = inode.aclMask ||  read: true, write: true, execute: true ;
      const effRead = userAcl.read && mask.read;
      const effWrite = userAcl.write && mask.write;
      const effExec = userAcl.execute && mask.execute;

      if (requestedAccess.read && !effRead) return false;
      if (requestedAccess.write && !effWrite) return false;
      if (requestedAccess.execute && !effExec) return false;
      return true;
    

    // 4. Check Group Matching (Primary GID or Supplementary Groups)
    const isGroupMember = user.gid === inode.gid || user.groups.includes(inode.gid);
    if (isGroupMember) 
      const groupBits = (inode.mode >> 3) & 0o7;
      let r = (groupBits & 4) !== 0;
      let w = (groupBits & 2) !== 0;
      let x = (groupBits & 1) !== 0;

      // If ACL mask exists, group triad is bounded by mask
      if (inode.aclMask) 
        r = r && inode.aclMask.read;
        w = w && inode.aclMask.write;
        x = x && inode.aclMask.execute;
      

      if (requestedAccess.read && !r) return false;
      if (requestedAccess.write && !w) return false;
      if (requestedAccess.execute && !x) return false;
      return true;
    

    // 5. Check Others Triad
    const otherBits = inode.mode & 0o7;
    if (requestedAccess.read && !(otherBits & 4)) return false;
    if (requestedAccess.write && !(otherBits & 2)) return false;
    if (requestedAccess.execute && !(otherBits & 1)) return false;

    return true;
  


// ============================================================================
// PART 2: PLUGGABLE AUTHENTICATION MODULES (PAM) ENGINE
// ============================================================================

export type PamControlFlag = 'required' | 'requisite' | 'sufficient' | 'optional';
export type PamResult = 'PAM_SUCCESS' | 'PAM_AUTH_ERR' | 'PAM_USER_UNKNOWN' | 'PAM_ACCT_EXPIRED';

export interface PamModule 
  name: string;
  evaluate: (username: string, credential?: string) => PamResult;


export interface PamRule 
  control: PamControlFlag;
  module: PamModule;


export class PamStack 
  private rules: PamRule[] = [];

  public addRule(control: PamControlFlag, module: PamModule): void 
    this.rules.push( control, module );
  

  // Evaluates a PAM Stack conforming to classical Linux PAM control semantics
  public authenticate(username: string, credential?: string):  success: boolean; auditTrail: string[]  
    const auditTrail: string[] = [];
    let stackFailed = false;
    let hadSufficientSuccess = false;

    for (const rule of this.rules) 
      const result = rule.module.evaluate(username, credential);
      const isSuccess = result === 'PAM_SUCCESS';

      auditTrail.push(`Module 'rule.module.name' [rule.control] returned: result`);

      switch (rule.control) 
        case 'required': 
          if (!isSuccess) 
            stackFailed = true; // Failure recorded, but continue evaluating remaining stack
          
          break;
        
        case 'requisite': 
          if (!isSuccess) 
            auditTrail.push(`  -> REQUISITE failure! Aborting stack evaluation immediately.`);
            return  success: false, auditTrail ;
          
          break;
        
        case 'sufficient': 
          if (isSuccess && !stackFailed) 
            auditTrail.push(`  -> SUFFICIENT success! Granting immediate authorization.`);
            return  success: true, auditTrail ;
          
          break;
        
        case 'optional': 
          // Ignored unless single module
          break;
        
      
    

    return 
      success: !stackFailed,
      auditTrail,
    ;
  


// Verification Harness and Demonstration
export function runDacPamSimulation(): void 
  console.log('=== Initializing POSIX DAC and PAM Evaluation Simulation ===\n');

  // 1. Test POSIX Permissions & ACL Mask
  const financeFile: Inode = 
    uid: 1001, // Alice
    gid: 2001, // Finance
    mode: 0o750, // rwxr-x---
    aclEntries: [
       type: 'named_user', id: 1002, read: true, write: true, execute: false , // Bob: rw-
    ],
    aclMask:  read: true, write: false, execute: false , // Mask caps write!
  ;

  const userAlice: UserContext =  uid: 1001, username: 'alice', gid: 2001, groups: [] ;
  const userBob: UserContext =  uid: 1002, username: 'bob', gid: 3000, groups: [] ;
  const userCarol: UserContext =  uid: 1003, username: 'carol', gid: 9999, groups: [] ;

  console.log('[DAC Test] Checking Alice (Owner) Write Access:');
  console.log(`  -> Alice Write: DacEvaluator.checkAccess(financeFile, userAlice,  write: true ) ? 'ALLOWED' : 'DENIED'`);

  console.log('[DAC Test] Checking Bob (Named User with ACL Mask Cap) Write Access:');
  console.log(`  -> Bob Write (Mask caps to r--): DacEvaluator.checkAccess(financeFile, userBob,  write: true ) ? 'ALLOWED' : 'DENIED'`);
  console.log(`  -> Bob Read: DacEvaluator.checkAccess(financeFile, userBob,  read: true ) ? 'ALLOWED' : 'DENIED'`);

  console.log('[DAC Test] Checking Carol (Others) Read Access:');
  console.log(`  -> Carol Read (Others mode is 0): DacEvaluator.checkAccess(financeFile, userCarol,  read: true ) ? 'ALLOWED' : 'DENIED'\n`);

  // 2. Test PAM Authentication Stack
  const pam = new PamStack();

  // Rule 1: pam_faillock.so [required] (Check brute-force lockout)
  pam.addRule('required', 
    name: 'pam_faillock',
    evaluate: (user) => (user === 'locked_user' ? 'PAM_AUTH_ERR' : 'PAM_SUCCESS'),
  );

  // Rule 2: pam_unix.so [sufficient] (Check local /etc/shadow password)
  pam.addRule('sufficient', 
    name: 'pam_unix',
    evaluate: (user, cred) => (cred === 'correct_password' ? 'PAM_SUCCESS' : 'PAM_AUTH_ERR'),
  );

  // Rule 3: pam_ldap.so [required] (Fallback to LDAP server)
  pam.addRule('required', 
    name: 'pam_ldap',
    evaluate: (user, cred) => (cred === 'valid_ldap_token' ? 'PAM_SUCCESS' : 'PAM_AUTH_ERR'),
  );

  console.log('=== Executing PAM Stack Authentication Scenarios ===');
  console.log('\n[PAM Scenario 1] User Alice authenticates with valid local password:');
  const res1 = pam.authenticate('alice', 'correct_password');
  res1.auditTrail.forEach((line) => console.log(`  line`));
  console.log(`  -> Final Authentication Decision: res1.success ? 'GRANTED' : 'DENIED'`);

  console.log('\n[PAM Scenario 2] User locked_user attempts login with valid password:');
  const res2 = pam.authenticate('locked_user', 'correct_password');
  res2.auditTrail.forEach((line) => console.log(`  line`));
  console.log(`  -> Final Authentication Decision: res2.success ? 'GRANTED' : 'DENIED'`);

7. Real-World Case Studies & Architectural Post-Mortems#

Theoretical permission structures and authentication stacks undergo relentless probing by adversaries:

Case Study 1: The Baron Samedit Sudo Buffer Overflow (CVE-2021-3156)#

In January 2021, security researchers at Qualys disclosed Baron Samedit, a heap-based buffer overflow in sudo that existed undetected in Unix codebases for nearly 10 years:

  • The Flaw: When sudo executes commands in shell mode (sudo -s or sudo -i), it escapes special characters by inserting backslashes. However, when parsing command-line arguments, if an argument ended with a single trailing backslash (sudoedit -s '\'), sudo incorrectly escaped the null byte terminator (\0), continuing to copy data past the end of the heap-allocated buffer.
  • The Local Privilege Escalation: Because the sudo binary had the SUID bit set (-rwsr-xr-x owned by root), unprivileged local users exploited the heap corruption to overwrite internal sudo_nss service structures in memory.
  • Attackers achieved complete, unrestricted root privileges without knowing any password or possessing any sudo permissions in /etc/sudoers.
  • Architectural Lesson: SUID binaries represent massive attack surfaces. Modern hardened systems deploy Seccomp filters, enforce NoNewPrivileges=yes, and replace monolithic SUID binaries with Polkit (PolicyKit) IPC daemons operating over unprivileged D-Bus interfaces.

Case Study 2: The SUID Binary Shell Escape via GTFOBins#

In 2019, a junior system administrator attempted to grant developers permission to search system logs by running:

chmod u+s /usr/bin/find

  • The Catastrophe: The administrator assumed find was merely a read utility.
  • However, find possesses an internal execution flag: -exec.
  • An unprivileged user executed:

/usr/bin/find . -exec /bin/sh -p \; -quit

  • Because find was SUID root, the spawned /bin/sh inherited EUID = 0 (with -p preserving privileges), instantly granting the unprivileged user a root interactive command prompt.
  • Architectural Lesson: Never place SUID on general-purpose utilities. Thousands of common Unix binaries (such as vim, less, awk, python, nmap, and tar) contain built-in shell escape capabilities documented in the GTFOBins catalog.

8. Common Identity & Permission Anti-Patterns#

Operational failures in access control frequently stem from convenience-driven shortcuts:

1. The Recursive `chmod -R 777` Disaster#

  • The Mistake: A web application fails to write to an upload directory due to permission errors. A frustrated developer executes: chmod -R 777 /var/www/.
  • The Failure: Granting world-write access (777) allows any local unprivileged process or compromised background daemon to overwrite application scripts, inject malicious PHP/JavaScript backdoors, and read sensitive configuration secrets.
  • The Fix: Use proper ownership and group delegation:

```bash

chown -R www-data:www-data /var/www/html/uploads

chmod -R 0750 /var/www/html/uploads

```

2. Granting Unrestricted Sudo Access (`ALL=(ALL) ALL`)#

  • The Mistake: Adding developers to the %wheel or %sudo group with unrestricted root execution to avoid managing specific permissions.
  • The Failure: Developers can execute sudo su - or modify /etc/shadow, completely bypassing all audit trails and compliance controls (violating SOC 2 and PCI-DSS standards).
  • The Fix: Declare granular, command-specific sudo rules with NOPASSWD: restricted strictly to designated binaries.

9. Checkpoint Practice Problems with Detailed Step-by-Step Solutions#

Problem 1: Octal umask File & Directory Permission Calculation#

Scenario: An enterprise Linux server enforces a strict corporate security umask policy:

  • System default umask: `0027`.
  • An unprivileged user developer executes a Bash script that runs two commands:

```bash

touch /data/app/output.log

mkdir /data/app/build_cache/

```

Task:

  1. State the default creation mode requested by the Linux kernel for files and directories prior to umask masking.
  2. Calculate the exact final numeric octal permission mode for output.log.
  3. Calculate the exact final numeric octal permission mode for build_cache/.
  4. Translate both final modes into their symbolic 9-character POSIX representation (rwx...) and explain what access rights are granted to members of the primary group versus Others.

Step-by-Step Solution:

Step 1: Baseline Requested Modes:

  • File baseline requested mode: 0666 (110 110 110_2).
  • Directory baseline requested mode: 0777 (111 111 111_2).

Step 2: Calculate Final Mode for `output.log`:

The umask is 0027:

umask = 000 010 111_2 ──> ~umask = 111 101 000_2Applying the bitwise AND mask:

Final Mode = 0666 & (~ 0027)- Owner triad: 6 & (~ 0) = 6 & 7 = 6.

  • Group triad: 6 & (~ 2) = 6 & 5 = 110_2 & 101_2 = 100_2 = 4.
  • Others triad: 6 & (~ 7) = 6 & 0 = 0.

Final Octal Mode = 0640Step 3: Calculate Final Mode for `build_cache/`:

Applying the bitwise AND mask to 0777:

  • Owner triad: 7 & (~ 0) = 7.
  • Group triad: 7 & (~ 2) = 7 & 5 = 5.
  • Others triad: 7 & (~ 7) = 7 & 0 = 0.

Final Octal Mode = 0750Step 4: Symbolic Translation & Security Analysis:

  • output.log (0640): `-rw-r-----`

- Owner: Read and Write (rw-).

- Group: Read-Only (r--).

- Others: Zero permissions (---).

  • build_cache/ (0750): `drwxr-x---`

- Owner: Read, Write, and Traverse (rwx).

- Group: Read and Traverse (r-x). Group members can list directory contents and enter the directory, but cannot create or delete files.

- Others: Zero permissions (---). Completely invisible and inaccessible to all other users on the system.


Problem 2: POSIX ACL Evaluation Trace with Effective Mask Cap#

Scenario: A sensitive financial dataset /srv/accounting/q3_ledger.dat has the following metadata:

  • Owner: alice (UID: 1001)
  • Group: finance (GID: 2001)
  • Standard Mode: 0640 (-rw-r-----)

The security administrator configures POSIX Access Control Lists on the file:

bash
setfacl -m u:bob:rwx /srv/accounting/q3_ledger.dat
setfacl -m g:auditors:rx /srv/accounting/q3_ledger.dat
setfacl -m m::r-x /srv/accounting/q3_ledger.dat

User Bob (UID: 1002, primary group engineering, supplementary group auditors) attempts two distinct operations on the file:

  • Operation 1: Read the file contents.
  • Operation 2: Write/append an entry into the file.

Task:

  1. Detail the exact ACL records and identify the active ACL Mask permissions.
  2. Trace the step-by-step evaluation algorithm executed by the Linux kernel when User Bob attempts Operation 1 (Read). Is it granted or denied?
  3. Trace the evaluation algorithm when User Bob attempts Operation 2 (Write). Is it granted or denied? Calculate the Effective Permissions granted to Bob.
  4. If the administrator deletes Bob's explicit named user ACL (setfacl -x u:bob), determine whether Bob can still read the file via his supplementary group membership in auditors.

Step-by-Step Solution:

Step 1: ACL Structure and Mask:

The ACL table consists of:

  • Owner: user::rw-
  • Named User: user:bob:rwx
  • Primary Group: group::r--
  • Named Group: group:auditors:r-x
  • Mask: mask::r-x (Read and Execute active; Write is disabled in mask).
  • Others: other::---

Step 2: Evaluation of Operation 1 (Read):

  1. Is Bob the file owner (UID 1001)? No (Bob is 1002).
  2. Does an explicit named user ACL exist for Bob? Yes: user:bob:rwx.
  3. The kernel calculates Bob's effective permissions by intersecting his granted ACL with the ACL Mask:

Effective = Granted (rwx) & Mask (r-x) = r-x4. Because the effective permissions contain the Read bit (r), Operation 1 is GRANTED.

Step 3: Evaluation of Operation 2 (Write):

  1. As determined in Step 2, Bob matches the named user ACL entry.
  2. His effective permissions are r-x.
  3. The Write bit (w) is cleared by the mask.
  4. Operation 2 is DENIED with an EACCES (Permission Denied) error. Even though Bob was granted write access in his individual ACL, the Mask successfully capped his permissions.

Step 4: Scenario after Deleting Bob's Named User ACL:

If u:bob is removed:

  1. Bob is not the file owner.
  2. No named user ACL matches Bob.
  3. Does Bob belong to the file's primary group (finance)? No.
  4. Does Bob belong to any named group in the ACL table? Yes: Bob is a member of auditors (group:auditors:r-x).
  5. Group effective permissions:

Effective = Granted (r-x) & Mask (r-x) = r-x

  • Conclusion: Bob can still read the file through his supplementary membership in the auditors group.

Problem 3: Multi-Module PAM Authentication Stack Logic Evaluation#

Scenario: A security engineer configures /etc/pam.d/corporate-portal with the following 4-module authentication (auth) stack:

ini
auth  required    pam_env.so
auth  requisite   pam_faillock.so      preauth
auth  sufficient  pam_google_authenticator.so
auth  required    pam_unix.so          try_first_pass
auth  required    pam_faillock.so      authfail

A user named alice attempts to log in under three distinct scenarios:

  • Scenario A: alice has NOT exceeded failed attempts (faillock passes). She successfully provides a valid Google Authenticator 2FA TOTP code.
  • Scenario B: alice has exceeded the lockout limit (faillock preauth fails).
  • Scenario C: alice has NOT exceeded failed attempts (faillock passes). Her 2FA code is invalid (fails), but she subsequently provides her correct local Unix password.

Task:

For each of the three scenarios:

  1. Trace the step-by-step execution path through the PAM stack.
  2. Specify which modules execute and which modules are skipped.
  3. Determine the final authentication outcome (SUCCESS or FAILURE).

Step-by-Step Solution:

Analysis of Scenario A:

  • Step 1: pam_env.so [required] executes and returns PAM_SUCCESS.
  • Step 2: pam_faillock.so [requisite] checks lockout status. No lockout exists; returns PAM_SUCCESS.
  • Step 3: pam_google_authenticator.so [sufficient] verifies 2FA code. Code is valid; returns PAM_SUCCESS.
  • Sufficient Logic Triggered: Because the module returned SUCCESS and no prior required module failed, the sufficient control flag immediately terminates stack evaluation.
  • Modules pam_unix.so and pam_faillock.so (authfail) are SKIPPED.
  • Final Result: SUCCESS (Authentication Granted).

Analysis of Scenario B:

  • Step 1: pam_env.so [required] returns PAM_SUCCESS.
  • Step 2: pam_faillock.so [requisite] detects that account is locked out; returns PAM_AUTH_ERR.
  • Requisite Logic Triggered: A requisite module failed! The PAM framework immediately aborts execution.
  • Modules 3, 4, and 5 are completely skipped.
  • Final Result: FAILURE (Immediate Lockout Rejection).

Analysis of Scenario C:

  • Step 1: pam_env.so [required] returns PAM_SUCCESS.
  • Step 2: pam_faillock.so [requisite] returns PAM_SUCCESS.
  • Step 3: pam_google_authenticator.so [sufficient] fails (invalid code).

- Sufficient Failure Rule: When a sufficient module fails, the failure is ignored and execution proceeds down the stack.

  • Step 4: pam_unix.so [required] prompts for Unix password. Password is valid; returns PAM_SUCCESS.
  • Step 5: pam_faillock.so (authfail) [required] clears failure counter on success; returns PAM_SUCCESS.
  • All required modules succeeded.
  • Final Result: SUCCESS (Authenticated via Password Fallback).

Problem 4: Sudoers Configuration Audit & Shell Escape Vector Mitigation#

Scenario: A security audit team inspects an enterprise /etc/sudoers configuration file and discovers the following entry assigned to an unprivileged operations team:

ini
%operators ALL=(ALL) /usr/bin/vi /var/log/*, /usr/bin/systemctl restart nginx

A member of the operators group logs into the server and executes:

sudo /usr/bin/vi /var/log/nginx/access.log

Inside the vi editor, the user executes the command:

:!/bin/bash

Task:

  1. What is the effective user identity (UID) of the spawned /bin/bash shell? Does this constitute a full root privilege escalation?
  2. Explain the architectural vulnerability inherent in granting sudo access to interactive text editors, pagers, and compilers (the GTFOBins problem).
  3. Formulate the remediated, production-hardened sudoers configuration entry using the NOEXEC: tag to prevent shell escaping.
  4. If the operators only require viewing log files (not editing them), propose the correct alternative binary and sudo configuration.

Step-by-Step Solution:

Step 1: Impact of `:!/bin/bash`:

  • When sudo executed /usr/bin/vi, it invoked setuid(0), setting EUID = 0 (root).
  • Inside vi, the :! command invokes the C library system("/bin/bash"), which calls fork() and execve("/bin/bash").
  • Because the parent process was running with root privileges, the spawned /bin/bash inherits EUID = 0.
  • Outcome: The user is granted an unrestricted, interactive root command shell. This is a complete local privilege escalation breakout.

Step 2: The GTFOBins Vulnerability:

Interactive utilities (such as vi, vim, less, more, man, awk, gdb, tar) contain built-in features that allow users to spawn sub-shells or read/write arbitrary files. Granting sudo access to these binaries is functionally equivalent to granting full root access (ALL=(ALL) ALL), completely bypassing administrative restrictions.

Step 3: Remediated Sudoers Entry using `NOEXEC:`:

ini
%operators ALL=(ALL) NOEXEC: /usr/bin/vi /var/log/*, /usr/bin/systemctl restart nginx

How `NOEXEC:` works:

Sudo injects a small shared library (e.g., /usr/libexec/sudo/sudo_noexec.so) via LD_PRELOAD. This library intercepts system calls like execve(), execl(), and fork(). If the user types :!/bin/bash inside vi, the library catches the execve() call and returns EPERM (Operation Not Permitted), blocking the shell escape while allowing file viewing.

Step 4: Recommended Operational Alternative:

Operators who only need to inspect logs should never be granted access to text editors:

Use a dedicated pager with shell escaping disabled (-X flag in less):

ini
%operators ALL=(ALL) NOEXEC: /usr/bin/view /var/log/*, /usr/bin/journalctl -u nginx

Or grant access exclusively to non-interactive inspection utilities like grep, tail, or journalctl.

Key Architectural Takeaways

  • Password hashes are segregated in /etc/shadow using salted, iterative KDFs (SHA-512 crypt or yescrypt) to prevent offline brute-force attacks.
  • PAM decouples system applications from authentication methods via four control flags: required, requisite, sufficient, and optional.
  • A PAM 'sufficient' module immediately grants authentication if it succeeds and no prior 'required' module failed.
  • POSIX ACLs extend standard 9-bit DAC permissions, allowing fine-grained user and group access rules on filesystem inodes.

Hands-on Code Playground Available

Execute, edit, and experiment with code for this topic in the live sandbox.

Practice Check • Quick Quiz

Test Your Understanding

In a Linux PAM authentication stack, what is the exact behavior of a module configured with the 'sufficient' control flag when it evaluates to SUCCESS?

A
It aborts authentication immediately and returns failure
B
It is ignored unless all subsequent modules also succeed
C
If no prior 'required' module has failed, authentication immediately succeeds, bypassing all remaining modules in the stack
D
It prompts the user to enter their password a second time for confirmation