fix(apparmor): attach disconnected paths in the sudo profile

Nix 2.35's root nix-env -- used by nixos-rebuild to set the system profile --
calls saveMountNamespace(), which opens /proc/self/ns/mnt before remounting
the read-only /nix/store writable. Those magic links have no connected path,
so AppArmor cannot match them against any rule and denies the open with
EACCES even in complain mode. The private mount namespace is then never
created and nixos-rebuild aborts with:

  cannot remount "/nix/store" writable: not in a private mount namespace

Adding flags=(attach_disconnected) lets AppArmor re-root those paths on / so
the existing `file /**` rule matches. This also fixes any other sudo'd tool
that needs namespaces (unshare, nsenter, bwrap, ...).

Rolling this out to a host that already has the old profile loaded needs one
deploy with --no-reexec, so the current system's Nix performs nix-env --set
instead of the new system's; after that generation is active, plain
`just niri` works and the Nix version is never rolled back.
This commit is contained in:
Ryan Yin committed 2026-10-07 19:39:42 +08:00
1 parent 94312fc2e5
commit 0d800c4d9c
1 file changed
+24 -3
+24 -3
View File
@@ -5,8 +5,11 @@
}:
{
# AppArmor: activate the LSM + load policies. Roll out in complain mode first
# (log violations, never block) so nothing can break; promote profiles to
# "enforce" individually once stable.
# (log violations, don't block) so nothing can break; promote profiles to
# "enforce" individually once stable. Complain mode is not absolute: opens of
# disconnected paths (/proc/self/ns/*, /proc/self/root, ...) are denied even in
# complain mode unless the profile sets flags=(attach_disconnected); see the
# sudo policy below.
services.dbus.apparmor = "enabled";
security.apparmor = {
@@ -44,7 +47,25 @@
# and AppArmor 5.0's parser rejects merged rules with conflicting exec
# modifiers ("profile has merged rule with conflicting x modifiers") when a
# broad rule uses Ux over those paths.
profile ${pkgs.sudo}/bin/sudo {
#
# NOTE: `attach_disconnected` is required. Magic links such as
# /proc/self/ns/mnt and /proc/self/root have no connected path, so AppArmor
# cannot match them against any rule and denies the open with EACCES even in
# complain mode. Nix 2.35 (nix-env --set as root) opens /proc/self/ns/mnt in
# its saveMountNamespace(), and once that fails the store is never remounted
# writable and nixos-rebuild dies with:
# cannot remount "/nix/store" writable: not in a private mount namespace
# Reproduce the underlying denial with:
# aa-exec -p ${pkgs.sudo}/bin/sudo -- cat /proc/self/ns/mnt
#
# Rolling this out to a machine that already has the old (non-attach)
# profile loaded needs one deploy where the *new* system's Nix does not
# run the profile switch, e.g.
# nixos-rebuild boot --sudo --no-reexec --flake .#<host> --accept-flake-config
# (with --no-reexec the current system's Nix, 2.34, runs nix-env --set and
# avoids the broken profile). After that generation is active, plain
# `just niri` works again and the Nix version is never rolled back.
profile ${pkgs.sudo}/bin/sudo flags=(attach_disconnected) {
include <abstractions/base>
file /** rwlkix,
}