Loading learning content...
In the realm of access control, the universe divides into two fundamental categories: those who act and those who are acted upon. This distinction—between active agents initiating operations and passive resources receiving operations—lies at the heart of every authorization decision ever made.
Yet this seemingly clear division hides subtle complexities. Is a running process a subject, an object, or both? What about a user account that can be modified by administrators? When a program reads a file and writes to another, how do we model this chain of access? Understanding subjects and objects in depth is essential for comprehending how access control actually works in real systems.
By the end of this page, you will understand the precise definitions of subjects and objects in access control theory, explore the various types of each in real operating systems, examine the crucial distinction between users and processes, and master the dual nature of entities that serve as both subjects and objects simultaneously.
A subject is an active entity that can initiate operations on objects. Subjects are the originators of access requests—they "do things" within the system. In formal terms:
Definition:
A subject is an entity that can cause information to flow among objects or change the system state.
The key characteristic is agency—subjects can initiate actions. They make requests of the form "I want to perform operation X on object Y."
Categories of Subjects:
The User vs. Process Distinction:
A critical subtlety: users are not directly subjects in most operating systems. Users cannot directly access files or execute operations—they must do so through processes. The process acts as their agent.
Consider the chain:
cat secret.txtcat process requests to read secret.txtThe process is the subject making the access request, but the user identity determines what rights apply. This is why we say processes "run as" or "on behalf of" users.
In practice, a subject's identity is rarely a single attribute. Linux processes have: real UID, effective UID, saved UID, real GID, effective GID, saved GID, supplementary groups, SELinux context, namespaces, and capability sets. All of these contribute to determining what the subject can access. The simple 'user = subject' model is a useful abstraction, but real systems are more nuanced.
Different operating systems model subjects in different ways. Let's examine how major systems represent subjects:
Unix/Linux Model:
In Unix, the fundamental subject is the process. Each process has:
struct process {
uid_t uid_real; // Real user ID (who started the process)
uid_t uid_effective; // Effective user ID (used for access checks)
uid_t uid_saved; // Saved set-user-ID
gid_t gid_real; // Real group ID
gid_t gid_effective; // Effective group ID
gid_t gid_saved; // Saved set-group-ID
gid_t groups[]; // Supplementary group list
};
Access decisions use the effective UID/GID, which allows privilege transitions via setuid programs.
Windows Model:
Windows uses Security Identifiers (SIDs) authenticated in access tokens:
TOKEN {
User SID: S-1-5-21-...-1001 (Alice)
Group SIDs: S-1-5-21-...-512 (Domain Admins)
S-1-5-32-545 (Users)
Privileges: SeBackupPrivilege
SeRestorePrivilege
Integrity Level: High
Session ID: 1
}
Every process and thread has a token. Access checks compare the token against object security descriptors.
| System | Primary Subject | Identity Attributes | Special Cases |
|---|---|---|---|
| Linux | Process | UID, GID, Groups, Capabilities | Kernel threads (UID 0) |
| Windows | Thread (token) | User SID, Group SIDs, Privileges | System (LocalSystem) |
| macOS | Process | UID, GID, Entitlements | System Integrity Protection subjects |
| Android | Application (APK) | UID per app, Permissions | Shared UID for related apps |
| SELinux | Security Context | user:role:type:level | Unconfined domain |
| AWS IAM | Principal | ARN, Policies, Roles | Service roles, Cross-account |
Service Accounts and Non-Human Subjects:
Modern systems increasingly feature non-human subjects:
These non-human subjects follow the same access matrix model—they're just rows in the matrix without a human behind them.
Subjects often exist in hierarchies. A thread runs within a process, which runs within a session, which belongs to a user, who belongs to groups, which are part of organizational units. Access decisions may consider any level of this hierarchy. The challenge is determining which identity attributes matter for a given access decision.
An object is a passive entity that contains or receives information. Objects are the targets of access requests—they are "acted upon" by subjects. In formal terms:
Definition:
An object is an entity to which access is controlled. It is typically a passive container for information or a representation of a resource.
The key characteristic is passivity—objects do not initiate actions. They respond to requests made by subjects.
Categories of Objects:
| Object Category | Examples | Typical Rights | Protection Mechanism |
|---|---|---|---|
| Files | Documents, executables, configs | read, write, execute, delete | Inode permissions, ACLs |
| Directories | Folders, mount points | read, write, execute, search | Directory permissions |
| Devices | /dev/sda, /dev/null | read, write, ioctl | Device file permissions |
| Memory | Heap, stack, mmap regions | read, write, execute | Page table permissions |
| Sockets | TCP connections, Unix sockets | connect, listen, accept | Socket options, firewall |
| Processes | PIDs, jobs | signal, terminate, trace | Process ownership |
| Semaphores | Named/unnamed semaphores | wait, post | IPC permissions |
Object Identification:
Objects must be uniquely identifiable for access control to work. Different systems use different identification schemes:
The naming scheme matters because it defines the granularity of protection. If you can't name something, you can't protect it separately from other things.
Unix's 'everything is a file' philosophy means almost everything becomes an object in the access matrix: regular files, directories, devices (/dev/), kernel interfaces (/proc/, /sys/*), network sockets, and even processes (via /proc/PID/). This uniformity simplifies access control by applying the same model everywhere.
Objects vary in their security sensitivity and integrity requirements. Classification schemes help organize access control policies:
Sensitivity Levels:
Multi-level security (MLS) systems assign sensitivity labels to objects:
| Level | Examples | Typical Treatment |
|---|---|---|
| Top Secret | National security data | Extreme restriction, air-gapped systems |
| Secret | Military operations | Need-to-know access |
| Confidential | Business-sensitive | Internal only |
| Restricted | Internal use | Limited external access |
| Public | Published content | Open access |
These levels create a lattice: subjects at one level can read same-or-lower levels (no read-up) and write same-or-higher levels (no write-down) under the Bell-LaPadula model.
Integrity Levels:
The Biba model inverts this for integrity:
Subjects at high integrity cannot be corrupted by low-integrity objects (no read-down for integrity).
Categories and Compartments:
Beyond levels, objects may belong to compartments or categories:
A subject needs clearance for the level AND all compartments to access an object:
Object: Secret // NATO // Project Alpha
Subject needs: Secret clearance + NATO access + Project Alpha access
This is the lattice model of access control, where objects and subjects are ordered by a partial ordering defined by (level, compartments) pairs.
Labels in Practice:
SELinux implements this through security contexts:
user_u:role_r:httpd_content_t:s0:c0.c255
s0: Sensitivity levelc0.c255: Categories (compartments)These labels are attached to objects (files, sockets, processes) and used for all access decisions.
The challenge with object classification isn't the access control mechanism—it's accurately classifying objects in the first place. Data that starts as 'public' may become sensitive when aggregated. Manual classification doesn't scale. Automated classification is imprecise. Organizations struggle with consistent labeling, and mislabeled objects are either over-protected (wasting resources) or under-protected (creating vulnerabilities).
One of the most subtle aspects of access control is that many entities are both subjects and objects simultaneously. A process initiates operations (subject behavior) but can also be operated upon by other processes (object behavior).
Process as Dual Entity:
Consider process P:
As a Subject:
As an Object:
In the Access Matrix, P appears as both a row (subject) and a column (object):
| File F | Process P | Process Q | |
|---|---|---|---|
| Process P | read, write | - | signal |
| Process Q | read | signal, trace | - |
| System | - | terminate | terminate |
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556
#include <stdio.h>#include <signal.h>#include <unistd.h>#include <sys/wait.h>#include <sys/ptrace.h> /** * Demonstrates process as both subject and object */ // Process acts as SUBJECT: initiating operationsvoid act_as_subject() { // Subject behavior: read a file FILE *f = fopen("/etc/passwd", "r"); // Access check on file if (f) { char buf[256]; fgets(buf, sizeof(buf), f); printf("Read: %s", buf); fclose(f); } // Subject behavior: send signal to another process pid_t target = 1234; // Some other process int result = kill(target, SIGUSR1); // Access check: can we signal target? if (result == -1) { perror("Cannot signal target"); // EPERM if not permitted } // Subject behavior: create child process pid_t child = fork(); // Access check: can we spawn? if (child == 0) { // Child process code execlp("ls", "ls", "-l", NULL); }} // Meanwhile, THIS process can be treated as OBJECT by others:// - kill(getpid(), SIGTERM) // Another process terminates us// - ptrace(PTRACE_ATTACH, pid) // Debugger attaches to us// - /proc/PID/mem // Memory can be read externally// - nice revalue // Priority can be modified // Handler for when WE are the object of a signalvoid signal_handler(int sig) { printf("I am object of signal %d\n", sig);} int main() { // Register handler - we're prepared to be an object signal(SIGUSR1, signal_handler); // But we're also a subject performing actions act_as_subject(); return 0;}Other Dual Entities:
| Entity | Subject Behavior | Object Behavior |
|---|---|---|
| Thread | Executes code, accesses memory | Terminated, prioritized, joined by other threads |
| User Account | Authenticates, makes requests | Modified by admin, disabled, password reset |
| Service | Handles client requests | Started/stopped by init system |
| Container | Runs applications, mounts volumes | Created/destroyed by orchestrator |
| Virtual Machine | Runs guest OS | Paused, migrated, snapshotted by hypervisor |
| Database Connection | Queries, modifies data | Killed, prioritized by DBA |
Implications for Access Control:
The dual nature means:
Consider the relationship between a parent process and its child. The parent can wait() on, signal, and in some systems memory-access the child. But can the child do the same to the parent? Usually yes for signaling, but often no for debugging (can't ptrace upward). The access matrix captures this asymmetry: A[parent, child] ≠ A[child, parent].
The connection between names and objects is crucial for security. Access control checks must happen on the right object, which means the mapping from name to object must be secure.
The Binding Problem:
When a subject requests access to "/home/alice/document.txt":
The vulnerability: If the binding changes between name resolution and access check (or between check and use), the wrong object may be accessed.
TOCTOU Attacks (Time-of-Check to Time-of-Use):
Thread 1 (victim): Thread 2 (attacker):
------------------------------------------------------
if (access("file", R_OK) == 0)
symlink("/etc/passwd", "file")
open("file", O_RDONLY) // Opens /etc/passwd!
Between the access() check and open(), the attacker changes what "file" points to. The check passed for one object, but the open operates on another.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657
#include <stdio.h>#include <unistd.h>#include <fcntl.h>#include <errno.h> /** * VULNERABLE CODE: Classic TOCTOU race condition * * This pattern is dangerous when the subject has elevated privileges * and is checking access on behalf of a less-privileged user. */void vulnerable_file_access(const char *filename) { // TIME-OF-CHECK: Verify user has access if (access(filename, R_OK) == 0) { // Check as real UID // <-- WINDOW OF VULNERABILITY --> // Between check and use, attacker can: // 1. Delete the file // 2. Replace with symlink to sensitive file // 3. Replace with different file // TIME-OF-USE: Actually open the file int fd = open(filename, O_RDONLY); // Opens as effective UID if (fd >= 0) { char buf[4096]; read(fd, buf, sizeof(buf)); close(fd); } }} /** * SAFE CODE: Avoid TOCTOU by using file descriptors * * Open first, then check permissions on the open descriptor. * Or use openat() with O_NOFOLLOW to prevent symlink attacks. */void safe_file_access(const char *filename) { // Open first (binds name to object atomically) int fd = open(filename, O_RDONLY | O_NOFOLLOW); if (fd < 0) { return; // Failed to open } // Now check access using the file descriptor // faccessat() with AT_EACCESS or fstat() + manual check struct stat st; if (fstat(fd, &st) == 0) { // Access checks on the actual opened object // No race condition possible - we're checking what we opened if (st.st_uid == getuid() || (st.st_mode & S_IROTH)) { char buf[4096]; read(fd, buf, sizeof(buf)); } } close(fd);}Secure Binding Mechanisms:
Systems use various techniques to ensure name-to-object binding security:
Capabilities: Reference objects by unforgeable tokens, not names. Once you have the capability (file descriptor), the object is bound.
Object handles: Windows uses handles (integers) that map to kernel objects. The mapping is per-process and protected by the kernel.
Authenticated naming: Object names include integrity checks (hashes, signatures) to detect tampering.
Protected namespaces: The namespace itself (directory) is protected, so only authorized subjects can modify bindings.
Atomic check-and-open: System calls like openat(dirfd, path, O_NOFOLLOW) combine resolution and access checking atomically.
Namespace Containerization:
Linux namespaces create isolated naming contexts. A process in namespace A sees different objects for "/tmp" than a process in namespace B, even though they use the same name. This provides object isolation at the naming level.
A fundamental security principle: Never confuse names with objects. Access control must ultimately protect objects, not names. Names can be shared (hard links), can redirect (symlinks), and can change (renames). Always verify that your access control is checking the actual object, not just a name that might point to different objects at different times.
Beyond simple access rights, subjects and objects can have structured relationships that affect access control:
Ownership:
The owner relationship grants special meta-rights. The owner typically can:
Ownership usually starts with creation: the subject that creates an object becomes its owner. In Unix, file ownership is stored in the inode (uid, gid fields).
Creator Relationship:
The creator of a process is its parent. This establishes:
Delegation:
An owner may delegate rights to other subjects without transferring ownership:
Delegation can be recursive if the copy flag (e.g., WITH GRANT OPTION) is set.
Static Relationships:
Some relationships are established at creation and don't change:
These form the immutable structure of the protection state.
Dynamic Relationships:
Other relationships evolve:
Dynamic relationships enable administration but also create attack surface.
Relationships can be indirect. If Alice owns directory D, and file F is in D, Alice has effective control over F even if Bob owns F. Delete access to the directory gives Alice the ability to remove Bob's file. These indirect relationships through container hierarchies often surprise users and create unexpected access patterns.
We've explored the two fundamental entity types in access control. Let's consolidate the key concepts:
What's Next:
Now that we understand who acts (subjects) and what is acted upon (objects), we'll explore what they can do: access rights. The next page examines the taxonomy of rights, how different operations map to rights, and the meta-rights that control the access matrix itself.
You now understand subjects and objects in depth—from their formal definitions to their real-world representations in modern operating systems. This knowledge is essential for understanding any access control system, whether you're configuring file permissions, designing API authorization, or auditing security configurations.