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 multi-user setup is ownership. A user may own, for example, a file. They are allowed to read from the file, write to the file, and, say, also delete it. Given that there are other users on the system as well: what is that user are allowed to do, and how is this defined and enforced? There are also activities which 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 the topic, we will first have a look at the fundamental relationship between users, processes, and files, from an access perspective. We will also review sandboxing and access control types. Next, we focus on what is the definition of a Linux user, what users can do, and how to manage users either locally and from a central place.”

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

We 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 mechanism let us step back a little and take a birds 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 multi-user operating system and as such has inherited the concept of a user (“Users”) from Unix. Users can be a human user, or a process that does something in the name of a user. 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, for example, as the case with syscalls. In Figure 4-1 you see the high-level relationships between users, processes, and files in Linux.

users processes files
Figure 4-1. Users, processes, and files in Linux
  • Users launch processes and own files. A process is a program (executable file) that the kernel has loaded into main memory and runs.

  • Files have owners, by default, the user who creates the file owns it.

  • Processes 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 different players here in greater detail.

Let’s have a look at the execution context of a process, first. That is, addressing the question how restricted a process is.


A term that we often come accross when talking about access to resources is sandboxing. This is a vaguely defined term and can refer to a lot of different technologies, 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 the 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.

Conceptualy, 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 (DAC)

With 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 having certain permissions can pass them on to other users.

Mandatory (MAC)

MAC is based on a hierarchical model representing security levels. The users get a clearance level assigned 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 MAC model, an admin strictly and exclusively controlls access, setting all permissions. In other words, users cannot set permissions themselves, even in the case that they own the resource

In addition, Linux traditionally has an all or nothing attitude, that is, you are either a superuser that has the power to change everything, or you are a normal user with limited access. There was no way to assign a user or process certain privileges such as “this process 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. We will 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 MAC for Linux is SELinux. It was developed to meet the high security requirements of government agencies and is usually exactly used in these environments since the usability suffers from the strict rules. Another option for MAC, included in the Linux kernel since version 2.6.36, and rather popular in the Ubuntu family of Linux distribution is AppArmor.

Let us 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 case of a popular relational database.

  • Regular users, that is, a human user that interactively uses Linux via the shell, for example.

The distinction between system and regular users is less of a technical one but more an of 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 and that user is usually called root. This “superuser” is allowed to do anything, that is, no restriction apply. Usually you want to avoid to directly use the root user since that’s just too much power to have and you can easily destroy a system if you’re not careful. We will 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 also Chapter 6, have the following convention (simplified in the following):

  • UID 0 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 the case with NFS.

  • 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 that of 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 which 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 as well as 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, defaults to /.


User information such as full name or contact data like phone number. Often also known as GECOS field, nowadays seldomly used.


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, must be 32 characters or less long.

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

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 dir.


Need to define a password.


Provide optional GECOS info.

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. Also, see /etc/adduser.conf for configuration, such as UID/GID range.

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 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.

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 that you wish to or have to manage users for, say, in a professional setup, then managing users locally quickly becomes old. You want a centralized way to maange users that you can apply locally, to one specific machine. There are a couple of approaches available to you, depending on your requirements and (time) budget:

  • Directory based: using Lightweight Directory Access Protocol (LDAP), a decades-old suite of protocols nowadays formalized by IETF that 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 Keycloack or outsource this to a cloud provider, such as Azure Active Directory.

  • With Kerberos it’s possible to authenticate users a network (we will look at Kerberos in detail in Chapter 9).

  • Using config management systems such as Ansible, Chef, Puppet, or SaltStack to consistently create users across machine.

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 baseline 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 user is the owner of the file.

  • A group has one or more members.

  • The other category is for everyone else, other than either the owner or a group member.

Further, three types of access:

  • Read (r): for a normal file, allows a user to view the contents of the file. For a directory, allows a user to view the names of files in the directory.

  • Write (w): for a normal file, allows a user to modify and delete the file. For a directory, allows a user create, rename, and delete files in the directory.

  • Execute (x): for a normal file, allows a user to execute a file if the user also has read permissions on the file. For a directory, allows a user to access, and access file infos in the directory, effectively permitting 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 above snippet, we further 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 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:

  • 755 … full access for owner, read and execute for everyone else

  • 700 … full access by its owner, none for everyone else

  • 664 … read/write access for owner and group, read-only for others

  • 644 … read/write for owner, read-only for everyone else

  • 400 … 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 using 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 really 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 further discuss this topic in “Good Practices”.

file perm example
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 I created and own.


After chown, now root owns that file.

With the basic permission management concluded let’s have a look at some more advanced techniques in this space.

Process Permissions

So far we have 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 begs the question: what are the relevant permissions, from a process point of view?

As documented in credentials(7) there are different users 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, used to use a dedicated filesystem UIDs (see below) for file access permissions, still kept around for compatibility reasons. A process can obtain its effective UID via geteuid(2).

Saved set-user UID

Saved set-user-ID are used in suid cases where a process can assume privileges by switching its effective UID between the real UID and saved set-user-ID. For example, for a process to be allowed to use certain network ports (Chapter 7) it needs elevated privileges, for example, run as root. A process can get its saved set-user-ID 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 UIDs 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 and saved set-suer IDs 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, Linuxs maintains at the kernel level 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:

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

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

  • Checking resource limits, which we will 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 we so far 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 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 non-zero 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 assgined on a per-thread level.

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

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

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 to set the capabilities of a running process


Allows various network-related actions such as interface config


Allows to use RAW and PACKET sockets


Allows to call chroot


Allows system admin operations including mounting filesystems


Allows to use strace to debug processes


Allows to load kernel modules

Let’s us now see capabilities in action. For starters, to view, 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, with the details and good practices 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, that of 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 (Chapter 6) both Docker and Kubernetes support seccomp.

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

Access Control Lists (ACL)

With access control list (ACL) 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 shortcomming 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 ACL since it’s slightly outside of the scope of the book, however, being aware of it and knowing where to start can be benefical, should you come across them in the wild.

With that, let us 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 assigns also other users some. That is, 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, then you should be using sudo rather than logging in as root.

Avoid setuid

Take advantage of capabilities rather than to 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 tempered with. You can then use this read-only log to verify who did what, when. We will 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.

In this chapter we first had a look at the fundamental relationship between users, processes, and files in the context of the multi-user operating system that Linux is. We reviewed access control types, defined what users in Linux are, what they can do and how to manage them both locally and centrally. We then moved on to the topic of file permissions and how to manage these. The last part of the chapter we dedicated to advanced permissions techniques and tecnologies available in Linux, including capabilities and seccomp profiles, as well as a short compilation of good practices around access control related security.

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

  1. A Survey of Access Control Policies by Amanda Crowell.

  2. Capabilities:

  3. Secomp:

  4. 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 will go into greater detail in Chapters 8 and 9, but for now let’s move on to the topic of filesystems.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.