| 1 |
# Alloy kernel hardening. |
| 2 |
# |
| 3 |
# The number is load-bearing. systemd-sysctl applies these files in |
| 4 |
# lexicographic order and the last value wins, so a `50-` file would lose to |
| 5 |
# Fedora's own `50-coredump.conf` on any key they share. `90-` wins on merit |
| 6 |
# and on order. |
| 7 |
# |
| 8 |
# Derived from secureblue's hardening.conf, itself derived from Kicksecure's, |
| 9 |
# and trimmed to what a development machine can pay. Rationale and the full |
| 10 |
# adopt/reject list: wiki `alloy-hardening-posture`. |
| 11 |
# |
| 12 |
# Every entry states what it costs. An entry with no cost worth naming says so |
| 13 |
# rather than staying silent, because silence here reads as "nobody measured". |
| 14 |
|
| 15 |
# --------------------------------------------------------------------- |
| 16 |
# Kernel information leaks |
| 17 |
# --------------------------------------------------------------------- |
| 18 |
|
| 19 |
# Kernel pointers read as zero for everyone, including root. Costs: a |
| 20 |
# profiler or a crash dump that wants symbol addresses gets zeros. perf |
| 21 |
# still works; kernel symbol resolution in third-party tools may not. |
| 22 |
kernel.kptr_restrict=2 |
| 23 |
|
| 24 |
# The kernel ring buffer needs CAP_SYSLOG to read. `dmesg` as a user stops |
| 25 |
# working; `sudo dmesg` and `journalctl -k` are unaffected. |
| 26 |
kernel.dmesg_restrict=1 |
| 27 |
|
| 28 |
# --------------------------------------------------------------------- |
| 29 |
# Kernel integrity |
| 30 |
# --------------------------------------------------------------------- |
| 31 |
|
| 32 |
# No kexec, ever, until reboot. Costs kexec-based fast reboot and |
| 33 |
# kdump-on-panic. Neither is in this image. |
| 34 |
kernel.kexec_load_disabled=1 |
| 35 |
|
| 36 |
# eBPF needs CAP_BPF. Costs: an unprivileged `bpftrace` or `bcc` script. |
| 37 |
# Both need root here anyway. |
| 38 |
kernel.unprivileged_bpf_disabled=1 |
| 39 |
|
| 40 |
# Constant blinding in the BPF JIT for privileged users too. Costs a small |
| 41 |
# amount of JIT throughput on a machine that runs almost no BPF. |
| 42 |
net.core.bpf_jit_harden=2 |
| 43 |
|
| 44 |
# --------------------------------------------------------------------- |
| 45 |
# NOT SET, and this is the entry that explains the shape of the file |
| 46 |
# --------------------------------------------------------------------- |
| 47 |
# |
| 48 |
# kernel.yama.ptrace_scope=2 is what secureblue ships and it is not here. |
| 49 |
# Fedora already sets scope 1 (/usr/lib/sysctl.d/10-default-yama-scope.conf), |
| 50 |
# which stops a process attaching to a non-descendant. Scope 2 requires |
| 51 |
# CAP_SYS_PTRACE for any attach at all, which breaks `gdb -p`, `perf record |
| 52 |
# -p`, and rust-gdb against an already-running process. |
| 53 |
# |
| 54 |
# Alloy is the machine its author develops on. A mitigation that makes the |
| 55 |
# debugger ask for root every time is one that gets turned off, and a |
| 56 |
# hardening file nobody leaves enabled hardens nothing. |
| 57 |
|
| 58 |
# --------------------------------------------------------------------- |
| 59 |
# Filesystem |
| 60 |
# --------------------------------------------------------------------- |
| 61 |
|
| 62 |
# Symlink and hardlink following restricted in world-writable sticky |
| 63 |
# directories: the classic /tmp race. Fedora sets both already; stated here |
| 64 |
# so the policy is legible in one file rather than inherited. |
| 65 |
fs.protected_symlinks=1 |
| 66 |
fs.protected_hardlinks=1 |
| 67 |
|
| 68 |
# Same idea for FIFOs and regular files: no writing to something you do not |
| 69 |
# own in a directory you do not own. 2 covers group-writable directories too. |
| 70 |
fs.protected_fifos=2 |
| 71 |
fs.protected_regular=2 |
| 72 |
|
| 73 |
# --------------------------------------------------------------------- |
| 74 |
# Network stack |
| 75 |
# --------------------------------------------------------------------- |
| 76 |
# |
| 77 |
# These matter more once the machine is on a network it does not control, |
| 78 |
# which for a laptop is most of them. |
| 79 |
|
| 80 |
# SYN flood mitigation. |
| 81 |
net.ipv4.tcp_syncookies=1 |
| 82 |
|
| 83 |
# Reverse-path filtering, LOOSE (2) rather than strict (1). Drops packets |
| 84 |
# whose source address has no route back at all, which is the spoofing case, |
| 85 |
# while permitting a reply to leave by a different interface than the request |
| 86 |
# arrived on. |
| 87 |
# |
| 88 |
# Strict is the stronger setting and it is not taken, because strict RPF is a |
| 89 |
# known way to break a Tailscale subnet router or exit node: the tailnet path |
| 90 |
# is legitimately asymmetric. Nothing here is an exit node today, and a |
| 91 |
# hardening default that quietly breaks the way every machine in this tree is |
| 92 |
# addressed the first time someone enables one is not worth the increment. |
| 93 |
net.ipv4.conf.all.rp_filter=2 |
| 94 |
net.ipv4.conf.default.rp_filter=2 |
| 95 |
|
| 96 |
# ICMP redirects, in both directions. A redirect is a router telling the |
| 97 |
# machine to change its route, and on an untrusted network that is an |
| 98 |
# attacker telling the machine to change its route. |
| 99 |
net.ipv4.conf.all.accept_redirects=0 |
| 100 |
net.ipv4.conf.default.accept_redirects=0 |
| 101 |
net.ipv4.conf.all.secure_redirects=0 |
| 102 |
net.ipv4.conf.default.secure_redirects=0 |
| 103 |
net.ipv4.conf.all.send_redirects=0 |
| 104 |
net.ipv4.conf.default.send_redirects=0 |
| 105 |
net.ipv6.conf.all.accept_redirects=0 |
| 106 |
net.ipv6.conf.default.accept_redirects=0 |
| 107 |
|
| 108 |
# Source routing: the sender choosing the path. No legitimate use here. |
| 109 |
net.ipv4.conf.all.accept_source_route=0 |
| 110 |
net.ipv4.conf.default.accept_source_route=0 |
| 111 |
net.ipv6.conf.all.accept_source_route=0 |
| 112 |
net.ipv6.conf.default.accept_source_route=0 |
| 113 |
|
| 114 |
# Log packets with impossible source addresses. |
| 115 |
net.ipv4.conf.all.log_martians=1 |
| 116 |
net.ipv4.conf.default.log_martians=1 |
| 117 |
|