Skip to content
Stephen Smalley edited this page Aug 14, 2025 · 59 revisions

Build Environment

When doing SELinux development, it is easiest to use a Linux distribution that enables SELinux by default, like Fedora and any of its derivatives. Otherwise you will need to work through enabling SELinux and ensuring you have a working base policy before you can develop and test new enhancements or bug fixes.

Get the Source Code

Clone the dev branch of the SELinux kernel tree

Traditionally one names the Linux kernel tree “linux”. Don’t leave it as selinux or it will conflict with the userspace clone below. The dev branch is where all new development happens.

# If you do not already have git installed, install it first by running:
#     sudo dnf install git
git clone -b dev https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git linux

Clone the SELinux kernel testsuite

git clone https://github.com/SELinuxProject/selinux-testsuite.git

Clone the Git Verification Scripts

This repository has some tooling used by Paul Moore, LSM/SELinux/audit kernel maintainer, to validate patches. Running these scripts on your patches may help catch things up front that he would otherwise complain about.

git clone https://github.com/pcmoore/git-verification_scripts
# Install dependencies - need clang-format, codespell, and python3 git module (used by spdxcheck from checkpatch.pl)
sudo dnf install clang-tools-extra codespell python3-GitPython
# Kernel scripts are under verify.linux.d directory.
# From a kernel tree with your patch applied, can run the script to check the top-most commit.
# Paul integrates the scripts into stacked git to apply to all patches.
# 10-linux_checkpatch just runs checkpatch.pl, which you should already be doing.
# 20-linux_build just tests a build, which you should already be doing.
# 30-linux_style checks formatting via clang-format and outputs a diff of differences.
# You should only be concerned with new diffs introduced by your patch, not pre-existing ones.
# 40-linux_codespell runs codespell to check spelling.
# You should only be concerned with new spelling errors introduced by your patch.

If you run git format-patch -1 (or -N for however many patches you want to check) to generate the patch files, you can then run the following script on a clean copy of the dev branch to run all of these scripts on all of the patch files, aborting if a patch doesn't apply or build and logging any checkpatch/style/codespell warnings to a verify.log file for review.

#!/bin/sh
rm -f verify.log
for f in *.patch; do
   (git am $f |& tee -a verify.log) || { echo Failed on $f; exit 1; }
   ../git-verification_scripts/verify.linux.d/10-linux_checkpatch |& tee -a verify.log;
   (../git-verification_scripts/verify.linux.d/20-linux_build |& tee -a verify.log) || { echo Failed on $f; exit 1; }
   ../git-verification_scripts/verify.linux.d/30-linux_style |& tee -a verify.log;
   ../git-verification_scripts/verify.linux.d/40-linux_codespell |& tee -a verify.log;
done

(Optional) Clone the SELinux userspace

You only need this if you plan on using/testing a feature that depends on a newer version of the selinux userspace than your distribution provides, or if you plan on making a change to the selinux userspace too e.g. introducing support for a new policy capability or version into the policy compiler.

git clone https://github.com/SELinuxProject/selinux.git
# If you need a feature that depends on newer selinux userspace than your distribution package,
# follow the instructions in the README.md to build and install the userspace components.
# NB Make sure you specify your LIBDIR and SHLIBDIR so that you don't accidentally install
# 64-bit libraries to /lib instead of /lib64.

(Optional) Clone the SELinux reference policy

You only need to do this if you plan on making a change to the SELinux reference policy too, e.g. defining new permissions. However, even for defining new permissions, you can often achieve that locally without needing to rebuild the entire policy through a local policy module or by extracting and patching the base module (e.g. semodule -c -E base; vi base.cil; semodule -i base.cil), see Add new permissions.

git clone https://github.com/SELinuxProject/refpolicy.git

Build the Kernel

NB You must have installed the build dependencies for building the kernel, e.g.

sudo dnf build-dep kernel

Then you can configure and build your kernel:

cd linux
# Use the configuration of the kernel you are running.
cp /boot/config-$(uname -r) .config
# Generate a reduced configuration with only locally-installed modules; accept the defaults for all.
yes "" | make localmodconfig
# Merge this reduced configuration with the requirements for the selinux testsuite.
./scripts/kconfig/merge_config.sh .config ../selinux-testsuite/defconfig
# Optionally, customize your config - see below.
make menuconfig
# Compile the kernel, if you have multiple CPUs, use make -jN where N=number of CPUs for a parallel build
make
# Install the kernel and its modules
sudo make modules_install install

NB You may want to do a make menuconfig after the merge_config.sh and selectively enable debugging options under "Kernel hacking". I typically enable many debugging options to automatically catch memory corruption or leaks, locking bugs, etc. Some good ones to make sure you have enabled in your .config when testing your changes:

CONFIG_PROVE_LOCKING=y
CONFIG_DEBUG_OBJECTS=y
CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_AUTO_SCAN=y
CONFIG_DEBUG_SPINLOCK=y
CONFIG_DEBUG_MUTEXES=y
CONFIG_DEBUG_LOCK_ALLOC=y
CONFIG_DEBUG_ATOMIC_SLEEP=y
CONFIG_DEBUG_LIST=y
CONFIG_UBSAN=y
CONFIG_KASAN=y # This imposes significant overhead so only use for testing; enable KFENCE instead for production.
CONFIG_KCSAN=y # Likewise imposes significant overhead and generates many false positives, only use for testing.

Boot your kernel

Before rebooting, make sure you have unhidden the GRUB menu or know how to unhide it at boot so you can select your new kernel, see https://fedoraproject.org/wiki/Changes/HiddenGrubMenu .

# Unhide the grub menu
sudo grub2-editenv - unset menu_auto_hide

Also, be sure to change the grub menu timeout from 0 to a positive integer, e.g. 5, to give yourself time to make a selection.

# Change GRUB_TIMEOUT from 0 to 5
sudo nano /etc/default/grub
# Regenerate your grub2 configuration.
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

Then you can safely reboot.

sudo reboot

Select your kernel from the grub menu. It should be the one at the top of the list, but you may need to cursor up to it and hit return.

Test your kernel

Verify that you are running the kernel you built.

# Check that the correct version and your username@hostname is listed along with the build timestamp.
cat /proc/version

Build and run the SELinux kernel testsuite. NB This requires that you have installed the dependencies mentioned in the README.md file, ala:

sudo dnf install perl-Test perl-Test-Harness perl-Test-Simple selinux-policy-devel gcc libselinux-devel net-tools netlabel_tools iptables-nft lksctp-tools-devel attr libbpf-devel keyutils-libs-devel quota xfsprogs-devel libuuid-devel   e2fsprogs jfsutils dosfstools rdma-core-devel

The above list may be incomplete; always check the README.md file for the most current list. Then you can run the testsuite.

cd selinux-testsuite
sudo make test

Confirm that there are no failures.

You will need to look at dmesg or cat /proc/kmsg output or journalctl -kb output to see any warnings/errors reported as a result of the debugging options; it is often good to check after running the testsuite to see if anything was reported during the run. KASAN, KFENCE, KCSAN, UBSAN, and KMEMLEAK usage are described in https://docs.kernel.org/dev-tools/index.html

Run static checkers on your kernel

Install and use sparse to check your kernel changes.

sudo dnf install sparse
cd linux
make C=2 security/selinux/*.o security/selinux/ss/*.o

Ditto for smatch.

sudo dnf install smatch
cd linux
make CHECK=smatch C=2 security/selinux/*.o security/selinux/ss/*.o

Debug your kernel

Refer to https://docs.kernel.org/process/debugging/index.html for general information.

Prerequisites:

  • Install gdb-server on the testing platform: sudo dnf install gdb-gdbserver
  • Install QEMU
  • Get a kernel:
  • Clone linux repo, checkout desired version
  • make menuconfig and enable "Provide GDB Scripts for kernel debugging" under 'Kernel hacking → Compile-time checks and compiler options'
  • Finally make and install your kernel

QEMU Setup:

We will need the following to invoke QEMU with its necessary arguments:

  • -kernel can be your installed vmlinuz at boot such as /boot/vmlinuz-6.10.0 or your bzImage located within /arch/x86/boot of your kernel build tree

  • -initrd is your initial ram filesystem, again the one that we got from installing should be acceptable: /boot/initramfs-6.10.0.img

  • -hda is our designated disk that we have to create like so:

    $ dd if=/dev/zero of=qemu-disk bs=1M count=2048 $ mkfs.ext4 qemu-disk

Note: my example uses "mkfs.ext4", – it's important that your chosen filesystem is supported on your initrd and kernel, otherwise it will hang forever or otherwise fail while mounting the root filesystem. If you're using your installed initrd (from /boot), you should use your host's filesystem. ext4 should be generally good, though.

The filesystem needs actual files too, otherwise you'll get a failure during the root filesystem switch. Install whatever flavor of minimal linux you want (I'll do fedora):

Mount your sparkly new disk we made:

sudo losetup -fP qemu-disk --show (if this doesn't work, try sudo modprobe loop)
sudo mount -o rw /dev/loop0 /mnt
sudo dnf --installroot=/mnt --releasever=42 install @core
sudo chroot /mnt followed by  passwd (set your root password and ensure you can login)
echo "/dev/sda / ext4 defaults 0 1" | sudo tee -a /mnt/etc/fstab

We're finished, so unmount:

sudo umount /mnt
sudo losetup -d /dev/loop0

Launch QEMU!

sudo qemu-system-x86_64 -kernel ~/bzImage -initrd /boot/initramfs-6.10.0.img -hda qemu-disk -append "root=/dev/sda nokaslr console=ttyS0" -m 1024 -nographic

Now we can try attaching gdb:

  • Rerun the above command, adding the options '-s -S', which makes gdbserver begin listening on port -1234 and starts the CPU in a stopped state, respectively.
  • Run 'gdb ', where that argument is your compiled kernel's vmlinux binary
  • Load the symbol table upon prompt, connect to your waiting QEMU instance with target remote :1234
  • Finally, you can set a breakpoint on a common function, like do_sys_open which triggers on an ls command to verify you can debug.

Secure Copy

If you want to have an easier means for file transfer, you can add the following netdev argument:

sudo qemu-system-x86_64 -kernel ~/bzImage -initrd /boot/initramfs-6.10.0.img -hda qemu-disk -append "root=/dev/sda nokaslr console=ttyS0" -m 1024 -nographic -netdev user,id=net0,hostfwd=tcp::2222-:22 -device e1000,netdev=net0

This will allow you to direct ssh traffic to your VM through port 2222 like so:

scp -P 2222 <some_path> root@localhost:<some_path>)

Debugging a Module

If you want to debug a loadable kernel module, you can follow these steps:

  • Resize your QEMU disk if necessary to accommodate the kernel modules
  • Expand the filesystem using something like xfs_growfs within QEMU
  • Now mount and copy your modules (/lib/modules/) to the same path on the QEMU disk
  • Re-load QEMU with GDB attached, and at long last you can follow the Linux documentation link at the top (Load lx-symbols, then set breakpoints for whatever module's functions, etc.). Happy debugging!

Add new permissions

To add a new class and/or permission to the SELinux kernel code, edit security/selinux/include/classmap.h in the kernel tree and add your definition. This will define the class and/or permission for use in the kernel; the corresponding symbol definitions will be automatically generated during the kernel build. If not defined in the policy, then the class and/or permission will be handled in accordance with your policy's handle_unknown definition, which can be reject (refuse to load the policy), deny (deny the undefined class/permission), or allow (allow the undefined class/permission). handle_unknown is set to allow in modern Fedora policies.

Then, update the policy to define the permission and allow it where needed. Ultimately, this should be done by creating and submitting a patch to the refpolicy (an example can be seen here) but for testing purposes, you can locally add the new permission to your base policy and policy headers so that you can exercise the new permissions in a test that you add to the selinux-testsuite (an example can be found at this link).

To update refpolicy, edit refpolicy/policy/flask/security_classes and/or access_vectors in the refpolicy tree and add your definition. This will define the class and permission for use in the policy. You generally need to add the class and/or permission at the end of the existing list of classes or permissions for that class for backward compatibility with older kernels. The class and/or permission definition in policy need not line up with the definition in the kernel's classmap, as the values will be dynamically mapped by the kernel. Then add allow rules as appropriate to the policy for the new permissions - at a minimum you need to ensure that unconfined domains are allowed the new permissions. You likely do NOT want to load this policy into the kernel on your test system because Fedora policy has significant changes and stock refpolicy is unlikely to work out of the box. You can however test building and installing the refpolicy with your patch to its own policy directory separate from the Fedora policy. If you truly want to test loading your patched refpolicy, the best way is to download the source RPM for selinux-policy-targeted, modify it to include your change as an additional patch, and rebuild it so that you retain all of the Fedora changes.

To temporarily add the permissions to your base policy and policy headers for testing purposes, you can do the following:

# Extract base policy module in CIL format 
sudo semodule -c -E base
# Edit and add your new permissions to the list of permissions in the class declaration.
# Or if introducing a new class, add a new class declaration, which can be done in a separate module instead.
# If the permission is likely to be required by other processes that are running, you should also add allow
# rules for the new permission to avoid breaking them.
vi base.cil
# Install the modified base module with your permission defined.
sudo semodule -i base.cil
# Edit the all_classname_perms macro to declare permission for use in test policy .te files
sudo dnf install selinux-policy-devel
cp /usr/share/selinux/devel/include/support/all_perms.spt .
sudo vi /usr/share/selinux/devel/include/support/all_perms.spt
# When done testing, remove your modified base module and revert the change to all_perms.spt
sudo semodule -r base
sudo cp all_perms.spt /usr/share/selinux/devel/include/support

Adding a new SELinux policy capability

When adding new or modified SELinux hook functions that check already existing SELinux permissions, it is generally necessary to wrap the new logic in a conditional based on a "SELinux policy capability" (not to be confused with a Linux capability) so that the new logic only takes effect on systems with newer policies that support them.

You can find an example of defining and using a SELinux policy capability in this way for the kernel at: https://github.com/SELinuxProject/selinux-kernel/commit/d1d991efaf34606d500dcbd28bedc0666eeec8e2

You will also need to patch the SELinux userspace to define this policy capability, which you can find an example of in: https://github.com/SELinuxProject/selinux/commit/9c7c6e15a2c6bb8d53c41b8fa4890df6228fa83c

This then requires enabling the policy capability in order to run your tests, which you can find an example of how to do in: https://github.com/SELinuxProject/selinux-testsuite/commit/023b79b8319e5fe222fb5af892c579593e1cbc50

Possible tasks

Before undertaking a new task, it is a good idea to first post to the [email protected] mailing list to see if anyone else is already working on it.

LSM/SELinux coverage tasks:

See the separate SELinux Namespaces issue tracker for SELinux namespace tasks.

SELinux kernel issues are listed at https://github.com/SELinuxProject/selinux-kernel/issues

SELinux userspace issues (some of which also involve kernel changes) are listed at https://github.com/SELinuxProject/selinux/issues

SELinux testsuite issues are listed at https://github.com/SELinuxProject/selinux-testsuite/issues

Outstanding patches for the SELinux kernel, userspace, and testsuite can be found at: https://patchwork.kernel.org/project/selinux/list/

Linux development process

See https://docs.kernel.org/process/development-process.html, https://docs.kernel.org/process/submitting-patches.html, and https://docs.kernel.org/process/coding-style.html to understand how to develop and submit code for Linux in a manner that will improve your odds of acceptance.

Public mailing list

SELinux has a public mailing list for developers, subscribe by sending an email to [email protected]. It is generally wise to read relevant postings to the list before beginning any area of new work. Searchable mailing list archives are available externally at https://lore.kernel.org/selinux/ . Patches for SELinux are tracked via https://patchwork.kernel.org/project/selinux/list/ .

Submitting patches

See git-send-email and submitting-patches for how to configure git-send-email for sending patches inline within email messages as required by the SELinux maintainers. To send patches for SELinux, use one of the following examples depending on what your patch is for (kernel, testsuite, userspace):

Submitting a Kernel Patch

You will send the patch via git send-email to the [email protected] mailing list with the set of SELinux kernel maintainers explicitly cc'd. The command below is for sending the topmost patch from your repository; if you need to send more than one patch or you are sending a patch from a file instead, you will need to adjust the command line accordingly. See MAINTAINERS in the linux directory for a list of maintainers for each Linux subsystem, and/or use the ./scripts/get_maintainer.pl script to generate a list of potential maintainers from your patch file. NB I do not blindly add all email addresses produced by the get_maintainer.pl script because not all are truly maintainers or relevant to the patch in question; the script looks at MAINTAINERS but also looks to see who has recently committed to the same file. I recommend pruning its output to only the most relevant mailing list(s) and maintainer(s). If someone else has commented on a previous version of your patch, it is good practice to explicitly cc them on subsequent revisions and use a --subject-prefix that specifies the version of the patch e.g. --subject-prefix="PATCH v2", along with putting a summary of the changes since the previous version below the diffstat before the patch itself.

Submitting a Testsuite Patch

You will send the patch in almost exactly the same way but should add a subject prefix to indicate that the patch is for the testsuite. Provide enough context in the patch description to allow someone not already familiar with it to replicate what you did to run the tests, including citing any requisite dependencies for the kernel or userspace and identifying any new policy capabilities or permissions required. See https://github.com/SELinuxProject/selinux-testsuite/commit/023b79b8319e5fe222fb5af892c579593e1cbc50 for an example description.

git send-email --subject-prefix="PATCH testsuite" -1 [email protected] [email protected] [email protected] [email protected]

Submitting a Userspace Patch

This follows a similar process but using a different subject prefix and there is no requirement/expectation to cc individual maintainers for the SELinux userspace since there is a larger and different set of SELinux userspace maintainers than the kernel or testsuite.

git send-email --subject-prefix="PATCH userspace" -1 [email protected]

Other Resources

Clone this wiki locally