16 KiB
layout, author
| layout | author |
|---|---|
| post | Sam Hadow |
SELinux origin
History
SELinux is an implementation of the MAC (Mandatory Access Control) for Linux. It was originally developped by the NSA with the project FLASK (Flux Advanced Security Kernel) in 1992 and 1993.
After almost 10 years, in 2000 it was published under the GPL license. Then in 2003 it was integrated into the Linux kernel with the LSM (Linux Security Modules). Nowadays Red Hat is the main contributor maintaining SELinux and distributing it on its systems (RHEL, Fedora...) and enabling it by default.
However most Linux kernels include SELinux support by default even if unused. To get a truly SELinux-less kernel you would need to compile your own with the configname CONFIG_SECURITY_SELINUX=n.
Motivations
Historically UNIX systems (so Linux included) use DAC (Discretionary Access Control) with ownership and permissions (user, group and others). In this model root is omnipotent and has all the rights, capabilities where also introduced to allow some privileged actions to be performed without needing to be root. And ACL (Access Control Lists) extend this DAC model for more granularity.
However this model has its limitations: users have the "discretion" (hence the name DAC) to control the permissions of all the files they own.
The issue with this model is in case of a compromised user process, an attacker inherits of all the permissions this user has and has access to all their files.
A typical example of why it's bad is the web browser example. A vulnerability in a web browser means an attacker could have access to SSH keys or other private keys the user owns which is not something a web browser should have access to in the first place.
It's why a stricter model was necessary, the MAC (Mandatory Access Control).
Fundamental concepts
basics
SELinux doesn't replace the traditional DAC model in UNIX, it adds a MAC layer above it.
To access a file both the DAC and the MAC rules in place should authorize it.
SELinux has two essential concepts:
- labels (or context, both words are used interchangeably to refer to the same thing)
- type enforcement
Labels
SELinux gives a label to everything you can think of:
- files and directories
- processes
- TCP/UDP ports, sockets, devices...
These labels can be stored in different places
- In the extended attributes of the filesystem (xattr)
attribute:security.selinux - When extended attributes are unsupported by the filesystem (for example exFAT, FAT32...) then static mappings are used to give contexts to precise paths or to the whole volume with
genfsconandfile_contexts - For ephemeral ressources (a device for example) and processes, they're stored in the kernel structure
task_security_structand exposed with/proc.
Labels structure
SELinux labels have the following structure:
user:role:type:level
For example:
system_u:system_r:httpd_t:s0
unconfined_u:object_r:config_home_t:s0
What each field means
| Field | Meaning |
|---|---|
| user | SELinux user (different from UNIX users) |
| role | Mainly used with MLS policies |
| type | The most important element as it's used by the type enforcement |
| level | MLS/MCS levels, for example s0:c1,c2 |
In the targeted policy (which we'll see later in the policies section), only the type really matters.
SELinux policies
SELinux policies are a set of rules defining:
- which processes can access which objects (files, sockets, devices...)
- what actions processes can do (read, write, bind, connect)
There are 3 important famillies of policies:
- Targeted (the default one on RHEL or Fedora)
- MLS (Multi-Level Security)
- MCS (Multi-Category Security)
Targeted policy
With a targeted policy:
- A configuration is neeeded for each processes (already configured for hundreds od processes by default on Red Hat systems)
- Type Enforcement checks is both the processes and the ressource said processes want to access have the same type to allow or deny the access
- Everything else is
unconfined_t
MLS (Multi-Level Security) policy
Originally MLS stems from a governmental/millitary model with level of clearance (classification levels from s0 to s15) and can include categories, for example s0:c0,c7.
It's a very strict model and as such it is (or at least was) used in American administrations and defense departments where strong isolation is needed.
However this model is not recommended on a system with the X window system (or even with wayland). X11 was designed in the 1980s and fundamentally violates the MLS model.
There is one X server receiving eery input (keystrokes and mouse inputs), rendering every window, and every client connects to this single server. As such the X server should be trusted at the highest classification with a MLS model and configuring all the rules would be extremely tedious.
With Wayland it's slightly better but still violates the MLS.
In general, any compositor is not made to work with MLS policies.
MCS (Multi-Category Security) policy
MCS is a simplified subset of MLS, here only the categories c0,c1,c2... are used instead of hierarchies and categories.
Categories are used to isolate workloads and are often used with containers, for example with podman:
echo "secret from host" > /srv/data/secret.txt
podman run -d --name c1 -v /srv/data:/data:Z alpine sleep infinity
podman exec c1 cat /data/secret.txt
podman run -d --name c2 -v /srv/data:/data:Z alpine sleep infinity
podman exec c1 cat /data/secret.txt
The first cat works but not the second one (we get a denial from SELinux), this is because when creating the second container, the Z option (uppercase) explicitely told SELinux to give unique categories to the volume.
If we wanted both containers to have shared categories and be able to acces the file /srv/data/secret.txt we would need to use the z option (lowercase).
Adminstration
See the current policy
To inspect the active policy, several tools exist. You might need to install the package setools-console to use these tools.
| Tool | Use case |
|---|---|
| semanage | SELinux user (different from UNIX users) |
| sesearch | Searching a specific rules in the policy |
| seinfo | Getting statistics on the policy |
For example:
semanage fcontext -l # list all contexts
semanage port -l | grep http # rules on the http port
sesearch --allow -s httpd_t -t httpd_t # show allow rules with a source of type httpd_t and a target of type httpd_t
Graphical tools
If you prefer graphical tools you can install the packages xorg-x11-xauth policycoreutils-gui setools-gui
And run system-config-selinux, sometimes replaced by sepolicy-gui. And run apol for statistics.
Handling errors
When can errors happen?
A SELinux error is like a fire alarm, it means something is wrong and you shouldn't ignore it. You shouldn't disable SELinux on a system without trying to understand why the error happened in the first place (and you shouldn't disable SELinux in any case anyway).
An error can happen in multiple case:
- A label is incorrect
- A new policy needs to be created
- A bug in a policy In case it's a preconfigured policy from Red Hat, which is rare but can happen, you should submit a bug report.
- An attacker is on your computer and SELinux protected you
How to analyse errors?
The package setroubleshoot includes convenient tools to get human readable messages from SELinux errors and sometimes is able to give advice on how to fix an error. Of course you shouldn't blindly copy paste the commands it gives, sometimes error happen because SELinux is protecting you.
To read the logs you can use these commands:
journalctl -t setroubleshoot -b -0 # logs related to SELinux since the last reboot with explanations
ausearch -m avc -ts recent # recent SELinux AVC (Access Vector Cache) denials
You can also use audit2why to understand why an access was denied by interpreting AVC denials. This tool is useful to easily distinguish:
- Mislabeled files (type not allowed)
- Missing booleans (for example:
boolean httpd_can_network_connect is off) - Missing rule, or explicit rule denying an access (strict policy)
To use this tool:
ausearch -m avc -ts recent | audit2why
Reading an AVC denial
AVC denial example:
type=AVC msg=audit(1763739064.115:290419): avc: denied { setattr } for pid=2911211 comm="chown" name="" dev="pipefs" ino=55615042 scontext=system_u:system_r:container_t:s0:c539,c681 tcontext=unconfined_u:unconfined_r:container_runtime_t:s0 tclass=fifo_file permissive=0
Let's analyse this AVC denial step by step:
- source context: a process in a container (
container_t) with MCS isolations0:c539,c681 - target context: the container runtime (
container_runtime_t) - action: changing the metadata (
setattrwithchown) on a named pipe belonging to the container runtime
This violates the SELinux confinement, and allowing a container to use the setattr system call on the runtime could be a container escape primitive. This AVC denial is normal and expected.
This AVC denial is usually caused by processes inside a container blindly running chown recursively.
Reading an AVC denial with audit2why
AVC denial example:
type=AVC msg=audit(1415714880.156:29): avc: denied { name_connect } for pid=1349
comm="nginx" dest=8080 scontext=unconfined_u:system_r:httpd_t:s0
tcontext=system_u:object_r:http_cache_port_t:s0 tclass=tcp_socket
Was caused by:
One of the following booleans was set incorrectly.
Description:
Allow httpd to act as a relay
Allow access by executing:
# setsebool -P httpd_can_network_relay 1
Description:
Allow HTTPD scripts and modules to connect to the network using TCP.
Allow access by executing:
# setsebool -P httpd_can_network_connect 1
Here audit2why explains why the error happened in a human readable format and fives us the step to solve the error, here the solution is to set a boolean to true. Sometimes the solution is to create a module.
However should you blindly follow advice given by audit2why? No, audit2why just explains you how to allow an access when an error happens, but it doesn't mean you should allow this access, as explained in the section earlier some AVC denials are normal and protect you.
In the previous example in the section earlier, audit2why suggested creating a module, this would of course be a very bad idea to create a module allowing a primitive for container escapes.
Checking and changing labels
In the examples in this section we'll use web files
To check the expected label of a file in a directory, for example:
ls -ldZ /var/www/html
To change a label manually we can use the full command:
chcon -u system_u -r object_r -t httpdt_t /var/www/html/index.html
Or the shortened version since only the type really matters with a targeted policy:
chcon -t httpdt_t /var/www/html/index.html
Or in most cases, we can use a reference file to be sure not apply a wrong label because of a typo or if we don't remember what the exact correct label type is:
chcon --reference /var/www/html/ /var/www/html/index.html
Default labels
In the previous examples we assumed /var/www/html/ had a default label already defined, it is the case on most systems with the shipped targeted policy. These default labels are defined in the following file:
/etc/selinux/targeted/contexts/files/file_contexts
However you shouldn't modify this file by hand if you want to define your own default labels, in case you want to do that you have to use the semanage command.
For example if you want to give the type httpd_sys_content_t to any file created in /foo/:
semanage fcontext -a -t httpd_sys_content_t "/foo(/.*)?"
Or to use another directory or file as a reference:
semanage fcontext -a -e /var/www/html /foo
In the second case notice the -e for equal. It means if the default context for /var/www/html is changed, the default context for /foo would also change.
And finally to apply the default labels defined in the policy we have to use the command restorecon:
restorecon -vR /var/www/html/
SELinux modules and booleans
Booleans
SELinux booleans exist to activate or deactivate optional rules in the policy and adapt the behavior of a service without needing to write a policy module ourselves.
To list all the booleans:
getsebool -a
To check the status of a specific boolean:
getsebool httpd_use_nfs
Then we can change this boolean temporarily or persistantly. For example (ommit the -P if you want to change it temporarily only):
setsebool -P httpd_use_nfs on
And to check all the booleans modified by the administrator, they're listed in this file (do not modify it by hand):
/etc/selinux/targeted/modules/active/booleans.local
Modules
Sometimes switching a boolean or changing labels isn't enough when a service need additional access. In this case it's necessary to create a module. A module can also be required when there isn't anything defined in the targeted policy shipped by the OS for a specific process.
You should preferably create modules in a development environment and not in production. The steps when creating a module are:
- Temporarily put SELinux in permissive mode
setenforce 0
- Run your specific service and try to do all the actions that can normally happen with this service (for example for mail server, trying to send and receive mails)
- Check all the AVC denials and write a module, to generate one automatically you can also use the following command:
grep httpd /var/log/audit/audit.log | audit2allow -M mypol
But be careful to review the generated module, audit2allow can sometimes write rules which are too broad.
- Install the module
semodule -i mypol.pp
- Finally put SELinux in enforcing mode again and check that everything works fine when running and using your service.
setenforce 1
After that the module can be deployed in the production environment.
And if you need to remove a module later you can use the command:
semodule -r mypol
Side notes:
- It can be a good idea to give all your module with a specific prefix or suffix to distinguish them from the modules shipped with the system policy later.
- for containers you can use the tool udica
Installing SELinux on an existing system
Installing SELinux on an existing system, especially a server with services already running can be tricky. The usual procedure is:
- Installing the required SELinux packages for your specific distribution (usually including a default targeted policy)
- Setting SELinux in permissive mode
- Creating a file
/.autorelabeland reboot to tell SELinux to apply the default label to all the files on the system (this operation can take some time depending on the amount of files you have) - Checking the AVC denials while still in permissive mode and changing booleans or writing policies until everything works fine
- And then finally setting SELinux in enforcing mode
Debian example
As en example here is the procedure on a debian system:
- First stop apparmor and install the SELinux packages
systemctl stop apparmor
systemctl disable apparmor
apt install selinux-basics selinux-policy-default
- Change the grub configuration (SELinux in permissive), create the autorelabel file and reboot
# /etc/default/grub
GRUB_CMDLINE_LINUX="security=selinux selinux=1 enforcing=0"
then run
update-grub
touch /.autorelabel
reboot
you might need to modify update-grub on debian to use the explicit path of grub-mkconfig
# /usr/sbin/update-grub
exec /usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg "$@"
- Check in which mode SELinux is (should be permissive) and the policy in place (should be targeted).
getenforce
sestatus
- Check if you have AVC denials and create the policies or change booleans until everything work fine before putting SELinux in enforcing mode.
# /etc/default/grub
GRUB_CMDLINE_LINUX="security=selinux selinux=1 enforcing=1"
Then again
update-grub
reboot