Introduction
As a young man, I was always very paranoid about security, and now that I'm all grown up and know more about computers I am even more paranoid. One day, I decided to try NixOS Unstable (the name is a misnomer, it's still very stable), and thought about this article I came across by security researcher "Madaidan" about hardening Linux systems.
Previously, I'd been manually applying these fixes on all of my servers and machines, but this proved time-consuming and honestly kinda boring to manage. This is where NixOS comes in: with a handy Git repository, I could sync all these settings between machines and automatically apply the fixes! So, I set to adapt to the Nix way.
Sysctl Tunables
The first, and one of the most important things to do, is make use of a tool called sysctl
. Sysctl
is a configuration utility that lets you change kernel settings in a permanent manner without necessarily rebuilding your entire kernel. The way it would normally work, is you'd create a file named xx-something.conf
with an array of key/value pairs inside it: however, we are using NixOS here, so we're gonna be more intelligent than that and use the cool configuration.nix
.
Here are the contents of my sysctl.nix
:
{ config, pkgs, stdenv, ... }:
{
boot.kernel.sysctl = {
# Restrict kernel pointers
"kernel.kptr_restrict" = 2;
"kernel.dmesg_restrict" = 1;
# Restrict eBPF
"kernel.unprivileged_bpf_disabled" = 1;
# Harden JIT
"net.core.bpf_jit_harden" = 2;
"dev.tty.ldisc_autoload" = 0;
"vm.unprivileged_userfaultfd" = 0;
# Disable loading other kernels at runtime
"kernel.kexec_load_disabled" = 1;
# Disable SysRq key for non-users (can be used in remote exploits)
"kernel.sysrq" = 4;
"kernel.perf_event_paranoid" = 3;
"kernel.unprivileged_userns_clone" = 1;
## NETWORK
# SYN flood attack prevention
"net.ipv4.tcp_syncookies" = 1;
# Prevent IP spoofing
"net.ipv4.conf.all.rp_filter" = 1;
"net.ipv4.conf.default.rp_filter" = 1;
# MITM attack prevention (disable redirect acceptance)
"net.ipv4.conf.all.accept_redirects" = 0;
"net.ipv4.conf.default.accept_redirects" = 0;
"net.ipv4.conf.all.secure_redirects" = 0;
"net.ipv4.conf.default.secure_redirects" = 0;
"net.ipv6.conf.all.accept_redirects" = 0;
"net.ipv6.conf.default.accept_redirects" = 0;
"net.ipv4.conf.all.send_redirects" = 0;
"net.ipv4.conf.default.send_redirects" = 0;
# Clock fingerprinting prevention (disabled ICMP requests)
"net.ipv4.icmp_echo_ignore_all" = 1;
# Restrict ptrace usage
"kernel.yama.ptrace_scope" = 2;
# ASLR exploit mitigation
"vm.mmap_rnd_bits" = 32;
"vm.mmap_rnd_compat_bits" = 16;
"fs.protected_fifos" = 2;
"fs.protected_regular" = 2;
};
}
Let's go through some of these individually.
Kernel Features
"kernel.kptr_restrict" = 2;
This prevents programs from exploiting memory pointer bugs for malicious purposes by simply hiding said pointers.
"kernel.dmesg_restrict" = 1;
This restricts dmesg
access, which can contain potentially sensitive information.
"kernel.unprivileged_bpf_disabled" = 1;
"net.core.bpf_jit_harden" = 2;
Here we restrict the eBPF system, which can be used for some very severe kernel exploits as it exposes big targets in the system.
"dev.tty.ldisc_autoload" = 0;
This prevents privilege escalation by abusing TTY line disciplines, by restricting this capability to authorized processes only.
"vm.unprivileged_userfaultfd" = 0;
This syscall can be exploited rather easily, so we restrict it.
"kernel.kexec_load_disabled" = 1;
kexec
is a system that lets you boot another kernel while the main one is running: while this is quite a cool thing to do, it also opens up a whole new world of severe security issues, so we're gonna leave it disabled.
"kernel.sysrq" = 4;
Apparently the SysRq key on keyboards can be abused through remote exploits to gain privileged access to the kernel, so we restrict it.
"kernel.perf_event_paranoid" = 3;
Restrict kernel performance events, for security as always.
"kernel.unprivileged_userns_clone" = 1;
User namespaces are another pretty cool feature in the Linux kernel, but they let you do privilege escalation so we don't want that here.
Networking
"net.ipv4.tcp_syncookies" = 1;
This prevents an exploit called "SYN Flood".
"net.ipv4.conf.all.rp_filter" = 1;
"net.ipv4.conf.default.rp_filter" = 1;
These settings prevent some cases of IP spoofing, where malicious agents change their IP to mimic another one.
"net.ipv4.conf.all.accept_redirects" = 0;
...
"net.ipv4.conf.default.send_redirects" = 0;
Here, we disable a feature called "redirect acceptance", which can sometimes be used for MITM attacks.
"net.ipv4.icmp_echo_ignore_all" = 1;
This one is fairly straightforward: disable ICMP requests to prevent people from knowing your clock time, and as a bonus this may increase your stealthiness on a network.
Userspace
"kernel.yama.ptrace_scope" = 2;
This one restricts access to ptrace
, which can leak sensitive information by default.
"vm.mmap_rnd_bits" = 32;
"vm.mmap_rnd_compat_bits" = 16;
Enabling ASLR, which randomizes position of process memory, may defeat some exploits which rely on data gleaned from memory.
"fs.protected_fifos" = 2;
"fs.protected_regular" = 2;
These make data spoofing attacks more difficult since they prevent creating files in certain directories.
Kernel Parameters
Here are the contents of my system.nix
:
{ config, pkgs, stdenv, ... }:
{
boot.kernelPackages = pkgs.linuxPackages_hardened;
boot.kernelParams = [
# Disable slab merging to prevent heap exploitation
"slab_nomerge"
# Enable zeroing memory during allocation and free time
"init_on_alloc=1" "init_on_free=1"
# Randomize page allocator freelists
"page_alloc.shuffle=1"
# Mitigations
"pti=on"
"vsyscall=none"
"debugfs=off"
"oops=panic"
# Enable lockdown LSM
"lockdown=confidentiality"
];
...
}
First, you may notice that we are using the linux-hardened
kernel: this is because that kernel contains some important security patches that most kernels don't apply.
We use some kernel parameters to enable Lockdown Mode and disable some vulnerable systems, as well as panic on "oops" events. You may need to remove "oops=panic"
when using certain drivers.
Kernel Modules
The Linux kernel is a monolithic kernel, and as such has all its drivers built into the kernel itself: while this may be useful for compatibility purposes, this also opens up the attack surface with vulnerable drivers. This config disabled some never-used drivers.
boot.blacklistedKernelModules = [
"dccp"
"sctp"
"rds"
"tipc"
"n-hdlc"
"ax25"
"netrom"
"x25"
"rose"
"decnet"
"econet"
"af_802154"
"ipx"
"appletalk"
"psnap"
"p8023"
"p8022"
"can"
"atm"
"cramfs"
"freevxfs"
"jffs2"
"hfs"
"hfsplus"
"squashfs"
"udf"
"cifs"
"nfs"
"nfsv3"
"nfsv4"
"ksmbd"
"gfs2"
"vivid"
];
Misc
OpenSSH Configuration
Here is my OpenSSH config, which disables password authentication and some vulnerable features of the SSH protocol.
services.openssh = {
settings = {
passwordAuthentication = false;
allowSFTP = false; # Don't set this if you need sftp
challengeResponseAuthentication = false;
};
extraConfig = ''
AllowTcpForwarding yes
X11Forwarding no
AllowAgentForwarding no
AllowStreamLocalForwarding no
AuthenticationMethods publickey
'';
};
Sudo
For security, you should restrict sudo
to users in the wheel
group only by using this line:
security.sudo.execWheelOnly = true;
Pipewire
It is recommended that you use Pipewire instead of PulseAudio, as it is more secure and supports sandboxing:
hardware.pulseaudio.enable = false;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
# If you want to use JACK applications, uncomment this
#jack.enable = true;
# use the example session manager (no others are packaged yet so this is enabled by default,
# no need to redefine it in your config for now)
#media-session.enable = true;
};
Wayland
I would recommend that you use Wayland, as Xorg is severely outdated and has not been updated in years: the Wayland protocol is stable, more secure and provides application sandboxing which the ancient Xorg does not.
# Fix GTK themes on Wayland
programs.dconf.enable = true;
# For KDE Plasma
services.xserver.displayManager.defaultSession = "plasmawayland";
Memory Allocator
For your memory allocator, I would recommend that you use either scudo
or graphene-hardened
, as the default one is routinely exploited and contains numerous security issues: scudo
is probably the best choice of the two, as it doesn't break most apps unlike the graphene-hardened
package which has issues with Firefox and more.
environment.memoryAllocator.provider = "scudo";
Full-Disk Encryption
Of course, no secure setup is complete without full-disk encryption! Be sure to use LUKS to encrypt all of your hard drives when setting up NixOS, as it will prevent unauthorized users from gaining access to your sensitive data or modify your system while you're not looking!
Conclusion
Overall, I'd like to thank Madaidan for their excellent guide to Linux hardening, as it is the source for most of this article. I hope that you have fun fiddling with NixOS!