Loading content...
Type Enforcement (TE) is the core access control mechanism of SELinux. While contexts provide identity and policy provides rules, Type Enforcement is where access decisions actually happen—where every file open, every network connection, every inter-process communication is evaluated against security policy.
The principle is deceptively simple:
Every access is denied unless explicitly allowed by a TE rule.
This is the "default deny" stance that makes SELinux so effective. Unlike traditional discretionary access control where access is permitted unless denied, SELinux starts from complete restriction. Every access—even ones that seem obviously correct—must be authorized by policy.
Understanding Type Enforcement is understanding how SELinux thinks about security. This page explores TE from conceptual foundations through practical analysis of real enforcement decisions.
By the end of this page, you will understand how Type Enforcement makes access decisions, the structure and semantics of TE rules, how to read and interpret access denials, how the Access Vector Cache optimizes enforcement, and how to analyze TE rules to understand system security.
Type Enforcement operates on a simple model with three components:
Subjects are active entities—processes that perform actions. Each subject has a type (its domain).
Objects are passive entities—resources that subjects act upon. Files, directories, sockets, ports, memory regions, kernel objects. Each object has a type.
Actions are operations subjects perform on objects—read, write, execute, connect, signal, etc. Actions are grouped by object class (file actions, socket actions, process actions).
When a subject attempts an action on an object:
allow rule permitting this combination?This happens for every access, thousands of times per second.
Type Enforcement Decision Making ACCESS REQUEST: httpd process (pid 1234) wants to read /var/www/html/index.html Step 1: Extract subject type Process context: system_u:system_r:httpd_t:s0 Subject type: httpd_t Step 2: Extract object type File context: system_u:object_r:httpd_content_t:s0 Object type: httpd_content_t Step 3: Identify object class Resource: regular file Object class: file Step 4: Requested permission Action: read (also open, getattr) Step 5: Policy lookup Search for: allow httpd_t httpd_content_t : file { read open getattr }; Step 6: Decision ┌─────────────────────────────────────────────────────────┐ │ Rule found: allow httpd_t httpd_content_t:file read; │ │ │ │ ══════════════════════════════════════════════════════ │ │ ║ DECISION: ALLOW ✓ ║ │ │ ══════════════════════════════════════════════════════ │ └─────────────────────────────────────────────────────────┘ If rule NOT found: ┌─────────────────────────────────────────────────────────┐ │ No matching allow rule │ │ │ │ ══════════════════════════════════════════════════════ │ │ ║ DECISION: DENY ✗ ║ │ │ ║ Log AVC denial to audit ║ │ │ ║ Return -EACCES to process ║ │ │ ══════════════════════════════════════════════════════ │ └─────────────────────────────────────────────────────────┘TE rules are additive—you can only add permissions, not remove them. If any rule allows an access, it's allowed. There's no 'deny' statement (neverallow is a compile-time check only). Security is achieved by simply not writing allow rules for unwanted access.
TE rules follow a precise syntax. Mastering this syntax enables you to read, write, and debug SELinux policies.
allow source_type target_type : object_class permission_set ;
| Component | Meaning | Examples |
|---|---|---|
| source_type | Subject's domain (who) | httpd_t, sshd_t, user_t |
| target_type | Object's type (what) | httpd_content_t, shadow_t |
| object_class | Kind of object | file, dir, socket, process |
| permission_set | Allowed actions | { read write }, read, manage_file_perms |
123456789101112131415161718192021222324252627282930313233
# Simple allow rule# "httpd_t may read files of type httpd_content_t"allow httpd_t httpd_content_t : file read; # Multiple permissions (use braces)allow httpd_t httpd_content_t : file { read open getattr map }; # Multiple object classes (use braces)allow httpd_t httpd_content_t : { file dir lnk_file } read; # Self reference - subject type same as target# "sshd_t may fork itself"allow sshd_t self : process fork;# "self" means the target type equals source type # Attribute as source - applies to all types with attribute# "All domains may read etc_t files"allow domain etc_t : file read; # Attribute as target - applies to all types with attribute# "init_t may signal any process"allow init_t domain : process signal; # Using macros/interfaces (expanded by m4)files_read_etc_files(httpd_t)# Expands to multiple allow rules for reading /etc # Nesting types and classesallow httpd_t { httpd_content_t httpd_script_t } : { file dir } { read getattr }; # Wildcard for all permissions of a class (use with caution!)allow myapp_t myapp_data_t : file *;# Grants ALL file permissions - rarely appropriateSELinux defines many permissions for each object class. Here are the most commonly used:
File Permissions:
read — Read file contentswrite — Modify file contentsappend — Append only (for logs)execute — Execute as programopen — Open file descriptorcreate — Create new fileunlink — Delete filerename — Rename filegetattr — Get file attributes (stat)setattr — Set file attributes (chmod/chown)map — Memory-map filelock — File lockingDirectory Permissions:
search — Search directory (traverse)read — Read directory contents (ls)write — Modify directory (general)add_name — Create entries in directoryremove_name — Delete entriesrmdir — Remove directory itselfProcess Permissions:
fork — Create child processsignal — Send signals (kill, etc.)ptrace — Debug/trace processtransition — Change domain on execexec — Execute a programSocket Permissions:
create — Create socketbind — Bind to addresslisten — Listen for connectionsaccept — Accept connectionsconnect — Initiate connectionname_bind — Bind to named portname_connect — Connect to named portPolicy writers rarely list individual permissions. Instead, they use macros like 'read_file_perms' (read, open, getattr, map) or 'manage_file_perms' (create, unlink, read, write, and more). These ensure consistent, complete permission sets.
When Type Enforcement denies access, it generates an Access Vector Cache (AVC) denial message in the audit log. Understanding these messages is essential for SELinux troubleshooting.
type=AVC msg=audit(1642589234.789:456): avc: denied { read } for pid=12345 comm="httpd" name="secret.conf" dev="sda1" ino=987654 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:admin_home_t:s0 tclass=file permissive=0 FIELD BREAKDOWN:─────────────────────────────────────────────────────────────────────────type=AVC Message type: Access Vector Cache eventmsg=audit(1642589234.789:456) Timestamp and message sequence numberavc: denied Decision: denied (or granted for auditallow){ read } ← PERMISSION(S) that were denied pid=12345 Process ID that made the requestcomm="httpd" Command/process namename="secret.conf" Name of target object (file in this case)dev="sda1" Device where object residesino=987654 Inode number of the object scontext=system_u:system_r:httpd_t:s0 ↑ SOURCE CONTEXT - who made the request Subject domain: httpd_t tcontext=system_u:object_r:admin_home_t:s0 ↑ TARGET CONTEXT - what they tried to access Object type: admin_home_t tclass=file ← OBJECT CLASS: what kind of objectpermissive=0 0=enforcing, 1=permissive mode TRANSLATION:"Process httpd (pid 12345) running in domain httpd_t tried to READ a FILE named 'secret.conf' with type admin_home_t and was DENIED because no allow rule permits it."1234567891011121314151617181920212223242526272829303132333435
# Search audit log for recent AVC messages$ sudo ausearch -m avc -ts recent # Filter by specific command/process$ sudo ausearch -m avc -c httpd # Filter by time range$ sudo ausearch -m avc -ts today$ sudo ausearch -m avc -ts "01/15/2025" -te "now" # Get human-readable explanation$ sudo ausearch -m avc -ts recent | audit2whytype=AVC msg=audit(1642589234.789:456): avc: denied { read }... Was caused by: Missing type enforcement (TE) allow rule. You can use audit2allow to generate a loadable module to allow this access. # On systems with setroubleshoot$ sudo sealert -a /var/log/audit/audit.logSELinux is preventing httpd from read access on the file secret.conf. ***** Plugin catchall (100. confidence) suggests ************************** If you believe that httpd should be allowed read access on the secret.conf file by default. Then you should report this as a bug.You can generate a local policy module to allow this access.Doallow this access for now by executing:# ausearch -c 'httpd' --raw | audit2allow -M my-httpd# semodule -X 300 -i my-httpd.pp # Quick view of dmesg for AVC$ dmesg | grep -i avcNot all denials are policy bugs. Common legitimate denial scenarios:
| Denial Pattern | Likely Cause | Action |
|---|---|---|
| Process probing paths | Application checking if files exist | Usually harmless; may dontaudit |
| Wrong file context | File mislabeled | restorecon the file |
| Non-standard path | Custom install location | semanage fcontext for new path |
| New functionality | Update added features | Update policy module |
| Security violation | Attack or misconfiguration | Investigate! Leave denied |
Policy includes 'dontaudit' rules that suppress logging for expected-but-harmless denials. If you suspect hidden denials: semodule -DB disables dontaudit rules temporarily. Re-enable with semodule -B. This reveals all denials, including normally-silenced ones.
Evaluating TE rules for every access would be prohibitively expensive. The Access Vector Cache solves this by caching recent access decisions.
The AVC makes SELinux practical. Most access patterns are repetitive (program reads same configs, writes same logs), so cache hit rates are typically 99%+.
12345678910111213141516171819202122232425262728
# View AVC statistics$ cat /sys/fs/selinux/avc/cache_statslookups hits misses allocations reclaims frees2157483 2156321 1162 1162 0 0 # Interpretation:# lookups: 2,157,483 - Total access checks# hits: 2,156,321 - Answered from cache (99.95% hit rate!)# misses: 1,162 - Required policy evaluation# allocations: new cache entries created# reclaims/frees: cache management # View AVC hash statistics$ cat /sys/fs/selinux/avc/hash_statsentries: 512buckets used: 389/512longest chain: 3 # View AVC cache threshold (entries before reclaim)$ cat /sys/fs/selinux/avc/cache_threshold512 # Tune cache size (root only, temporary)$ echo 1024 | sudo tee /sys/fs/selinux/avc/cache_threshold # Clear AVC cache (forces re-evaluation)# Useful after policy changes$ echo 1 | sudo tee /sys/fs/selinux/avc/cache_resetCache Key: The AVC indexes decisions by (source_type, target_type, object_class). Within each entry, individual permissions are tracked as bitmasks.
Cache Invalidation: The AVC is automatically invalidated when:
semodule -i, semodule -r)setsebool)Negative Caching: Denials are also cached. If Apache was denied access to /root/.bashrc, subsequent attempts return DENY from cache without re-evaluating policy.
Per-Object Decisions: Note that the AVC caches by type, not by individual object. Once httpd_t is allowed to read httpd_content_t files, all such accesses hit cache—regardless of specific file.
When you toggle a boolean (setsebool), the AVC cache is flushed for affected decisions. This is why boolean changes take effect immediately—the next access requires fresh policy evaluation.
Understanding what a domain CAN do is as important as understanding what it CAN'T. Security auditing requires knowing the actual access surface of each domain.
123456789101112131415161718192021222324252627282930313233
# All allow rules where httpd_t is the source (what can httpd_t do?)$ sesearch --allow -s httpd_t | wc -l847 # httpd_t has 847 allow rules! # What file types can httpd_t read?$ sesearch --allow -s httpd_t -c file -p read | headallow httpd_t cert_t:file { getattr ioctl map open read };allow httpd_t fonts_t:file { getattr ioctl map open read };allow httpd_t httpd_content_t:file { getattr ioctl map open read };allow httpd_t net_conf_t:file { getattr ioctl map open read };... # What can write to shadow_t? (password file)$ sesearch --allow -t shadow_t -c file -p writeallow chkpwd_t shadow_t:file { append read write ... };allow passwd_t shadow_t:file { append read write ... };allow updpwd_t shadow_t:file { append read write ... };# Very limited! Only password utilities. # What can httpd_t connect to over network?$ sesearch --allow -s httpd_t -c tcp_socket -p name_connectallow httpd_t http_port_t:tcp_socket name_connect;# Only http_port_t by default. Database ports require boolean. # With boolean enabled:$ sesearch --allow -s httpd_t -c tcp_socket -p name_connect -b httpd_can_network_connect_dballow httpd_t mysqld_port_t:tcp_socket name_connect;allow httpd_t postgresql_port_t:tcp_socket name_connect; # What processes can httpd_t signal?$ sesearch --allow -s httpd_t -c process -p signalallow httpd_t httpd_t:process { signal ... }; # Can signal selfallow httpd_t httpd_rotatelogs_t:process { signal ... }; # Log rotate12345678910111213141516171819
# Can httpd_t read shadow_t files?$ sesearch --allow -s httpd_t -t shadow_t -c file -p read# No output = no rule = DENIED # Can httpd_t execute bin_t files?$ sesearch --allow -s httpd_t -t bin_t -c file -p executeallow httpd_t bin_t:file { execute ... };# Rule exists = ALLOWED # Comprehensive check: all permissions httpd_t has on a type$ sesearch --allow -s httpd_t -t httpd_content_tallow httpd_t httpd_content_t:dir { getattr ioctl read search ... };allow httpd_t httpd_content_t:file { getattr ioctl map open read };allow httpd_t httpd_content_t:lnk_file { getattr read }; # Check access via attribute$ sesearch --allow -s httpd_t -t file_type -c file -p read# Shows rules where target is the 'file_type' attribute# All types with 'file_type' attribute are coveredRules using attributes (like 'domain' or 'file_type') apply to ALL types with that attribute. A rule 'allow domain public_content_t:file read' means every process domain can read public_content_t. Attributes are powerful—use carefully.
Beyond basic TE rules, SELinux supports constraints—additional restrictions that apply across all rules. Constraints implement MLS, role separation, and other cross-cutting security requirements.
Constraints add conditions that must be satisfied even when TE allows access:
constrain file write (
u1 == u2
or t1 == admin_type
);
This constraint says: file write is only allowed if the user fields match OR the source has admin_type attribute. Even if a TE rule allows the write, this constraint can deny it.
1234567891011121314151617181920212223242526272829
# View all constraints in policy$ seinfo --constrain | head -30constrain dir { add_name create remove_name rename reparent rmdir setattr write } ( u1 == u2 or t1 == ubac_constrained_type);constrain file { append create relabelto rename setattr write } ( u1 == u2 or t1 == ubac_constrained_type);... # MLS constraints (if MLS policy)constrain file { write } ( l1 dom l2 # Source level dominates target or t2 == mls_exempt_type); # Check constraints for specific class and permission$ seinfo --constrain=file:writeconstrain file write( u1 == u2 or t1 == ubac_constrained_type or t1 == svirt_lxc_domain);User-Based Access Control (UBAC) Constraints
Limit access to objects owned by the same SELinux user. Prevents user A from writing to user B's files even if types would allow it.
Role-Based Constraints
Ensure processes only enter domains allowed for their role. A user_r role can't transition to sysadm_t domain.
MLS Constraints Enforce Bell-LaPadula and Biba models:
Type Bounds Limit what child domains can do. A bounded type can't exceed its parent's permissions.
Constraint denials can be confusing. You find an allow rule (sesearch shows it), yet access fails. Check if constraints apply. audit2why will mention 'constrained' if a constraint caused the denial. In targeted policy, most constraints are minimal, but MLS policies have many.
Writing effective TE rules requires understanding security principles and SELinux conventions. Here are practices from experienced policy developers.
Grant only the permissions actually needed. Never:
# BAD: Allow everything
allow myapp_t file_type : file *;
# GOOD: Specific permissions for specific types
allow myapp_t myapp_config_t : file { read open getattr };
allow myapp_t myapp_log_t : file { create append open };
# BAD: Access to all etc files
allow myapp_t etc_t : file read;
# GOOD: Access only to own config
allow myapp_t myapp_etc_t : file read;
# Define types for different resources
type myapp_t; # Process domain
type myapp_exec_t; # Executable
type myapp_etc_t; # Config files
type myapp_log_t; # Log files
type myapp_var_run_t; # Runtime files (pid, sock)
type myapp_data_t; # Persistent data
type myapp_tmp_t; # Temp files
# Then give appropriate access to each
allow x y:file * grants create, unlink, write, relabel, and more. Always enumerate needed permissions.read_file_perms, manage_file_perms are tested, complete, and meaningful.logging_log_file(mylog_t) is better than manual log type setup.neverallow myapp_t shadow_t:file * ensures no accidental shadow access.audit2allow -M | semodule -i. Review and minimize.123456789101112131415161718192021222324252627282930313233343536373839404142434445464748
policy_module(mywebapp, 1.0.0) ######################################### Declarations######################################## type mywebapp_t; # Domaintype mywebapp_exec_t; # Executable # Register as daemon domain init_daemon_domain(mywebapp_t, mywebapp_exec_t) # Define file typestype mywebapp_config_t; # Configuration in /etcfiles_type(mywebapp_config_t) type mywebapp_log_t; # Logslogging_log_file(mywebapp_log_t) type mywebapp_data_t; # Persistent data in /var/libfiles_type(mywebapp_data_t) ######################################### Policy Rules######################################## # Read own configurationallow mywebapp_t mywebapp_config_t:dir search;allow mywebapp_t mywebapp_config_t:file read_file_perms; # Write logsallow mywebapp_t mywebapp_log_t:dir { search add_name };allow mywebapp_t mywebapp_log_t:file { create_file_perms append }; # Manage application dataallow mywebapp_t mywebapp_data_t:dir manage_dir_perms;allow mywebapp_t mywebapp_data_t:file manage_file_perms; # Network: bind to http portcorenet_tcp_bind_http_port(mywebapp_t) # Read system state (common need)kernel_read_system_state(mywebapp_t)files_read_etc_files(mywebapp_t) # Security assertionsneverallow mywebapp_t shadow_t:file ~getattr; # Never access passwordsneverallow mywebapp_t admin_home_t:file *; # Never access admin homesWe've explored Type Enforcement, the mechanism at the core of SELinux security. Let's consolidate the key insights:
allow source target : class { permissions }; defines permitted access.What's Next:
With Type Enforcement understood, we'll complete our SELinux exploration with Policy Management—the operational aspects of running SELinux in production, including troubleshooting workflows, policy updates, and managing SELinux across infrastructure.
You now understand Type Enforcement—how SELinux makes access decisions, how to interpret those decisions, how to analyze policy, and how to write effective TE rules. This is the core knowledge for working with SELinux security.