SELinux
Table of Contents
SELinux is an implementation of the Mandatory Access Control security model in the Linux kernel. It bears more weight than the Discretionary Access Control security model, implemented as the Unix-based file permissions (ugo, rwx); To illustrate the power of SELinux, a Linux user, even the root account, gets denied access to his own files if the result of the SELinux authorization chain is a deny.
SELinux assigns a context (aka label) to every Linux user, process, file, directory, port, socket, hardware device, etc. These are considered as either subjects (Linux users and processes) or objects (files, directories, ports, …).
Every SELinux context is written in this format:
user_identity:role:type_or_domain:sensitivity
For SELinux subjects, the type field is called domain. For SELinux objects, the type field is called type.
A SELinux context neither define access permissions by itself nor is compared with other SELinux contexts looking for a match. I initially thought that SELinux contexts define the access level. They do not. They are just fields that are evaluated by SELinux against the binary policy every time SELinux subjects require access to an SELinux object.
A nice analogy to understand its purpose is to consider travel documents: the person’s name, job, country of residence and visa status are claims. The border police controls the elements of the claim (name, role, country, visa). Access into the country is permitted only if he finds a rule that permits that person’s information into the country. The travel documents are the SELinux context. The country is the SELinux object, the border police agent is the SELinux binary policy. This applies to every SELinux subject claiming access to a SELinux object, even if the SELinux subject maps to the root user.
policy source files vs binary policy#
The SELinux store of policies is a set of source files that are compiled into a binary policy. The latter is what is injected in the kernel.
the type field in a label of a SELinux object#
An SELinux object requests no access. All the context fields, except the type field, are placeholders. The type field is called out by SELinux when evaluating the autorization of a domain of a SELinux subject’s context to access the SELinux object’s context type field against the SELinux binary policy.
impact of SELinux on users and the authorization chain#
RBAC#
When a user logs in to a RHEL system, the user has no SELinux label. At login time, a SELinux user identity is selected by some process for the Linux user. That process queries the kernel whether the suggested SELinux user identity is authorized to access a suggested list of roles. The kernel performs a lookup in the binary policy. Among the positive answers from the kernel, the process selects one of the roles, typically a default role. Then a similar cycle starts for the role; The process queries the kernel whether that role is authorized to access a list of suggested domains. The kernel performs a lookup in the binary policy. Among the positive answers from the kernel, the process picks one domain, typically a default one. And that is how the label for the logged-in Linux user is crafted. The shell of the Linux user inherits the same label. The shell, and any processes it calls out, run in the same domain as the Linux user, unless a domain transition is executed.
Once a Linux user receives a label, no more RBAC lookups take place for it.
Type Enforcement (TE)#
If one of the processes of the shell of the Linux user “John” wanted to access a file mission144.draft that has label userB:roleB:typeB:levelB, then Type Enforcement lookup is executed. TE lookup answers the question: is typeA allowed to enter typeB? TE performs a lookup against the binary policy to try to find a rule that allows typeA to access typeB. If a rule does exist, typeA is authorized to enter typeB. If no rule matches, the authorization chain is interrupted and the SELinux subject is denied access to the SELinux object, i.e. Linux user “John” can not access file mission144.draft, even if he were its owner.
Both RBAC and Type Enforcement are possible thanks to the binary policy.
When I write X enters Y I mean that ‘X will issue access requests sourced from Y’. So when a process enters a domain, the process will issue future access requests (to files) sourced by its domain.
verify the SELinux posture#
SELinux is enabled by default in RHEL10. To verify its status:
user1@rhel10-vm2:~$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
user1@rhel10-vm2:~$
The Mode from config file and the Loaded policy name keys refer to directives in the /etc/selinux/config file:
user1@rhel10-vm2:~$ cat /etc/selinux/config
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
# See also:
#
# NOTE: In earlier Fedora kernel builds, SELINUX=disabled would also
# fully disable SELinux during boot. If you need a system with SELinux
# fully disabled instead of SELinux running with no policy loaded, you
# need to pass selinux=0 to the kernel command line. You can use grubby
# to persistently set the bootloader to boot with selinux=0:
#
# grubby --update-kernel ALL --args selinux=0
#
# To revert back to SELinux enabled:
#
# grubby --update-kernel ALL --remove-args selinux
#
SELINUX=enforcing
# SELINUXTYPE= can take one of these three values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected.
# mls - Multi Level Security protection.
SELINUXTYPE=targeted
user1@rhel10-vm2:~$
The effect of changing the value of the SELINUX directive in the configuration file is noticeable in the result of sestatus and withstands a system reboot.
user1@rhel10-vm2:~$ sudo vim /etc/selinux/config
[sudo] password for user1:
<---- output omitted ---->
# modified by me
SELINUX=permissive
<---- output omitted ---->
user1@rhel10-vm2:~$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: permissive
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ uptime
18:29:27 up 0 min, 2 users, load average: 0.00, 0.00, 0.00
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: permissive
Mode from config file: permissive
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
user1@rhel10-vm2:~$
In the SELINUX directive in the configuration file, the permissive value means an access denial will be logged while the access request itself is allowed. So it will not disable SELinux at all.
There is the /etc/sestatus.conf that lists some files and processes. The SELinux label of these is displayed with sestatus -v. I do not know on which basis these files were chosen. But I do not see the label of the files /lib/libc.so.6, /lib/ld-linux.so.2, /lib/ld.so.1. I also think that for /bin/sh and for /sbin/init a domain translation rule is displayed.
user1@rhel10-vm2:/$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sudo cat /etc/sestatus.conf
[sudo] password for user1:
[files]
/etc/passwd
/etc/shadow
/bin/bash
/bin/login
/bin/sh
/sbin/agetty
/sbin/init
/sbin/mingetty
/usr/sbin/sshd
/lib/libc.so.6
/lib/ld-linux.so.2
/lib/ld.so.1
[process]
/sbin/mingetty
/sbin/agetty
/usr/sbin/sshd
user1@rhel10-vm2:/$ sestatus -v
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
Process contexts:
Current context: unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Init context: system_u:system_r:init_t:s0
File contexts:
Controlling terminal: unconfined_u:object_r:user_devpts_t:s0
/etc/passwd system_u:object_r:passwd_file_t:s0
/etc/shadow system_u:object_r:shadow_t:s0
/bin/bash system_u:object_r:shell_exec_t:s0
/bin/login system_u:object_r:login_exec_t:s0
/bin/sh system_u:object_r:bin_t:s0 -> system_u:object_r:shell_exec_t:s0
/sbin/agetty system_u:object_r:getty_exec_t:s0
/sbin/init system_u:object_r:bin_t:s0 -> system_u:object_r:init_exec_t:s0
/usr/sbin/sshd system_u:object_r:sshd_exec_t:s0
user1@rhel10-vm2:/$
The Current context key shows the SELinux label of the shell that executed the sestatus -v command. In my sample output, the label of my shell is unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023, which matches the result of ps -Z:
user1@rhel10-vm2:/$ ps -Z
LABEL PID TTY TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1517 pts/0 00:00:00 bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 2067 pts/0 00:00:00 ps
user1@rhel10-vm2:/$
getenforce, setenforce#
user1@rhel10-vm2:~$ getenforce
Enforcing
user1@rhel10-vm2:~$ getenforce
a .bash_history .bash_logout .bash_profile .bashrc .cache/ .lesshst .local/ q test/ .var/ .viminfo
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ getenforce
Enforcing
user1@rhel10-vm2:~$ setenforce 0
setenforce: security_setenforce() failed: Permission denied
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo setenforce 0
[sudo] password for user1:
user1@rhel10-vm2:~$ getenforce
Permissive
user1@rhel10-vm2:~$ sudo setenforce 1
user1@rhel10-vm2:~$ getenforce
Enforcing
user1@rhel10-vm2:~$ sudo setenforce 2
usage: setenforce [ Enforcing | Permissive | 1 | 0 ]
user1@rhel10-vm2:~$
Observing the impact of a reboot on a SELinux mode changed with setenforce:
user1@rhel10-vm2:~$ sudo setenforce 0
[sudo] password for user1:
user1@rhel10-vm2:~$ getenforce
Permissive
user1@rhel10-vm2:~$ systemctl reboot
<---- system rebooted ---->
user1@rhel10-vm2:~$ getenforce
Enforcing
user1@rhel10-vm2:~$
seinfo#
user1@rhel10-vm2:~$ seinfo
-bash: seinfo: command not found
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ dnf search setools
Not root, Subscription Management repositories not updated
Last metadata expiration check: 0:51:39 ago on Sun 11 Oct 2026 09:21:43 AM CEST.
=============================== Name Exactly Matched: setools ================================
setools.x86_64 : Policy analysis tools for SELinux
=================================== Name Matched: setools ====================================
python3-setools.x86_64 : Policy analysis tools for SELinux
setools-console.x86_64 : Policy analysis command-line tools for SELinux
setools-console-analyses.x86_64 : Policy analysis command-line tools for SELinux
setools-gui.x86_64 : Policy analysis graphical tools for SELinux
user1@rhel10-vm2:~$ sudo dnf install -y setools-console.x86_64
[sudo] password for user1:
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered with an entitlement server. You can use "rhc" or "subscription-manager" to register.
lab2-1-BaseOS 0.0 B/s | 0 B 00:00
Errors during downloading metadata for repository 'labII-IxBaseOS':
- Curl error (37): Could not read a file:// file for file:///mnt/cdrom/BaseOS/repodata/repomd.xml [Couldn't open file /mnt/cdrom/BaseOS/repodata/repomd.xml]
Error: Failed to download metadata for repo 'labII-IxBaseOS': Cannot download repomd.xml: Cannot download repodata/repomd.xml: All mirrors were tried
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 409.6M 0 disk
└─sda1 8:1 0 9M 0 part
sdb 8:16 0 409.6M 0 disk
├─sdb1 8:17 0 13M 0 part
└─sdb2 8:18 0 100M 0 part
sdc 8:32 0 409.6M 0 disk
sr0 11:0 1 9.5G 0 rom
vda 252:0 0 25G 0 disk
├─vda1 252:1 0 1M 0 part
├─vda2 252:2 0 1G 0 part /boot
└─vda3 252:3 0 24G 0 part
├─rhel-root 253:0 0 22G 0 lvm /
└─rhel-swap 253:1 0 2G 0 lvm [SWAP]
user1@rhel10-vm2:~$ sudo mount /dev/sr0 /mnt/cdrom
mount: /mnt/cdrom: WARNING: source write-protected, mounted read-only.
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo dnf install -y setools-console.x86_64
Updating Subscription Management repositories.
Unable to read consumer identity
This system is not registered with an entitlement server. You can use "rhc" or "subscription-manager" to register.
lab2-1-BaseOS 2.7 MB/s | 2.7 kB 00:00
lab2-1-AppStream 2.7 MB/s | 2.8 kB 00:00
Dependencies resolved.
==============================================================================================
Package Architecture Version Repository Size
==============================================================================================
Installing:
setools-console x86_64 4.5.1-5.el10 labII-IxBaseOS 46 k
Upgrading:
python3-setools x86_64 4.5.1-5.el10 labII-IxBaseOS 743 k
Transaction Summary
==============================================================================================
Install 1 Package
Upgrade 1 Package
Total size: 789 k
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing : 1/1
Upgrading : python3-setools-4.5.1-5.el10.x86_64 1/3
Installing : setools-console-4.5.1-5.el10.x86_64 2/3
Cleanup : python3-setools-4.5.1-4.el10.x86_64 3/3
Running scriptlet: python3-setools-4.5.1-4.el10.x86_64 3/3
Installed products updated.
Upgraded:
python3-setools-4.5.1-5.el10.x86_64
Installed:
setools-console-4.5.1-5.el10.x86_64
Complete!
user1@rhel10-vm2:~$ seinfo
Statistics for policy file: /sys/fs/selinux/policy
Policy Version: 33 (MLS enabled)
Target Policy: selinux
Handle unknown classes: allow
Classes: 133 Permissions: 458
Sensitivities: 1 Categories: 1024
Types: 4718 Attributes: 251
Users: 8 Roles: 15
Booleans: 324 Cond. Expr.: 352
Allow: 59796 Neverallow: 0
Auditallow: 156 Dontaudit: 7999
Type_trans: 244015 Type_change: 80
Type_member: 34 Range_trans: 3893
Role allow: 39 Role_trans: 318
Constraints: 70 Validatetrans: 0
MLS Constrain: 72 MLS Val. Tran: 0
Permissives: 24 Polcap: 6
Defaults: 7 Typebounds: 0
Allowxperm: 0 Neverallowxperm: 0
Auditallowxperm: 0 Dontauditxperm: 0
Ibendportcon: 0 Ibpkeycon: 0
Initial SIDs: 27 Fs_use: 35
Genfscon: 112 Portcon: 667
Netifcon: 0 Nodecon: 0
user1@rhel10-vm2:~$
To print the list of all supported SELinux users:
user1@rhel10-vm2:~$ seinfo -u
Users: 8
guest_u
root
staff_u
sysadm_u
system_u
unconfined_u
user_u
xguest_u
user1@rhel10-vm2:~$
semanage#
To display the list of SELinux users:
root@linux:~# semanage login -l
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
root@linux:~# exit
logout
Wassim@linux:~$ semanage login -l
ValueError: SELinux policy is not managed or store cannot be accessed.
Wassim@linux:~$ sudo semanage login -l
[sudo] password for Wassim:
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
Wassim@linux:~$
verify SELinux contexts#
of Linux users#
I can use semanage to check the available types of the context of SELinux users that map to Linux users.
user01@rhel10-vm5:~$ semanage login
usage: semanage login [-h] [-n] [-N] [-S STORE] [ --add -s SEUSER -r RANGE LOGIN | --delete LOGIN | --deleteall | --extract | --list -C | --modify -s SEUSER -r RANGE LOGIN ]
positional arguments:
login login_name | %groupname
options:
-h, --help show this help message and exit
-C, --locallist List login local customizations
-n, --noheading Do not print heading when listing login object types
-N, --noreload Do not reload policy after commit
-S STORE, --store STORE
Select an alternate SELinux Policy Store to manage
-r RANGE, --range RANGE
MLS/MCS Security Range (MLS/MCS Systems only) SELinux Range
for SELinux login mapping defaults to the SELinux user record
range. SELinux Range for SELinux user defaults to s0.
-a, --add Add a record of the login object type
-d, --delete Delete a record of the login object type
-m, --modify Modify a record of the login object type
-l, --list List records of the login object type
-E, --extract Extract customizable commands, for use within a transaction
-D, --deleteall Remove all login objects local customizations
-s SEUSER, --seuser SEUSER
SELinux user name
semanage login: error: one of the arguments -a/--add -d/--delete -m/--modify -l/--list -E/--extract -D/--deleteall is required
user01@rhel10-vm5:~$
The right option seems to be -l:
user01@rhel10-vm5:~$ semanage login -l
ValueError: SELinux policy is not managed or store cannot be accessed.
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ sudo semanage login -l
[sudo] password for user01:
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
user01@rhel10-vm5:~$
I learned that default includes all non-root Linux users.
I then learned that running runcon with neither arguments nor options displays the SELinux context of the current Linux user. Here is an extract of the man page:
RUNCON(1) User Commands RUNCON(1)
NAME
runcon - run command with specified security context
SYNOPSIS
runcon CONTEXT COMMAND [args]
runcon [ -c ] [-u USER] [-r ROLE] [-t TYPE] [-l RANGE] COMMAND [args]
DESCRIPTION
Run COMMAND with completely-specified CONTEXT, or with current or transitioned security context modified by one or more of LEVEL, ROLE, TYPE, and USER.
If none of -c, -t, -u, -r, or -l, is specified, the first argument is used as the complete context. Any additional arguments after COMMAND are interpreted as argu‐
ments to the command.
Only carefully-chosen contexts are likely to run successfully.
Run a program in a different SELinux security context. With neither CONTEXT nor COMMAND, print the current security context.
of directories and files#
ls -Z to display the security context of files and ls -dZ to display the security context of directories:
user01@rhel10-vm5:~$ sudo ls -dZ /root/
system_u:object_r:admin_home_t:s0 /root/
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ sudo touch /root/selinux-test
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ ls -Z /root/selinux-test
ls: cannot access '/root/selinux-test': Permission denied
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ sudo ls -Z /root/selinux-test
unconfined_u:object_r:admin_home_t:s0 /root/selinux-test
user01@rhel10-vm5:~$
of processes#
user1@rhel10-vm2:~$ ps -Z
LABEL PID TTY TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1495 pts/0 00:00:00 bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1551 pts/0 00:00:00 ps
user1@rhel10-vm2:~$
AVC#
An SELinux authorization chain that results in a denial is recorded as a AVC message. AVC stands for Access Vector Cache. AVC messages are recorded in the log file of the auditd.service service, /var/log/audit/audit.log, assuming that auditd.service is enabled and active. On my lab VM, auditd is enabled and running.
user01@rhel10-vm5:~$ sudo grep AVC /var/log/audit/audit.log
type=AVC msg=audit(1791342391.577:4340): avc: denied { noatsecure } for pid=8266 comm="kworker/u10:1" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
type=AVC msg=audit(1791342391.578:4341): avc: denied { rlimitinh } for pid=8266 comm="systemd-coredum" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
type=AVC msg=audit(1791342391.578:4342): avc: denied { siginh } for pid=8266 comm="systemd-coredum" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
type: type of the audit message pid the process ID that requested access to the SELinux object. tclass: class of the target object? #QA permissive=n when permissive=1, SELinux is not enforced and the AVC message is for logging and information purposes only. When permissive=0, SELinux is enforced and the logged AVC message describes actual denial of some sort of access type. avc: denied {}: it is an AVC message that records the denial of the access type mentioned between curly brackets. scontext: source context; It is the context of the SELinux subject tcontext: target context; It is the context of the SELinux object that the subject is trying to access.
And I learned that I can search within the auditd log using ausearch with the -m option to filter on message types.
user01@rhel10-vm5:~$ sudo ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -ts recent
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.577:4340): avc: denied { noatsecure } for pid=8266 comm="kworker/u10:1" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.578:4341): avc: denied { rlimitinh } for pid=8266 comm="systemd-coredum" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.578:4342): avc: denied { siginh } for pid=8266 comm="systemd-coredum" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
----
<---- output omitted ---->
I learned that not seeing AVC messages in the auditd logs might be due to the donotaudit flag, which prevents logging of all AVC messages, probably to reduce CPU and disk solicitation. The -D option of the semodule command disables the flag temporarily, which is perfect for a short troubleshooting gig. And the -B option of the same command rebuilds policies.
SEMODULE(8) System Manager's Manual SEMODULE(8)
NAME
semodule - Manage SELinux policy modules.
SYNOPSIS
semodule [option]... MODE...
DESCRIPTION
semodule is the tool used to manage SELinux policy modules, including installing, upgrading, listing and removing modules. semodule may also be used to force a re‐
build of policy from the module store and/or to force a reload of policy without performing any other transaction. semodule acts on module packages created by se‐
module_package. Conventionally, these files have a .pp suffix (policy package), although this is not mandated in any way.
MODES
-R, --reload
force a reload of policy
-B, --build
force a rebuild of policy (also reloads unless -n is used)
--refresh
Like --build, but reuses existing linked policy if no changes to module files are detected (by comparing with checksum from the last transaction). One can
use this instead of -B to ensure that any changes to the module store done by an external tool (e.g. a package manager) are applied, while automatically skip‐
ping the module re-linking if there are no module changes.
-D, --disable_dontaudit
Temporarily remove dontaudits from policy. Reverts whenever policy is rebuilt
So I disabled the dontaudits temporarily, rebuilt the policy ( #QA which one was impacted?) tried an action that probably generates a AVC message, then rebuilt the policy to revert the dontaudits flag.
user01@rhel10-vm5:~$ sudo semodule -DB
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ ls /root/
ls: cannot open directory '/root/': Permission denied
user01@rhel10-vm5:~$ rm -d /root/
rm: attempt removal of inaccessible directory '/root/'? y
rm: cannot remove '/root/': Permission denied
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ sudo ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -ts recent
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.577:4340): avc: denied { noatsecure } for pid=8266 comm="kworker/u10:1" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.578:4341): avc: denied { rlimitinh } for pid=8266 comm="systemd-coredum" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.578:4342): avc: denied { siginh } for pid=8266 comm="systemd-coredum" scontext=system_u:system_r:kernel_t:s0 tcontext=system_u:system_r:systemd_coredump_t:s0 tclass=process permissive=0
----
time->Tue Oct 6 23:06:31 2026
type=AVC msg=audit(1791342391.594:4343): avc: denied { kill } for pid=647 comm="systemd-journal" capability=5 scontext=system_u:system_r:syslogd_t:s0 tcontext=system_u:system_r:syslogd_t:s0 tclass=cap_userns permissive=0
----
I initially thought those AVC messages were related to my action of accessing /root and trying to delete it. But I was not confident because the text of the AVC messages read a bit different from what I wanted to achieve. At that point the only approximation heuristic I had was to run ausearch -m AVC -ts recent immediately after I trigger a SELinux denial. But that is only partially useful when the denial and the troubleshooting exercise are in the same time window. So how to tell, with high confidence, whether a SELinux subject should succeed at accessing an SELinux object at any time?
logging SELinux denials when auditd.service is disabled#
My theory is that SELinux denials will be logged to journalctl even when auditd is disabled. I did not verify whether journalctl logs SELinux denials in addition to auditd logs when the latter were active.
To test my theory, I modified the httpd listening port to something that is not known by the binary policy:
user1@rhel10-vm5:~$ sudo semanage port -l | grep 8888
user1@rhel10-vm5:~$
user1@rhel10-vm5:~$ sudo grep ^Listen /etc/httpd/conf/httpd.conf
Listen 8888
user1@rhel10-vm5:~$
I disabled the auditd.service, rebooted the VM.
After the machine came back, I made sure auditd.service is disabled, then tried to start httpd:
user01@rhel10-vm5:~$ systemctl status auditd.service
○ auditd.service - Security Audit Logging Service
Loaded: loaded (/usr/lib/systemd/system/auditd.service; disabled; preset: enabled)
Active: inactive (dead)
Docs: man:auditd(8)
https://github.com/linux-audit/audit-documentation
user01@rhel10-vm5:~$ grep ^Listen /etc/httpd/conf/httpd.conf
Listen 8888
user01@rhel10-vm5:~$ sudo semanage port -l | grep 8888
[sudo] password for user01:
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ sudo systemctl start httpd.service
Job for httpd.service failed because the control process exited with error code.
See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for details.
user01@rhel10-vm5:~$
After that, I consulted journalctl with a filter on httpd. There I found the denial logged:
user01@rhel10-vm5:~$ journalctl | grep httpd
Oct 13 13:29:11 rhel10-vm5 sudo[3670]: user01 : TTY=pts/0 ; PWD=/home/user01 ; USER=root ; COMMAND=/bin/systemctl start httpd.service
Oct 13 13:29:11 rhel10-vm5 systemd[1]: Starting httpd.service - The Apache HTTP Server...
Oct 13 13:29:11 rhel10-vm5 (httpd)[3703]: httpd.service: Referenced but unset environment variable evaluates to an empty string: OPTIONS
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::a00:27ff:fe41:1e5%enp0s3. Set the 'ServerName' directive globally to suppress this message
Oct 13 13:29:16 rhel10-vm5 kernel: audit: type=1400 audit(1791912556.593:4): avc: denied { name_bind } for pid=3703 comm="httpd" src=8888 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
Oct 13 13:29:16 rhel10-vm5 kernel: audit: type=1400 audit(1791912556.594:5): avc: denied { name_bind } for pid=3703 comm="httpd" src=8888 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: (13)Permission denied: AH00072: make_sock: could not bind to address [::]:8888
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: (13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:8888
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: no listening sockets available, shutting down
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: AH00015: Unable to open logs
Oct 13 13:29:16 rhel10-vm5 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Oct 13 13:29:16 rhel10-vm5 systemd[1]: httpd.service: Failed with result 'exit-code'.
Oct 13 13:29:16 rhel10-vm5 systemd[1]: Failed to start httpd.service - The Apache HTTP Server.
user01@rhel10-vm5:~$
Since journalctl processes logs from /var/log/, I should be able to find httpd related logs in /var/log/messages:
user01@rhel10-vm5:~$ sudo grep httpd /var/log/messages
[sudo] password for user01:
Oct 13 13:29:11 rhel10-vm5 systemd[1]: Starting httpd.service - The Apache HTTP Server...
Oct 13 13:29:11 rhel10-vm5 (httpd)[3703]: httpd.service: Referenced but unset environment variable evaluates to an empty string: OPTIONS
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using fe80::a00:27ff:fe41:1e5%enp0s3. Set the 'ServerName' directive globally to suppress this message
Oct 13 13:29:16 rhel10-vm5 kernel: audit: type=1400 audit(1791912556.593:4): avc: denied { name_bind } for pid=3703 comm="httpd" src=8888 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
Oct 13 13:29:16 rhel10-vm5 kernel: audit: type=1400 audit(1791912556.594:5): avc: denied { name_bind } for pid=3703 comm="httpd" src=8888 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: (13)Permission denied: AH00072: make_sock: could not bind to address [::]:8888
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: (13)Permission denied: AH00072: make_sock: could not bind to address 0.0.0.0:8888
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: no listening sockets available, shutting down
Oct 13 13:29:16 rhel10-vm5 httpd[3703]: AH00015: Unable to open logs
Oct 13 13:29:16 rhel10-vm5 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Oct 13 13:29:16 rhel10-vm5 systemd[1]: httpd.service: Failed with result 'exit-code'.
Oct 13 13:29:16 rhel10-vm5 systemd[1]: Failed to start httpd.service - The Apache HTTP Server.
user01@rhel10-vm5:~$
The output confirms the theory.
map a SELinux denial to the right AVC messages#
I leverage the runcon command to trigger either a SELinux authorization success (which will not be logged) or a denial. And I am more interested in the denial result. So I ran the command by specifying the source context, the target context and the command to access the object:
user01@rhel10-vm5:~$ runcon system_u:system_r:passwd_t:s0 cat /root/selinux-test
runcon: ‘cat’: Permission denied
user01@rhel10-vm5:~$ sudo runcon system_u:system_r:passwd_t:s0 cat /root/selinux-test
runcon: ‘cat’: Permission denied
user01@rhel10-vm5:~$ runcon system_u:system_r:passwd_t:s0 sudo cat /root/selinux-test
runcon: ‘sudo’: Permission denied
user01@rhel10-vm5:~$
At first I thought I needed elevated privileges to run runcon because of the Permission denied message. But then I looked into ausearch and sealert and found out that the Permission denied was in fact part of SELinux in action.
user01@rhel10-vm5:~$ sudo ausearch -m AVC -ts recent
----
time->Tue Oct 6 23:36:16 2026
type=PROCTITLE msg=audit(1791344176.613:8135): proctitle=72756E636F6E0073797374656D5F753A73797374656D5F723A7061737377645F743A733000636174002F72756E2F73656C696E75782D74657374
type=SYSCALL msg=audit(1791344176.613:8135): arch=c000003e syscall=59 success=no exit=-13 a0=7ffe386337d0 a1=7ffe38633ab8 a2=7ffe38633ad0 a3=555e71f5e010 items=0 ppid=3536 pid=12416 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts0 ses=3 comm="runcon" exe="/usr/bin/runcon" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
type=AVC msg=audit(1791344176.613:8135): avc: denied { entrypoint } for pid=12416 comm="runcon" path="/usr/bin/cat" dev="dm-0" ino=1754910 scontext=system_u:system_r:passwd_t:s0 tcontext=system_u:object_r:bin_t:s0 tclass=file permissive=0
----
time->Tue Oct 6 23:36:46 2026
type=PROCTITLE msg=audit(1791344206.544:8198): proctitle=72756E636F6E0073797374656D5F753A73797374656D5F723A7061737377645F743A733000636174002F72756E2F73656C696E75782D74657374
type=SYSCALL msg=audit(1791344206.544:8198): arch=c000003e syscall=59 success=no exit=-13 a0=7fff6acfe800 a1=7fff6acfeac8 a2=7fff6acfeae0 a3=5559cef00010 items=0 ppid=12499 pid=12500 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts5 ses=3 comm="runcon" exe="/usr/bin/runcon" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
type=AVC msg=audit(1791344206.544:8198): avc: denied { entrypoint } for pid=12500 comm="runcon" path="/usr/bin/cat" dev="dm-0" ino=1754910 scontext=system_u:system_r:passwd_t:s0 tcontext=system_u:object_r:bin_t:s0 tclass=file permissive=0
----
time->Tue Oct 6 23:36:46 2026
type=PROCTITLE msg=audit(1791344206.545:8199): proctitle=72756E636F6E0073797374656D5F753A73797374656D5F723A7061737377645F743A733000636174002F72756E2F73656C696E75782D74657374
type=SYSCALL msg=audit(1791344206.545:8199): arch=c000003e syscall=59 success=no exit=-13 a0=7fff6acfe800 a1=7fff6acfeac8 a2=7fff6acfeae0 a3=5559cef00010 items=0 ppid=12499 pid=12500 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts5 ses=3 comm="runcon" exe="/usr/bin/runcon" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
type=AVC msg=audit(1791344206.545:8199): avc: denied { entrypoint } for pid=12500 comm="runcon" path="/usr/bin/cat" dev="dm-0" ino=1754910 scontext=system_u:system_r:passwd_t:s0 tcontext=system_u:object_r:bin_t:s0 tclass=file permissive=0
----
time->Tue Oct 6 23:37:08 2026
type=PROCTITLE msg=audit(1791344228.929:8242): proctitle=72756E636F6E0073797374656D5F753A73797374656D5F723A7061737377645F743A7330007375646F00636174002F726F6F742F73656C696E75782D74657374
type=SYSCALL msg=audit(1791344228.929:8242): arch=c000003e syscall=59 success=no exit=-13 a0=7ffcfb0f5920 a1=7ffcfb0f5c28 a2=7ffcfb0f5c48 a3=55d4c1e9d010 items=0 ppid=3536 pid=12560 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts0 ses=3 comm="runcon" exe="/usr/bin/runcon" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
type=AVC msg=audit(1791344228.929:8242): avc: denied { entrypoint } for pid=12560 comm="runcon" path="/usr/bin/sudo" dev="dm-0" ino=2220505 scontext=system_u:system_r:passwd_t:s0 tcontext=system_u:object_r:sudo_exec_t:s0 tclass=file permissive=0
user01@rhel10-vm5:~$
user01@rhel10-vm5:~$ sealert -l "*"
SELinux is preventing /usr/bin/runcon from entrypoint access on the file /usr/bin/cat.
***** Plugin catchall (100. confidence) suggests **************************
If you believe that runcon should be allowed entrypoint access on the cat file by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'runcon' --raw | audit2allow -M my-runcon
# semodule -X 300 -i my-runcon.pp
Additional Information:
Source Context system_u:system_r:passwd_t:s0
Target Context system_u:object_r:bin_t:s0
Target Objects /usr/bin/cat [ file ]
Source runcon
Source Path /usr/bin/runcon
Port <Unknown>
Host rhel10-vm5
Source RPM Packages coreutils-9.5-6.el10.x86_64
Target RPM Packages coreutils-9.5-6.el10.x86_64
SELinux Policy RPM selinux-policy-targeted-42.1.18-4.el10_2.3.noarch
Local Policy RPM selinux-policy-targeted-42.1.18-4.el10_2.3.noarch
Selinux Enabled True
Policy Type targeted
Enforcing Mode Enforcing
Host Name rhel10-vm5
Platform Linux rhel10-vm5 6.12.0-55.9.1.el10_0.x86_64 #1
SMP PREEMPT_DYNAMIC Tue Mar 25 09:14:09 EDT 2025
x86_64
Alert Count 3
irst Seen 2026-10-06 23:36:16 EDT
Last Seen 2026-10-06 23:36:46 EDT
Local ID 6c9dbc7d-dff7-4b36-9c81-e37d0a47eaad
Raw Audit Messages
type=AVC msg=audit(1791344206.545:8199): avc: denied { entrypoint } for pid=12500 comm="runcon" path="/usr/bin/cat" dev="dm-0" ino=1754910 scontext=system_u:system_r:passwd_t:s0 tcontext=system_u:object_r:bin_t:s0 tclass=file permissive=0
type=SYSCALL msg=audit(1791344206.545:8199): arch=x86_64 syscall=execve success=no exit=EACCES a0=7fff6acfe800 a1=7fff6acfeac8 a2=7fff6acfeae0 a3=5559cef00010 items=0 ppid=12499 pid=12500 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts5 ses=3 comm=runcon exe=/usr/bin/runcon subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
Hash: runcon,passwd_t,bin_t,file,entrypoint
SELinux is preventing /usr/bin/runcon from entrypoint access on the file /usr/bin/sudo.
***** Plugin catchall (100. confidence) suggests **************************
If you believe that runcon should be allowed entrypoint access on the sudo file by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'runcon' --raw | audit2allow -M my-runcon
# semodule -X 300 -i my-runcon.pp
Additional Information:
Source Context system_u:system_r:passwd_t:s0
Target Context system_u:object_r:sudo_exec_t:s0
Target Objects /usr/bin/sudo [ file ]
Source runcon
Source Path /usr/bin/runcon
Port <Unknown>
Host rhel10-vm5
Source RPM Packages coreutils-9.5-6.el10.x86_64
Target RPM Packages sudo-1.9.15-8.p5.el10.x86_64
SELinux Policy RPM selinux-policy-targeted-42.1.18-4.el10_2.3.noarch
Local Policy RPM selinux-policy-targeted-42.1.18-4.el10_2.3.noarch
Selinux Enabled True
Policy Type targeted
Enforcing Mode Enforcing
Host Name rhel10-vm5
Platform Linux rhel10-vm5 6.12.0-55.9.1.el10_0.x86_64 #1
SMP PREEMPT_DYNAMIC Tue Mar 25 09:14:09 EDT 2025
x86_64
Alert Count 1
First Seen 2026-10-06 23:37:08 EDT
Last Seen 2026-10-06 23:37:08 EDT
Local ID 6eb36127-b029-4f0d-9bea-bbd1e67ad903
Raw Audit Messages
type=AVC msg=audit(1791344228.929:8242): avc: denied { entrypoint } for pid=12560 comm="runcon" path="/usr/bin/sudo" dev="dm-0" ino=2220505 scontext=system_u:system_r:passwd_t:s0 tcontext=system_u:object_r:sudo_exec_t:s0 tclass=file permissive=0
type=SYSCALL msg=audit(1791344228.929:8242): arch=x86_64 syscall=execve success=no exit=EACCES a0=7ffcfb0f5920 a1=7ffcfb0f5c28 a2=7ffcfb0f5c48 a3=55d4c1e9d010 items=0 ppid=3536 pid=12560 auid=1000 uid=1000 gid=1000 euid=1000 suid=1000 fsuid=1000 egid=1000 sgid=1000 fsgid=1000 tty=pts0 ses=3 comm=runcon exe=/usr/bin/runcon subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key=(null)
Hash: runcon,passwd_t,sudo_exec_t,file,entrypoint
user01@rhel10-vm5:~$
sealert interpreted the AVC messages and generated a message in a human-friendly language. I inferred from the sealert message that the context of runcon failed the SELinux authorization chain to access sudo and cat files.
restoring the context#
/etc/selinux/targeted/contexts/files form the context policy. This tree path contains files, each file contains entries and each entry is in the form directory context. Those are the expected SELinux context values for every directory or file in the system that is created under a specific directory.
user01@rhel10-vm5:~$ ls /etc/selinux/targeted/contexts/
customizable_types files securetty_types users
dbus_contexts initrc_context sepgsql_contexts virtual_domain_context
default_contexts lxc_contexts snapperd_contexts virtual_image_context
default_type openssh_contexts systemd_contexts x_contexts
failsafe_context removable_context userhelper_context
user01@rhel10-vm5:~$
user01@rhel10-vm5:/etc/selinux/targeted/contexts$
user01@rhel10-vm5:/etc/selinux/targeted/contexts$ cd files
user01@rhel10-vm5:/etc/selinux/targeted/contexts/files$ ls
file_contexts file_contexts.homedirs.bin file_contexts.subs
file_contexts.bin file_contexts.local file_contexts.subs_dist
file_contexts.homedirs file_contexts.local.bin media
user01@rhel10-vm5:/etc/selinux/targeted/contexts/files$
user01@rhel10-vm5:/etc/selinux/targeted/contexts/files$ cat file_contexts
/.* system_u:object_r:default_t:s0
/[^/]+ -- system_u:object_r:etc_runtime_t:s0
/a?quota\.(user|group) -- system_u:object_r:quota_db_t:s0
/efi(/.*)? system_u:object_r:boot_t:s0
/nsr(/.*)? system_u:object_r:var_t:s0
/sys(/.*)? system_u:object_r:sysfs_t:s0
/xen(/.*)? system_u:object_r:xen_image_t:s0
/mnt(/[^/]*)? -d system_u:object_r:mnt_t:s0
/mnt(/[^/]*)? -l system_u:object_r:mnt_t:s0
/dev/.* system_u:object_r:device_t:s0
/etc/.* system_u:object_r:etc_t:s0
/lib/.* system_u:object_r:lib_t:s0
/opt/.* system_u:object_r:usr_t:s0
/run/.* system_u:object_r:var_run_t:s0
/srv/.* system_u:object_r:var_t:s0
/tmp/.* <<none>>
/usr/.* system_u:object_r:usr_t:s0
/var/.* system_u:object_r:var_t:s0
/root(/.*)? system_u:object_r:admin_home_t:s0
/opt/.*\.cgi -- system_u:object_r:httpd_sys_script_exec_t:s0
/usr/.*\.cgi -- system_u:object_r:httpd_sys_script_exec_t:s0
/run/.*\.*pid <<none>>
/dev/[0-9].* -c system_u:object_r:usb_device_t:s0
/mnt/[^/]*/.* <<none>>
/run/.*cron.* -- system_u:object_r:crond_var_run_t:s0
/opt/(.*/)?etc -d system_u:object_r:etc_t:s0
<---- output omitted ---->
For instance, the context policy has a file named file_contexts.homedirs which dictates the expected SELInux context value for files and subdirectories under /home/:
ser01@rhel10-vm5:~/Videos$ grep -i user /etc/selinux/targeted/contexts/files/file_contexts.homedirs
# User-specific file contexts, generated via libsemanage
# use semanage command to manage system users to change the file_context
# Home Context for user user_u
/home/[^/]+/.+ unconfined_u:object_r:user_home_t:s0
/home/[^/]+/((www)|(web)|(public_html))(/.+)? unconfined_u:object_r:httpd_user_content_t:s0
/home/[^/]+/((www)|(web)|(public_html))/cgi-bin(/.+)? unconfined_u:object_r:httpd_user_script_exec_t:s0
/home/[^/]+/((www)|(web)|(public_html))(/.*)?/\.htaccess -- unconfined_u:object_r:httpd_user_htaccess_t:s0
/home/[^/]+/((www)|(web)|(public_html))(/.*)?/logs(/.*)? unconfined_u:object_r:httpd_user_ra_content_t:s0
<---- output omitted ---->
According to the first entry in that file, I expect that a new file vid2 will get the unconfined_u:object_r:user_home_t:s0 context at the time of its creation:
user01@rhel10-vm5:~/Videos$ ls -l
total 0
-rw-r--r--. 1 user01 user01 0 Oct 7 14:24 vid1
user01@rhel10-vm5:~/Videos$ touch vid2
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ ls -Z vid2
unconfined_u:object_r:user_home_t:s0 vid2
user01@rhel10-vm5:~/Videos$
The observed output confirms my assumption.
If a subject or object is suspected to have a wrong label, I can restore the correct context. This is done with restorecon.
user01@rhel10-vm5:~$ ls -l
total 0
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Desktop
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Documents
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Downloads
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Music
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Pictures
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Public
drwxr-xr-x. 2 user01 user01 6 Sep 29 23:25 Templates
drwxr-xr-x. 2 user01 user01 18 Oct 7 14:24 Videos
-rw-r--r--. 1 user01 user01 0 Sep 30 14:49 vim
user01@rhel10-vm5:~$ ls -l Videos
total 0
-rw-r--r--. 1 user01 user01 0 Oct 7 14:24 vid1
user01@rhel10-vm5:~$ ls -Z Videos
unconfined_u:object_r:user_home_t:s0 vid1
user01@rhel10-vm5:~$ restorecon -n Videos/vid1
user01@rhel10-vm5:~$ restorecon -Rv Videos
user01@rhel10-vm5:~$ ls -Z Videos
unconfined_u:object_r:user_home_t:s0 vid1
user01@rhel10-vm5:~$
The options I’ve used for restorecon:
-nfor a white run. No change is executed.-Rfor recursive: to apply the changes to sudirectories and files,-vfor verbose.
I combine -R with -v and -n with -v for verbosity. -n alone gave me nothing.
In myh example, the SELinux context of the file vid1 remained the same after a restorecon, which means its SELinux context is the one that is expected by the system.
restorecon did not ask for root privileges.
changing the SELinux context#
To immediately change the value of the SELinux context type, I use chcon with the -t option. For instance, I change the SELinux context type for vid1 with chcon then perform a white run on the file with restorecon -n, during which I emulate what the new context type would look like. Finally, I execute the change, restore the expected SELinux context and verify the result.
user01@rhel10-vm5:~$ cd Videos/
user01@rhel10-vm5:~/Videos$ ls -Z
unconfined_u:object_r:user_home_t:s0 vid1
user01@rhel10-vm5:~/Videos$ chcon -t httpd_sys_content_t /home/user01/Videos/vid1
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ ls -Z
unconfined_u:object_r:httpd_sys_content_t:s0 vid1
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ restorecon -n vid1
user01@rhel10-vm5:~/Videos$ ls -Z vid1
unconfined_u:object_r:httpd_sys_content_t:s0 vid1
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ restorecon -nv vid1
Would relabel /home/user01/Videos/vid1 from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:user_home_t:s0
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ restorecon -v vid1
Relabeled /home/user01/Videos/vid1 from unconfined_u:object_r:httpd_sys_content_t:s0 to unconfined_u:object_r:user_home_t:s0
user01@rhel10-vm5:~/Videos$ ls -Z vid1
unconfined_u:object_r:user_home_t:s0 vid1
user01@rhel10-vm5:~/Videos$
There is a caveat though: chcon does not add any rule to the context policy. So it might break the SELinux logic if the changes are not traceable, documented and accessible to the administrators of the system.
list SELinux fcontext rules#
user1@rhel10-vm2:~$ sudo semanage fcontext -a -t httpd_sys_content_t "/home/user1/test(/.*)?"
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ semanage fcontext -l
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -l | less
SELinux fcontext type Context
/ directory system_u:object_r:root_t
:s0
/.* all files system_u:object_r:defaul
t_t:s0
/[^/]+ regular file system_u:object_r:etc_ru
ntime_t:s0
/\.autofsck regular file system_u:object_r:etc_ru
ntime_t:s0
/\.autorelabel regular file system_u:object_r:etc_ru
ntime_t:s0
/\.ismount-test-file regular file system_u:object_r:sosrep
ort_tmp_t:s0
/\.journal all files <<None>>
/\.snapshots(/.*)? all files system_u:object_r:snappe
rd_data_t:s0
/\.suspended regular file system_u:object_r:etc_ru
ntime_t:s0
/a?quota\.(user|group) regular file system_u:object_r:quota_
<---- output omitted ---->
The listing is multi page. I refine the result to display entries that match a keyword in the rule I’ve created earlier.
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep "user1"
/home/user1/test(/.*)? all files system_u:object_r:httpd_sys_content_t:s0
delete SELinux context rules#
Instead of simply replacing the -a option with -d only, I ignored the -t option and kept the Linux tree path. I also tried to delete a non-existing rule first then gave the right one.
user1@rhel10-vm2:~$ semanage fcontext -d "/home/user1/test(/.x)?"
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -d "/home/user1/test(/.x)?"
ValueError: File context for /home/user1/test(/.x)? is not defined
user1@rhel10-vm2:~$ sudo semanage fcontext -d "/home/user1/test(/.*)?"
user1@rhel10-vm2:~$
To verify that the context rule is really deleted, I ran the same deletion command again and relied on the system error message as a feedback mechanism:
user1@rhel10-vm2:~$ sudo semanage fcontext -d "/home/user1/test(/.*)?"
ValueError: File context for /home/user1/test(/.*)? is not defined
user1@rhel10-vm2:~$
modify SELinux context rule#
I looked into the list of possible context type values (with semanage -l) and decided to pick something irrelevant to user home directories; the type called usb_device_t.
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep "/home/user1"
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep "/home/user"
/home/user*(/.*)? all files system_u:object_r:httpd_sys_content_t:s0
user1@rhel10-vm2:~$ sudo semanage fcontext -m -t usb_device_t "/home/user*(/.*)?"
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep "home/user"
/home/user*(/.*)? all files system_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$
I ran restorecon in a no-change way and the system displayed nothing. I thought maybe the no-change mode of restorecon failed. So I ran it without the -n option again. Nothing changed. I created a new dummy file. It received the context type value of user_home_t instead of usb_device_t:
user1@rhel10-vm2:~/test$ touch file3.dummy
user1@rhel10-vm2:~/test$ ls -Z file3.dummy
unconfined_u:object_r:user_home_t:s0 file3.dummy
user1@rhel10-vm2:~/test$
user1@rhel10-vm2:~/test$ restorecon -nv file3.dummy
user1@rhel10-vm2:~/test$
Could it be possible that my rule has lower priority than another one in the SELinux database? I looked for context rules that contain the /home directory:
user1@rhel10-vm2:~/test$ sudo semanage fcontext -l | grep "/home" | wc -l
[sudo] password for user1:
216
user1@rhel10-vm2:~/test$
I refined with /home/user:
user1@rhel10-vm2:~/test$ sudo semanage fcontext -l | grep "/home/user" | wc -l
1
user1@rhel10-vm2:~/test$ sudo semanage fcontext -l | grep "/home/user"
/home/user*(/.*)? all files system_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~/test$
That is my own rule. Refining on either home or user_home_t gave the same number of entries. So all entries with user_home_t are related to /home.
user1@rhel10-vm2:~/test$ sudo semanage fcontext -l | grep -iE "/home|user_home_t" | wc -l
216
user1@rhel10-vm2:~/test$
I searched for entries whose context match exactly unconfined_u:object_r:user_home_t:s0 and I found a single one.
user1@rhel10-vm2:~/test$
user1@rhel10-vm2:~/test$ sudo semanage fcontext -l | grep -i "unconfined_u:object_r:user_home_t:s0" | wc -l
1
user1@rhel10-vm2:~/test$ sudo semanage fcontext -l | grep -i "unconfined_u:object_r:user_home_t:s0"
/home/[^/]+/.+ all files unconfined_u:object_r:user_home_t:s0
user1@rhel10-vm2:~/test$
That is the one rule getting higher priority than mine. Why is that? #QA
creating context policy rules#
for files#
with semanage fcontext. The man page of semanage is a bit dry.
semanage(8) semanage(8)
NAME
semanage - SELinux Policy Management tool
SYNOPSIS
semanage {import,export,login,user,port,interface,module,node,fcon‐
text,boolean,permissive,dontaudit,ibpkey,ibendport}
... positional arguments:
import Import local customizations
export Output local customizations
login Manage login mappings between linux users and SELinux confined users
user Manage SELinux confined users (Roles and levels for an SELinux user)
port Manage network port type definitions
interface Manage network interface type definitions
module Manage SELinux policy modules
node Manage network node type definitions
fcontext Manage file context mapping definitions
<--- output omitted --->
I checked the help page:
user01@rhel10-vm5:~$ semanage fcontext --help
usage: semanage fcontext [-h] [-n] [-N] [-S STORE] [ --add ( -t TYPE -f FTYPE -r RANGE -s SEUSER | -e TARGET_PATH ) FILE_SPEC | --delete ( -t TYPE -f FTYPE | -e TARGET_PATH ) FILE_SPEC | --deleteall | --extract | --list [-C] | --modify ( -t TYPE -f FTYPE -r RANGE -s SEUSER | -e TARGET_PATH ) FILE_SPEC ]
positional arguments:
FILE_SPEC Path to be labeled (may be in the form of a Perl compatible
regular expression)
options:
-h, --help show this help message and exit
-C, --locallist List fcontext local customizations
-n, --noheading Do not print heading when listing fcontext object types
-N, --noreload Do not reload policy after commit
-S STORE, --store STORE
Select an alternate SELinux Policy Store to manage
-a, --add Add a record of the fcontext object type
-d, --delete Delete a record of the fcontext object type
-m, --modify Modify a record of the fcontext object type
-l, --list List records of the fcontext object type
<---- output omitted ---->
I will be using -a, -d and -l options.
I created a rule to assing the type value of httpd_sys_content_t to anything created under /home/user01/Videos:
sudo semanage fcontext -a -t httpd_sys_content_t "/home/user01/Videos/(/.*)?"
When I created a new file under /home/user01/Videos, it did not take the new SELinux context type value. I reevaluated the command I’ve entered and noticed that the syntax was incorrect: the command will be evaluated as /home/user01/Videos// instead of /home/user01/Videos/. I deleted the rule and recreated it:
sudo semanage fcontext -d -t httpd_sys_content_t "/home/user01/Videos/(/.*)?"
I created another file, vid4, but I got the same result, which is no difference in the context type value. Then I learned that restorecon is required for existing files to update the context type value. What if I ran restorecon for new files too?
user01@rhel10-vm5:~/Videos$ touch vid6
user01@rhel10-vm5:~/Videos$ ls -Z vid6
unconfined_u:object_r:user_home_t:s0 vid6
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ restorecon -nv vid6
Would relabel /home/user01/Videos/vid6 from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ restorecon -v vid6
Relabeled /home/user01/Videos/vid6 from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
user01@rhel10-vm5:~/Videos$
user01@rhel10-vm5:~/Videos$ ls -Z vid6
unconfined_u:object_r:httpd_sys_content_t:s0 vid6
user01@rhel10-vm5:~/Videos$
#LessonLearned When creating SELinux file context policy rules, new files will not automatically receive the update unless restorecon is executed on them.
And the change in the context type field value is persistent after system reboots.
user01@rhel10-vm5:~$ uptime
00:46:25 up 0 min, 2 users, load average: 1.79, 0.45, 0.15
user01@rhel10-vm5:~$ ls -Z Videos/vid6
unconfined_u:object_r:httpd_sys_content_t:s0 Videos/vid6
user01@rhel10-vm5:~$
What if I tried to create a policy rule that involves a creative context type value?
user1@rhel10-vm2:~$ pwd
/home/user1
user1@rhel10-vm2:~$ ls
a q test
user1@rhel10-vm2:~$ cd test
user1@rhel10-vm2:~/test$ cd ..
user1@rhel10-vm2:~$ ls -dZ test
unconfined_u:object_r:user_home_t:s0 test
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ touch test/file1.dummy
user1@rhel10-vm2:~$ ls -Z test/file1.dummy
unconfined_u:object_r:user_home_t:s0 test/file1.dummy
user1@rhel10-vm2:~$ semanage fcontext -a -t dummy_sys_t "/home/user1/test(/.*)?"
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -a -t dummy_sys_t "/home/user1/test(/.*)?"
[sudo] password for user1:
ValueError: Type dummy_sys_t is invalid, must be a file or device type
user1@rhel10-vm2:~$
#LessonLearned The context type value must be picked from a system-known list of values when ccreating an SELinux policy rule manually with semanage.
for directories that exist in no predefined rules#
I create a dummy directory, a dummy file, check its context, try to add a rule for the directory, then try to restorecon the directory. The restore worked on the files under the directory, but the directory itself not:
user1@rhel10-vm2:~/test$ sudo mkdir -p /dummyd1/dummyd2
[sudo] password for user1:
user1@rhel10-vm2:~/test$ ls -dZ /dummyd1
unconfined_u:object_r:default_t:s0 /dummyd1
user1@rhel10-vm2:~/test$ touch /dummyd1/f1
touch: cannot touch '/dummyd1/f1': Permission denied
user1@rhel10-vm2:~/test$ sudo touch /dummyd1/f1
user1@rhel10-vm2:~/test$ ls -Z /dummyd1/f1
unconfined_u:object_r:default_t:s0 /dummyd1/f1
user1@rhel10-vm2:~/test$
user1@rhel10-vm2:~/test$ restorecon -nv /dummyd1/f1
user1@rhel10-vm2:~/test$ sudo semanage fcontext -a -t usb_device_t "/dummyd1(/.*)?"
user1@rhel10-vm2:~/test$
user1@rhel10-vm2:~/test$ restorecon -nv /dummyd1/f1
Would relabel /dummyd1/f1 from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~/test$ restorecon -nv /dummyd1
Would relabel /dummyd1 from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~/test$ restorecon -Rv /dummyd1/
restorecon: Could not set context for /dummyd1: Operation not permitted
restorecon: Could not set context for /dummyd1/dummyd2: Operation not permitted
restorecon: Could not set context for /dummyd1/f1: Operation not permitted
user1@rhel10-vm2:~/test$ sudo restorecon -Rv /dummyd1/
restorecon: Could not set context for /dummyd1: Permission denied
restorecon: Could not set context for /dummyd1/dummyd2: Permission denied
Relabeled /dummyd1/f1 from unconfined_u:object_r:default_t:s0 to unconfined_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~/test$
By the way, user-defined context rules are placed in /etc/selinux/targeted/contexts/files:
user1@rhel10-vm2:~$ sudo semanage fcontext -a -t usb_device_t "/home/user1(/.*)?"
[sudo] password for user1:
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ grep "home/user1" /etc/selinux/targeted/contexts/files/file_contexts.local
/home/user1(/.*)? system_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$
I learned later that defining file context rules and applying them are two different uncorrelated actions. A file context rule that I define lands in the /etc/selinux/targeted/contexts/files/file_contexts.local file. This file sits side by side with the system-defined file context rules that are stored in files under /etc/selinux/targeted/contexts/files/.
And I must make the distinction between the binary policy and the file context database: The former is called out when performing RBAC for user logon actions or TE. The latter is called out when a SELinux subject requests access to a SELinux object.
In my example, the parent directory, /home/user1/ had the unconfined_u:object_r:user_home_dir_t:s0 label. And file4.dummy seemes to be created with that same label:
user1@rhel10-vm2:~$ ls -dZ /home/user1/
unconfined_u:object_r:user_home_dir_t:s0 /home/user1/
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ touch /home/user1/file4.dummy
user1@rhel10-vm2:~$ ls -Z /home/user1/file4.dummy
unconfined_u:object_r:user_home_t:s0 /home/user1/file4.dummy
user1@rhel10-vm2:~$
It becomes clear that the kernel either does not care about user-defined file context rules at the time of creating a file, or there is a better-matching policy rule already in place. I learned later that the kernel applies a label to a new object (file, directory, …) based on this logic: 1/ the subdirectory or file inherits the SELinux context type of its parent directory, 2/ if a transition policy is defined, the label type is transitioned to whatever value defined in the transition policy.
So I need to distinguish between transition policies (used by the kernel at object creation time) and file context rules (which constitute the file context database and are used by restorecon).
I create another file, file5.dummy, verify its label, the label of its parent directory, and look which rule would be applied if I ran restorecon against that file:
user1@rhel10-vm2:~$ ls -dZ
unconfined_u:object_r:user_home_dir_t:s0 .
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep "/home/user1"
/home/user1(/.*)? all files system_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$ touch file5.dummy
user1@rhel10-vm2:~$ ls -Z file5.dummy
unconfined_u:object_r:user_home_t:s0 file5.dummy
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep -E "/home" | grep -E "user_home_t"
/home/[^/]+/.+ all files unconfined_u:object_r:user_home_t:s0
user1@rhel10-vm2:~$
If I applied the kernel logic (which I described above), it looks clear to me that there were a type transition involved during the creation of file5.dummy.
Back to file4.dummy: I simulated a relabeling action with restorecon -nv and I could see that my custom context rule will be applied:
user1@rhel10-vm2:~$ sudo restorecon -nv /home/user1/file4.dummy
[sudo] password for user1:
Would relabel /home/user1/file4.dummy from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$
#LessonLearned labels assigned to new objects are not submitted to SELinux binary policy or file context database rules; They submit to the kernel logic (type inheritance plus type transition policy).
#LessonLearned Custom file context rules are applied after a restorecon is executed.
matchpathcon is a straightforward command to find out SELinux file context database’s view on what the label of a directory path should be. I applied it to both file4.dummy and /home/user1/:
user1@rhel10-vm2:~$ matchpathcon -V /home/user1/file4.dummy
/home/user1/file4.dummy has context unconfined_u:object_r:user_home_t:s0, should be system_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ matchpathcon -V /home/user1/
/home/user1 has context unconfined_u:object_r:user_home_dir_t:s0, should be system_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$
I assume that applying the context change command chcon to the home directory will make future-created files there take the same label as the parent directory. I tried it:
user1@rhel10-vm2:~$ sudo restorecon -v /home/user1/
[sudo] password for user1:
restorecon: Could not set context for /home/user1: Permission denied
user1@rhel10-vm2:~$ sudo restorecon -v /home/user1/file4.dummy
Relabeled /home/user1/file4.dummy from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:usb_device_t:s0
user1@rhel10-vm2:~$
I was able to change the label for file4.dummy but not for its parent directory /home/user1/. Why? #QA
files with no rule for the label#
I found a situation where a newly created file receives a label. But when I run matchpathcon against it, the system tells me another story:
user1@rhel10-vm2:~$ ls -Zd /tmp
system_u:object_r:tmp_t:s0 /tmp
user1@rhel10-vm2:~$ ls -Zd /tmp/
system_u:object_r:tmp_t:s0 /tmp/
user1@rhel10-vm2:~$ sudo touch /tmp/selinux-drill
[sudo] password for user1:
user1@rhel10-vm2:~$ ls -Z /tmp/selinux-drill
unconfined_u:object_r:user_tmp_t:s0 /tmp/selinux-drill
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ chcon -t ssh_home_t /tmp/selinux-drill
chcon: failed to change context of '/tmp/selinux-drill' to ‘unconfined_u:object_r:ssh_home_t:s0’: Operation not permitted
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo chcon -t ssh_home_t /tmp/selinux-drill
user1@rhel10-vm2:~$ ls -Z /tmp/selinux-drill
unconfined_u:object_r:ssh_home_t:s0 /tmp/selinux-drill
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ matchpathcon -V /tmp/selinux-drill
/tmp/selinux-drill has context unconfined_u:object_r:ssh_home_t:s0, should be <<none>>
user1@rhel10-vm2:~$
It seems that selinux-drill would receive no label after executing restorecon, which is likely due to the absence of an entry in the SELinux context database that matches the exact file or a wildcard under the /tmp/ directory. I learned that it is a correct design case to have new files that are assigned no label. In fact, I looked for the file context rules that could match /tmp/ and I found something interesting: There was a rule that maps all subdirectories of /tmp/ to the None label:
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage fcontext -l | grep -E "^/tmp/"
/tmp/.* all files <<None>>
/tmp/\.ICE-unix(/.*)? all files system_u:object_r:user_tmp_t:s0
/tmp/\.X0-lock regular file system_u:object_r:user_tmp_t:s0
/tmp/\.X11-unix(/.*)? all files system_u:object_r:user_tmp_t:s0
/tmp/\.font-unix(/.*)? all files system_u:object_r:user_fonts_t:s0
/tmp/\.journal all files <<None>>
/tmp/brltty\.log.* regular file system_u:object_r:brltty_log_t:s0
/tmp/fdouser regular file system_u:object_r:fdo_tmp_t:s0
/tmp/gconfd-[^/]+ directory unconfined_u:object_r:user_tmp_t:s0
/tmp/gconfd-[^/]+/.* regular file unconfined_u:object_r:gconf_tmp_t:s0
/tmp/hsperfdata_root all files system_u:object_r:user_tmp_t:s0
/tmp/lost\+found directory system_u:object_r:lost_found_t:s0
/tmp/lost\+found/.* all files <<None>>
/tmp/passt\.pcap all files system_u:object_r:passt_log_t:s0
/tmp/pasta\.pcap all files system_u:object_r:pasta_log_t:s0
user1@rhel10-vm2:~$
This finding reinforces my assumption; The system did not find an exact matching rule for /tmp/selinux-drill. The second best-matching rule seems to map all subdirectories of /tmp/ to None, which explains why restorecon warned about reverting the label of the file to None.
I went ahead with restorecon but the system refused to give a blank label to the file:
user1@rhel10-vm2:~$ sudo restorecon -nv /tmp/selinux-drill
Warning no default label for /tmp/selinux-drill
user1@rhel10-vm2:~$ sudo restorecon -v /tmp/selinux-drill
Warning no default label for /tmp/selinux-drill
user1@rhel10-vm2:~$ ls -Z /tmp/selinux-drill
unconfined_u:object_r:ssh_home_t:s0 /tmp/selinux-drill
user1@rhel10-vm2:~$
So I created a custom file context rule to map the directory tree to a type:
user1@rhel10-vm2:~$ sudo semanage fcontext -a -t user_home_t "/tmp/selinux-drill(.*)?"
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ restorecon -nv /tmp/selinux-drill
Would relabel /tmp/selinux-drill from unconfined_u:object_r:ssh_home_t:s0 to unconfined_u:object_r:user_home_t:s0
user1@rhel10-vm2:~$
I still do not know which mechanism was responsible to allocate the user, role and level values. #QA
SELinux booleans#
Booleans are a mechanism to activate and deactivated SELinux policy rules without having to create or delete them. A boolean can have one of two values: on, off. All SELinux booleans are stored in the /sys/fs/selinux/booleans directory.
user1@rhel10-vm2:~$ ls /sys/fs/selinux/booleans/
abrt_anon_write mysql_connect_any
abrt_handle_event mysql_connect_http
abrt_upload_watch_anon_write named_tcp_bind_http_port
auditadm_exec_content named_write_master_zones
authlogin_nsswitch_use_ldap neutron_can_network
authlogin_radius nfsd_anon_write
authlogin_yubikey nfs_export_all_ro
cdrecord_read_content nfs_export_all_rw
<---- output omitted ---->
The number of boolean files under /sys/fs/selinux/booleans matches the number of booleans displayed with getsebool -a:
user1@rhel10-vm2:~$ ls /sys/fs/selinux/booleans | wc -l
324
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ getsebool -a | wc -l
324
user1@rhel10-vm2:~$
The booleans and their state can be displayed with sestatus -b:
user01@rhel10-vm5:~$ sestatus -b
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
Policy booleans:
abrt_anon_write off
abrt_handle_event on
abrt_upload_watch_anon_write on
auditadm_exec_content on
authlogin_nsswitch_use_ldap off
authlogin_radius off
authlogin_yubikey off
cdrecord_read_content off
<---- output omitted ---->
getsebool -a lists all rules that use booleans. setsebool is used to flip a boolean.
user1@rhel10-vm2:~$ getsebool
usage: getsebool -a or getsebool boolean...
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ getsebool -a | wc -l
324
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ getsebool -a | more
abrt_anon_write --> off
abrt_handle_event --> on
abrt_upload_watch_anon_write --> on
auditadm_exec_content --> on
authlogin_nsswitch_use_ldap --> off
authlogin_radius --> off
authlogin_yubikey --> off
cdrecord_read_content --> off
cluster_can_network_connect --> off
cluster_manage_all_files --> off
cluster_use_execmem --> off
colord_use_nfs --> off
condor_tcp_network_connect --> off
container_connect_any --> off
container_manage_cgroup --> off
container_read_certs --> off
container_use_cephfs --> off
container_use_devices --> off
container_use_dri_devices --> on
container_use_ecryptfs --> off
container_use_xserver_devices --> off
container_user_exec_content --> on
cron_can_relabel --> off
cron_system_cronjob_use_shares --> off
cron_userdomain_transition --> on
cups_execmem --> off
daemons_dontaudit_scheduling --> on
daemons_dump_core --> off
daemons_enable_cluster_mode --> off
daemons_use_tcp_wrapper --> off
daemons_use_tty --> off
dbadm_exec_content --> on
dbadm_manage_user_files --> off
dbadm_read_user_files --> off
deny_bluetooth --> off
deny_execmem --> off
deny_ptrace --> off
dhcpc_exec_iptables --> off
dhcpd_use_ldap --> off
dnsmasq_use_ipset --> off
domain_can_mmap_files --> off
domain_can_write_kmsg --> off
--More--
<---- output omitted ---->
Looking for the boolean value of a rule that involves a particular directory. I tried to grep my way into that:
user1@rhel10-vm2:~$ getsebool -a | grep home
git_cgi_enable_homedirs --> off
git_system_enable_homedirs --> off
httpd_enable_homedirs --> off
mpd_enable_homedirs --> off
samba_create_home_dirs --> off
samba_enable_home_dirs --> off
sanlock_enable_home_dirs --> off
spamd_enable_home_dirs --> on
ssh_chroot_rw_homedirs --> off
tftp_home_dir --> off
use_ecryptfs_home_dirs --> off
use_fusefs_home_dirs --> off
use_nfs_home_dirs --> off
use_samba_home_dirs --> off
xdm_write_home --> off
user1@rhel10-vm2:~$
Then I tried with protocol names:
user1@rhel10-vm2:~$ getsebool -a | grep snmp
squid_bind_snmp_port --> off
user1@rhel10-vm2:~$ getsebool -a | grep smtp
user1@rhel10-vm2:~$ getsebool -a | grep isis
user1@rhel10-vm2:~$ getsebool -a | grep sql
mysql_connect_any --> off
mysql_connect_http --> off
postgresql_can_rsync --> off
postgresql_selinux_transmit_client_label --> off
postgresql_selinux_unconfined_dbadm --> on
postgresql_selinux_users_ddl --> on
selinuxuser_mysql_connect_enabled --> off
selinuxuser_postgresql_connect_enabled --> off
user1@rhel10-vm2:~$
Another method to list booleans including the current state and the default value of the boolean:
user1@rhel10-vm2:/$ semanage boolean -l
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sudo semanage boolean -l
[sudo] password for user1:
SELinux boolean State Default Description
abrt_anon_write (off , off) Allow abrt to anon write
abrt_handle_event (on , on) Allow abrt to handle event
abrt_upload_watch_anon_write (on , on) Allow abrt to upload watch anon write
auditadm_exec_content (on , on) Allow auditadm to exec content
authlogin_nsswitch_use_ldap (off , off) Allow authlogin to nsswitch use ldap
authlogin_radius (off , off) Allow authlogin to radius
authlogin_yubikey (off , off) Allow authlogin to yubikey
cdrecord_read_content (off , off) Allow cdrecord to read content
cluster_can_network_connect (off , off) Allow cluster to can network connect
cluster_manage_all_files (off , off) Allow cluster to manage all files
cluster_use_execmem (off , off) Allow cluster to use execmem
colord_use_nfs (off , off) Allow colord to use nfs
<---- output omitted ---->
If the name of a boolean is known, getsebool can be executed on it without the -a option:
user1@rhel10-vm2:/tmp$ getsebool -a | grep 'nfs_export_all_rw'
nfs_export_all_rw --> on
user1@rhel10-vm2:/tmp$ sudo setsebool nfs_export_all_rw off
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ getsebool nfs_export_all_rw
nfs_export_all_rw --> off
user1@rhel10-vm2:/tmp$
flipping a boolean and observing system behavior#
user1@rhel10-vm2:~$ setsebool httpd_can_network_connect on
Could not change active booleans. Please try as root: Permission denied
user1@rhel10-vm2:~$ sudo setsebool httpd_can_network_connect on
[sudo] password for user1:
user1@rhel10-vm2:~$ getsebool -a | grep httpd_can_network_connect
httpd_can_network_connect --> on
httpd_can_network_connect_cobbler --> off
httpd_can_network_connect_db --> off
user1@rhel10-vm2:~$
After a reboot, the boolean is reset to its previous value. So the effect of setsebool is transient.
user1@rhel10-vm2:~$ uptime
11:27:02 up 4 min, 2 users, load average: 0.00, 0.00, 0.00
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ getsebool -a | grep httpd_can_network_connect
httpd_can_network_connect --> off
httpd_can_network_connect_cobbler --> off
httpd_can_network_connect_db --> off
user1@rhel10-vm2:~$
To make the change permanent: setsebool -P as explained in the man page:
setsebool(8) SELinux Command Line documentation setsebool(8)
NAME
setsebool - set SELinux boolean value
SYNOPSIS
setsebool [ -PNV ] boolean value | bool1=val1 bool2=val2 ...
DESCRIPTION
setsebool sets the current state of a particular SELinux boolean or a list of booleans to a given value. The value may be 1 or true or on to enable the boolean, or 0 or false or
off to disable it.
Without the -P option, only the current boolean value is affected; the boot-time default settings are not changed.
If the -P option is given, all pending values are written to the policy file on disk. So they will be persistent across reboots.
<---- output omitted ---->
user1@rhel10-vm2:~$ sudo setsebool -P httpd_can_network_connect on
[sudo] password for user1:
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo systemctl reboot
user1@rhel10-vm2:~$ getsebool -a | grep "httpd_can_network_connect "
httpd_can_network_connect --> on
user1@rhel10-vm2:~$ uptime
11:31:40 up 1 min, 2 users, load average: 0.00, 0.00, 0.00
user1@rhel10-vm2:~$
SELinux context for ports#
Every object in Linux has a SELinux label, even network ports.
Creating rules that map port numbers to protocols and SELinux types takes place with semanage port. On a successful execution of the command, the result is pushed to the binary policy in the kernel.
The output of semanage port -l lists for each service the ports it is allowed to bind to.
user1@rhel10-vm2:~$ semanage port -l
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage port -l
[sudo] password for user1:
SELinux Port Type Proto Port Number
afs3_callback_port_t tcp 7001
afs3_callback_port_t udp 7001
afs_bos_port_t udp 7007
afs_fs_port_t tcp 2040
afs_fs_port_t udp 7000, 7005
<---- output omitted ---->
In the output, each entry is structured in three columns:
- in the first column, the SELInux port type seems to be written in the form of ServiceName_port_t,
- in the second column, Proto, the value depicts the transport protocol,
- the last column, Port Number, depicts the port(s) to which the service is allowed to bind.
A single SELinux Port Type might be displayed more than once with different port numbers.
For instance, the tftp service and the time service are allowed to bound to UDP port 69 and to UDP and TCP ports 37 respectively.
user1@rhel10-vm2:~$ sudo semanage port -l | more
SELinux Port Type Proto Port Number
<---- output omitted ---->
time_port_t tcp 37
time_port_t udp 37
<---- output omitted ---->
tftp_port_t udp 69
<---- output omitted ---->
user1@rhel10-vm2:~$
testing port labels#
I installed Apache web server software. I looked for ports to which httpd is bound, using the search word http_port. The reason I chose that search word is because I assumed that all SELinux port types were written in the same form, namely ServiceName_port
user1@rhel10-vm2:~$ sudo semanage port -l | grep http_port
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
http_port_t udp 80, 443
pegasus_http_port_t tcp 5988
user1@rhel10-vm2:~$
So assumed that what I saw was the complete list of ports to which httpd can be bound. I then modified, in the httpd configuration file, the port number on which the httpd service listens, from its default to a port that did not appear in the output of my command above. I chose port 8123:
user1@rhel10-vm2:~$ grep -i listen /etc/httpd/conf/httpd.conf
# Listen: Allows you to bind Apache to specific IP addresses and/or
# Change this to Listen on a specific IP address, but note that if
#Listen 12.34.56.78:80
Listen 8123
user1@rhel10-vm2:~$
Then I started the httpd service. I assumed that httpd would not start, because I asked it to listen on a network port that is not bound to http_port_t. It did start. So I reformulated my search pattern in semanage port -l to http instead of http_; I found out that I unluckily chose a port number that was already allowed by the SELinux policy:
user1@rhel10-vm2:~$ sudo semanage port -l | grep http_
[sudo] password for user1:
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
http_cache_port_t udp 3130
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
http_port_t udp 80, 443
pegasus_http_port_t tcp 5988
user1@rhel10-vm2:~$
So I stopped httpd and changed the listening port to 8888 which does not appear in the allowed bound ports with httpd.
user1@rhel10-vm2:~$ sudo semanage port -l | grep -w 8123
[sudo] password for user1:
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo semanage port -l | grep -w 8888
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ grep -i listen /etc/httpd/conf/httpd.conf
# Listen: Allows you to bind Apache to specific IP addresses and/or
# Change this to Listen on a specific IP address, but note that if
#Listen 12.34.56.78:80
Listen 8888
user1@rhel10-vm2:~$
I initiated a restart of httpd again: It did not start this time and threw an error instead.
Oct 11 15:49:04 rhel10-vm2 systemd[1]: httpd.service: Failed with result 'exit-code'.
░░ Subject: Unit failed
░░ Defined-By: systemd
░░ Support: https://access.redhat.com/support
░░
░░ The unit httpd.service has entered the 'failed' state with result 'exit-code'.
Oct 11 15:49:04 rhel10-vm2 systemd[1]: Failed to start httpd.service - The Apache HTTP Server.
░░ Subject: A start job for unit httpd.service has failed
I called out ausearch and found the AVC denial related to httpd:
user1@rhel10-vm2:~$ sudo ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -ts recent
----
time->Sun Oct 11 15:49:04 2026
type=PROCTITLE msg=audit(1791726544.522:163): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1791726544.522:163): arch=c000003e syscall=49 success=no exit=-13 a0=4 a1=55d8fab70480 a2=1c a3=7ffd032d53dc items=0 ppid=1 pid=2120 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1791726544.522:163): avc: denied { name_bind } for pid=2120 comm="httpd" src=8888 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
----
time->Sun Oct 11 15:49:04 2026
type=PROCTITLE msg=audit(1791726544.522:164): proctitle=2F7573722F7362696E2F6874747064002D44464F524547524F554E44
type=SYSCALL msg=audit(1791726544.522:164): arch=c000003e syscall=49 success=no exit=-13 a0=3 a1=55d8fab703c0 a2=10 a3=7ffd032d53dc items=0 ppid=1 pid=2120 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm="httpd" exe="/usr/sbin/httpd" subj=system_u:system_r:httpd_t:s0 key=(null)
type=AVC msg=audit(1791726544.522:164): avc: denied { name_bind } for pid=2120 comm="httpd" src=8888 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
user1@rhel10-vm2:~$
The audit log shows the AVC message (type=AVC and avc:) which is a denial (denied) of the access of type name_bind, the process ID (pid=2120), the command (*comm=“httpd”), the source port (src=8888), the source context and the target context, the target class (tclass=tcp_socket) and the enforcing policy of SELinux (permissive=0).
#LessonLearned When searching for ports that are bound with a particular service, use semanage port -l | grep -w instead of searching for a string that contains the port number.
Now I want to add the port to the SELinux binary policy with semanage:
user1@rhel10-vm2:/$ sudo semanage port -a -t http_port_t -p tcp 8888
user1@rhel10-vm2:/$
And try to start the httpd service again. This time it did start. That was expected because I added port 8888 with semanage port and the configuration file of httpd instructs the server to listen on that port:
user1@rhel10-vm2:/$ sudo systemctl start httpd.service
user1@rhel10-vm2:/$ systemctl status httpd.service
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
Active: active (running) since Mon 2026-10-12 19:12:55 CEST; 7s ago
Invocation: af6c4c07374b43778f54b3dff5de3ef7
Docs: man:httpd.service(8)
Main PID: 2892 (httpd)
Status: "Started, listening on: port 8888"
Tasks: 177 (limit: 10680)
Memory: 13.2M (peak: 13.6M)
CPU: 53ms
CGroup: /system.slice/httpd.service
├─2892 /usr/sbin/httpd -DFOREGROUND
├─2893 /usr/sbin/httpd -DFOREGROUND
├─2894 /usr/sbin/httpd -DFOREGROUND
├─2896 /usr/sbin/httpd -DFOREGROUND
└─2911 /usr/sbin/httpd -DFOREGROUND
Oct 12 19:12:50 rhel10-vm2 systemd[1]: Starting httpd.service - The Apache HTTP Server...
Oct 12 19:12:50 rhel10-vm2 (httpd)[2892]: httpd.service: Referenced but unset environment var>
Oct 12 19:12:55 rhel10-vm2 httpd[2892]: AH00558: httpd: Could not reliably determine the serv>
Oct 12 19:12:55 rhel10-vm2 httpd[2892]: Server configured, listening on: port 8888
Oct 12 19:12:55 rhel10-vm2 systemd[1]: Started httpd.service - The Apache HTTP Server.
To delete the rule:
user1@rhel10-vm2:/$ sudo semanage port -d -t http_port_t -p tcp 8888
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ systemctl status httpd.service
● httpd.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
Active: active (running) since Mon 2026-10-12 19:12:55 CEST; 3min 32s ago
Invocation: af6c4c07374b43778f54b3dff5de3ef7
Docs: man:httpd.service(8)
Main PID: 2892 (httpd)
Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec: >
Tasks: 177 (limit: 10680)
Memory: 13.2M (peak: 13.6M)
CPU: 168ms
CGroup: /system.slice/httpd.service
├─2892 /usr/sbin/httpd -DFOREGROUND
├─2893 /usr/sbin/httpd -DFOREGROUND
├─2894 /usr/sbin/httpd -DFOREGROUND
├─2896 /usr/sbin/httpd -DFOREGROUND
└─2911 /usr/sbin/httpd -DFOREGROUND
Oct 12 19:12:50 rhel10-vm2 systemd[1]: Starting httpd.service - The Apache HTTP Server...
Oct 12 19:12:50 rhel10-vm2 (httpd)[2892]: httpd.service: Referenced but unset environment var>
Oct 12 19:12:55 rhel10-vm2 httpd[2892]: AH00558: httpd: Could not reliably determine the serv>
Oct 12 19:12:55 rhel10-vm2 httpd[2892]: Server configured, listening on: port 8888
Oct 12 19:12:55 rhel10-vm2 systemd[1]: Started httpd.service - The Apache HTTP Server.
The service seems active but idle. I restart it.
user1@rhel10-vm2:/$ sudo systemctl restart httpd.service
Job for httpd.service failed because the control process exited with error code.
See "systemctl status httpd.service" and "journalctl -xeu httpd.service" for details.
user1@rhel10-vm2:/$
It did not. That is what I expected.
domain transition#
For the RHCSA I learned the following: for a process to execute a binary, it typically transitions into a new domain. This is called domain transition. A couple of conditions must be met for that to happen:
- there should be a policy rule that allows access to the target type (the context type of the file) from a domain X,
- there should be a policy rule that allows the process to transition from its original domain into the new domain X,
- the file should be allowed to constitute an entrypoint into the new domain X.
I do not need more details on the mechanism for the moment, since they are out of the RHCSA exam scope.
file operations and label#
standard file copy to another directory path#
user1@rhel10-vm2:/$ cd /tmp
user1@rhel10-vm2:/tmp$ ls -dZ
system_u:object_r:tmp_t:s0 .
user1@rhel10-vm2:/tmp$ touch sefile2
user1@rhel10-vm2:/tmp$ ls -Z sefile2
unconfined_u:object_r:user_tmp_t:s0 sefile2
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ cp sefile2 /etc/default/
cp: cannot create regular file '/etc/default/sefile2': Permission denied
user1@rhel10-vm2:/tmp$ sudo cp sefile2 /etc/default/
[sudo] password for user1:
user1@rhel10-vm2:/tmp$ ls -Z /etc/default/sefile2
unconfined_u:object_r:etc_t:s0 /etc/default/sefile2
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ ls -dZ /etc/default/
system_u:object_r:etc_t:s0 /etc/default/
user1@rhel10-vm2:/tmp$
The file copy took the label of the parent directory.
file copy to another directory path with attribute preservation#
If I do not specify which attribute to preserve, no preservation of the context happens on the copy of the file:
user1@rhel10-vm2:/tmp$ cp --preserve tmp1.dummy /etc/default/
cp: cannot create regular file '/etc/default/tmp1.dummy': Permission denied
user1@rhel10-vm2:/tmp$ sudo cp --preserve tmp1.dummy /etc/default/
[sudo] password for user1:
user1@rhel10-vm2:/tmp$ ls /etc/default/tmp1.dummy
/etc/default/tmp1.dummy
user1@rhel10-vm2:/tmp$ ls -Z /etc/default/tmp1.dummy
unconfined_u:object_r:etc_t:s0 /etc/default/tmp1.dummy
user1@rhel10-vm2:/tmp$
When I specify the atrribute in the --preserve option, the attribute in this case being context, the context is preserved during the copy:
user1@rhel10-vm2:/tmp$ sudo rm /etc/default/tmp1.dummy
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ sudo cp --preserve=context tmp1.dummy /etc/default/
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ ls -Z /etc/default/tmp1.dummy
unconfined_u:object_r:user_tmp_t:s0 /etc/default/tmp1.dummy
user1@rhel10-vm2:/tmp$
If I moved the file instead of copying it:
user1@rhel10-vm2:/tmp$ ls -Z tmp1.dummy
unconfined_u:object_r:user_tmp_t:s0 tmp1.dummy
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ sudo rm /etc/default/tmp1.dummy
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ sudo mv tmp1.dummy /etc/default/
user1@rhel10-vm2:/tmp$
user1@rhel10-vm2:/tmp$ sudo ls -Z /etc/default/tmp1.dummy
unconfined_u:object_r:user_tmp_t:s0 /etc/default/tmp1.dummy
user1@rhel10-vm2:/tmp$
the file kept its label.
create a mapping between a Linux user and a SELinux user#
This is done with semanage login. Example:
- currently, user1 and every non-root Linux user are represented by default:
user1@rhel10-vm2:/$ semanage login -l
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sudo semanage login -l
[sudo] password for user1:
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
user1@rhel10-vm2:/$
I create a Linux user to SELinux user mapping for user1:
user1@rhel10-vm2:/$ seinfo -u
Users: 8
guest_u
root
staff_u
sysadm_u
system_u
unconfined_u
user_u
xguest_u
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sudo semanage login -a -s staff_u user1
user1@rhel10-vm2:/$
and the change takes effect immediately:
user1@rhel10-vm2:/$ sudo semanage login -l
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
user1 staff_u s0-s0:c0.c1023 *
user1@rhel10-vm2:/$
But I was not aware of the impact of such a change: I wanted to continue using semanage as before, but I could not:
user1@rhel10-vm2:/$ sudo semanage fcontext -l | grep "/tmp"
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sudo semanage fcontext -l
ValueError: SELinux policy is not managed or store cannot be accessed.
user1@rhel10-vm2:/$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ id
uid=1000(user1) gid=1000(user1) groups=1000(user1),10(wheel) context=staff_u:staff_r:staff_t:s0-s0:c0.c1023
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Memory protection checking: actual (secure)
Max kernel policy version: 33
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ seinfo -u
Users: 8
guest_u
root
staff_u
sysadm_u
system_u
unconfined_u
user_u
xguest_u
user1@rhel10-vm2:/$ ps -Z
LABEL PID TTY TIME CMD
staff_u:staff_r:staff_t:s0-s0:c0.c1023 1544 pts/0 00:00:00 bash
staff_u:staff_r:staff_t:s0-s0:c0.c1023 2273 pts/0 00:00:00 ps
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ sudo su -
Last login: Mon Jul 20 21:48:25 CEST 2026 on tty1
Last failed login: Sun Oct 4 10:52:05 CEST 2026 on pts/0
There were 2 failed login attempts since the last successful login.
-bash: /root/.bash_profile: Permission denied
root@rhel10-vm2:~# semanage login -C
usage: semanage login [-h] [-n] [-N] [-S STORE] [ --add -s SEUSER -r RANGE LOGIN | --delete LOGIN | --deleteall | --extract | --list -C | --modify -s SEUSER -r RANGE LOGIN ]
semanage login: error: one of the arguments -a/--add -d/--delete -m/--modify -l/--list -E/--extract -D/--deleteall is required
root@rhel10-vm2:~# semanage login -lC
ValueError: Cannot read policy store.
root@rhel10-vm2:~# sudo semanage login -lC
ValueError: SELinux policy is not managed or store cannot be accessed.
root@rhel10-vm2:~# whoami
root
root@rhel10-vm2:~# semanage login -l
ValueError: Cannot read policy store.
root@rhel10-vm2:~# ps -Z
LABEL PID TTY TIME CMD
staff_u:staff_r:staff_sudo_t:s0-s0:c0.c1023 2276 pts/1 00:00:00 sudo
staff_u:staff_r:staff_sudo_t:s0-s0:c0.c1023 2277 pts/1 00:00:00 su
staff_u:staff_r:staff_t:s0-s0:c0.c1023 2278 pts/1 00:00:00 bash
staff_u:staff_r:staff_t:s0-s0:c0.c1023 2344 pts/1 00:00:00 ps
root@rhel10-vm2:~#
After I changed the SELinux user of user1 from the default unconfined_u to staff_u, many commands were not possible anymore. I exited the root shell and tried to su - root again but failed, although I repeatedly entered the correct root password. The user shell appeared to have entered the domain staff_t and it seems that this domain lacks some rules in the binary policy to access other SELinux label types. Not only that, but when I was in the root shell, it also got restricted, since it displayed staff_t as its domain instead of the unconfined_t default domain.
I restarted the machine, hoping that the login process reestablishes the default items of the label of user1. At login, I got this error:
Could not chdir to home directory /home/user1: Permission denied
-bash: /home/user1/.bash_profile: Permission denied
user1@rhel10-vm2:/$
user1@rhel10-vm2:/$ ps -Z
LABEL PID TTY TIME CMD
staff_u:staff_r:staff_t:s0-s0:c0.c1023 1518 pts/0 00:00:00 bash
staff_u:staff_r:staff_t:s0-s0:c0.c1023 1986 pts/0 00:00:00 ps
user1@rhel10-vm2:/$
Nothing changed. The rule I created seems to have been integrated into the binary policy. Also, I did not write the password of the root account. I am going to initiate a root password recovery then enter root shell then modify user1’s SELinux user again. At the boot menu, I typed ’e’, added rd.break then pressed CTRL-x. The boot is stuck at Booting a command list. Thankfully I have a snapshot.
#LessonLearned Do a white run of the root password recovery procedure before considering a VM as a lab candidate.
Reverting to the VM snapshot restored the previous label:
You can learn more about how to register your system
using rhc at https://red.ht/registration
Last login: Tue Oct 13 05:01:00 2026
user1@rhel10-vm2:~$ id
uid=1000(user1) gid=1000(user1) groups=1000(user1),10(wheel) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ ps -Z | grep bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1571 pts/0 00:00:00 bash
user1@rhel10-vm2:~$ sudo semanage login -l
[sudo] password for user1:
Login Name SELinux User MLS/MCS Range Service
__default__ unconfined_u s0-s0:c0.c1023 *
root unconfined_u s0-s0:c0.c1023 *
user1@rhel10-vm2:~$
This time I am going to create user2 as a playground. If it breaks, I would still have user1.
user1@rhel10-vm2:~$ grep user /etc/passwd
yggdrasil:x:999:999:yggdrasil system user:/var/lib/yggdrasil:/usr/sbin/nologin
yggdrasil-worker:x:998:998:yggdrasil worker user:/:/usr/sbin/nologin
clevis:x:992:992:Clevis Decryption Framework unprivileged user:/var/cache/clevis:/usr/sbin/nologin
chrony:x:990:990:chrony system user:/var/lib/chrony:/sbin/nologin
user1:x:1000:1000:user1:/home/user1:/bin/bash
user1@rhel10-vm2:~$
user1@rhel10-vm2:~$ sudo useradd user2
[sudo] password for user1:
user1@rhel10-vm2:~$ sudo passwd user2
New password:
BAD PASSWORD: The password is shorter than 8 characters
Retype new password:
passwd: password updated successfully
user1@rhel10-vm2:~$ su - user2
Password:
user2@rhel10-vm2:~$ pwd
/home/user2
user2@rhel10-vm2:~$
user2@rhel10-vm2:~$ ps -Z
LABEL PID TTY TIME CMD
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1685 pts/0 00:00:00 bash
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 1714 pts/0 00:00:00 ps
user2@rhel10-vm2:~$
I modified the mapping for user2:
user1@rhel10-vm2:~$ sudo semanage login -lC
user1@rhel10-vm2:~$ sudo semanage login -a -s user_u user2
user1@rhel10-vm2:~$ sudo semanage login -lC
Login Name SELinux User MLS/MCS Range Service
user2 user_u s0 *
user1@rhel10-vm2:~$
deactivating SELinux#
This takes place at the kernel level, by adding an argument for SELinux to it. I demonstrated an example in my Linux Grub2 post. Once added and the machine is rebooted, sestatus confirms that SELinux is disabled:
user1@rhel10-vm2:~$ sestatus
SELinux status: disabled
user1@rhel10-vm2:~$
To reactivate SELinux, I remove the argument. I demonstrated how to do this in my Linux Grub2 post.