skip to content

A daemon on a RHEL host is refused access to a file that it owns and that is mode 0644, and `chmod 777` on the file changes nothing. Which access-control layer is denying it, and how does a process's domain and a file's type produce that decision?

level: middleimportance: must knowfreq 62%

answer

  1. two checks, one after the other
  2. mode bits already said yes
  3. labels, not ownership
  4. domain on the process, type on the file
  5. the xattr travels with the inode

basics

~20 s

SELinux is denying it. Traditional owner/mode checks run first and already passed, so loosening them cannot help. SELinux then compares the process's domain label with the file's type label against the loaded policy, and refuses because no rule allows that pair.

solid answer

~50 s

There are two checks in series. The classic discretionary check — owner, group, mode bits — runs first; since the file is 0644 and owned by the daemon, that check already succeeds, which is exactly why `chmod 777` accomplishes nothing. The second check is SELinux's mandatory one. Every process runs in a *domain* (a label such as `httpd_t`) and every file carries a *type* (such as `httpd_sys_content_t` or `default_t`), and the loaded policy is essentially a list of allow rules over those labels. If no rule permits `httpd_t` to read `default_t` files, the kernel refuses and writes an AVC denial naming the source context, the target context, the object class and the permission. The label is not derived from ownership — it lives in the file's `security.selinux` extended attribute, visible with `ls -Z`, while `ps -Z` shows the domain a process is running in. The fix is to correct the label or the policy, never to widen the mode bits.

code

bash · 9 lines
bash
# the DAC side says yes
ls -l /srv/www/index.html

# the MAC side is what is refusing: compare the two labels
ls -Z /srv/www/index.html
ps -eZ | grep -m1 httpd

# and the kernel's own record of the refusal
ausearch -m AVC -ts recent

go deeper

for a junior

Recognise the symptom: a permission error on a file the process owns, unchanged by chmod, on a RHEL-family host means look at SELinux. Know that ls -Z shows a file's label.

for a middle

Explain the two checks in series and the domain-versus-type comparison, name the four context fields, and say that the label lives in an extended attribute on the inode rather than being derived from ownership.

for a senior

Diagnose from the denial record itself — source context, target context, object class, permission — and decide whether the label or the policy is wrong, without reaching for chmod or setenforce as a shortcut.

for a principal

Frame mandatory access control as defence in depth that survives an application compromise, and set the rule that widening mode bits or unlabelling data is never an acceptable trade for a failing deployment.

## Two gates, in order A Linux file access passes through discretionary access control (DAC) first: the kernel compares the calling process's UID/GID against the file's owner, group and mode bits, plus any POSIX ACL. Only if that succeeds does the request reach the Linux Security Module hooks where SELinux makes its own decision. Mandatory access control (MAC) can therefore only ever *subtract* access — it never grants something the mode bits refused. This ordering explains the symptom completely. If a `chmod 777` makes no difference, the failing check was never the mode bits, because they were already passing. Recognising that is the whole point of the question: the candidate who reaches for `chmod` a second time has not understood that there are two independent gates. ## Domains, types and the security context SELinux attaches a *security context* to every subject and object. Written out, it has four fields: ``` system_u:object_r:httpd_sys_content_t:s0 user : role : type :level ``` Under the `targeted` policy that RHEL and Fedora ship, the field that carries almost all of the weight is the **type**. A type applied to a process is conventionally called a *domain*; a type applied to a file, socket or port is just a type. The user and role fields matter mainly for confined user logins, and the level field carries MCS categories used to keep otherwise-identical instances apart. The policy is a compiled set of rules of the shape "allow *source domain* *target type*:*object class* { *permissions* }". A read of a file by the web server is a question about `httpd_t` reading `httpd_sys_content_t:file`. If such a rule exists, the access proceeds; if not, it is denied. Nothing about the file's owner or mode enters into that decision. Inspect both sides with the `-Z` flag that the coreutils and procps tools carry: ```bash ls -Z /srv/www/index.html ps -eZ | grep httpd id -Z ``` ## Where a label comes from A file's type is stored on the inode, in the `security.selinux` extended attribute, so it travels with the file rather than with its path string. New files normally inherit a type from the directory they are created in, subject to the policy's transition rules — which is why content dropped into `/srv/www` by an unpacking script may end up as `default_t` or, if it came from a user's home directory with `mv`, keeps whatever it had there. A process's domain comes from an *entrypoint transition*: when a confined parent executes a binary whose file type is an entrypoint for a domain, the new process starts in that domain instead of inheriting the parent's. That is how a service launched by the init system ends up in its own confined domain rather than running unconfined. ## Reading the denial The kernel records the refusal as an AVC message in the audit log. The fields to read are `scontext` (who was asking), `tcontext` (what they asked about), `tclass` (the object class — file, dir, tcp_socket, and so on) and the permission in braces. Those four items usually tell you immediately whether the *file* is mislabelled or the *policy* genuinely does not cover what the application is trying to do. The application itself sees only `EACCES` and typically logs "permission denied", so its own logs will not mention SELinux at all. ## "Even root is confined" — with a caveat Mandatory access control is mandatory in the sense that the object's owner cannot relax it: a process running as UID 0 inside a confined domain is bound by the same rules as any other process in that domain, which is the containment value of the whole system. The honest caveat is that under the targeted policy an administrator's interactive shell normally runs as `unconfined_t`, a domain the policy allows almost everything, so root at a login shell is in practice not confined. Saying that nuance out loud is what distinguishes someone who has run the system from someone who has read a summary of it. ## Fixing it properly The fix depends on which side is wrong. If the file's type is wrong for where it lives, restore it from the policy's default-context database. If the type is right but the policy genuinely does not allow the access, look for an existing tunable before writing anything new. What you should never do is widen the mode bits, move the data somewhere with looser labelling, or switch the machine out of enforcing — all three trade a real containment boundary for a symptom that stops being visible.

  • Why can widening the mode bits never resolve an SELinux denial?
    Because the discretionary check runs first and the SELinux hook is only reached once it has passed. A denial from SELinux therefore proves the mode bits already allowed the access, so relaxing them further changes nothing. The two layers intersect: an access needs a yes from both, and neither can override the other.
  • If SELinux confines even root, why does an administrator at a root shell seem to be able to do anything on a RHEL box?
    Under the targeted policy, interactive administrator sessions run in `unconfined_t`, a domain the policy allows very broadly, so it is not that root bypasses SELinux — it is that root's shell is running in a domain with almost no restrictions. Confined system services get real domains and are genuinely bound by them. `id -Z` shows which domain your own shell is in.
  • Where does a service's domain come from — does the policy list process names?
    No. It comes from a transition on exec: the executable file carries an entrypoint type, and the policy says that when a given parent domain executes a file of that type, the new process runs in the associated domain. Labels on the binary drive it, which is why a service copied to an unlabelled path can end up running unconfined instead of in its own domain.

saying these in an interview costs you the question

  • Just chmod 777 the file and the denial goes away
  • SELinux decides based on file owner and group
  • The file's SELinux type is derived from its path at access time
  • Root always bypasses SELinux
  • An SELinux denial appears in the application's own log as an SELinux error

context