From 949f73fcf983e5e3bd627ce62187d7684192d93a Mon Sep 17 00:00:00 2001 From: Aliaksandr Date: Sat, 28 Feb 2026 16:15:30 +0200 Subject: [PATCH 1/2] nixos/shadow: use su from sudo-rs when enabled When security.sudo-rs.enable is true, use the memory-safe su implementation from sudo-rs instead of the one from shadow. --- nixos/modules/programs/shadow.nix | 9 ++++++++- nixos/modules/security/sudo-rs.nix | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix index 12c9a84333bf..d6d48af0f877 100644 --- a/nixos/modules/programs/shadow.nix +++ b/nixos/modules/programs/shadow.nix @@ -22,6 +22,13 @@ in ''; }; + security.shadow.su.package = lib.mkPackageOption pkgs [ "shadow" "su" ] { + extraDescription = '' + This can be overridden by other modules (e.g. sudo-rs) to provide + an alternative `su` implementation. + ''; + }; + security.loginDefs = { package = lib.mkPackageOption pkgs "shadow" { }; @@ -262,7 +269,7 @@ in }; in { - su = mkSetuidRoot "${cfg.package.su}/bin/su"; + su = mkSetuidRoot "${config.security.shadow.su.package}/bin/su"; sg = mkSetuidRoot "${cfg.package.out}/bin/sg"; newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp"; newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap"; diff --git a/nixos/modules/security/sudo-rs.nix b/nixos/modules/security/sudo-rs.nix index 50e50e77b851..7965bbe3c7f3 100644 --- a/nixos/modules/security/sudo-rs.nix +++ b/nixos/modules/security/sudo-rs.nix @@ -215,6 +215,8 @@ in ]; security.sudo.enable = lib.mkDefault false; + security.shadow.su.package = lib.mkDefault cfg.package; + security.sudo-rs.extraRules = let defaultRule = From 4fd1cb40f692d6db8fd033c6b916fe7426de4be0 Mon Sep 17 00:00:00 2001 From: Aliaksandr Date: Wed, 4 Mar 2026 23:20:26 +0200 Subject: [PATCH 2/2] nixos/tests: verify su wrapper source in shadow and sudo-rs tests Add subtests to confirm that the su setuid wrapper points to the correct package: shadow by default, and sudo-rs when enabled. --- nixos/tests/shadow.nix | 5 +++++ nixos/tests/sudo-rs.nix | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/nixos/tests/shadow.nix b/nixos/tests/shadow.nix index 04ee0a2bf95d..17ab1a3710ae 100644 --- a/nixos/tests/shadow.nix +++ b/nixos/tests/shadow.nix @@ -171,5 +171,10 @@ in shadow.wait_for_file("/tmp/leo") assert "leo" in shadow.succeed("cat /tmp/leo") shadow.send_chars("logout\n") + + with subtest("su wrapper should point to shadow by default"): + output = shadow.succeed("grep -aoP '/nix/store/[a-z0-9]{32}-[^\\x00]+' /run/wrappers/bin/su | head -1").strip() + assert "shadow" in output, \ + f"su should come from shadow, but points to: {output}" ''; } diff --git a/nixos/tests/sudo-rs.nix b/nixos/tests/sudo-rs.nix index 7e691ef7e51b..62891ed01d53 100644 --- a/nixos/tests/sudo-rs.nix +++ b/nixos/tests/sudo-rs.nix @@ -162,5 +162,10 @@ in with subtest("non-wheel users should be unable to run sudo thanks to execWheelOnly"): strict.fail('faketty -- su - noadmin -c "sudo --help"') + + with subtest("su should come from sudo-rs"): + output = machine.succeed("grep -aoP '/nix/store/[a-z0-9]{32}-[^\\x00]+' /run/wrappers/bin/su | head -1").strip() + assert "sudo-rs" in output, \ + f"su should come from sudo-rs, but points to: {output}" ''; }