SELinux Security: Control your Linux system down to the millimeter

Last update: March 27th 2026
  • SELinux adds mandatory access control to the Linux kernel through tags and policies that go beyond traditional DAC permissions.
  • Security contexts (user, role, type, level) and type-based policies allow for very granular access control over processes, files, and ports.
  • Tools such as getenforce, chcon, semanage, semodule and booleans facilitate the practical management of SELinux in production.
  • When properly configured, SELinux mitigates zero-day exploits and misconfigurations by strictly limiting what each service or application can do.

SELinux security in Linux systems

SELinux might sound like technology reserved for kernel geeks, but it's actually one of the most powerful security tools available in Linux today . If you manage servers, Docker container security , cloud infrastructure, or even slightly sensitive desktop computers, understanding how SELinux works makes all the difference between a system that's simply "well-configured" and one that's difficult to compromise, even with zero-day vulnerabilities.

Despite its reputation for being "complicated," SELinux offers a very logical model: it defines what each process can do and what objects it can communicate with, and if anything deviates from that script, the kernel stops it immediately . Instead of blindly trusting that root will behave or that your daemons won't have bugs, SELinux enforces mandatory access control that applies even to the superuser, using very detailed labels and policies.

What is SELinux and what problem does it solve?

Security-Enhanced Linux (SELinux) is a Linux kernel security module based on LSM (Linux Security Modules). It was originally developed by the NSA in collaboration with Red Hat and other partners, and since kernel version 2.6, it has been an official part of the kernel. It is not a separate application, but rather a kernel extension that adds a mandatory access control (MAC) system and role-based access control (RBAC).

Unlike the classic Unix discretionary access control (DAC)—owner , group, others, and rwx permissions —in SELinux, access decisions are not based on the file owner's choice, but rather on a global policy established by the security administrator. DAC is still present and takes precedence: if DAC denies, SELinux doesn't intervene; but even if DAC allows, SELinux can still block the operation according to its policy.

The SELinux architecture clearly separates the components: on one hand, the kernel code that makes security decisions , and on the other, the policy modules that define what is allowed and what is not. This separation allows for adjusting the rules without recompiling the kernel and streamlines the number of components that can affect system security.

SELinux has been adopted by default in distributions such as Fedora, Red Hat Enterprise Linux, CentOS, and Scientific Linux, and is also deeply integrated into systems like Android, where it is used to confine system processes and apps to very specific domains. In the BSD and GNU/Linux world, there are alternatives such as AppArmor, TOMOYO, and TrustedBSD (on macOS/FreeBSD), but SELinux stands out for the level of granularity it offers over all system objects.

From DAC to MAC: why the classic model is no longer enough

In a traditional Unix system, the Domain-Object model is managed using DAC: each file or resource has an owner and permissions, and any process running as that user can do whatever it wants with those resources . This means that if a daemon runs as root, any exploitable bug can open the door to controlling half the system with its root context.

Typical examples include databases whose data files should only be manipulated through the DBMS, but which are actually readable and modifiable by processes with the root UID ; or critical daemons running with excessive privileges. A programming error, a buffer overflow, or poor input validation can turn the service into a highway to the entire system.

SELinux adds a Mandatory Access Control (MAC) layer on top of DAC. "Mandatory" means that access control is centrally defined by the administrator through policies, and neither users nor processes can relax these rules on their own. The operating system enforces these policies by evaluating each relevant kernel operation before allowing it.

The kernel, using LSM hooks, queries SELinux on every sensitive system call (opening files, creating sockets, mounting file systems, communicating via IPC, etc.). At each decision point, SELinux evaluates the operation based on the loaded policy and the security context of the subject and object . If the policy does not explicitly grant permission, the action is denied, regardless of whether the process is root or not.

Operating modes: enforcing, permissive, and disabled

SELinux can operate in three clearly differentiated operating states, which are important to understand to avoid going crazy in production:

  • EnforcingSELinux is enabled and fully enforces the policy. All actions not permitted by the rules are blocked and logged.
  • permissiveSELinux is active, loads the policy and labels the filesystem, but does not block It only records the transactions as if they were denied. This is ideal for debugging and adjusting policies.
  • DisabledSELinux is disabled. No policies are applied and no tagging is performed. The system relies on the classic DAC model.
  How to switch from Linux to Windows 11 and combine them on the same PC

For quick and temporary changes between enforcing and permissive modes, the `setenforce` command is used , where the mode is specified as 0 (permissive) or 1 (enforcing). It's important to know that this change is volatile: after the next reboot, the mode will revert to the one defined in the configuration.

If you need a permanent change, you need to edit the file. /etc/selinux/config (or /etc/sysconfig/selinux in some distributions) and adjust the value of the directive SELINUX=disabled|permissive|enforcingThe changes will be applied on the next boot and, in many cases, will involve relabeling the file system.

To check the active mode, you can use commands like getenforce or sestatus . The former simply returns Enforcing, Permissive, or Disabled; the latter provides a more complete summary of the SELinux status, loaded policies, and active modules.

Security contexts and labeling system

The heart of SELinux is its system of security labels or contexts . Every file, process, network port, socket, device, etc., has an associated context that describes how it can be used. This context is composed of several fields that together form SELinux's view of that object.

The general format of a context is user_u:role_r:type_t:level , with some nuances depending on the policy (especially if MLS/MCS is used). Each field has a specific purpose: the SELinux user, the role, the type (also called domain when referring to processes), and the sensitivity level or category.

In practice, the most critical element is the type (the third field), since the vast majority of policy rules are formulated as relationships between types . For example, allowing processes of type httpd_t to access files labeled httpd_sys_content_t, or allowing a specific domain to communicate with sockets labeled http_port_t.

These contexts are stored as extended file system attributesTherefore, it is essential to use filesystems that support xattrs (such as ext4, XFS, etc.). In the case of processes, the current context and other related contexts are exposed through the pseudo-filesystem. /proc/<pid>/attr/ in files such as current, exec, fscreate, prev, sockcreate or keycreate.

Typical examples of contexts in a system with targeted policy would be:

  • system_u:object_r:httpd_sys_content_t:s0 for web content served by Apache or Nginx.
  • system_u:object_r:home_user_t:s0 for user home directories.
  • system_u:system_r:httpd_t:s0 for the execution domain of the web server itself.

Breaking down the components of the SELinux context

When you encounter a context like unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 , it may seem like hieroglyphics, but each piece corresponds to a specific concept within SELinux and its reference policy.

El SELinux user (by convention with suffix) _uThis is not the same as the Unix user in /etc/passwd. SELinux maintains its own user database and maps them to Linux users. An SELinux user can group several Unix users, because the idea is for the MAC layer to be independent of the DAC.

El SELinux role (with suffix _rThis defines what roles a user or domain can adopt. If used strictly, it's called full RBAC. Most file objects use the object_r role, while roles like system_r, user_r, staff_r, or sysadm_r are applied to processes depending on the security context they must assume.

El SELinux type or domain (suffix _tIn practice, the type is the crucial element. The type describes the class of object or the execution domain of a process. The policy specifies which interactions are allowed between types: which domains can read, write, execute, or communicate with which other types of objects.

Levels and categories are used when multi-level security (MLS) or multi-category security (MCS) policies are enabled. Sensitivities are hierarchical (e.g., s0, s1, etc.), while categories are not (c0, c1, c2, ...). In highly critical environments—for example, certain government organizations—they are used to ensure that data can only be read downwards and written to the same level or upwards , and to isolate data according to very specific compartments.

SELinux policies: targeted, strict, MLS/MCS and modularity

The security enforced by SELinux is determined by the loaded policy . A policy is simply a large set of rules that describe which domains can do what on which types, as well as other elements (transitions, tagging rules, etc.). Distributions usually package standard policies so you don't have to write everything from scratch.

The most common policy is called targeted . In this mode, only specific processes—mainly network services and high-risk daemons like Apache, Nginx, DNS, proxies, SNMP, syslog, etc.—run within confined domains. All other user processes run in "unconfined" domains, where standard Linux security is essentially applied, along with logging of certain actions.

There is also the strict policy , in which virtually all processes are confined under a specific policy. It is much more secure, but it can also be considerably more difficult to maintain if the model is not well understood, because any discrepancy in labeling or rules can disrupt standard workflows.

For high-security environments, there are MLS/MCS (Multi-Level/Multi-Category Security) policies that leverage sensitivities and categories to impose even finer controls. These are typical of very rigid military or administrative environments and are rarely used outside of specific contexts due to their high operational complexity.

Modern policies are distributed in a modular fashion . Instead of a single monolithic policy file, separate modules are used for specific services, greatly simplifying management and updates. These modules are managed with tools like semodule and semanage module , which allow you to install, remove, enable, or disable parts of the policy without recompiling the entire policy each time.

  Security risks in browsers with AI agents

How SELinux decides: subjects, objects, classes, and permissions

When a process (the subject) attempts to access a resource (the object), SELinux conceptually poses the question: "Can a domain of type X perform operation Y on an object of type Z belonging to class C?" . The answer is then sought in the policy through the defined rules.

In Type Enforcement (TE) policies, which are used by most distributions and Android, each object belongs to a class (file, dir, fifo_file, tcp_socket, process, etc.) and the policy defines what permissions are possible for each class: read, write, execute, bind, connect, getattr, open, and a long etcetera.

TE rules are expressed very directly. A basic example would be:

allow httpd_t http_port_t:tcp_socket name_bind;

With this rule, the policy indicates that processes in the httpd_t domain can perform the name_bind operation on TCP sockets labeled http_port_t. The approach focuses on object types and classes, not specific paths , which prevents surprises when moving files or changing directory structures.

In Android, for example, SELinux attributes are used to group types under more general labels like `appdomain` , so that a single rule can apply to multiple domains (`untrusted_app`, `isolated_app`, etc.) without repeating definitions. Macros like `rw_file_perms` are also used to group several common file permissions and reduce errors caused by oversights.

Internal statuses, AVC and denial registry

When an access request occurs, SELinux first consults the Access Vector Cache (AVC) , a cache that stores recent access decisions to speed up the process. If the decision is already in the cache, it is used directly; otherwise, the firewall (the internal part of SELinux in the kernel) is queried, which evaluates the operation based on the policy and the subject and object context.

If there is no rule that explicitly permits the requested action, the default decision is to deny . This "deny by default" philosophy is one of the pillars of SELinux and what gives it its robustness against permissive configuration errors.

When a denial occurs in enforcing mode, the kernel logs a message of type avc: denied in the system logs. Depending on the distribution, it may appear in /var/log/audit/audit.log, /var/log/messages or be captured by the auditd daemon. These messages include the process context (scontext), the object context (tcontext), the class, the requested operation, and other data very useful for debugging.

In permissive mode, the operation is allowed but the event avc: denied is still generated , marked with permissive=1. This is pure gold for building and adjusting policies, because it allows you to see what would break the system if enforcing were implemented without interrupting normal operation.

Integrating SELinux into distributions and Android

In the Linux server and desktop ecosystem, SELinux is enabled by default in Fedora, RHEL, CentOS and derivativesDebian and Ubuntu provide full support in their kernels and packages, although activation is usually optional and requires installing packages such as selinux-basics, selinux-policy-default, and auditd, followed by global re-tagging with fixfiles relabel.

Android initially incorporated SELinux in version 4.3 in permissive mode, transitioned to partial use in 4.4 (only for critical domains such as installd, netd, vold, and zygote), and has been fully integrated since Android 5.0 . Android's policy focuses on isolating apps, system services, and sensitive processes using types and attributes, aiming to minimize the impact of a compromise on any of these components.

Android uses concepts like the untrusted_app type for common application processes, appdomain attributes to group app domains, and MLS/MCS categories to isolate data between apps and between physical users. All of this works together to prevent a compromised application from escaping its sandbox, even if it obtains very broad user permissions.

Important: Android simplifies the SELinux model by ignoring users, roles, and advanced sensitivities. There is only one SELinux user (u), two basic roles (r for subjects and object_r for objects), and the sensitivity is always s0. Categories are what make the difference for data isolation.

Comparison with AppArmor and other LSMs

In many discussions, the comparison between SELinux and AppArmor inevitably arises , as both are Linux security modules and offer MACs. However, their approaches are quite different, and it's worth understanding this before choosing one or the other for your environment.

SELinux defines a policy centered on objects and their types: every object in the system (files, processes, sockets, ports, devices, IPCs, etc.) is assigned a label. Access decisions are made based on subject and object contexts, without depending on the exact file path. This makes the system more stable in the face of directory structure changes or alternative file system views (chroot, containers, bind mounts, etc.).

AppArmor, on the other hand, applies a task-centric , path-based policy. It defines profiles for each program, specifying which file paths, ports, etc., it can access and with what permissions. It is more intuitive to configure and is generally more user-friendly for administrators who don't want to become experts in SELinux, but its control is somewhat less granular and more dependent on the file system structure.

  What is Wazuh: An Open Source Platform for Modern Cybersecurity

Both share the principle of denying by default, but they apply it differently: AppArmor denies by default only the tasks it covers with profiles, while SELinux, when in strict mode, extends it to the entire system and all tagged objects. As a result, SELinux typically offers a deeper level of confinement , at the cost of a more extensive and complex policy.

Practical tools for managing SELinux

Working with SELinux relies on a series of command-line tools that simplify the management of modes, tagging, booleans, and policy modules. While this may seem like an overwhelming arsenal at first, a few utilities cover most day-to-day tasks.

To view the security context of files and processes, you can use the option -Z in common commands like ls, ps, or id. For example, ls -Z In addition to DAC permissions, it will show the SELinux context of each file, allowing you to quickly check if the tagging is as expected.

The `chcon` ("change context") command allows you to manually modify the entire context of a file or only specific parts (role, type, range) using options such as `-r`, `-to`, and `-l`. It is useful for one-off corrections, but it's important to remember that your changes may be lost if the labeling is reapplied according to the policy using tools like `restorecon` or `fixfiles`.

The semanage tool is the Swiss Army knife of runtime policy management. With various subcommands, you can manage persistent file contexts (fcontext), login mappings between Linux and SELinux users (login), SELinux users and their roles (user), tagged ports (port), booleans, and even policy modules. All of this without needing to recompile the entire policy from source.

To check and change the state of booleans—small switches that enable or disable blocks of rules within the policy— getsebool and setsebool are used , in addition to the setsebool boolean itself . A typical boolean is httpd_enable_homedirs, which, when active, allows the web server to access users' home directories (useful for ~user/public_html/).

Finally, commands like fixfiles allow you to force a complete relabeling of the file system according to the defined rules, and semodule takes care of installing, listing, enabling or disabling policy modules (.pp) packaged and distributed by the reference policy or by the administrator.

Creation and adjustment of customized policies

When an application doesn't have its own SELinux module or you need more specific confinement, it's time to get down to business and create custom policies . It might sound complicated, but the workflow is quite well-defined if you follow certain steps carefully.

The first step is to ensure that the relevant objects (executables, data directories, sockets, etc.) are labeled with appropriate types. This can be achieved by defining file context rules with ` semanage fcontext` and applying them with `restorecon`, using regular expressions to encompass entire directory trees.

The system is then typically put into permissive mode for that machine (or, in some cases, for a specific domain) and the application is allowed to run normally while SELinux logs all the ` avc: denied` statements that would have occurred. These logs, usually analyzed with tools like audit2allow , are used to extract candidate rules.

Reference policies are typically structured in three files per application: a .te file containing TE rules (allow, type, domain_type, etc.), a .fc file containing file context rules, and a .if file containing public interfaces that other modules can reuse. All of this is compiled into .pp modules using make and loaded with semodule.

It's crucial not to blindly trust everything that audit2allow suggests: it tends to be more permissive than strictly necessary . Ideally, you should manually review the suggested rules, create new types where appropriate to separate sensitive data from irrelevant data, and, when a denied operation isn't critical, consider using dontaudit rules to stop recording noise without granting permissions.

In environments such as SUSE Linux Micro or Android, additional tools are provided (for example, Udica to generate container policies from JSON descriptions) that automate part of the process by quickly adapting the policy to specific containers without having to learn the entire policy language from scratch.

This entire ecosystem of contexts, modular policies, booleans, and management tools makes SELinux an extremely robust and flexible security platform . With some initial investment in learning, it allows for very precise limitation of what each service or application can do, drastically hardening the system's attack surface against exploits, malware, and human error.

hardening Linux SELinux
Related articles:
Hardening Linux with SELinux: a complete and practical guide