Linux Access-Control Layers
One syscall, three AND-composed gates. Left: a denied read. Right: an allowed read. Hover any box for detail.
flowchart TD
PROC["Process: httpd, UID=apache
AppArmor / SELinux label: httpd_t"]:::proc PROC -- "read /etc/shadow" --> DAC1 PROC -- "read /var/www/html/index.html" --> DAC2 DAC1["DAC check
/etc/shadow is 0640 root:shadow
apache is not owner/group, no other read"]:::dac CAP1["Capability check
holds CAP_DAC_READ_SEARCH? No"]:::cap MAC1["MAC check
may httpd_t read shadow_t? Policy: NO"]:::mac DENY["Result: EACCES
read denied, logged in audit.log"]:::deny DAC2["DAC check
index.html is 0644, world-readable
DAC: ALLOW"]:::dac CAP2["Capability check
not needed, DAC already permits"]:::cap MAC2["MAC check
may httpd_t read httpd_sys_content_t? Yes"]:::mac ALLOW["Result: file contents returned
read succeeds"]:::allow DAC1 -- "DENY" --> DENY DAC1 -. "if it had passed" .-> CAP1 -.-> MAC1 DAC2 --> CAP2 --> MAC2 -- "ALLOW" --> ALLOW classDef proc fill:#ffffff,stroke:#455a64,stroke-width:2px,color:#263238,font-size:14px classDef dac fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d3a73,font-size:13px classDef cap fill:#00acc1,stroke:#00707d,stroke-width:2px,color:#ffffff,font-size:13px classDef mac fill:#1565c0,stroke:#0d3a73,stroke-width:2px,color:#ffffff,font-size:13px classDef deny fill:#d32f2f,stroke:#8e0000,stroke-width:2px,color:#ffffff,font-size:14px classDef allow fill:#4caf50,stroke:#2e7031,stroke-width:2px,color:#ffffff,font-size:14px linkStyle default stroke:#607d8b,stroke-width:2px,font-size:12px
AppArmor / SELinux label: httpd_t"]:::proc PROC -- "read /etc/shadow" --> DAC1 PROC -- "read /var/www/html/index.html" --> DAC2 DAC1["DAC check
/etc/shadow is 0640 root:shadow
apache is not owner/group, no other read"]:::dac CAP1["Capability check
holds CAP_DAC_READ_SEARCH? No"]:::cap MAC1["MAC check
may httpd_t read shadow_t? Policy: NO"]:::mac DENY["Result: EACCES
read denied, logged in audit.log"]:::deny DAC2["DAC check
index.html is 0644, world-readable
DAC: ALLOW"]:::dac CAP2["Capability check
not needed, DAC already permits"]:::cap MAC2["MAC check
may httpd_t read httpd_sys_content_t? Yes"]:::mac ALLOW["Result: file contents returned
read succeeds"]:::allow DAC1 -- "DENY" --> DENY DAC1 -. "if it had passed" .-> CAP1 -.-> MAC1 DAC2 --> CAP2 --> MAC2 -- "ALLOW" --> ALLOW classDef proc fill:#ffffff,stroke:#455a64,stroke-width:2px,color:#263238,font-size:14px classDef dac fill:#e3f2fd,stroke:#1565c0,stroke-width:2px,color:#0d3a73,font-size:13px classDef cap fill:#00acc1,stroke:#00707d,stroke-width:2px,color:#ffffff,font-size:13px classDef mac fill:#1565c0,stroke:#0d3a73,stroke-width:2px,color:#ffffff,font-size:13px classDef deny fill:#d32f2f,stroke:#8e0000,stroke-width:2px,color:#ffffff,font-size:14px classDef allow fill:#4caf50,stroke:#2e7031,stroke-width:2px,color:#ffffff,font-size:14px linkStyle default stroke:#607d8b,stroke-width:2px,font-size:12px
Even one DENY is enough; the checks are AND-composed.
DAC (file permissions), Linux capabilities, and MAC (AppArmor/SELinux) are
each consulted in turn. If DAC permits but MAC denies, MAC wins. The shadow
read fails at the very first gate; the index.html read clears all three.