Security

Overview

UraniumOS 0.1.1 is meant for one user, or a few users, on an x86-64 machine with UEFI. This page describes the settings that the system ships with.

Defaults

Notes

Unprivileged user namespaces are onBubblewrap creates the sandboxes of hazmat and silo without a setuid helper, so the kernel has CONFIG_USER_NS=y, and no sysctl turns it off. Any local user can therefore create namespaces, and can reach kernel code, for example nftables, that is otherwise limited to root. See kernel.
The sandboxes share the kernel and the user idA kernel bug crosses the sandbox. Inside it, the process has your numeric user and group id (root when it was started by root), and not a separate one. Neither tool passes --new-session, and the kernel has LEGACY_TIOCSTI=y with dev.tty.legacy_tiocsti unset in /etc/sysctl.conf, so a sandboxed process can push input into the terminal from which it was started.
An empty home covers files onlyThe sandbox hides files. It does not limit the network (which is shared unless -n is given), the CPU, the memory, the processes that it can spawn, or the host store: /nix is mounted read-only in full, and every store path is readable.
The one setuid program is doasIt is a copy at /rod/wrappers/doas with mode 4755, which is made when the stage is built, because files in the store cannot be setuid. The tools and the installer do not refresh the copy. doas is built without PAM and reads /etc/shadow itself.
ssh allows passwordsPasswordAuthentication yes is set in /etc/ssh/sshd_config. The firewall limits new ssh connections to ten a minute for all sources together, which slows down guessing and allows one source to keep others out.
Frames are on the same diskThey are not a backup. See Frames.
Nix on a non-NixOS layoutThe layout is not FHS. /lib links into the profile, and a prebuilt foreign binary fails unless its libraries are installed. See packages not in rod.
No build sandbox/etc/nix/nix.conf sets sandbox = false and an empty build-users-group.
Home and /tmp allow executionnoexec is set on /boot and /dev/shm only. A downloaded program may therefore run from the home directory or from /tmp.

Where each setting lives

The files below are installed from stage/skel/etc in the source tree into /etc of the system. The kernel settings come from recipes and are read from /rod/profile/boot/config on a running system.

/rod/recipes/linux-uranium.configKernel options, merged over the default x86-64 configuration. The result is /rod/profile/boot/config. See kernel.
/etc/cmdlineKernel command line additions. Read by scram boot.
/etc/sysctl.confRuntime kernel settings, applied by /etc/rc with sysctl -q -p.
/etc/nftables.conf, /etc/nftables.d/*.nftFirewall. Loaded by /etc/rc.
/etc/doas.conf, /rod/wrappers/doasPrivilege escalation. The recipe recipes/doas-uranium.nix builds doas with withPAM = false; withTimestamp = true.
/etc/ssh/sshd_configThe ssh server. See ssh.
/etc/fstab, /etc/rcMount options. The installer writes /etc/fstab, and /etc/rc mounts /tmp and /dev/shm.
/etc/nix/nix.confBuild and cache settings.
/etc/scram.d/00-uranium.confPaths that frames leave out.
/etc/signify/uranium-0.1.pubThe public release key.

Accounts and doas

The file /etc/doas.conf contains one line:

permit persist :wheel

The recipe compiles in timestamp support (withTimestamp = true). setup puts the first user in wheel.

$ doas rod +i git

The root account has a password only if the installer or setup --root-password-stdin set one. setup stores passwords with chpasswd in /etc/shadow, which has mode 600. On the console, login is busybox login. Over ssh, PermitRootLogin no.

Network

The firewall rules are listed rule by rule in blastdoor. Input and forward drop by default. Loopback, replies, all ICMP and ICMPv6 and DHCP replies are accepted, new ssh connections are accepted at ten a minute, and the output chain is open. The ruleset is loaded at boot with errors discarded; geiger warns when no policy is loaded.

The ssh server is off after a fresh install of the minimal profile. setup --ssh and the server profile enable it. The settings in /etc/ssh/sshd_config:

Port 22
HostKey /etc/ssh/ssh_host_ed25519_key
PermitRootLogin no
PasswordAuthentication yes
KbdInteractiveAuthentication no
UsePAM no
PrintMotd no
AllowTcpForwarding yes
X11Forwarding no

Only an Ed25519 host key is used. The run script creates it on first start. TCP forwarding is allowed. The server separates privileges with the user sshd (uid 74, home /var/empty, shell /rod/profile/bin/false).

Kernel

Options are described one by one in kernel. The command line is in /etc/cmdline:

slab_nomerge vsyscall=none debugfs=off randomize_kstack_offset=on

CONFIG_MODULES is off, so no module can be loaded. CONFIG_USER_NS is on. CONFIG_BPF_SYSCALL is on, BPF_UNPRIV_DEFAULT_OFF is on and BPF_JIT is not set. KEXEC, HIBERNATION and PROC_KCORE are off. Yama and Landlock are built, and SELinux is compiled in but does not start. The kernel lockdown module is not built.

Runtime settings

/etc/sysctl.conf is applied by /etc/rc at every boot. Output and errors of the command are discarded, so a failing key is not reported.

kernel.kptr_restrict = 2
Kernel pointers in /proc and in the log print as zeros for every user.
kernel.dmesg_restrict = 1
Only root reads the kernel log. The kernel is also built with SECURITY_DMESG_RESTRICT.
kernel.perf_event_paranoid = 3
The value 3 is defined by patches that mainline Linux does not have. In the mainline kernel of UraniumOS a value above 2 acts as 2: users without privileges cannot profile the kernel, but they can still use perf on their own processes in user space.
kernel.yama.ptrace_scope = 2
Only a process with CAP_SYS_PTRACE can use ptrace. gdb and strace need doas.
kernel.unprivileged_bpf_disabled = 1
Unprivileged bpf() is off until the next reboot. The setting cannot be changed back at run time.
kernel.randomize_va_space = 2
Stack, heap and mappings of programs are randomized. This is the kernel default.
kernel.sysrq = 0
The magic SysRq key is off.
kernel.core_pattern = |/bin/false, fs.suid_dumpable = 0
Core dumps are piped to /bin/false and discarded. Setuid programs do not dump. For debugging, set kernel.core_pattern = core with doas sysctl -w.
fs.protected_symlinks = 1, fs.protected_hardlinks = 1, fs.protected_fifos = 2, fs.protected_regular = 2
In sticky world-writable directories such as /tmp, a program does not follow a symlink, or open a fifo or a regular file, that another user created.
net.ipv4.tcp_syncookies = 1, net.ipv4.tcp_rfc1337 = 1
SYN cookies under SYN flood, and protection of connections in TIME-WAIT from forged resets.
net.ipv4.conf.all.rp_filter = 1, net.ipv4.conf.default.rp_filter = 1
Strict reverse-path filtering: packets whose source address would not be routed back through the interface they came in on are dropped.
net.ipv4.conf.{all,default}.accept_redirects = 0, net.ipv6.conf.{all,default}.accept_redirects = 0
ICMP redirects are not accepted.
net.ipv4.conf.all.send_redirects = 0
The machine does not send redirects.
net.ipv4.conf.all.accept_source_route = 0, net.ipv6.conf.all.accept_source_route = 0
Source-routed packets are refused.
net.ipv4.conf.all.log_martians = 1
Packets with impossible source addresses are logged to the kernel log.
net.ipv4.icmp_echo_ignore_broadcasts = 1
Echo requests sent to a broadcast address are ignored.

For the redirect and source-route keys the kernel combines the all value with the value of each interface, so an interface that was configured seperately can behave differently.

Mounts

/btrfs subvolume @, noatime. Setuid and device files work on it, so doas lives here.
/homebtrfs subvolume @home, noatime,nodev,nosuid.
/bootvfat EFI System Partition, umask=0077,nodev,nosuid,noexec. Only root can read it.
/tmptmpfs, nosuid,nodev,mode=1777, set in /etc/rc.
/dev/shmtmpfs, nosuid,nodev,noexec,mode=1777, set in /etc/rc.

A setuid file or a device node in a home directory or in /tmp has no effect. /var/tmp is on the root subvolume with mode 1777 and has none of these options.

Sandboxes and containers

Both tools replace themselves with bubblewrap. The comments in the code blocks are not part of the call.

hazmat

nix shell -f /rod/nixpkgs PACKAGE... -c
  bwrap --unshare-all [--share-net] --die-with-parent
    --ro-bind /nix /nix --ro-bind /rod/profile /rod/profile
    --ro-bind /bin /bin --ro-bind /usr /usr
    --ro-bind /lib /lib --ro-bind /lib64 /lib64
    --ro-bind /etc/resolv.conf /etc/resolv.conf --ro-bind /etc/ssl /etc/ssl
    --proc /proc --dev /dev --tmpfs /tmp --tmpfs /home
    [--ro-bind P P | --bind P P]...      # from -r and -w
    --setenv HOME /tmp --chdir /tmp
    sh -c COMMAND

hazmat -s (services)

bwrap --unshare-all [--share-net] --die-with-parent
  --proc /proc --dev /dev --tmpfs /tmp --chdir /
  [--ro-bind P P | --bind P P]...        # from the sandbox file
  --ro-bind X X                          # for each of /nix /rod/profile /bin /usr /lib /lib64 /etc/ssl /etc/resolv.conf that exists
  COMMAND...

silo

bwrap --unshare-all [--share-net] --die-with-parent --hostname NAME
  --ro-bind /nix /nix --ro-bind PROFILE /rod/profile
  --dir /usr --symlink rod/profile/bin /bin --symlink ../rod/profile/bin /usr/bin
  --symlink rod/profile/bin /sbin --symlink rod/profile/lib /lib
  --bind C/etc /etc
  --ro-bind /etc/resolv.conf /etc/resolv.conf --ro-bind /etc/ssl /etc/ssl
  --bind C/home /root
  --proc /proc --dev /dev --tmpfs /tmp
  --setenv HOME /root --setenv PATH /rod/profile/bin --setenv PS1 PROMPT
  --chdir /root
  /rod/profile/bin/sh -c COMMAND

--unshare-all in bubblewrap means new user, IPC, PID, UTS and cgroup namespaces and a network namespace, plus the mount namespace that bubblewrap always creates. The user namespace is created with --unshare-user-try. --share-net keeps the network of the host and is passed unless -n is given. The tools give the process these things:

The unprivileged user namespace is why CONFIG_USER_NS stays on. With user.max_user_namespaces = 0 these two tools cannot run. Details of each tool are in hazmat and silo.

Packages and updates

Packages come from nixpkgs through nix-env profiles on a layout that is not NixOS, managed by rod. The system uses the binary cache https://cache.nixos.org (substituters in /etc/nix/nix.conf). The file sets no trusted-public-keys, so nix uses it's built-in default, which is the key of that cache, and checks signatures of what it downloads. A package that the cache does not have is built on the machine. The file also contains:

build-users-group =
sandbox = false
experimental-features = nix-command

A local build therefore runs as the calling user with access to the file system and the network. The index is pinned to one nixpkgs revision and moves only when you run rod +s or rod +u. A security fix reaches the machine when the pin moves and you update. geiger warns when the index is more than 60 days old. rod +h shows what an update would change and rod +u builds it in a copy of the system. The pin and the file from rod +o give the same package set again. They do not give identical binaries, since a build may differ between machines.

Frames

scram takes a frame before every package operation, every update and every flash, and the autoframe service takes one every hour. A frame is a read-only btrfs snapshot of the root subvolume @ and of @home. The root holds /etc, /nix, /rod and /var, so the frame holds the package store and the profiles, the firewall, the users and the containers.

A frame leaves out:

A flash replaces the live subvolumes with the frame. It does not undo what happened outside them: data on excluded paths, other machines, secrets that were already copied away. It also returns home directories to the time of the frame unless --root is given. Frames contain files deleted later, so a secret removed from the live system stays in older frames until they are pruned. Frames are read-only to your user, root can delete them, and they share the disk with the system: a failed disk takes them with it. For copies on another machine see backups.

An update that is committed boots on trial. The service trial runs scram verify, which waits five minutes, then runs geiger up to three times, and reboots to revert the update if all runs fail. The scram manual page says that a system that cannot boot at all reverts at the second power cycle.

Signatures

stage/release.sh writes the file SHA256 with sha256sum for the ISO, the stage tarball and the tools tarball, and signs that file with signify into SHA256.sig. The public key is uranium-0.1.pub. It is on the download page, in the live system and in an installed system as /etc/signify/uranium-0.1.pub. To check the list and then the files:

$ signify -V -p uranium-0.1.pub -m SHA256 -x SHA256.sig
$ sha256sum -c --ignore-missing SHA256

See download.