Loading learning content...
Authentication establishes who you are. But knowing your identity is just the beginning. The critical security decision is: what are you allowed to do? Can you read this file? Can you modify that configuration? Can you execute this command? Can you access this network resource?
Authorization (sometimes called access control) is the process of determining whether a particular identity should be granted access to a particular resource for a particular operation. Every file access, every system call, every network connection involves an authorization decision. The operating system makes millions of these decisions every second.
By the end of this page, you will understand authorization as distinct from authentication, learn the core authorization models used in operating systems, appreciate the implementation of permission checking, understand the principle of least privilege, and recognize how authorization policies are expressed and enforced.
Authorization requires three pieces of information:
An authorization decision evaluates: 'Should this subject be permitted to perform this operation on this object?' The answer comes from a policy that maps (subject, object, operation) tuples to permit/deny decisions.
Authorization vs. Authentication:
These concepts are distinct but related:
Authentication precedes authorization. First, verify identity; then, consult authorization policy for that identity.
| Concept | Definition | Example in Unix |
|---|---|---|
| Subject | Entity requesting access | User (UID), Process, Group (GID) |
| Object | Resource being accessed | File, directory, device, socket |
| Operation | Action being requested | Read, write, execute, chmod |
| Permission | Right to perform operation | rwx bits, ACL entries |
| Policy | Rules determining access | File mode bits, SELinux policy |
| Reference Monitor | Enforces policy on every access | Kernel permission checks, LSM |
The Reference Monitor Concept:
The reference monitor is a security component that mediates all access between subjects and objects. It is:
In operating systems, the kernel implements the reference monitor. Every system call that accesses resources passes through kernel authorization checks. The kernel's privileged position makes it the only component that can reliably enforce authorization.
A fundamental authorization principle: access should be denied by default unless explicitly granted. If the authorization system fails or cannot make a determination, denying access is safer than permitting it. This 'default deny' approach ensures that authorization failures create unavailability (which is often recoverable) rather than security breaches (which may not be).
Discretionary Access Control (DAC) is the authorization model where resource owners determine who can access their resources. The 'discretionary' aspect means owners have discretion—they set permissions according to their judgment.
Traditional Unix Permissions:
The classic Unix permission model is DAC. Every file has:
For each category, three bits control: read (r), write (w), execute (x). This creates the familiar 'rwxrwxrwx' notation seen in ls -l output.
| Permission | On Files | On Directories | Octal Value |
|---|---|---|---|
| Read (r) | View file contents | List directory contents | 4 |
| Write (w) | Modify file contents | Create/delete files in directory | 2 |
| Execute (x) | Run as program | Enter directory (cd) | 1 |
| SetUID (s) | Run as owner | N/A | 4000 |
| SetGID (s) | Run as group | New files inherit group | 2000 |
| Sticky (t) | N/A | Only owner can delete their files | 1000 |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
#include <sys/stat.h>#include <unistd.h>#include <stdio.h> /** * Demonstrates how Unix permission checking works. * This reflects the logic inside the kernel. */ /* Permission bits */#define S_IRWXU 0700 /* rwx for owner */#define S_IRWXG 0070 /* rwx for group */#define S_IRWXO 0007 /* rwx for others */ /** * Check if process with given credentials can access file. * Simplified version of kernel access check. * * Real check considers: * - Capabilities (CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH) * - Supplementary groups * - ACLs if present */int check_permission(struct stat *st, uid_t uid, gid_t gid, int requested_mode) { mode_t mode = st->st_mode; mode_t granted; /* Root (UID 0) bypasses most checks */ if (uid == 0) { /* For execute: at least one execute bit must be set */ if (requested_mode & X_OK) { if (!(mode & (S_IXUSR | S_IXGRP | S_IXOTH))) { return -1; /* No execute permission */ } } return 0; /* Root can do everything else */ } /* Determine which permission bits apply */ if (uid == st->st_uid) { /* We are the owner - use owner bits */ granted = (mode >> 6) & 7; } else if (gid == st->st_gid) { /* We are in the group - use group bits */ /* Note: Real check also verifies supplementary groups */ granted = (mode >> 3) & 7; } else { /* We are 'other' */ granted = mode & 7; } /* Check if requested permissions are subset of granted */ if (requested_mode & R_OK) { if (!(granted & 4)) return -1; /* Read not permitted */ } if (requested_mode & W_OK) { if (!(granted & 2)) return -1; /* Write not permitted */ } if (requested_mode & X_OK) { if (!(granted & 1)) return -1; /* Execute not permitted */ } return 0; /* Access permitted */} /* * Special permissions: SetUID/SetGID * When executable with setuid is run, process gets owner's UID */void explain_setuid() { /* * /usr/bin/passwd has setuid bit: * -rwsr-xr-x 1 root root 54256 Jan 10 08:00 /usr/bin/passwd * ^-- 's' means setuid on owner execute * * When alice runs passwd: * - Real UID: alice (1000) * - Effective UID: root (0) <-- from setuid * * This allows passwd to update /etc/shadow (owned by root) * even though alice cannot directly write /etc/shadow. */} /* Check file permissions from user space */int main() { /* access() checks using real UID/GID */ if (access("/etc/shadow", R_OK) == 0) { printf("You can read /etc/shadow\n"); } else { printf("Cannot read /etc/shadow: %m\n"); } /* euidaccess() or eaccess() uses effective UID/GID */ /* This matters for setuid programs */ return 0;}DAC has a fundamental weakness: the 'confused deputy' vulnerability. If a privileged program (deputy) can be tricked into acting on behalf of a less-privileged user, it may perform unauthorized operations using its own privileges. For example, if a setuid program opens a file path provided by user input, an attacker might symlink that path to a protected file. The program, running as root, happily accesses the protected file on the attacker's behalf.
Mandatory Access Control (MAC) takes authorization decisions away from resource owners and centralizes them in system policy. Users cannot change security labels or bypass MAC restrictions, even on resources they 'own.'
Why MAC Exists:
DAC fails in environments requiring strong security guarantees. If users can set any permissions on their files, nothing prevents them from accidentally (or intentionally) making sensitive data accessible. Military and government systems required an authorization model where policy is mandatory, not discretionary.
Labels and Security Levels:
In MAC systems, every subject (process) and object (file) has a security label. Operations are permitted only when labels satisfy the security policy. The classic model uses hierarchical labels (Top Secret > Secret > Confidential > Unclassified) plus compartments (need-to-know categories).
| Aspect | DAC | MAC |
|---|---|---|
| Who sets policy | Resource owner | System administrator |
| User can change permissions | Yes | No (unless admin grants) |
| Root can bypass | Yes | Not necessarily |
| Focus | User convenience | System-wide security |
| Typical use | General purpose systems | High-security environments |
| Complexity | Simple | Complex policies |
| Examples | Unix permissions, Windows ACLs | SELinux, AppArmor, MLS |
SELinux:
Security-Enhanced Linux (SELinux) is the most prominent MAC implementation for Linux. Developed by the NSA, it adds mandatory access control to the standard Linux DAC. Every process runs in a domain, every file has a type, and policy rules determine which domains can access which types.
Type Enforcement:
SELinux primarily uses Type Enforcement (TE). Policy rules have the form:
allow source_domain target_type:class permissions;
For example: allow httpd_t httpd_sys_content_t:file read;
This permits the Apache web server (httpd_t domain) to read files labeled httpd_sys_content_t. Apache cannot read files with other labels, even if Unix permissions would allow it.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
# SELinux adds security labels to all subjects and objects# Labels have format: user:role:type:level # View file security context$ ls -Z /var/www/html/index.html-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 index.html ^^^^^^^^^^^^^^^^^^^^^^^^^ SELinux type # View process security context $ ps -eZ | grep httpdsystem_u:system_r:httpd_t:s0 1234 ? 00:00:05 httpd ^^^^^^^^ Process type/domain # Type Enforcement: httpd_t can read httpd_sys_content_t# but NOT other types # Create file with wrong label$ echo "test" > /var/www/html/test.html$ ls -Z /var/www/html/test.html-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 test.html # Apache cannot read this file (wrong type)!# Access denied even though Unix permissions allow it # Check for SELinux denials in audit log$ ausearch -m AVC -ts recenttype=AVC msg=audit(...): avc: denied { read } for pid=1234 comm="httpd" name="test.html" scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file permissive=0 # Fix the label$ restorecon -v /var/www/html/test.html# Or manually:$ chcon -t httpd_sys_content_t /var/www/html/test.html # Now Apache can read it # Key SELinux commands:getenforce # Show current modesetenforce 0|1 # Set permissive (0) or enforcing (1)sesearch # Query loaded policyseinfo # Show policy statistics audit2allow # Generate policy from denialssepolicy # Analyze installed policiesMAC's primary value is confining compromised applications. If an attacker exploits Apache, they're trapped in the httpd_t domain. They cannot read /etc/shadow (shadow_t type), cannot write to system binaries (/bin = bin_t), cannot access other users' files. MAC limits the blast radius of security vulnerabilities.
Role-Based Access Control (RBAC) introduces an abstraction layer between users and permissions. Instead of directly assigning permissions to users, permissions are assigned to roles, and users are assigned to roles. This simplifies administration in large organizations.
The RBAC Model:
A user may be assigned multiple roles. During a session, a user can activate a subset of their roles. Permissions for a session are the union of permissions from active roles.
Why RBAC?
Consider an organization with 10,000 users and 100,000 files. With DAC, managing access requires potentially 10,000 × 100,000 individual permission entries. With RBAC, define 50 roles, assign permissions to roles, assign users to roles. Now you manage 50 × 100,000 role-permission assignments plus 10,000 × 50 user-role assignments—dramatically simpler.
| System | RBAC Feature | How It Works |
|---|---|---|
| Linux (sudo) | Sudoers roles | Groups in sudoers grant command execution rights |
| SELinux | SELinux roles | Roles determine available domains for users |
| Solaris RBAC | Rights profiles | Profiles bundle authorizations and commands |
| Windows | Built-in groups | Administrators, Users, Power Users, etc. |
| Windows | Group Policy | Assign rights via security groups |
| FreeBSD | MAC Framework | Biba, MLS labels with role hierarchies |
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
# /etc/sudoers - RBAC-style role definitions # Define command aliases (permission bundles)Cmnd_Alias DATABASE = /usr/bin/mysql, /usr/bin/mysqldump, \ /usr/bin/pg_dump, /usr/bin/psql Cmnd_Alias NETWORKING = /sbin/ifconfig, /sbin/ip, /sbin/iptables, \ /sbin/route, /usr/bin/ss Cmnd_Alias SERVICES = /bin/systemctl start *, \ /bin/systemctl stop *, \ /bin/systemctl restart *, \ /bin/systemctl status * Cmnd_Alias READONLY = /usr/bin/cat, /usr/bin/less, /usr/bin/head, \ /usr/bin/tail, /bin/ls Cmnd_Alias DANGEROUS = /bin/rm, /bin/dd, /sbin/fdisk, /sbin/mkfs.* # Define roles (groups) and their permissions # Database administrators - full database access%dba_role ALL=(ALL) DATABASE # Network operators - network configuration%netops_role ALL=(ALL) NETWORKING # Service operators - start/stop services%service_ops ALL=(ALL) SERVICES # Security auditors - read-only access to logs%auditor_role ALL=(ALL) NOPASSWD: READONLY # Junior admins - services and networking, no dangerous commands%junior_admin ALL=(ALL) SERVICES, NETWORKING, !DANGEROUS # Senior admins - everything except explicit denials%senior_admin ALL=(ALL) ALL, !DANGEROUS # Full administrators - everything%wheel ALL=(ALL) ALL # Role assignment happens via group membership:# usermod -aG dba_role alice # Alice gets database admin role# usermod -aG netops_role bob # Bob gets network operator role# usermod -aG auditor_role carol # Carol gets auditor role # Verify effective permissions$ sudo -l -U aliceUser alice may run the following commands: (ALL) /usr/bin/mysql, /usr/bin/mysqldump, /usr/bin/pg_dump, /usr/bin/psqlRBAC enables separation of duties—ensuring that critical operations require multiple people. For example, policy might specify that 'approver' and 'initiator' roles are mutually exclusive; no one person can both initiate and approve their own transactions. This constraint-based RBAC prevents insider abuse through role engineering.
Capability-based security takes a fundamentally different approach. Instead of checking 'does this subject have permission for this object?', capabilities bundle object reference WITH permission. Possession of a capability IS authorization.
What Is a Capability?
A capability is an unforgeable token that references an object and specifies permitted operations. Key properties:
Real-World Analogy:
A physical key to a door is capability-like. Holding the key means you can open the door. You don't need to prove your identity or look up permissions. The key itself is authorization. You can give the key to others (delegation). But if someone copies the key, you can't easily revoke their access without changing the lock.
Linux Capabilities:
Linux implements a form of capabilities that subdivides root privilege. Instead of all-or-nothing superuser, root's powers are divided into ~40 distinct capabilities:
CAP_NET_ADMIN: Configure network interfacesCAP_SYS_ADMIN: Many administrative operationsCAP_DAC_OVERRIDE: Bypass file permission checksCAP_SETUID: Change UIDCAP_KILL: Send signals to any processProcesses can have capabilities without being root, and root processes can drop capabilities to limit damage from compromise.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
# Linux capabilities subdivide root privilege # View capabilities of a process$ getpcaps $$Capabilities for 1234: = # View capabilities of a binary$ getcap /usr/bin/ping/usr/bin/ping = cap_net_raw+ep # ping needs CAP_NET_RAW to send ICMP packets# Before capabilities: ping was setuid root (dangerous)# With capabilities: ping only has network rights # Set capabilities on binary (requires CAP_SETFCAP)$ sudo setcap cap_net_bind_service+ep /usr/local/bin/myserver# Now myserver can bind to ports <1024 without being root # Capability sets in a process:# Permitted (P): Max capabilities process can acquire# Effective (E): Currently active capabilities # Inheritable (I): Passed to child processes via exec# Bounding: Upper limit, can only be reduced # Drop capabilities for defense in depth# /usr/bin/myapp.wrapper:#!/bin/bash# Drop all capabilities except what's neededexec capsh \ --caps="cap_net_bind_service+eip" \ --keep=1 \ --user=nobody \ --addamb=cap_net_bind_service \ -- -c "/usr/bin/myapp" # View a running process's capabilities$ cat /proc/1234/status | grep CapCapInh: 0000000000000000CapPrm: 0000000000000000CapEff: 0000000000000000CapBnd: 0000003fffffffffCapAmb: 0000000000000000 # Decode capability bits$ capsh --decode=0000003fffffffff # Run command with only specific capabilities$ sudo capsh --caps='cap_sys_admin+eip cap_dac_read_search+eip' \ --keep=1 --uid=1000 --gid=1000 \ -- -c "ls -la /root"Unix file descriptors approximate capabilities. An open file descriptor is an unforgeable token granting access to a file. Processes can pass file descriptors over Unix sockets, delegating access. Once a file is opened, the process can access it even if permissions change. This capability-like design, present since early Unix, inspires modern capability-secure systems.
The Principle of Least Privilege is the foundational authorization design principle: every subject should have only the minimum privileges necessary to perform its function. This seems obvious but requires continuous effort to implement.
Why Least Privilege Matters:
Every unnecessary privilege is an opportunity for:
Implementing Least Privilege:
Least privilege isn't a one-time configuration—it's an ongoing discipline:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
#include <unistd.h>#include <sys/types.h>#include <pwd.h>#include <grp.h>#include <sys/prctl.h>#include <linux/seccomp.h> /* * Example: Web server privilege separation * * Pattern: Start as root to bind port 80, * then immediately drop privileges. */ int main() { int listen_fd; struct sockaddr_in addr; /* ===== PRIVILEGED SECTION ===== */ /* This code runs as root */ /* Create socket and bind to privileged port 80 */ listen_fd = socket(AF_INET, SOCK_STREAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(80); addr.sin_addr.s_addr = INADDR_ANY; if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return 1; } listen(listen_fd, 128); /* ===== DROP PRIVILEGES ===== */ /* Get target user info */ struct passwd *pw = getpwnam("www-data"); if (pw == NULL) { fprintf(stderr, "User www-data not found\n"); return 1; } /* Change to restricted directory */ if (chroot("/var/www") != 0) { perror("chroot"); return 1; } chdir("/"); /* Drop supplementary groups */ if (setgroups(0, NULL) != 0) { perror("setgroups"); return 1; } /* Drop group privileges (must be before setuid) */ if (setgid(pw->pw_gid) != 0) { perror("setgid"); return 1; } /* Drop user privileges */ if (setuid(pw->pw_uid) != 0) { perror("setuid"); return 1; } /* Prevent regaining privileges */ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) { perror("prctl"); /* Non-fatal, but log it */ } /* * Optional: Further restrict with seccomp * Allow only needed system calls */ /* ===== UNPRIVILEGED SECTION ===== */ /* Now running as www-data, chrooted to /var/www */ /* Cannot bind new privileged ports */ /* Cannot access files outside /var/www */ /* Cannot regain root privileges */ /* Main server loop now runs with minimum privileges */ while (1) { int client_fd = accept(listen_fd, NULL, NULL); handle_request(client_fd); /* Runs unprivileged */ close(client_fd); } return 0;}In practice, privileges tend to accumulate. Users request access for one task and never relinquish it. Service accounts acquire unnecessary rights during debugging and keep them. Regular privilege audits are essential. Ask: 'Does this subject still need these permissions?' Remove what's unnecessary. This requires ongoing effort but dramatically reduces attack surface.
Real-world authorization combines multiple models using layered checks. A typical access decision might involve DAC, then MAC, then application-level controls. Understanding this layered approach helps design comprehensive authorization.
Linux Security Module (LSM) Framework:
Linux uses the LSM framework to implement mandatory access control. LSM inserts hooks at authorization decision points throughout the kernel. When a process attempts an operation, the relevant LSM hook is called, allowing security modules (SELinux, AppArmor, SMACK, etc.) to permit or deny the operation.
Authorization Priority:
In Linux with SELinux:
Access is permitted only if all applicable layers permit it. A single deny from any layer denies access.
| Check | What's Verified | Denial Result |
|---|---|---|
| DAC (permissions) | Unix mode bits allow operation | EACCES (Permission denied) |
| DAC (ownership) | User/group matches owner/group | EACCES |
| Capabilities | Process has required capability | EPERM (Operation not permitted) |
| SELinux | Policy allows domain->type access | EACCES (with AVC audit message) |
| AppArmor | Profile allows path access | EACCES (with audit message) |
| Namespaces | Resource visible in namespace | ENOENT (No such file) |
| seccomp | Syscall in allowed set | SIGSYS or EPERM |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
# Debugging authorization denials # Scenario: Process cannot read a file - why? # Check DAC permissions$ ls -la /path/to/file.txt-rw------- 1 alice alice 1234 Jan 15 10:00 file.txt # If running as bob, DAC will deny (no 'other' permissions) # Check if SELinux is involved$ getenforceEnforcing # Check SELinux context requirements$ ls -Z /path/to/file.txt-rw-------. alice alice unconfined_u:object_r:user_home_t:s0 file.txt $ ps -eZ | grep myprocesssystem_u:system_r:httpd_t:s0 myprocess # SELinux may deny httpd_t -> user_home_t access # Check for SELinux denials$ ausearch -m AVC -ts recent # Check AppArmor (if used instead of SELinux)$ aa-status$ dmesg | grep apparmor # Check if capabilities are relevant$ getpcaps $(pgrep myprocess)# Does process need CAP_DAC_READ_SEARCH or CAP_DAC_OVERRIDE? # Check namespace isolation$ ls -la /proc/$(pgrep myprocess)/ns/# Is the process in a different mount namespace? # Check seccomp filtering$ grep Seccomp /proc/$(pgrep myprocess)/statusSeccomp: 2# 2 means SECCOMP_MODE_FILTER (BPF filtering active) # Strace to see actual syscall and errno$ strace -f -e trace=file myprocess 2>&1 | grep -E "open|access"openat(AT_FDCWD, "/path/to/file.txt", O_RDONLY) = -1 EACCES # Summary: For any denial, check in order:# 1. DAC permissions (ls -la)# 2. MAC (ls -Z, ausearch for SELinux; dmesg for AppArmor)# 3. Capabilities (getpcaps)# 4. Namespace visibility (ls -la /proc/PID/ns)# 5. Seccomp filters (grep Seccomp /proc/PID/status)Modern infrastructure treats authorization policies as code—version controlled, reviewed, tested, and deployed through CI/CD pipelines. SELinux policies, AppArmor profiles, and cloud IAM policies can all be managed this way. This approach provides audit trails, enables rollback, and allows policy to evolve with the application while maintaining security guarantees.
Authorization determines what authenticated subjects may do with system resources. Operating systems implement authorization through discretionary controls, mandatory access control, role-based access, and capabilities—often layered together for defense in depth. Let's consolidate our understanding:
Module Complete:
With this page, we complete Module 1: Protection Goals. We've explored the five fundamental security objectives:
These five goals form the conceptual foundation for all security mechanisms. Every firewall rule, every access control list, every encryption scheme ultimately serves one or more of these goals. Understanding them deeply enables you to reason about security at any level of system design.
Congratulations! You've completed Module 1: Protection Goals. You now understand the fundamental security objectives that operating systems must achieve: Confidentiality, Integrity, Availability, Authentication, and Authorization. In the next module, we'll explore Protection Domains—how operating systems create isolated execution contexts with different privilege levels to implement these protection goals.