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.
This commit is contained in:
Grimmauld
2025-05-18 12:07:10 +02:00
parent 6d295c7e43
commit 4e440ec124
2 changed files with 56 additions and 16 deletions
+32 -13
View File
@@ -61,25 +61,44 @@ else
fi
# Make /nix/store a read-only bind mount to enforce immutability of
# the Nix store. Note that we can't use "chown root:nixbld" here
# Give /nix/store the defined mount options.
# Typically, this should be:
# - 'ro' to enforce immutability of the Nix store
# - 'nosuid' to enforce no suid binaries make it into the store and get executed by accident.
# suid-binaries should only exist in /run/wrappers.
# If an attacker can make the nix builder produce suid binaries in the store, those should be useless.
# Another example is tampering with the store from an outside system.
# - 'nodev' to enforce no device files in the store
# Note that we can't use "chown root:nixbld" here
# because users/groups might not exist yet.
# Silence chown/chmod to fail gracefully on a readonly filesystem
# like squashfs.
chown -f 0:30000 /nix/store
chmod -f 1775 /nix/store
if [ -n "@readOnlyNixStore@" ]; then
# #375257: Ensure that we pick the "top" (i.e. last) mount so we don't get a false positive for a lower mount.
if ! [[ "$(findmnt --direction backward --first-only --noheadings --output OPTIONS /nix/store)" =~ (^|,)ro(,|$) ]]; then
if [ -z "$container" ]; then
mount --bind /nix/store /nix/store
else
mount --rbind /nix/store /nix/store
fi
mount -o remount,ro,bind /nix/store
fi
fi
missing_opts=() # stores the missing mount options that still need to be applied to the nix store
current_opts="$(findmnt --direction backward --first-only --noheadings --output OPTIONS /nix/store)"
for mount_opt in @nixStoreMountOpts@ ; do
# #375257: Ensure that we pick the "top" (i.e. last) mount so we don't get a false positive for a lower mount.
# matches '$opt', foo,$opt', '$opt,foo', 'foo,$opt,bar'
# crucially, it does not match 'foo$opt', otherwise e.g. 'errors=remount-ro' would yield false positives for 'ro'
if ! [[ "$current_opts" =~ (^|,)"$mount_opt"(,|$) ]]; then
missing_opts+=("$mount_opt")
fi
done
# only change the mount options if any need changing
if [[ ${#missing_opts[@]} != 0 ]]; then
if [ -z "$container" ]; then
mount --bind /nix/store /nix/store
else
mount --rbind /nix/store /nix/store
fi
# apply the missing mount options
mount -o remount,"$(IFS=, ; echo "${missing_opts[*]}")",bind /nix/store
fi
if [ "${IN_NIXOS_SYSTEMD_STAGE1:-}" != true ]; then
# Use /etc/resolv.conf supplied by systemd-nspawn, if applicable.
+24 -3
View File
@@ -17,7 +17,8 @@ let
replacements = {
shell = "${pkgs.bash}/bin/bash";
systemConfig = null; # replaced in ../activation/top-level.nix
inherit (config.boot) readOnlyNixStore systemdExecutable;
inherit (config.boot) systemdExecutable;
nixStoreMountOpts = lib.concatStringsSep " " (map lib.escapeShellArg config.boot.nixStoreMountOpts);
inherit (config.system.nixos) distroName;
inherit useHostResolvConf;
inherit (config.system.build) earlyMountScript;
@@ -57,11 +58,28 @@ in
description = ''
If set, NixOS will enforce the immutability of the Nix store
by making {file}`/nix/store` a read-only bind
mount. Nix will automatically make the store writable when
mount. Nix will automatically make the store writable when
needed.
'';
};
nixStoreMountOpts = mkOption {
type = types.listOf types.nonEmptyStr;
default = [
"ro"
"nodev"
"nosuid"
];
description = ''
Defines the mount options used on a bind mount for the {file}`/nix/store`.
This affects the whole system except the nix store daemon, which will undo the bind mount.
`ro` enforces immutability of the Nix store.
The store daemon should already not put device mappers or suid binaries in the store,
meaning `nosuid` and `nodev` enforce what should already be the case.
'';
};
systemdExecutable = mkOption {
default = "/run/current-system/systemd/lib/systemd/systemd";
type = types.str;
@@ -85,6 +103,9 @@ in
config = {
system.build.bootStage2 = bootStage2;
boot.nixStoreMountOpts = [
"nodev"
"nosuid"
] ++ lib.optional config.boot.readOnlyNixStore "ro";
};
}