Chapter 4. Access Control

After the wide scope in the previous chapter on all things shell and scripting, we now focus on one specific and crucial security aspect in Linux. In this chapter, we discuss the topic of users and controlling access to resources in general and files in particular.

One question that immediately comes to mind in such a multiuser setup is ownership. A user may own, for example, a file. They are allowed to read from the file, write to the file, and also, say, delete it. Given that there are other users on the system as well, what are those users allowed to do, and how is this defined and enforced? There are also activities that you might not necessarily associate with files in the first place. For example, a user may (or may not) be allowed to change networking-related settings.

To get a handle on this topic, we’ll first take a look at the fundamental relationship between users, processes, and files, from an access perspective. We’ll also review sandboxing and access control types. Next, we’ll focus on the definition of a Linux user, what users can do, and how to manage users either locally or alternatively from a central place.

Then, we’ll move on to the topic of permissions, where we’ll look at how to control access to files and how processes are impacted by such restrictions.

We’ll wrap up this chapter covering a range of advanced Linux features in the access control space, including capabilities, seccomp profiles, and ACLs. To round things off, we’ll provide some security good practices around permissions and access control.

With that, let’s jump right into the topic of users and resource ownership, laying the basis for the rest of the chapter.


Before we get into access control mechanisms, let’s step back a little and take a bird’s-eye view on the topic. This will help us to establish some terminology and clarify relationships between the main concepts.

Resources and Ownership

Linux is a multiuser operating system and as such has inherited the concept of a user (see “Users”) from UNIX. Each user account is associated with a user ID that can be given access to executables, files, devices, and other Linux assets. A human user can log in with a user account, and a process can run as a user account. Then, there are resources (which we will simply refer to as files), which are any hardware or software components available to the user. In the general case, we will refer to resources as files, unless we explicitly talk about access to other kinds of resources, such as with syscalls. In Figure 4-1 and the passage that follows, you see the high-level relationships between users, processes, and files in Linux.

lmlx 0401
Figure 4-1. Users, processes, and files in Linux

Launch processes and own files. A process is a program (executable file) that the kernel has loaded into main memory and runs.


Have owners; by default, the user who creates the file owns it.


Use files for communication and persistency. Of course, users indirectly also use files, but they need to do so via processes.

This depiction of the relationships between users, processes, and files is of course a very simplistic view, but it allows us to understand the actors and their relationships and will come in handy later on when we discuss the interaction between these different players in greater detail.

Let’s first look at the execution context of a process, addressing the question of how restricted the process is. A term that we often come across when talking about access to resources is sandboxing.


Sandboxing is a vaguely defined term and can refer to a range of different methods, from jails to containers to virtual machines, which can be managed either in the kernel or in user land. Usually there is something that runs in the sandbox—typically some application—and the supervising mechanism enforces a certain degree of isolation between the sandboxed process and the hosting environment. If all of that sounds rather theoretical, I ask you for a little bit of patience. We will see sandboxing in action later in this chapter, in “seccomp Profiles”, and then again in Chapter 9 when we talk about VMs and containers.

With a basic understanding of resources, ownership, and access to said resources in your mind, let’s talk briefly about some conceptual ways to go about access control.

Types of Access Control

One aspect of access control is the nature of the access itself. Does a user or process directly access a resource, maybe in an unrestricted manner? Or maybe there is a clear set of rules about what kind of resources (files or syscalls) a process can access, under what circumstances. Or maybe the access itself is even recorded.

Conceptually, there are different access control types. The two most important and relevant to our discussion in the context of Linux are discretionary and mandatory access control:

Discretionary access control

With discretionary access control (DAC), the idea is to restrict access to resources based on the identity of the user. It’s discretionary in the sense that a user with certain permissions can pass them on to other users.

Mandatory access control

Mandatory access control is based on a hierarchical model representing security levels. Users are assigned a clearance level, and resources are assigned a security label. Users can only access resources corresponding to a clearance level equal to (or lower than) their own. In a mandatory access control model, an admin strictly and exclusively controls access, setting all permissions. In other words, users cannot set permissions themselves, even when they own the resource.

In addition, Linux traditionally has an all-or-nothing attitude—that is, you are either a superuser who has the power to change everything or you are a normal user with limited access. Initially, there was no easy and flexible way to assign a user or process certain privileges. For example, in the general case, to enable that “process X is allowed to change networking settings,” you had to give it root access. This, naturally, has a concrete impact on a system that is breached: an attacker can misuse these wide privileges easily.


To qualify the “all-or-nothing attitude” in Linux a bit: the defaults in most Linux systems allow read access to almost every file and executable by “others”—that is, all users on the system. For example, with SELinux enabled, mandatory access control restricts access to only those assets that are explicitly given permission. So, for example, a web server can only use ports 80 and 443, only share files and scripts from specific directories, only write logs to specific places, and so on.

We’ll revisit this topic in “Advanced Permission Management” and see how modern Linux features can help overcome this binary worldview, allowing for more fine-grained management of privileges.

Probably the best-known implementation of mandatory access control for Linux is SELinux. It was developed to meet the high security requirements of government agencies and is usually used in these environments since the usability suffers from the strict rules. Another option for mandatory access control, included in the Linux kernel since version 2.6.36 and rather popular in the Ubuntu family of Linux distributions, is AppArmor.

Let’s now move on to the topic of users and how to manage them in Linux.


In Linux we often distinguish between two types of user accounts, from a purpose or intended usage point of view:

So-called system users, or system accounts

Typically, programs (sometimes called daemons) use these types of accounts to run background processes. The services provided by these programs can be part of the operating system, such as networking (sshd, for example), or on the application layer (for example, mysql, in the case of a popular relational database).

Regular users

For example, a human user that interactively uses Linux via the shell.

The distinction between system and regular users is less of a technical one and more an organizational construct. To understand that, we first have to introduce the concept of a user ID (UID), a 32-bit numerical value managed by Linux.

Linux identifies users via a UID, with a user belonging to one or more groups identified via a group ID (GID). There is a special kind of user with the UID 0, usually called root. This “superuser” is allowed to do anything, that is, no restriction apply. Usually, you want to avoid working as the root user, because it’s just too much power to have. You can easily destroy a system if you’re not careful (believe me, I’ve done this). We’ll get back to this later in the chapter.

Different Linux distributions have their own ways to decide how to manage the UID range. For example, systemd-powered distributions (see “systemd”), have the following convention (simplified here):


Is root

UID 1 to 999

Are reserved for system users

UID 65534

Is user nobody—used, for example, for mapping remote users to some well-known ID, as is the case with “Network File System”

UID 1000 to 65533 and 65536 to 4294967294

Are regular users

To figure out your own UIDs, you can use the (surprise!) id command like so:

$ id -u

Now that you know the basics about Linux users, let’s see how you can manage users.

Managing Users Locally

The first option, and traditionally the only one available, is managing users locally. That is, only information local to the machine is used, and user-related information is not shared across a network of machines.

For local user management, Linux uses a simple file-based interface with a somewhat confusing naming scheme that is a historic artifact we have to live with, unfortunately. Table 4-1 lists the four files that, together, implement user management.

Table 4-1. Reference of local user management files
Purpose File

User database


Group database


User passwords


Group passwords


Think of /etc/passwd as a kind of mini user database keeping track of user names, UIDs, group membership, and other data, such as home directory and login shell used, for regular users. Let’s have a look at a concrete example:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash 1
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin 2
mh9:x:1000:1001::/home/mh9:/usr/bin/fish 3

The root user has UID 0.


A system account (the nologin gives it away; see more below).


My user account.

Let’s have a closer look at one of the lines in /etc/passwd to understand the structure of a user entry in detail:

^    ^ ^ ^ ^    ^     ^
|    | | | |    |     └──  1
|    | | | |    └──  2
|    | | | └──  3
|    | | └── 4
|    | └──  5
|    └──  6
└──  7

The login shell to use. To prevent interactive logins, use /sbin/nologin.


The user’s home directory; this defaults to /.


User information such as full name or contact data like phone number. Often also known as GECOS field. Note that GECOS formatting is not used, but rather the field itself is used typically for the full name of the person associated with the account.


The user’s primary group (GID); see also /etc/group.


The UID. Note that Linux reserves UIDs below 1000 for system usage.


The user’s password, with the x character meaning that the (encrypted) password is stored in /etc/shadow, which is the default these days.


The username, which must be 32 characters or fewer.

One thing we notice is absent in /etc/passwd is the one thing we would expect to find there, based on its name: the password. Passwords are, for historic reasons, stored in a file called /etc/shadow. While every user can read /etc/passwd, you usually need root privileges to read for /etc/shadow.

To add a user, you can use the adduser command as follows:

$ sudo adduser mh9
Adding user `mh9' ...
Adding new group `mh9' (1001) ...
Adding new user `mh9' (1000) with group `mh9' ...
Creating home directory `/home/mh9' ... 1
Copying files from `/etc/skel' ... 2
New password: 3
Retype new password:
passwd: password updated successfully
Changing the user information for mh9
Enter the new value, or press ENTER for the default 4
        Full Name []: Michael Hausenblas
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n] Y

The adduser command creates a home directory.


It also copies a bunch of default config files into the home directory.


Need to define a password.


Provide optional GECOS information.

If you want to create a system account, pass in the -r option. This will disable the ability to use a login shell and also avoid home directory creation. For configuration details, see also /etc/adduser.conf, including options such as the UID/GID range to be used.

In addition to users, Linux also has the concept of groups, which in a sense is just a collection of one or more users. Any regular user belongs to one default group but can be a member of additional groups. You can find out about groups and mappings via the /etc/group file:

$ cat /etc/group 1
mh9:x:1001: 2

Display the content of the group mapping file.


An example group of my user with the GID 1001. Note that you can add a comma-separated list of user names after the last colon to allow multiple users to have that group permission.

With this basic user concept and management under our belt, we move on to a potentially better way to manage users in a professional setup, allowing for scale.

Centralized User Management

If you have more than one machine or server for which you have to manage users—say, in a professional setup—then managing users locally quickly becomes old. You want a centralized way to manage users that you can apply locally, to one specific machine. There are a few approaches available to you, depending on your requirements and (time) budget:

Directory based

Lightweight Directory Access Protocol (LDAP), a decades-old suite of protocols now formalized by IETF, defines how to access and maintain a distributed directory over Internet Protocol (IP). You can run an LDAP server yourself—for example, using projects like Keycloak—or outsource this to a cloud provider, such as Azure Active Directory.

Via a network

Users can be authenticated in this manner with Kerberos. We’ll look at Kerberos in detail in “Kerberos”.

Using config management systems

These systems, which include Ansible, Chef, Puppet, or SaltStack, can be used to consistently create users across machines.

The actual implementation is often dictated by the environment. That is, a company might already be using LDAP, and hence the choices might be limited. The details of the different approaches and pros and cons are, however, beyond the scope of this book.


In this section, we first go into detail concerning Linux file permissions, which are central to how access control works, and then we look at permissions around processes. That is, we review runtime permissions and how they are derived from file permissions.

File Permissions

File permissions are core to Linux’s concept of access to resources, since everything is a file in Linux, more or less. Let’s first review some terminology and then discuss the representation of the metadata around file access and permissions in detail.

There are three types or scopes of permissions, from narrow to wide:


The owner of the file


Has one or more members


The category for everyone else

Further, there are three types of access:

Read (r)

For a normal file, this allows a user to view the contents of the file. For a directory, it allows a user to view the names of files in the directory.

Write (w)

For a normal file, this allows a user to modify and delete the file. For a directory, it allows a user to create, rename, and delete files in the directory.

Execute (x)

For a normal file, this allows a user to execute the file if the user also has read permissions on it. For a directory, it allows a user to access file information in the directory, effectively permitting them to change into it (cd) or list its content (ls).

Let’s see file permissions in action (note that the spaces you see here in the output of the ls command have been expanded for better readability):

$ ls -al
total 0
-rw-r--r--  1  mh9  devs  9  Apr 12 11:42  test
^           ^  ^    ^     ^  ^             ^
|           |  |    |     |  |             └──  1
|           |  |    |     |  └──  2
|           |  |    |     └──  3
|           |  |    └── 4
|           |  └──  5
|           └──  6
└──  7

File name


Last modified time stamp


File size in bytes


Group the file belongs to


File owner


Number of hard links


File mode

When zooming in on the file mode—that is, the file type and permissions referred to as 7 in the preceding snippet—we have fields with the following meaning:

. rwx rwx rwx
^ ^   ^   ^
| |   |   └──  1
| |   └──  2
| └──  3
└── 4

Permissions for others


Permissions for the group


Permissions for the file owner


The file type (Table 4-2)

The first field in the file mode represents the file type; see Table 4-2 for details. The remainder of the file mode encodes the permissions set for various targets, from owner to everyone, as listed in Table 4-3.

Table 4-2. File types used in mode
Symbol Semantics


A regular file (such as when you do touch abc)


Block special file


Character special file


High-performance (contiguous data) file


A directory


A symbolic link


A named pipe (create with mkfifo)


A socket


Some other (unknown) file type

There are some other (older or obsolete) characters such as M or P used in the position 0, which you can by and large ignore. If you’re interested in what they mean, run info ls -n "What information is listed".

In combination, these permissions in the file mode define what is allowed for each element of the target set (user, group, everyone else), as shown in Table 4-3, checked and enforced through access.

Table 4-3. File permissions
Pattern Effective permission Decimal representation











Write and execute






Read and execute



Read and write



Read, write, execute


Let’s have a look at a few examples:


Full access for its owner; read and execute for everyone else


Full access by its owner; none for everyone else


Read/write access for owner and group; read-only for others


Read/write for owner; read-only for everyone else


Read-only by its owner

The 664 has a special meaning on my system. When I create a file, that’s the default permission it gets assigned. You can check that with the umask command, which in my case gives me 0002.

The setuid permissions are used to tell the system to run an executable as the owner, with the owner’s permissions. If a file is owned by root, that can cause issues.

You can change the permissions of a file using chmod. Either you specify the desired permission settings explicitly (such as 644) or you use shortcuts (for example, +x to make it executable). But how does that look in practice?

Let’s make a file executable with chmod:

$ ls -al /tmp/masktest
-rw-r--r-- 1 mh9 dev 0 Aug 28 13:07 /tmp/masktest 1

$ chmod +x /tmp/masktest 2

$ ls -al /tmp/masktest
-rwxr-xr-x 1 mh9 dev 0 Aug 28 13:07 /tmp/masktest 3

Initially the file permissions are r/w for the owner and read-only for everyone else, aka 644.


Make the file executable.


Now the file permissions are r/w/x for the owner and r/x for everyone else, aka 755.

In Figure 4-2 you see what is going on under the hood. Note that you might not want to give everyone the right to execute the file, so a chmod 744 might have been better here, giving only the owner the correct permissions while not changing it for the rest. We will discuss this topic further in “Good Practices”.

lmlx 0402
Figure 4-2. Making a file executable and how the file permissions change with it

You can also change the ownership using chown (and chgrp for the group):

$ touch myfile
$ ls -al myfile
-rw-rw-r-- 1 mh9 mh9 0 Sep 4 09:26 myfile 1

$ sudo chown root myfile 2
-rw-rw-r-- 1 root mh9 0 Sep 4 09:26 myfile

The file myfile, which I created and own.


After chown, root owns that file.

Having discussed basic permission management, let’s take a look at some more advanced techniques in this space.

Process Permissions

So far we’ve focused on how human users access files and what the respective permissions in play are. Now we shift the focus to processes. In “Resources and Ownership”, we talked about how users own files as well as how processes use files. This raises the question: what are the relevant permissions, from a process point of view?

As documented on the credentials(7) manual page, there are different user IDs relevant in the context of runtime permissions:

Real UID

The real UID is the UID of the user that launched the process. It represents process ownership in terms of human user. The process itself can obtain its real UID via getuid(2), and you can query it via the shell using stat -c "%u %g" /proc/$pid/.

Effective UID

The Linux kernel uses the effective UID to determine permissions the process has when accessing shared resources such as message queues. On traditional UNIX systems, they are also used for file access. Linux, however, previously used a dedicated filesystem UID (see the following discussion) for file access permissions. This is still supported for compatibility reasons. A process can obtain its effective UID via geteuid(2).

Saved set-user-ID

Saved set-user-IDs are used in suid cases where a process can assume privileges by switching its effective UID between the real UID and the saved set-user-ID. For example, in order for a process to be allowed to use certain network ports (see “Ports”), it needs elevated privileges, such as being run as root. A process can get its saved set-user-IDs via getresuid(2).

Filesystem UID

These Linux-specific IDs are used to determine permissions for file access. This UID was initially introduced to support use cases where a file server would act on behalf of a regular user while isolating the process from signals by said user. Programs don’t usually directly manipulate this UID. The kernel keeps track of when the effective UID is changed and automatically changes the filesystem UID with it. This means that usually the filesystem UID is the same as the effective UID but can be changed via setfsuid(2). Note that technically this UID is no longer necessary since kernel v2.0 but is still supported, for compatibility.

Initially, when a child process is created via fork(2), it inherits copies of its parent’s UIDs, and during an execve(2) syscall, the process’s real UID is preserved, whereas the effective UID and saved set-user-ID may change.

For example, when you run the passwd command, your effective UID is your UID, let’s say 1000. Now, passwd has suid set enabled, which means when you run it, your effective UID is 0 (aka root). There are also other ways to influence the effective UID—for example, using chroot and other sandboxing techniques.


POSIX threads require that credentials are shared by all threads in a process. However, at the kernel level, Linux maintains separate user and group credentials for each thread.

In addition to file access permissions, the kernel uses process UIDs for other things, including but not limited to the following:

  • Establishing permissions for sending signals—for example, to determine what happens when you do a kill -9 for a certain process ID. We’ll get back to this in Chapter 6.

  • Permission handling for scheduling and priorities (for example, nice).

  • Checking resource limits, which we’ll discuss in detail in the context of containers in Chapter 9.

While it can be straightforward to reason with effective UID in the context of suid, once capabilities come into play it can be more challenging.

Advanced Permission Management

While so far we’ve focused on widely used mechanisms, the topics in this section are in a sense advanced and not necessarily something you would consider in a casual or hobby setup. For professional usage—that is, production use cases where business critical workloads are deployed—you should definitely be at least aware of the following advanced permission management approaches.


In Linux, as is traditionally the case in UNIX systems, the root user has no restrictions when running processes. In other words, the kernel only distinguishes between two cases:

  • Privileged processes, bypassing the kernel permission checks, with an effective UID of 0 (aka root)

  • Unprivileged processes, with a nonzero effective UID, for which the kernel does permission checks, as discussed in “Process Permissions”

With the introduction of the capabilities syscall in kernel v2.2, this binary worldview has changed: the privileges traditionally associated with root are now broken down into distinct units that can be independently assigned on a per-thread level.

In practice, the idea is that a normal process has zero capabilities, controlled by the permissions discussed in the previous section. You can assign capabilities to executables (binaries and shell scripts) as well as processes to gradually add privileges necessary to carry out a task (see the discussion in “Good Practices”).

Now, a word of caution: capabilities are generally relevant only for system-level tasks. In other words: most of the time you won’t necessarily depend on them.

In Table 4-4 you can see some of the more widely used capabilities.

Table 4-4. Examples of useful capabilities
Capability Semantics


Allows user to make arbitrary changes to files’ UIDs/GIDs


Allows sending of signals to processes belonging to other users


Allows changing the UID


Allows setting the capabilities of a running process


Allows various network-related actions, such as interface config


Allows using RAW and PACKET sockets


Allows calling chroot


Allows system admin operations, including mounting filesystems


Allows using strace to debug processes


Allows loading kernel modules

Let’s now see the capabilities in action. For starters, to view the capabilities, you can use commands as shown in the following (output edited to fit):

$ capsh --print 1
Current: =
Bounding set =cap_chown,cap_dac_override,cap_dac_read_search,

$ grep Cap /proc/$$/status 2
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

Overview of all capabilities on the system


Capabilities for the current process (the shell)

You can manage capabilities in a fine-grained manner—that is, on a per-file basis—with getcap and setcap (the details and good practices are beyond the scope of this chapter).

Capabilities help to transition from an all-or-nothing approach to finer-grained privileges on a file basis. Let’s now move on to a different advanced access control topic: the sandboxing technique of seccomp.

seccomp Profiles

Secure computing mode (seccomp) is a Linux kernel feature available since 2005. The basic idea behind this sandboxing technique is that, using a dedicated syscall called seccomp(2), you can restrict the syscalls a process can use.

While you might find it inconvenient to manage seccomp yourself directly, there are ways to use it without too much hassle. For example, in the context of containers (see “Containers”), both Docker and Kubernetes support seccomp.

Let’s now have a look at an extension of the traditional, granular file permission.

Access Control Lists

With access control lists (ACLs), we have a flexible permission mechanism in Linux that you can use on top of or in addition to the more “traditional” permissions discussed in “File Permissions”. ACLs address a shortcoming of traditional permissions in that they allow you to grant permissions for a user or a group not in the group list of a user.

To check if your distribution supports ACLs, you can use grep -i acl /boot/config* where you’d hope to find a POSIX_ACL=Y somewhere in the output to confirm it. In order to use ACL for a filesystem, it must be enabled at mount time, using the acl option. The docs reference on acl has a lot of useful details.

We won’t go into greater detail here on ACLs since they’re slightly outside the scope of this book; however, being aware of them and knowing where to start can be beneficial, should you come across them in the wild.

With that, let’s review some good practices for access control.

Good Practices

Here are some security “good practices” in the wider context of access control. While some of them might be more applicable in professional environments, everyone should at least be aware of them.

Least privileges

The least privileges principle says, in a nutshell, that a person or process should only have the necessary permissions to achieve a given task. For example, if an app doesn’t write to a file, then it only needs read access. In the context of access control, you can practice least privileges in two ways:

  • In “File Permissions”, we saw what happens when using chmod +x. In addition to the permissions you intended, it also assigns some additional permissions to other users. Using explicit permissions via the numeral mode is better than symbolic mode. In other words: while the latter is more convenient, it’s less strict.

  • Avoid running as root as much as you can. For example, when you need to install something, you should be using sudo rather than logging in as root.

Note that if you’re writing an application, you can use an SELinux policy to restrict access to only selected files, directories, and other features. In contrast, the default Linux model could potentially give the application access to any files left open on the system.

Avoid setuid

Take advantage of capabilities rather than relying on setuid, which is like a sledgehammer and offers attackers a great way to take over your system.


Auditing is the idea that you record actions (along with who carried them out) in a way that the resulting log can’t be tampered with. You can then use this read-only log to verify who did what, when. We’ll dive into this topic in Chapter 8.


Now that you know how Linux manages users, files, and access to resources, you have everything at your disposal to carry out routine tasks safely and securely.

For any practical work with Linux, remember the relationship between users, processes, and files. This is crucial, in the context of the multiuser operating system that Linux is, for a safe and secure operation and to avoid damages.

We reviewed access control types, defined what users in Linux are, what they can do, and how to manage them both locally and centrally. The topic of file permissions and how to manage them can be tricky, and mastering it is mostly a matter of practice.

Advanced permissions techniques including capabilities and seccomp profiles are super relevant in the context of containers.

In the last section, we discussed good practices around access control–related security, especially applying least privileges.

If you want to dive deeper into the topics discussed in this chapter, here are some resources:

Access Control Lists

Remember that security is an ongoing process, so you want to make sure to keep an eye on users and files, something we’ll go into greater detail on in Chapters 8 and 9, but for now let’s move on to the topic of filesystems.

Get Learning Modern Linux now with the O’Reilly learning platform.

O’Reilly members experience live online training, plus books, videos, and digital content from nearly 200 publishers.