diff --git a/nixos/modules/security/default.nix b/nixos/modules/security/default.nix index 5170383d6f5e..c1a4f7cf34a1 100644 --- a/nixos/modules/security/default.nix +++ b/nixos/modules/security/default.nix @@ -1,4 +1,8 @@ -{ config, lib, ... }: +{ + config, + lib, + ... +}: let cfg = config.security; in @@ -16,10 +20,16 @@ in config = lib.mkMerge [ { # We set the default LSM's here due to them not being present if set when enabling AppArmor. - security.lsm = [ - "landlock" - "yama" - "bpf" + security.lsm = lib.mkMerge [ + [ + "landlock" + "yama" + ] + # Load BPF last unconditionally. See: https://github.com/NixOS/nixpkgs/pull/533428. + # Apparmor (and potentially other modules) will load incorrectly if they are not before BPF. + # It is believed that there was a regression between kernel 6.12 and 6.18 which caused the + # passthrough stub or LSM stacking of the BPF module to interact with other modules incorrectly. + (lib.mkAfter [ "bpf" ]) ]; } (lib.mkIf (lib.lists.length cfg.lsm > 0) { diff --git a/nixos/modules/virtualisation/incus.nix b/nixos/modules/virtualisation/incus.nix index 83a5a731ccb7..466e359c3147 100644 --- a/nixos/modules/virtualisation/incus.nix +++ b/nixos/modules/virtualisation/incus.nix @@ -363,21 +363,27 @@ in include ${cfg.lxcPackage}/etc/apparmor.d/lxc-containers ''; "incusd".profile = '' - # This profile allows everything and only exists to give the - # application a name instead of having the label "unconfined" + # incusd is deliberatly left unconfined, with NO named profile attached to the binary. + # Incus checks its own confinement at startup by reading /proc/self/attr/current + # (https://github.com/lxc/incus/blob/92b0cbbc5728ed45578fdeeec634606af8826404/internal/server/sys/apparmor.go). + # Anything other than "unconfined" makes Incus believe that the host process is + # itself confined, which sends every container down the "reuse my own profile" branch in + # https://github.com/lxc/incus/blob/92b0cbbc5728ed45578fdeeec634606af8826404/internal/server/instance/drivers/driver_lxc.go + # instead of generating a "proper" per-container profile. Furthermore, + # that branch only strips " (enforce)" suffix before handing the string to lxc.apparmor.profile + # (https://github.com/lxc/incus/blob/92b0cbbc5728ed45578fdeeec634606af8826404/internal/server/instance/drivers/driver_lxc.go#L96), + # so the named profile with flags=(unconfined) produces a literal string + # "incusd (unconfined)", which the kernel rejects at change_profile() time + # with "label not found", failing every `incus start` when AppArmor is enabled. + # This was not caught before as AppArmor was stifled by bpf. + + # We keep this policy to pull in the per-container / + # per-archive profiles incusd generates at runtime so + # apparmor_parser loads them. abi , include - profile incusd ${lib.getExe' config.virtualisation.incus.package "incusd"} flags=(unconfined) { - userns, - - include "/var/lib/incus/security/apparmor/cache" - - # Site-specific additions and overrides. See local/README for details. - include if exists - } - include "/var/lib/incus/security/apparmor/profiles" ''; };