371 lines
16 KiB
Markdown
371 lines
16 KiB
Markdown
---
|
|
layout: post
|
|
author: Sam Hadow
|
|
---
|
|
|
|
In this blog post I'll explain why you should use SELinux (or at least an implementation of Mandatory Access Control). Then how you can use SELinux, administer it, and install it on an existing system.
|
|
|
|
# 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](https://web.archive.org/web/20201022103915/https://www.nsa.gov/what-we-do/research/selinux/) (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](https://man7.org/linux/man-pages/man7/capabilities.7.html) were 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 `genfscon` and `file_contexts`
|
|
+ For ephemeral ressources (a device for example) and processes, they're stored in the kernel structure `task_security_struct` and 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:
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
```bash
|
|
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 isolation `s0:c539,c681`
|
|
+ target context: the container runtime (`container_runtime_t`)
|
|
+ action: changing the metadata (`setattr` with `chown`) 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:
|
|
```bash
|
|
ls -ldZ /var/www/html
|
|
```
|
|
|
|
To change a label manually we can use the full command:
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
```bash
|
|
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:
|
|
```bash
|
|
/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/`:
|
|
```bash
|
|
semanage fcontext -a -t httpd_sys_content_t "/foo(/.*)?"
|
|
```
|
|
|
|
Or to use another directory or file as a reference:
|
|
```bash
|
|
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`:
|
|
```bash
|
|
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:
|
|
```bash
|
|
getsebool -a
|
|
```
|
|
|
|
To check the status of a specific boolean:
|
|
```bash
|
|
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):
|
|
```bash
|
|
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)*:
|
|
```bash
|
|
/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
|
|
```bash
|
|
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:
|
|
```bash
|
|
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
|
|
```bash
|
|
semodule -i mypol.pp
|
|
```
|
|
+ Finally put SELinux in enforcing mode again and check that everything works fine when running and using your service.
|
|
```bash
|
|
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:
|
|
```bash
|
|
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](https://github.com/containers/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 `/.autorelabel` and 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:
|
|
1. First stop apparmor and install the SELinux packages
|
|
```bash
|
|
systemctl stop apparmor
|
|
systemctl disable apparmor
|
|
apt install selinux-basics selinux-policy-default
|
|
```
|
|
2. Change the grub configuration (SELinux in permissive), create the autorelabel file and reboot
|
|
```bash
|
|
# /etc/default/grub
|
|
GRUB_CMDLINE_LINUX="security=selinux selinux=1 enforcing=0"
|
|
```
|
|
then run
|
|
```bash
|
|
update-grub
|
|
touch /.autorelabel
|
|
reboot
|
|
```
|
|
*you might need to modify update-grub on debian to use the explicit path of grub-mkconfig*
|
|
```bash
|
|
# /usr/sbin/update-grub
|
|
exec /usr/sbin/grub-mkconfig -o /boot/grub/grub.cfg "$@"
|
|
```
|
|
3. Check in which mode SELinux is (should be permissive) and the policy in place (should be targeted).
|
|
```bash
|
|
getenforce
|
|
sestatus
|
|
```
|
|
4. Check if you have AVC denials and create the policies or change booleans until everything work fine before putting SELinux in enforcing mode.
|
|
```bash
|
|
# /etc/default/grub
|
|
GRUB_CMDLINE_LINUX="security=selinux selinux=1 enforcing=1"
|
|
```
|
|
Then again
|
|
```bash
|
|
update-grub
|
|
reboot
|
|
```
|
|
|