Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)

nixos/stage-2-init: support nosuid/nodev mount options for /nix/store

This is part of security-in-depth.
No suid binaries or devices should ever be in the nix store.
If they are, something is seriously wrong.
Disallowing this from a file system level should be non-breaking.

Grimmauld 4e440ec1 6d295c7e

+54 -14
+30 -11
nixos/modules/system/boot/stage-2-init.sh
··· 61 61 fi 62 62 63 63 64 - # Make /nix/store a read-only bind mount to enforce immutability of 65 - # the Nix store. Note that we can't use "chown root:nixbld" here 64 + # Give /nix/store the defined mount options. 65 + # Typically, this should be: 66 + # - 'ro' to enforce immutability of the Nix store 67 + # - 'nosuid' to enforce no suid binaries make it into the store and get executed by accident. 68 + # suid-binaries should only exist in /run/wrappers. 69 + # If an attacker can make the nix builder produce suid binaries in the store, those should be useless. 70 + # Another example is tampering with the store from an outside system. 71 + # - 'nodev' to enforce no device files in the store 72 + # Note that we can't use "chown root:nixbld" here 66 73 # because users/groups might not exist yet. 74 + 67 75 # Silence chown/chmod to fail gracefully on a readonly filesystem 68 76 # like squashfs. 69 77 chown -f 0:30000 /nix/store 70 78 chmod -f 1775 /nix/store 71 - if [ -n "@readOnlyNixStore@" ]; then 79 + 80 + missing_opts=() # stores the missing mount options that still need to be applied to the nix store 81 + current_opts="$(findmnt --direction backward --first-only --noheadings --output OPTIONS /nix/store)" 82 + for mount_opt in @nixStoreMountOpts@ ; do 72 83 # #375257: Ensure that we pick the "top" (i.e. last) mount so we don't get a false positive for a lower mount. 73 - if ! [[ "$(findmnt --direction backward --first-only --noheadings --output OPTIONS /nix/store)" =~ (^|,)ro(,|$) ]]; then 74 - if [ -z "$container" ]; then 75 - mount --bind /nix/store /nix/store 76 - else 77 - mount --rbind /nix/store /nix/store 78 - fi 79 - mount -o remount,ro,bind /nix/store 84 + # matches '$opt', foo,$opt', '$opt,foo', 'foo,$opt,bar' 85 + # crucially, it does not match 'foo$opt', otherwise e.g. 'errors=remount-ro' would yield false positives for 'ro' 86 + if ! [[ "$current_opts" =~ (^|,)"$mount_opt"(,|$) ]]; then 87 + missing_opts+=("$mount_opt") 80 88 fi 81 - fi 89 + done 82 90 91 + # only change the mount options if any need changing 92 + if [[ ${#missing_opts[@]} != 0 ]]; then 93 + if [ -z "$container" ]; then 94 + mount --bind /nix/store /nix/store 95 + else 96 + mount --rbind /nix/store /nix/store 97 + fi 98 + 99 + # apply the missing mount options 100 + mount -o remount,"$(IFS=, ; echo "${missing_opts[*]}")",bind /nix/store 101 + fi 83 102 84 103 if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then 85 104 # Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+24 -3
nixos/modules/system/boot/stage-2.nix
··· 17 17 replacements = { 18 18 shell = "${pkgs.bash}/bin/bash"; 19 19 systemConfig = null; # replaced in ../activation/top-level.nix 20 - inherit (config.boot) readOnlyNixStore systemdExecutable; 20 + inherit (config.boot) systemdExecutable; 21 + nixStoreMountOpts = lib.concatStringsSep " " (map lib.escapeShellArg config.boot.nixStoreMountOpts); 21 22 inherit (config.system.nixos) distroName; 22 23 inherit useHostResolvConf; 23 24 inherit (config.system.build) earlyMountScript; ··· 57 58 description = '' 58 59 If set, NixOS will enforce the immutability of the Nix store 59 60 by making {file}`/nix/store` a read-only bind 60 - mount. Nix will automatically make the store writable when 61 + mount. Nix will automatically make the store writable when 61 62 needed. 62 63 ''; 63 64 }; 64 65 66 + nixStoreMountOpts = mkOption { 67 + type = types.listOf types.nonEmptyStr; 68 + default = [ 69 + "ro" 70 + "nodev" 71 + "nosuid" 72 + ]; 73 + description = '' 74 + Defines the mount options used on a bind mount for the {file}`/nix/store`. 75 + This affects the whole system except the nix store daemon, which will undo the bind mount. 76 + 77 + `ro` enforces immutability of the Nix store. 78 + The store daemon should already not put device mappers or suid binaries in the store, 79 + meaning `nosuid` and `nodev` enforce what should already be the case. 80 + ''; 81 + }; 82 + 65 83 systemdExecutable = mkOption { 66 84 default = "/run/current-system/systemd/lib/systemd/systemd"; 67 85 type = types.str; ··· 85 103 config = { 86 104 87 105 system.build.bootStage2 = bootStage2; 88 - 106 + boot.nixStoreMountOpts = [ 107 + "nodev" 108 + "nosuid" 109 + ] ++ lib.optional config.boot.readOnlyNixStore "ro"; 89 110 }; 90 111 }