Building a Free and Minimal Unix System (Part 3)- Sensible Security Hardening

Security is often framed as something that must be added to a system: more daemons, more policies, more layers of complexity. In practice, the most reliable improvements usually come from the opposite direction — removing unnecessary functionality, tightening defaults, and making the system’s behaviour easier to understand.

This post documents the security hardening applied to my Artix Linux system, running runit, as part of an ongoing effort to build a free and minimal Unix environment. The emphasis here is not on exhaustive defence, but on sensible reduction of attack surface, applied deliberately and with an understanding of the trade-offs involved.

If you have read part 2 of this series, then after ensuring I have a free, secure encrypted operating system installed. I apply these next steps. I do this before installing any graphical environment. Then after this process is complete I move on to configuring my system.

Where appropriate, the linux-hardened kernel should be used for maximum hardening, but in my experience it caused issues with software and hardware. So I opted to add the hardening settings and tools that I thought were of most importance.


Packages required

Before applying the hardening steps below, ensure the following packages are installed. This list reflects what is actually used on my system; you may not need every package depending on your hardware and workflow.

sudo pacman -S \
  apparmor \
  apparmor-utils \
  audit \
  ufw \
  usbguard \
  openssh \
  pam \
  libpwquality \
  intel-ucode

Notes:

If you are using the linux-hardened kernel, AppArmor support is already enabled at build time.


Kernel hardening via sysctl

Kernel hardening is applied using small, purpose-specific files in /etc/sysctl.d/. This keeps changes auditable and avoids a single monolithic configuration that becomes difficult to reason about over time.

Apply changes with:

sysctl --system

Below are the exact files I use.

Disable core dumps

File: /etc/sysctl.d/coredump.conf

# disable core dumps
kernel.core_pattern=|/bin/false

File: /etc/sysctl.d/suid_dumpable.conf

# Prevent setuid programs from dumping memory.
fs.suid_dumpable=0

Restrict kernel information leaks

File: /etc/sysctl.d/dmesg_restrict.conf

# Only root can read kernel logs.
kernel.dmesg_restrict=1

File: /etc/sysctl.d/kptr_restrict.conf

# Hide kernel pointers (strong setting).
kernel.kptr_restrict=2

Disable dangerous debug facilities

File: /etc/sysctl.d/sysrq.conf

# Disable SysRq.
kernel.sysrq=0

File: /etc/sysctl.d/kexec.conf

# Disable kexec (prevents replacing the running kernel).
kernel.kexec_load_disabled=1

Harden BPF

File: /etc/sysctl.d/harden_bpf.conf

# Disable unprivileged BPF and harden the JIT.
kernel.unprivileged_bpf_disabled=1
net.core.bpf_jit_harden=2

Harden ASLR for mmap

File: /etc/sysctl.d/mmap_asir.conf

# Improve ASLR effectiveness for mmap.
vm.mmap_rnd_bits=32
vm.mmap_rnd_compat_bits=16

Restrict process inspection (ptrace)

File: /etc/sysctl.d/ptrace_scope.conf

# Restrict ptrace to processes with CAP_SYS_PTRACE.
kernel.yama.ptrace_scope=2

Disable unprivileged user namespaces

File: /etc/sysctl.d/unprivileged_userns_clone.conf

# Disable unprivileged user namespaces (reduces kernel attack surface).
kernel.unprivileged_userns_clone=0

# Note: may break some sandboxing tools (e.g. bubblewrap) unless adjusted.

Swap behaviour

File: /etc/sysctl.d/ram-swap.conf

vm.swappiness=5

TCP / network hardening

File: /etc/sysctl.d/tcp_hardening.conf

# SYN flood mitigation
net.ipv4.tcp_syncookies=1

# Time-wait assassination protection
net.ipv4.tcp_rfc1337=1

# Source validation (anti-spoofing)
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1

# Disable ICMP 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

# Disable ICMP redirect sending (non-router)
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.default.send_redirects=0

# Optional: ignore ICMP echo requests (stealth, but reduces debuggability)
net.ipv4.icmp_echo_ignore_all=1

Disable TCP SACK (optional, read the trade-off)

File: /etc/sysctl.d/tcp_sack.conf

# Disables TCP SACK. May reduce performance on lossy/high-latency links.
net.ipv4.tcp_sack=0

IPv6 privacy extensions (optional)

File: /etc/sysctl.d/ipv6_privacy.conf

net.ipv6.conf.all.use_tempaddr = 2
net.ipv6.conf.default.use_tempaddr = 2
net.ipv6.conf.wlan0.use_tempaddr = 2

WireGuard forwarding (specific use case)

File: /etc/sysctl.d/90-wireguard.conf

net.ipv4.ip_forward=1

Reducing kernel attack surface with module blacklisting

Unused kernel modules represent unnecessary attack surface. Modules are disabled using the install <module> /bin/true mechanism so they cannot be loaded.

All files live in /etc/modprobe.d/.

Disable Bluetooth

File: /etc/modprobe.d/blacklist-bluetooth.conf

install btusb /bin/true
install bluetooth /bin/true

Disable DMA-capable hardware

File: /etc/modprobe.d/blacklist-dma.conf

install firewire-core /bin/true
install thunderbolt /bin/true

Optional: disable audio devices

File: /etc/modprobe.d/blacklist-microphone-speakers.conf

# install snd_hda_intel /bin/true

Disable conntrack helpers

File: /etc/modprobe.d/no-conntrack-helper.conf

options nf_conntrack nf_conntrack_helper=0

Disable uncommon filesystems

File: /etc/modprobe.d/uncommon-filesystems.conf

install cramfs /bin/true
install freevxfs /bin/true
install jffs2 /bin/true
# install hfs /bin/true
# install hfsplus /bin/true
install squashfs /bin/true
install udf /bin/true

Disable uncommon / legacy network protocols

File: /etc/modprobe.d/uncommon-network-protocols.conf

install dccp /bin/true
install sctp /bin/true
install rds /bin/true
install tipc /bin/true
install n-hdlc /bin/true
install ax25 /bin/true
install netrom /bin/true
install x25 /bin/true
install rose /bin/true
install decnet /bin/true
install econet /bin/true
install af_802154 /bin/true
install ipx /bin/true
install appletalk /bin/true
install psnap /bin/true
install p8023 /bin/true
install llc /bin/true
install p8022 /bin/true

Authentication hardening with PAM

All authentication hardening is applied in /etc/pam.d/system-auth.

PAM is order-sensitive; test changes in a second session before logging out.

Edit the file:

sudoedit /etc/pam.d/system-auth

Account lockout (pam_faillock)

Ensure the auth section contains the following logic in this order:

# FAILLOCK: Pre-authentication check
auth required pam_faillock.so preauth silent audit deny=3 unlock_time=900

# Validate password
auth [success=1 default=bad] pam_unix.so try_first_pass

# FAILLOCK: Handle failed attempts
auth [default=die] pam_faillock.so authfail deny=3 unlock_time=900

# FAILLOCK: Successful authentication
auth required pam_faillock.so authsucc

Password policy (pam_pwquality) and hashing

Locate the password section and ensure it contains:

password required pam_pwquality.so retry=3 minlen=12 difok=6 \
ucredit=-1 lcredit=-1 dcredit=-1 ocredit=-1 enforce_for_root

Immediately after, ensure hashing uses strong defaults:

password sufficient pam_unix.so sha512 shadow try_first_pass \
use_authtok rounds=65536

SSH hardening

SSH is often the only externally reachable service. The goal here is not to detect attacks, but to remove entire classes of them.

Edit:

sudoedit /etc/ssh/sshd_config

Suggested baseline:

Protocol 2
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes

PubkeyAuthentication yes

X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no

LoginGraceTime 30
MaxAuthTries 3
MaxSessions 2

LogLevel VERBOSE

Reload SSH (runit):

sudo sv restart sshd

With password authentication disabled, most automated SSH attacks become irrelevant.


Restricting root access

The root account exists, but it does not need to be usable all the time.

Lock root by default:

sudo passwd -l root

Unlock only when required:

sudo passwd -u root

Re-lock afterwards:

sudo passwd -l root

Locking root does not prevent administrative access via sudo. Even if SSH were misconfigured, a locked root account prevents the most damaging failure mode.


Firewalling with UFW (safe, boring defaults)

The firewall is configured to fail closed by default:

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw default deny routed

Allow SSH:

sudo ufw allow 22/tcp

Enable and verify:

sudo ufw enable
sudo ufw status verbose

Restrictive outbound firewalls often create fragile systems that fail in surprising ways when networks change. Simplicity here is a feature.


USB device control with USBGuard (brief)

USB devices represent a physical attack surface, especially on laptops.

Install:

sudo pacman -S usbguard

Generate an initial policy:

sudo usbguard generate-policy > /etc/usbguard/rules.conf

Enable the service (runit):

sudo sv up usbguard
sudo ln -s /etc/runit/sv/usbguard /run/runit/service/

USBGuard blocks new USB devices by default and allows only explicitly authorised ones. This requires a short learning period.


Bootloader and early boot hardening (GRUB)

The bootloader is part of the trusted computing base. If an attacker can modify kernel parameters at boot, most OS-level hardening becomes irrelevant.

Kernel hardening parameters (GRUB)

File: /etc/default/grub

Edit:

sudoedit /etc/default/grub

These are the key hardening lines from my system (adjust to taste):

GRUB_CMDLINE_LINUX="apparmor=1 security=apparmor slab_nomerge init_on_alloc=1 init_on_free=1 page_alloc.shuffle=1 pti=on vsyscall=none oops=panic module.sig_enforce=1 lockdown=integrity mce=0 quiet loglevel=0"
GRUB_DISABLE_RECOVERY=true
# OS probing disabled for security (default on my setup)
# GRUB_DISABLE_OS_PROBER=false

Notes:

After editing, regenerate the GRUB config:

sudo grub-mkconfig -o /boot/grub/grub.cfg

GRUB password protection (superuser)

This prevents casual tampering with kernel parameters and boot entries.

  1. Generate a PBKDF2 hash:
grub-mkpasswd-pbkdf2

Copy the resulting grub.pbkdf2.sha512... string.

  1. Add it to GRUB custom config.

File: /etc/grub.d/40_custom

Edit:

sudoedit /etc/grub.d/40_custom

Add (replace username with a username of your choice):

set superusers="username"
password_pbkdf2 username grub.pbkdf2.sha512.10000.007CD9056C3ADD76E502EB624AA0DEF96E3E947CA4021A30BCF2C8A1648A1C9D6C572AD5BFA21700B338F967A69931499B2155754DEC67153E3C8CE2D5872B24.876BC296F2D8EEACB499043994F3AF3D3D5F8C0B5AF7FD04C413AE17F6E6D545F552900AA846AD2953798845B7914DC9D040E987B97DC110E7BC9D1FCAC14C82
  1. Regenerate GRUB config:
sudo grub-mkconfig -o /boot/grub/grub.cfg

Microcode updates (Intel)

CPU microcode updates patch real hardware vulnerabilities.

Install and integrate:

sudo pacman -S intel-ucode
sudo mkinitcpio -P
sudo grub-mkconfig -o /boot/grub/grub.cfg

Verify after reboot:

dmesg | grep -i microcode

Why this is enough (for my threat model, at least)

Security hardening is only meaningful when tied to a threat model.

This system is a single-user workstation, sometimes mobile, occasionally connected to untrusted networks. The goal is to remove unused functionality, prevent trivial escalation, and keep the system understandable over time.

I have not attempted to defend against supply-chain attacks, malicious hardware, or targeted physical compromise. Defending against those requires different tools and a different level of complexity.

This is not maximum security. It is sufficient, intentional security.


Conclusion

A secure system is not one with the most controls enabled, but one whose behaviour you understand when something breaks.