Kernel
UraniumOS runs the mainline Linux kernel, built from the source of the nixpkgs pin without patches and without module support. Version 0.1.0 uses Linux 7.3.0, with the release string 7.3.0-uranium. UraniumOS adds a configuration file, recipes/linux-uranium.config, and the recipe that builds with it. The kernel and the initramfs are the packages linux-uranium and initramfs-uranium, and scram boot copies them to the EFI System Partition (see boot). A frame stores the kernel it was taken with and boots with it. The runtime settings are described in security.
How it is built
The recipe recipes/linux-uranium.nix takes version and src from the nixpkgs attribute linux_latest and runs this:
make defconfig
scripts/kconfig/merge_config.sh -m .config linux-uranium.config
make olddefconfig
make -j$NIX_BUILD_CORES bzImage
The result is installed as boot/vmlinuz and boot/config (the final .config) in the package. The build needs bc bison flex perl openssl elfutils. The recipe sets hardeningDisable = [ "all" ], which turns off the compiler flags nixpkgs adds, because the kernel sets it's own. Binaries are not stripped. The license is GPL-2.0-only.
The file is a fragment on top of the default x86-64 configuration, so every option it does not mention keeps the default of that kernel version, and olddefconfig changes options whose dependencies are not met. The built configuration can therefore differ from the fragment. The tables below come from the final boot/config.
The kernel is not in the binary cache, so rod +i linux-uranium compiles it on the machine. The version follows the package index pin, so rod +s and an update moves it.
What is in the configuration
The fragment covers, besides hardening: EFI boot and the EFI framebuffer, the simple DRM driver with virtio-gpu, Bochs and i915, virtio devices, NVMe, SATA and USB storage, USB HID, Intel and Realtek Ethernet (e1000, e1000e, igb, r8169), btrfs, ext4, vfat, XFS, exFAT, NTFS3, NFS, squashfs, ISO 9660, overlayfs and FUSE, device mapper with dm-crypt, zram swap, nftables (NF_TABLES with conntrack, NAT, masquerade, log, limit and reject), WireGuard, tun, bridge, veth, macvlan and VLAN, KVM for Intel and AMD, namespaces and cgroups, Intel HDA and USB audio. Wi-Fi chip drivers for Intel, Atheros, Realtek, Mediatek and Broadcom are not set, although cfg80211 and mac80211 are present from the default configuration. Bluetooth and the AMD and Nouveau graphics drivers are not set either. See hardware and firmware.
Hardening options that are on
All of these are set to y in the built configuration.
Memory layout
RANDOMIZE_BASE,RANDOMIZE_MEMORY- The kernel image and the large memory regions (direct map, vmalloc, vmemmap) are placed at random addresses at each boot.
STRICT_KERNEL_RWX,DEBUG_WX- Kernel code and read-only data are not writable, and data is not executable. At boot the kernel checks its page tables and warns about any page that is both writable and executable.
STRICT_DEVMEM,IO_STRICT_DEVMEM/dev/memonly gives access to memory that is not RAM, and to I/O regions that no driver has claimed.DEVMEMitself stays on.LEGACY_VSYSCALL_NONE- The default for the legacy vsyscall page is none, so it is not mapped. The command line also has
vsyscall=none.
Stacks
STACKPROTECTOR,STACKPROTECTOR_STRONG- Functions with arrays or with locals whose address is taken get a canary. A smashed canary stops the kernel.
VMAP_STACK,SCHED_STACK_END_CHECK- Kernel stacks are mapped with guard pages, and the end of the stack is checked on scheduling.
RANDOMIZE_KSTACK_OFFSET,RANDOMIZE_KSTACK_OFFSET_DEFAULT- The kernel stack gets a random offset on each system call. It is on by default, and
randomize_kstack_offset=onon the command line says so again. INIT_STACK_ALL_ZERO- Stack variables are initialized to zero.
Heap and copies
INIT_ON_ALLOC_DEFAULT_ON- Pages and heap objects are zeroed when they are allocated. Zeroing on free (
INIT_ON_FREE_DEFAULT_ON) is not set. ZERO_CALL_USED_REGS- Registers a function used are cleared when it returns.
SLAB_FREELIST_RANDOM,SLAB_FREELIST_HARDENED,SHUFFLE_PAGE_ALLOCATOR- The slab and page allocators hand out memory in a random order, and the free-list pointers of slab objects are obfuscated.
slab_nomerge(command line)- Caches of similar objects are not merged. The option
SLAB_MERGE_DEFAULTis on, so the command line is what turns merging off. HARDENED_USERCOPY,FORTIFY_SOURCE- Copies between user and kernel memory are checked against the size of the object, and string and memory functions are bounds-checked.
LIST_HARDENED,BUG_ON_DATA_CORRUPTION- Linked lists are checked, and detected corruption stops the kernel and does not continue.
The CPU
X86_KERNEL_IBT- Indirect branches in the kernel must land on marked entry points. This works on CPUs with indirect branch tracking.
X86_USER_SHADOW_STACK- Programs that opt in can use the hardware shadow stack.
MITIGATION_PAGE_TABLE_ISOLATION,MITIGATION_RETPOLINE,MITIGATION_SLS- Meltdown, Spectre variant 2 and straight-line speculation. The other
MITIGATION_*options andCPU_MITIGATIONSkeep their defaults and are on.
Security modules and filters
SECCOMP,SECCOMP_FILTER- A program can restrict its own system calls.
SECURITY_LANDLOCK- A program can restrict its own file access without privileges.
SECURITY_YAMA- Provides
kernel.yama.ptrace_scope, which/etc/sysctl.confsets to 2. SECURITY_DMESG_RESTRICT- The kernel log is for root.
BPF_UNPRIV_DEFAULT_OFF- Unprivileged use of
bpf()starts disabled. CONFIG_LSM="landlock,lockdown,yama,integrity"- The order of the security modules. Only
landlockandyamado anything here, becauselockdownis not built andintegrityhas no policy.
Options that are off or not what the fragment asks for
Off on purpose
MODULES- Everything is built in and no module can be loaded. A driver that is not in the configuration is missing.
KEXEC,HIBERNATION,PROC_KCORE- A second kernel cannot be booted from a running one, memory cannot be written to disk for resume, and kernel memory is not exposed as
/proc/kcore. X86_IOPL_IOPERM,BINFMT_MISC,LEGACY_PTYS,COMPAT_BRK- The
ioplandiopermcalls, registration of extra binary formats, BSD-style ptys and the legacy brk layout are not built.
In the fragment as off, but on or absent in the built configuration
DEBUG_FS=n- Built in anyway.
debugfs=offon the command line keeps it from being mounted or used. MODIFY_LDT_SYSCALL=n- On in the built kernel. The
modify_ldtsystem call exists. BPF_JIT_ALWAYS_ON=y- The option does not exist in the built kernel, and
BPF_JITis not set. BPF programs that root loads run in the interpreter.net.core.bpf_jit_hardendoes not exist either, see security. RANDOM_KMALLOC_CACHES=y- The option does not exist in 7.3.
KMALLOC_PARTITION_CACHES, the option of that kernel for seperate kmalloc caches, is not set. ACPI_CUSTOM_METHOD=n,FB_SIMPLE=y- Neither option exists in the built configuration.
DRM_SIMPLEDRMand the EFI framebuffer (FB_EFI) are on.
A fragment can ask for an option that the current version no longer has, and olddefconfig drops it without a message.
Left at the default, and wider than a hardened kernel would have
USER_NS(andNAMESPACES,PID_NS,NET_NS,UTS_NS,IPC_NS)- On. See the section on user namespaces below.
BPF_SYSCALL,BPF_EVENTS,KPROBES,FTRACE,PERF_EVENTS- On. The
bpf()call exists, for root only after the sysctl. IA32_EMULATION- On, so 32-bit programs run and the 32-bit system call entry exists.
X86_X32_ABIis off. IO_URING- On, with no sysctl to limit it.
LEGACY_TIOCSTI- On, and
/etc/sysctl.confdoes not setdev.tty.legacy_tiocsti. A process can push characters into the input of its own terminal. See sandboxes in security. DEVPORT,X86_MSR,X86_CPUID,KALLSYMS,X86_VSYSCALL_EMULATION- On. Reading kernel symbol addresses needs
kernel.kptr_restrict, which the sysctl file sets. SECURITY_SELINUX,SECURITY_NETWORK,AUDIT- SELinux is compiled in from the default configuration, but it is not in
CONFIG_LSM, so it does not start, and no policy is installed. AppArmor, Smack, Tomoyo, LoadPin and SafeSetID are not built.
Hardening options that are not set
These are common in hardened configurations and are not set here: INIT_ON_FREE_DEFAULT_ON, SECURITY_LOCKDOWN_LSM, KFENCE, UBSAN, PAGE_TABLE_CHECK, STATIC_USERMODEHELPER, KSTACK_ERASE, IMA, SECURITY_LOADPIN, SECURITY_SAFESETID, RESET_ATTACK_MITIGATION. Kernel lockdown is not built, so nothing stops root from using /dev/mem within the limits above, /dev/port and the MSR interface. Secure Boot is not supported, so the kernel and limine on the EFI System Partition are not signed.
User namespaces and BPF
Both are on, and both matter for the trade-off in security.
USER_NS is on because hazmat and silo run bubblewrap, and bubblewrap is not setuid here, since the only setuid file in the base system is doas. Without unprivileged user namespaces bubblewrap cannot create the sandbox. The cost is attack surface. In a user namespace a process gets capabilities over its own namespaces, for example CAP_NET_ADMIN in a new network namespace, and can reach kernel code that is otherwise restricted to root, such as the nftables code built into this kernel. Local privilege escalations in nftables have been exploited through user namespaces. The mainline kernel has no switch to allow user namespaces for root only. user.max_user_namespaces set to 0 turns them off for everyone and breaks the two tools, and /etc/sysctl.conf does not set it.
BPF_SYSCALL is on and unprivileged use is off, from BPF_UNPRIV_DEFAULT_OFF and from kernel.unprivileged_bpf_disabled = 1 in the sysctl file, which cannot be reverted without a reboot. A user namespace does not give the right to load BPF programs.
Kernel arguments
scram boot builds the command line of every menu entry as root=LABEL=uranium rw console=tty0 console=ttyS0,115200 followed by the single line of /etc/cmdline, and uranium.frame=ID for a frame entry. The file as installed:
slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on
slab_nomerge- Do not merge slab caches of similar objects.
vsyscall=none- Do not emulate the legacy vsyscall page.
debugfs=off- debugfs is not available, though it is built.
randomize_kstack_offset=on- Randomize the kernel stack offset on each system call.
The file is read when scram boot runs, so run it after editing and reboot. It has to stay a single line, and a new file replaces the hardening arguments. To add an argument, append to the first line:
# sed -i '1s/$/ quiet/' /etc/cmdline
# scram boot
The arguments of the initramfs (uranium.shell, uranium.luks and so on) are in boot.
The initramfs
The package initramfs-uranium builds a gzip-compressed cpio archive with the script recipes/uranium-init.sh as /init, btrfs and cryptsetup, and some busybox applets. It has no kernel modules, no firmware and no CPU microcode. CONFIG_MICROCODE is on and late microcode loading is not.
Update
A new kernel comes with the package. rod +u builds the update in a copy of the system and leaves it waiting, and scram commit --boot schedules it for the next boot. scram boot copies the kernel of the profile to the EFI System Partition, and rod runs it after its own operations. Frames keep the kernels they were taken with. The boot partition holds one copy of each kernel, and at most three besides the current one (SCRAM_KERNELS). A frame that would need a fourth boots with the current kernel.
# rod +u
# scram commit --boot
# reboot
The configuration is /rod/recipes/linux-uranium.config, and the kernel in use has its final configuration in /rod/profile/boot/config. geiger compares the version in it with the running kernel.
Your own kernel
A recipe of the same name in /rod/recipes replaces the system one, see recipes. Copy linux-uranium.nix and the configuration, change the configuration and run rod +r linux-uranium +i linux-uranium. If the new kernel does not boot, choose a frame in the boot menu.
Modules
Drivers are built in and modules are not loaded. A driver that is not in the configuration is missing, and the fix is a rebuilt kernel.