-
Notifications
You must be signed in to change notification settings - Fork 61
Getting Started
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.
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
git clone https://github.com/SELinuxProject/selinux-testsuite.git
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, failing if the patch doesn't apply or build and logging any 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
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.
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
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.
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.
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
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
Refer to https://docs.kernel.org/process/debugging/index.html for general information.
- 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
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
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.
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>)
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!
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
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
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:
- Audit the set of LSM hooks, especially those added recently, and check whether SELinux is missing implementations for any that are relevant.
- See https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/include/linux/lsm_hook_defs.h for recent additions.
- Check https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/lsm.git/log/security/selinux/hooks.c?h=dev and https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git/log/security/selinux/hooks.c?h=dev to see if SELinux hook functions have been added already.
- Audit the set of system calls, especially those added recently, and check whether LSM has one or more hooks that provide control of the system call. If not, add LSM hooks and SELinux hook function implementations for the new system call.
- See https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/log/arch/x86/entry/syscalls/syscall_64.tbl for recent additions.
- Use https://elixir.bootlin.com/linux/latest/source or https://opensource.com/article/21/7/linux-kernel-trace-cmd or https://github.com/x2c3z4/kernel_visualization to determine if there are LSM hooks already covering the new system call(s).
- If no, add LSM hooks and SELinux hook function implementations to cover all such system call(s).
- If yes, check whether the existing LSM hooks are adequate to control the system call, e.g. are all security-relevant parameters passed to the existing hooks, is there any reason to distinguish the new system call from others that call the same LSM hooks, etc.
- If the existing hooks are inadequate, extend them or add new ones to address.
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/
See the separate SELinux Namespaces issue tracker for SELinux namespace tasks.
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.
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/ .
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):
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).
git send-email -1 [email protected] [email protected] [email protected] [email protected]
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.
git send-email --subject-prefix="PATCH testsuite" -1 [email protected] [email protected] [email protected] [email protected]
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]