LearnRHCSA (EX200)Security

SELinux File and Process Contexts

ls - a hands-on Linux lab on a real virtual machine.

Every file and process on RHEL carries a SELinux security context, four colon-separated fields user:role:type:level. The third field, the type ending in _t, is what the targeted policy actually checks: httpd_sys_content_t for web files, init_t for systemd, unconfined_t for an interactive shell. Read a file context with ls -Z, your own shell context with id -Z, and a process context with ps -Z -p PID. Reading the label is the first move in every SELinux denial. Serves EX200 security: list and identify SELinux file and process context.

You set up Apache to serve a page. The file is right there at /var/www/html/index.html. You can cat it, the permissions read -rw-r--r--, the owner is correct, the firewall is open. Every ordinary check passes. And still the browser gets a stony 403 Forbidden. Nothing in the file's mode or owner explains it. You are staring at a file that looks perfect and behaves like it is locked.

There is a second layer of access control on RHEL that never shows up in ls -l. It is called SELinux, and it decides what a running program is allowed to touch based on invisible labels attached to every file and every process. When Apache is denied a file it clearly owns, the label is almost always the reason. This lesson teaches you to read those labels: on a file, on a process, and on yourself. Reading the label is the first move in every SELinux problem you will ever face.

The black boxes below are a practice terminal: a safe sandbox that checks the one command each step teaches. The outputs are from a real RHEL 10 machine (AlmaLinux 10.2) running SELinux in Enforcing mode with the targeted policy. Some values, like the exact MCS level fields at the end of a context and the process IDs, are assigned per machine, so treat those as illustrative. What is stable everywhere is the SHAPE: a context is four colon-separated fields, -Z is the flag that reveals it, and the third field, the TYPE ending in _t, is the one the policy actually checks.

SELinux attaches a label to every file, directory, port, and process on the system. That label is called the security context, or just the context. It is written as four fields joined by colons:

user:role:type:level

Think of the context as an ID badge clipped to every object. The badge has four lines printed on it. Ordinary Linux permissions ask a yes-or-no question about the person holding the file: can this user read, write, execute? SELinux asks a different question about the badges: is a process wearing THIS badge allowed to touch a file wearing THAT badge? The policy is a giant rulebook of which badges may interact.

Of the four fields, one does almost all the work: the third field, the type. By convention every type name ends in _t. On a file it is called the file type; on a process it is called the domain, but it is the same third field. The targeted policy that ships with RHEL makes its allow-or-deny decision almost entirely by comparing the type on the process against the type on the file.

So when you read a context, your eye goes straight to the third field. A web file should carry httpd_sys_content_t. If it carries something else, Apache's domain is not allowed to read it, and you get that 403 no matter how perfect the ordinary permissions look. The first field (user) and second field (role) rarely change on RHEL and rarely cause trouble. The fourth field (level) is for multi-level security you will not touch on the exam. Learn to find the _t.

The tool you already know for listing files, ls, grows a new flag for SELinux: -Z. Add -Z and ls prints the security context of each file alongside it. This is how you answer the question at the heart of every web-server denial: what type is this file labeled with?

Before you run it, know what you are looking for: a four-field context, and specifically its third field. Read the context of the Apache index file:

ls -Z /var/www/html/index.html

prompt: [root@servera ~]# answer: ls -Z /var/www/html/index.html output: unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html hint: It is ls with the capital -Z flag, then the file path: ls -Z /var/www/html/index.html

Read the context left to right, splitting on the colons. unconfined_u is the SELinux user, first field. object_r is the role, second field, and object_r is the role every file carries. httpd_sys_content_t is the type, third field, and this is the one that matters: it is the type that Apache's domain is allowed to read, which is exactly why web content belongs here. s0 is the level, fourth field, the MCS sensitivity. The whole point of ls -Z is that third field. Seeing httpd_sys_content_t on a file under /var/www/html tells you the label is correct and SELinux is not the reason for a denial. Seeing anything else there tells you it is.

The s0 at the end, and the longer level strings you will see on processes and users, are assigned by the policy and can differ between machines and releases. Do not memorize them. The four-field shape user:role:type:level is what is stable, and within it the _t type is what you read every time.

You are a process too. Every command you type runs inside a shell, and that shell has a context just like a file does. The tool id, which you already use to see your user and group IDs, takes the same -Z flag to print the SELinux context of the current process instead. This answers a different question: what domain am I operating in right now?

Check your own context:

id -Z

prompt: [root@servera ~]# answer: id -Z output: unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 hint: It is id with the capital -Z flag and nothing else: id -Z

Your shell reports unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023. The third field is unconfined_t, and that word is the key. An unconfined domain is one the targeted policy deliberately leaves alone: it runs with normal Linux permissions and SELinux does not box it in. That is why an interactive admin login usually is not what SELinux blocks. The trouble lands on confined domains like Apache's httpd_t, which the policy fences tightly. The tail s0-s0:c0.c1023 is the MCS level range and varies per machine, so read the _t and move on. id -Z is how you confirm which domain your commands are running in.

Files have labels; so does every running program. To see the context of a process rather than a file, use ps with the -Z flag. Combine it with -p <pid> to inspect one specific process by its process ID. The natural target is PID 1, systemd, the very first process the kernel starts and the ancestor of everything else.

Inspect the context of PID 1:

ps -Z -p 1

prompt: [root@servera ~]# answer: ps -Z -p 1 output: system_u:system_r:init_t:s0 1 ? systemd hint: It is ps with the capital -Z flag, then -p to name one PID: ps -Z -p 1

PID 1, systemd, runs in the context system_u:system_r:init_t:s0. Look at the fields against the file you read earlier. The first field is system_u here, not unconfined_u: system daemons started at boot carry the system_u user. The second field is system_r, the role for system processes, where a file carried object_r. And the third field, the domain, is init_t, the type reserved for the init process that manages every service. This is the pattern to lock in: a file carries object_r in its role and a content type like httpd_sys_content_t; a process carries a _r role like system_r and a domain like init_t or httpd_t. Same four-field shape, read the same way, always with your eye on the _t.

The flag is a capital -Z, every time. A lowercase -z is a different, unrelated option on some tools and will not print contexts. If ls, ps, or id prints its normal output with no context field, check that you typed the capital Z. This is the single most common slip when people first learn to read contexts.

Scaffolding off. No command is printed this time. You have every piece you need.

Apache is serving 403 Forbidden on a page you can see is present at /var/www/html/index.html, with correct owner and permissions. You suspect the SELinux label. You need to print that file's security context so you can read its third field, the type, and confirm it reads httpd_sys_content_t. This is a file, not a process, so ps is wrong and id reports only yourself. Which single command, given the file path, prints its four-field context?

prompt: [root@servera ~]# answer: ls -Z /var/www/html/index.html output: unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html hint: The file lister with the capital context flag: ls -Z, then the path /var/www/html/index.html.

ls -Z /var/www/html/index.html prints unconfined_u:object_r:httpd_sys_content_t:s0 next to the path. The third field reads httpd_sys_content_t, the type Apache's domain is permitted to read, so on this file SELinux is labeled correctly and is not the cause. That is the whole diagnostic move: ls -Z the file, read the _t, compare it to what the service needs. When the type is wrong, the next lesson's restorecon and semanage fcontext are how you fix it. Reading the label always comes first.

You earned this cheat sheet. The first three rows are the forms you ran yourself; the last is the natural next step for listing every process at once:

The one thing to burn in for the exam: a context is user:role:type:level, and the type, the third field ending in _t, is what the policy checks. A file carries a content type like httpd_sys_content_t; a process carries a domain like init_t or the unconfined unconfined_t. When a service is denied a file it plainly owns, ls -Z the file and read that third field first.

The -Z flag is a family, but read the direction carefully. On ls, ps, and id the -Z you learned here SHOWS the context: it brings the label into view. On cp and mkdir the same -Z goes the other way and SETS the context on what you create, defaulting it to the target's type. Either way, learn the four-field shape once and you can read the label on anything.

The practice terminal walked you through reading a context wherever it lives. ls -Z on a file, id -Z on your own shell, and ps -Z -p 1 on a process, each one printing the same four-field user:role:type:level badge, each time with your eye landing on the third field, the _t type the policy actually checks. Every one of those you typed yourself.

Operation Lockdown, the security capstone, is where you run these against a real RHEL 10 machine. A full VM boots for you, running SELinux in Enforcing mode with real services to lock down. The mission hands you objectives that start exactly where this lesson ends: a service is denied a file, and you have to read the label to see why. One difference from this lesson: the mission shows no commands. You read the objective, recall the ls -Z and ps -Z and id -Z forms, and type them. That recall is what makes it stick on exam day.

Finish the other security lessons, then take on Operation Lockdown and read the labels on a live, locked-down machine.

Practice SELinux File and Process Contexts in a real Linux terminal at The Linux Camp. Progress is verified automatically as you type commands on the machine.