Skip to content

Instantly share code, notes, and snippets.

@shawngmc
Last active January 9, 2024 00:52
Show Gist options
  • Save shawngmc/9925ae9b2d6a128eb3ab0eeca0658dc4 to your computer and use it in GitHub Desktop.
Save shawngmc/9925ae9b2d6a128eb3ab0eeca0658dc4 to your computer and use it in GitHub Desktop.
I hate SELinux!

Foreword

OK, so I love the idea of SELinux. I like the idea of process permissions and quasi-sandboxing. What I hate about SELinux is:

  • The lack of good, straightforward documentation
  • The awful UI/UX
  • The inconsistency and community split between AppArmor and SELinux

Let's see if I can help myself (and maybe you!) understand it a bit better.

Mode of Operation (How It Works)

Security context/label

Every process has a security context/label. This consists of a user, role, type, and optional range, separated by colons. Each type ends with a matching suffix, so an example of a unguarded process might be unconfined_u:unconfined_r:unconfined_t. A kernel worker typically is system_u:system_r:kernel_t:s0, while system daemons typically specify their own type, such as for NetworkManager system_u:system_r:NetworkManager_t:s0.

Type Enforcement

Type Enforcement is used to see if a security context (scontext) can access a resource (tcontext). Primarily, this is done via Access Vector (AV) rules, which are stored in the Access Vector Cache (AVC). (Some SELinux-aware applications can provide a custom type, but this is uncommon.) Kernel types provide clean context for certain common kernel paths.

Access Vector

These will be type=AVC for system events or type=USER_AVC for userspace events, and may be followed by kernel types for additional context.

allow Statements

These are the primary The FAQ has a great example:

allow firefox_t user_home_t : file { read write };
          |              |      |     ^ Allow reading/writing
          |              |      ^ Manage file operations
          |              ^ types labeled as being the user home directory (typically just /home/$USER/ and descendants)
          ^ types labeled as being firefox (ie, typically just the Firefox browser)

dontaudit Statements

Exclude an event from auditing, which is useful to streamline the logs.

auditallow Statements

Log the event for auditing, but allow it.

neverallow Statements

Deny (don't allow), but prevent other rules from being added to allow.

SE-Linux Aware Application

There are a few generated by the kernel that you will often see. These provide useful context about events.

  • PROCTITLE - the title of the process that caused an event
  • PATH - a path that was passed as a parameter to a syscall
  • CWD - any change to which directory is the current working directory
  • EXECVE - The full command that caused an event; generally (but not always) more reliable than PROCTITLE
  • SYSCALL - The actual kernel system call, including pid, success, and more

SE-Linux Aware Application

These have custom types, so documenting them here is difficult. Per the docs, one example is Pluggable Authentication Manager (PAM).

Logging

Per the SELinux docs, logs are in three locations overall, with the third being the one typically used.

  • The SELinux kernel boot events are logged in the /var/log/dmesg log.
  • The system log /var/log/messages contains messages generated by SELinux before the audit daemon has been loaded, although some kernel messages continue to be logged here as well.
  • The audit log /var/log/audit/audit.log contains events that take place after the audit daemon (auditd) has been loaded. The AVC audit messages of interest are described in the AVC Audit Events section with others described in the General SELinux Audit Events section.

From a pracical perspective, many denials will end up in Journald journalctl -xe for systemd units, and ausearch sudo ausearch -m AVC,USER_AVC -ts today can also filter down events.

Debugging

Some very useful tools are available. Let's use them - you'll want the following packages:

  • bzip2
  • checkpolicy
  • setools-console

Information Gathering

Listing the security context for a process

# For example, NetworkManager:
ps -fZ $(pgrep NetworkManager)
# You can also just pipe-grep, but then you get a grep entry
ps -efZ | grep NetworkManager

Listing Recent Errors

# For today:
sudo ausearch -m AVC,USER_AVC -ts today

Listing Active/Installed Policy Objects

Generally use -x to get more info. There are 6 main types of installed object:

  • Attribute (-a)
  • Boolean (-b)
  • Class (-c)
  • Role (-r)
  • Type (-t)
  • User (-u)
# Get a specific attribute
seinfo -a ATTRIBUTE_NAME -x
# Get all attributes
seinfo -a -x
# For example, here's an important object provided by container-selinux:
seinfo -a container_runtime_domain -x
# If it exists:
Type attributes: 1
  attribute container_runtime_domain
       container_runtime_t
       kubelet_t
# If it doesn't:
Type attributes: 0

Local Policies from Recent Errors

These are useful, but generally less flexible than packaged policies from OEMs.

Building a New Local Policy from Recent Errors

# Build a policy package For rules failed today
sudo ausearch -m AVC,USER_AVC -ts today | audit2allow -a -M MY_POLICY_NAME
# Apply it
sudo semodule -i MY_POLICY_NAME.pp

Debugging Existing Policies

Interpreting OEM policy files

First off, when installing, watch for script hook errors! Many packages will install files successfully but fail to actually install the policies. You'll get errors like Failed to resolve typeattributeset statement at /etc/selinux/targeted/tmp/modules/200/k3s/cli:21 These are often caused by a parent package (for k3s-selinux, container-selinux) not installing properly.

Finding the Policy Files from a Yum Package

# Assuming you know the name of the package, look for .pp/.pp.bz/.cil files
# For example k3s-selinux:
rpm -ql k3s-selinux

Decompiling a Policy

# First, if the file is compressed (with a .pp.bz extension, of if commands below fail), decompress it:
bunzip2 MYPOLICY.pp.bz
# Next, use the interactive interpreter
sedismod MYPOLICY.pp
# You can redirect output to a file for easy searching via 'f' and giving it a filename 
# (this can't be done via flags - it's a pretty rough tool!)
# A short 'one-liner' that would be handy is:
MYPOLICY=k3s sedismod MYPOLICY.pp <<EOF
f
${MYPOLICY}_cond_avtab.txt
2
f
${MYPOLICY}_uncond_avtab.txt
2
f
${MYPOLICY}_users.txt
3
f
${MYPOLICY}_bools.txt
4
f
${MYPOLICY}_roles.txt
5
f
${MYPOLICY}_types.txt
6
f
${MYPOLICY}_role_trans.txt
7
f
${MYPOLICY}_role_allows.txt
8
f
${MYPOLICY}_init_SIDs.txt
0
f
${MYPOLICY}_filename_trans_rules.txt
F
EOF

Fixing policies

Some issues I've seen are:

  • An RPM-provided policy may install the package successfully, but the RPM script hooks failed.
  • As above, but with dependencies: An RPM may install a policy, and the RPM may depend on another RPM that provides another policy - but that parent policy was never hooked.
  • Antivirus and endpoint security applications may prevent an SELinux policy from installing.
  • An SELinux policy could prevent an SELinux policy from being installed.

Often, reinstalling RPM policies (including parents) or adding local policies can fix these failures.

For example, let's say an endpoint security product blocks the script trigger for container-selinux via SELinux, which says it is installed, so k3s-selinux fails. In this case, adding a local policy for the audited event of the initial denial, then reinstalling the packages can yield can fix issues.

Capturing stack traces

Sometimes, the security log doesn't give you enough information and you need to dig a little deeper. One thing that can be useful is capturing a stack trace using '''perf''' on linux.

Capturing stack traces

sudo perf record -a -g -e avc:selinux_audited Simply press Ctrl-C after the failure has occurred.

Viewing stack traces

sudo perf report -g perf.data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment