SELinux on NixOS

The world of MAC security on Linux in NixOS

Since Wednesday, I’ve been hard at work with trying to get SELinux (Security Enhanced Linux) to function on NixOS. This is because I am building a Linux distro called ExpidusOS. One of the goals for that is to implement a mobile OS security setup into the OS. With ExpidusOS being based on NixOS, this means the entire OS is declaratively built. However, it is more in-line with an appliance OS. This is to increase security and make it easier for the end user to utilize.

Packaging SELinux

SELinux was luckily already packaged in nixpkgs, but it hadn’t been maintained in a long time. That is until I picked it up last year. I had been a the maintainer of it for about a year. But now, I am finally getting involved in making SELinux on NixOS a reality. The only real work has been making sure there’s little existing problems reported and reviewing any pull requests. However, I did have to package the SELinux reference policy. The refpolicy is a simple SELinux policy that provides enough for a usable experience. It is in no way a fully secure policy but it is a good enough policy to work off of.

To package it, I started out with a typical Nix derivation which fetches from GitHub. However, I did have to add a few things that are unique like makeFlags. This attribute holds all the flags to pass over to GNU Make as refpolicy is built with GNU Makefiles. One of the few differences was adding in make conf as the configurePhase and passing in the various SELinux userspace tools in through makeFlags. From there, I was able to build the policy. Though, it did end up with $out/usr but that was as simple as adding this to the makeFlags:

"prefix=${placeholder "out"}"

With the refpolicy derivation outputting correctly, it was time to figure out how SELinux works and getting it functioning.

Starting the SELinux NixOS module

The first thing I did on the NixOS side was I created a basic module. This module added libselinux and policycoreutils to environment.systemPackages. It also created /etc/selinux/config, the configuration file which configures SELinux on the distro. I also added a kernel patch via boot.kernelPatches which enabled CONFIG_SECURITY_SELINUX and CONFIG_SECURITY_SELINUX_BOOTPARAM. I then added security=selinux to boot.kernelParams. With this, I booted up the NixOS VM and checked sestatus to ensure SELinux was enabled. However, it reported as disabled. I tried various other kernel parameters and versions of the kernel. This led me to trying systemd with SELinux enabled in systemd.package. I booted up the VM and got a warning saying /etc/selinux/refpolicy/policy/policy.33 did not exist. sestatus also reported that SELinux was indeed enabled. This was the first large step towards getting SELinux working. The next step was getting the policy.33 file to exist.

Getting a policy loaded

Making a policy load on NixOS was a bit of an endeavor as there wasn’t many tutorials I could find which explained things very aptly. I checked what Gentoo and Arch Linux does and found that Arch Linux’s selinux-refpolicy-git’s install script from the AUR is what I needed. The solution I came up with was to tack it into the activation script. This would install the policy upon the NixOS generation was activated.

At this point, I got stuck and ran into a broken pipe error. I eventually managed to figure out via verbose mode that it was looking for hll in a FHS path and not within the Nix store. I grepped SELinux’s source code and stumbled upon libsemanage having a configuration file format. This would be perfect as it allows for specifying the paths to the various tools semodule calls. I added the paths for hll, load_policy, setfiles, and sefcontext_compile. I also had to tack on the arguments that libsemanage uses by default for the last three tools.

Once semanage was using its configuration file inside /etc/selinux, I booted up the VM one more time. The boot stalled and the error about /etc/selinux/refpolicy/policy/policy.33 was still present. I checked the path and found /etc/selinux/refpolicy/policy/policy.34 existed. I checked sestatus and it said the system was using policy v33.

Getting the right policy version

SELinux spitting out policy.34 instead of policy.33 stumpted me. Until, I grepped the SELinux source tree more. I found out that SELinux’s userspace tools default to the max version. The max version which at this time is 34. The kernel I was using has an SELinux version of 33. This meant that I had to get refpolicy to build for SELinux policy 33 instead of 34. I found that various tools in SELinux support specifying the version. However, refpolicy has a flag for changing the version. This led me to add two new attributes which can be overridden when building refpolicy, policyVersion and moduleVersion. I also added a policyVersion option to the SELinux module in NixOS.

After rebuilding the VM and deleting the qcow2 image as a precaution, I booted up the VM one more time. It did the usual stall on boot when semanage would install the policy. After about 40 seconds, the system initialization continued. However, the error about the missing policy.33 file went away. Other messages complaining about various systemd things also disappeared. I checked /etc/selinux/refpolicy/policy/policy.33 and it existed. sestatus also mentioned the policy is refpolicy. These two indicated that SELinux is indeed working.

Configuring SELinux in nixpkgs

With SELinux properly loading a policy, I decided to start cleaning up how SELinux is handled on nixpkgs. I started with checking for everything which optionally added SELinux based on an attribute. I also added a new nixpkgs config flag called selinuxSupport. This config flag is able to rebuild everything from coreutils to postgres and emacs. It enables SELinux support everywhere it is possibly supported. This process was pretty straight forward and went very smoothly.

Upstreaming into nixpkgs

All this work for ExpidusOS had been made with the intention of it going back into nixpkgs and NixOS upstream. I made the following pull requests:

These four pull requests ended up being all it takes to get basic SELinux support into NixOS and nixpkgs. In my time of looking at various things, I came across an RFC ([RFC 0041] SELinux Support #41). Reading through it, I see why SELinux support hadn’t been done to this degree before. However, I believe the work I have done here and the ExpidusOS project to push SELinux support to be capable of securing a NixOS appliance OS.

Next steps

The next step from here is get everything upstreamed. From there, I plan on working out a way to do declarative policy creation within the NixOS module system. This would make handling policies better than using refpolicy. From there, it can be investigated how this impacts the Nix daemon and building in general.