Merge pull request #126289 from rnhmjoj/wrappers

nixos/security/wrappers: make well-typed

+10
nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
···
<itemizedlist>
<listitem>
<para>
The <literal>paperless</literal> module and package have been
removed. All users should migrate to the successor
<literal>paperless-ng</literal> instead. The Paperless project
···
<itemizedlist>
<listitem>
<para>
+
The <literal>security.wrappers</literal> option now requires
+
to always specify an owner, group and whether the
+
setuid/setgid bit should be set. This is motivated by the fact
+
that before NixOS 21.11, specifying either setuid or setgid
+
but not owner/group resulted in wrappers owned by
+
nobody/nogroup, which is unsafe.
+
</para>
+
</listitem>
+
<listitem>
+
<para>
The <literal>paperless</literal> module and package have been
removed. All users should migrate to the successor
<literal>paperless-ng</literal> instead. The Paperless project
+2
nixos/doc/manual/release-notes/rl-2111.section.md
···
## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
- The `paperless` module and package have been removed. All users should migrate to the
successor `paperless-ng` instead. The Paperless project [has been
···
## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
+
- The `security.wrappers` option now requires to always specify an owner, group and whether the setuid/setgid bit should be set.
+
This is motivated by the fact that before NixOS 21.11, specifying either setuid or setgid but not owner/group resulted in wrappers owned by nobody/nogroup, which is unsafe.
- The `paperless` module and package have been removed. All users should migrate to the
successor `paperless-ng` instead. The Paperless project [has been
+3 -1
nixos/modules/programs/bandwhich.nix
···
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [ bandwhich ];
security.wrappers.bandwhich = {
-
source = "${pkgs.bandwhich}/bin/bandwhich";
capabilities = "cap_net_raw,cap_net_admin+ep";
};
};
}
···
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [ bandwhich ];
security.wrappers.bandwhich = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw,cap_net_admin+ep";
+
source = "${pkgs.bandwhich}/bin/bandwhich";
};
};
}
+4
nixos/modules/programs/captive-browser.nix
···
);
security.wrappers.udhcpc = {
capabilities = "cap_net_raw+p";
source = "${pkgs.busybox}/bin/udhcpc";
};
security.wrappers.captive-browser = {
capabilities = "cap_net_raw+p";
source = pkgs.writeShellScript "captive-browser" ''
export PREV_CONFIG_HOME="$XDG_CONFIG_HOME"
···
);
security.wrappers.udhcpc = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
source = "${pkgs.busybox}/bin/udhcpc";
};
security.wrappers.captive-browser = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
source = pkgs.writeShellScript "captive-browser" ''
export PREV_CONFIG_HOME="$XDG_CONFIG_HOME"
+2
nixos/modules/programs/ccache.nix
···
# "nix-ccache --show-stats" and "nix-ccache --clear"
security.wrappers.nix-ccache = {
group = "nixbld";
setgid = true;
source = pkgs.writeScript "nix-ccache.pl" ''
#!${pkgs.perl}/bin/perl
···
# "nix-ccache --show-stats" and "nix-ccache --clear"
security.wrappers.nix-ccache = {
+
owner = "nobody";
group = "nixbld";
+
setuid = false;
setgid = true;
source = pkgs.writeScript "nix-ccache.pl" ''
#!${pkgs.perl}/bin/perl
+6 -1
nixos/modules/programs/firejail.nix
···
};
config = mkIf cfg.enable {
-
security.wrappers.firejail.source = "${lib.getBin pkgs.firejail}/bin/firejail";
environment.systemPackages = [ pkgs.firejail ] ++ [ wrappedBins ];
};
···
};
config = mkIf cfg.enable {
+
security.wrappers.firejail =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${lib.getBin pkgs.firejail}/bin/firejail";
+
};
environment.systemPackages = [ pkgs.firejail ] ++ [ wrappedBins ];
};
+2
nixos/modules/programs/gamemode.nix
···
polkit.enable = true;
wrappers = mkIf cfg.enableRenice {
gamemoded = {
source = "${pkgs.gamemode}/bin/gamemoded";
capabilities = "cap_sys_nice+ep";
};
···
polkit.enable = true;
wrappers = mkIf cfg.enableRenice {
gamemoded = {
+
owner = "root";
+
group = "root";
source = "${pkgs.gamemode}/bin/gamemoded";
capabilities = "cap_sys_nice+ep";
};
+3 -1
nixos/modules/programs/iftop.nix
···
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.iftop ];
security.wrappers.iftop = {
-
source = "${pkgs.iftop}/bin/iftop";
capabilities = "cap_net_raw+p";
};
};
}
···
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.iftop ];
security.wrappers.iftop = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
+
source = "${pkgs.iftop}/bin/iftop";
};
};
}
+3 -1
nixos/modules/programs/iotop.nix
···
};
config = mkIf cfg.enable {
security.wrappers.iotop = {
-
source = "${pkgs.iotop}/bin/iotop";
capabilities = "cap_net_admin+p";
};
};
}
···
};
config = mkIf cfg.enable {
security.wrappers.iotop = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_admin+p";
+
source = "${pkgs.iotop}/bin/iotop";
};
};
}
+6 -1
nixos/modules/programs/kbdlight.nix
···
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.kbdlight ];
-
security.wrappers.kbdlight.source = "${pkgs.kbdlight.out}/bin/kbdlight";
};
}
···
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.kbdlight ];
+
security.wrappers.kbdlight =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.kbdlight.out}/bin/kbdlight";
+
};
};
}
+3 -1
nixos/modules/programs/liboping.nix
···
security.wrappers = mkMerge (map (
exec: {
"${exec}" = {
-
source = "${pkgs.liboping}/bin/${exec}";
capabilities = "cap_net_raw+p";
};
}
) [ "oping" "noping" ]);
···
security.wrappers = mkMerge (map (
exec: {
"${exec}" = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
+
source = "${pkgs.liboping}/bin/${exec}";
};
}
) [ "oping" "noping" ]);
+2
nixos/modules/programs/msmtp.nix
···
source = "${pkgs.msmtp}/bin/sendmail";
setuid = false;
setgid = false;
};
environment.etc."msmtprc".text = let
···
source = "${pkgs.msmtp}/bin/sendmail";
setuid = false;
setgid = false;
+
owner = "root";
+
group = "root";
};
environment.etc."msmtprc".text = let
+3 -1
nixos/modules/programs/mtr.nix
···
environment.systemPackages = with pkgs; [ cfg.package ];
security.wrappers.mtr-packet = {
-
source = "${cfg.package}/bin/mtr-packet";
capabilities = "cap_net_raw+p";
};
};
}
···
environment.systemPackages = with pkgs; [ cfg.package ];
security.wrappers.mtr-packet = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
+
source = "${cfg.package}/bin/mtr-packet";
};
};
}
+3 -1
nixos/modules/programs/noisetorch.nix
···
config = mkIf cfg.enable {
security.wrappers.noisetorch = {
-
source = "${cfg.package}/bin/noisetorch";
capabilities = "cap_sys_resource=+ep";
};
};
}
···
config = mkIf cfg.enable {
security.wrappers.noisetorch = {
+
owner = "root";
+
group = "root";
capabilities = "cap_sys_resource=+ep";
+
source = "${cfg.package}/bin/noisetorch";
};
};
}
+14 -7
nixos/modules/programs/shadow.nix
···
'';
in
{
···
};
security.wrappers = {
-
su.source = "${pkgs.shadow.su}/bin/su";
-
sg.source = "${pkgs.shadow.out}/bin/sg";
-
newgrp.source = "${pkgs.shadow.out}/bin/newgrp";
-
newuidmap.source = "${pkgs.shadow.out}/bin/newuidmap";
-
newgidmap.source = "${pkgs.shadow.out}/bin/newgidmap";
} // lib.optionalAttrs config.users.mutableUsers {
-
chsh.source = "${pkgs.shadow.out}/bin/chsh";
-
passwd.source = "${pkgs.shadow.out}/bin/passwd";
};
};
}
···
'';
+
mkSetuidRoot = source:
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
inherit source;
+
};
+
in
{
···
};
security.wrappers = {
+
su = mkSetuidRoot "${pkgs.shadow.su}/bin/su";
+
sg = mkSetuidRoot "${pkgs.shadow.out}/bin/sg";
+
newgrp = mkSetuidRoot "${pkgs.shadow.out}/bin/newgrp";
+
newuidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newuidmap";
+
newgidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newgidmap";
} // lib.optionalAttrs config.users.mutableUsers {
+
chsh = mkSetuidRoot "${pkgs.shadow.out}/bin/chsh";
+
passwd = mkSetuidRoot "${pkgs.shadow.out}/bin/passwd";
};
};
}
+6 -1
nixos/modules/programs/singularity.nix
···
config = mkIf cfg.enable {
environment.systemPackages = [ singularity ];
-
security.wrappers.singularity-suid.source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
systemd.tmpfiles.rules = [
"d /var/singularity/mnt/session 0770 root root -"
"d /var/singularity/mnt/final 0770 root root -"
···
config = mkIf cfg.enable {
environment.systemPackages = [ singularity ];
+
security.wrappers.singularity-suid =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${singularity}/libexec/singularity/bin/starter-suid.orig";
+
};
systemd.tmpfiles.rules = [
"d /var/singularity/mnt/session 0770 root root -"
"d /var/singularity/mnt/final 0770 root root -"
+6 -1
nixos/modules/programs/slock.nix
···
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.slock ];
-
security.wrappers.slock.source = "${pkgs.slock.out}/bin/slock";
};
}
···
config = mkIf cfg.enable {
environment.systemPackages = [ pkgs.slock ];
+
security.wrappers.slock =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.slock.out}/bin/slock";
+
};
};
}
+2
nixos/modules/programs/ssmtp.nix
···
source = "${pkgs.ssmtp}/bin/sendmail";
setuid = false;
setgid = false;
};
};
···
source = "${pkgs.ssmtp}/bin/sendmail";
setuid = false;
setgid = false;
+
owner = "root";
+
group = "root";
};
};
+3 -1
nixos/modules/programs/traceroute.nix
···
config = mkIf cfg.enable {
security.wrappers.traceroute = {
-
source = "${pkgs.traceroute}/bin/traceroute";
capabilities = "cap_net_raw+p";
};
};
}
···
config = mkIf cfg.enable {
security.wrappers.traceroute = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
+
source = "${pkgs.traceroute}/bin/traceroute";
};
};
}
+6 -1
nixos/modules/programs/udevil.nix
···
options.programs.udevil.enable = mkEnableOption "udevil";
config = mkIf cfg.enable {
-
security.wrappers.udevil.source = "${lib.getBin pkgs.udevil}/bin/udevil";
};
}
···
options.programs.udevil.enable = mkEnableOption "udevil";
config = mkIf cfg.enable {
+
security.wrappers.udevil =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${lib.getBin pkgs.udevil}/bin/udevil";
+
};
};
}
+3 -1
nixos/modules/programs/wavemon.nix
···
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [ wavemon ];
security.wrappers.wavemon = {
-
source = "${pkgs.wavemon}/bin/wavemon";
capabilities = "cap_net_admin+ep";
};
};
}
···
config = mkIf cfg.enable {
environment.systemPackages = with pkgs; [ wavemon ];
security.wrappers.wavemon = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_admin+ep";
+
source = "${pkgs.wavemon}/bin/wavemon";
};
};
}
+6 -1
nixos/modules/programs/wshowkeys.nix
···
};
config = mkIf cfg.enable {
-
security.wrappers.wshowkeys.source = "${pkgs.wshowkeys}/bin/wshowkeys";
};
}
···
};
config = mkIf cfg.enable {
+
security.wrappers.wshowkeys =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.wshowkeys}/bin/wshowkeys";
+
};
};
}
+6 -1
nixos/modules/security/chromium-suid-sandbox.nix
···
config = mkIf cfg.enable {
environment.systemPackages = [ sandbox ];
-
security.wrappers.${sandbox.passthru.sandboxExecutableName}.source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
};
}
···
config = mkIf cfg.enable {
environment.systemPackages = [ sandbox ];
+
security.wrappers.${sandbox.passthru.sandboxExecutableName} =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}";
+
};
};
}
+6 -3
nixos/modules/security/doas.nix
···
}
];
-
security.wrappers = {
-
doas.source = "${doas}/bin/doas";
-
};
environment.systemPackages = [
doas
···
}
];
+
security.wrappers.doas =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${doas}/bin/doas";
+
};
environment.systemPackages = [
doas
+6 -1
nixos/modules/security/duosec.nix
···
config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
environment.systemPackages = [ pkgs.duo-unix ];
-
security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo";
system.activationScripts = {
login_duo = mkIf cfg.ssh.enable ''
···
config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
environment.systemPackages = [ pkgs.duo-unix ];
+
security.wrappers.login_duo =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.duo-unix.out}/bin/login_duo";
+
};
system.activationScripts = {
login_duo = mkIf cfg.ssh.enable ''
+3 -2
nixos/modules/security/pam.nix
···
security.wrappers = {
unix_chkpwd = {
source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
-
owner = "root";
-
setuid = true;
};
};
···
security.wrappers = {
unix_chkpwd = {
+
setuid = true;
+
owner = "root";
+
group = "root";
source = "${pkgs.pam}/sbin/unix_chkpwd.orig";
};
};
+12 -2
nixos/modules/security/pam_usb.nix
···
# Make sure pmount and pumount are setuid wrapped.
security.wrappers = {
-
pmount.source = "${pkgs.pmount.out}/bin/pmount";
-
pumount.source = "${pkgs.pmount.out}/bin/pumount";
};
environment.systemPackages = [ pkgs.pmount ];
···
# Make sure pmount and pumount are setuid wrapped.
security.wrappers = {
+
pmount =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.pmount.out}/bin/pmount";
+
};
+
pumount =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.pmount.out}/bin/pumount";
+
};
};
environment.systemPackages = [ pkgs.pmount ];
+12 -2
nixos/modules/security/polkit.nix
···
security.pam.services.polkit-1 = {};
security.wrappers = {
-
pkexec.source = "${pkgs.polkit.bin}/bin/pkexec";
-
polkit-agent-helper-1.source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
};
systemd.tmpfiles.rules = [
···
security.pam.services.polkit-1 = {};
security.wrappers = {
+
pkexec =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.polkit.bin}/bin/pkexec";
+
};
+
polkit-agent-helper-1 =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1";
+
};
};
systemd.tmpfiles.rules = [
+188 -94
nixos/modules/security/wrappers/default.nix
···
parentWrapperDir = dirOf wrapperDir;
-
programs =
-
(lib.mapAttrsToList
-
(n: v: (if v ? program then v else v // {program=n;}))
-
wrappers);
-
securityWrapper = pkgs.callPackage ./wrapper.nix {
inherit parentWrapperDir;
};
###### Activation script for the setcap wrappers
mkSetcapProgram =
{ program
, capabilities
, source
-
, owner ? "nobody"
-
, group ? "nogroup"
-
, permissions ? "u+rx,g+x,o+x"
, ...
}:
assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
''
-
cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
-
echo -n "${source}" > $wrapperDir/${program}.real
# Prevent races
-
chmod 0000 $wrapperDir/${program}
-
chown ${owner}.${group} $wrapperDir/${program}
# Set desired capabilities on the file plus cap_setpcap so
# the wrapper program can elevate the capabilities set on
# its file into the Ambient set.
-
${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program}
# Set the executable bit
-
chmod ${permissions} $wrapperDir/${program}
'';
###### Activation script for the setuid wrappers
mkSetuidProgram =
{ program
, source
-
, owner ? "nobody"
-
, group ? "nogroup"
-
, setuid ? false
-
, setgid ? false
-
, permissions ? "u+rx,g+x,o+x"
, ...
}:
''
-
cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
-
echo -n "${source}" > $wrapperDir/${program}.real
# Prevent races
-
chmod 0000 $wrapperDir/${program}
-
chown ${owner}.${group} $wrapperDir/${program}
-
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program}
'';
mkWrappedPrograms =
builtins.map
-
(s: if (s ? capabilities)
-
then mkSetcapProgram
-
({ owner = "root";
-
group = "root";
-
} // s)
-
else if
-
(s ? setuid && s.setuid) ||
-
(s ? setgid && s.setgid) ||
-
(s ? permissions)
-
then mkSetuidProgram s
-
else mkSetuidProgram
-
({ owner = "root";
-
group = "root";
-
setuid = true;
-
setgid = false;
-
permissions = "u+rx,g+x,o+x";
-
} // s)
-
) programs;
in
{
imports = [
···
options = {
security.wrappers = lib.mkOption {
-
type = lib.types.attrs;
default = {};
example = lib.literalExample
''
-
{ sendmail.source = "/nix/store/.../bin/sendmail";
-
ping = {
-
source = "${pkgs.iputils.out}/bin/ping";
-
owner = "nobody";
-
group = "nogroup";
-
capabilities = "cap_net_raw+ep";
-
};
}
'';
description = ''
-
This option allows the ownership and permissions on the setuid
-
wrappers for specific programs to be overridden from the
-
default (setuid root, but not setgid root).
-
-
<note>
-
<para>The sub-attribute <literal>source</literal> is mandatory,
-
it must be the absolute path to the program to be wrapped.
-
</para>
-
-
<para>The sub-attribute <literal>program</literal> is optional and
-
can give the wrapper program a new name. The default name is the same
-
as the attribute name itself.</para>
-
-
<para>Additionally, this option can set capabilities on a
-
wrapper program that propagates those capabilities down to the
-
wrapped, real program.</para>
-
-
<para>NOTE: cap_setpcap, which is required for the wrapper
-
program to be able to raise caps into the Ambient set is NOT
-
raised to the Ambient set so that the real program cannot
-
modify its own capabilities!! This may be too restrictive for
-
cases in which the real program needs cap_setpcap but it at
-
least leans on the side security paranoid vs. too
-
relaxed.</para>
-
</note>
'';
};
···
###### implementation
config = {
-
security.wrappers = {
-
# These are mount related wrappers that require the +s permission.
-
fusermount.source = "${pkgs.fuse}/bin/fusermount";
-
fusermount3.source = "${pkgs.fuse3}/bin/fusermount3";
-
mount.source = "${lib.getBin pkgs.util-linux}/bin/mount";
-
umount.source = "${lib.getBin pkgs.util-linux}/bin/umount";
-
};
boot.specialFileSystems.${parentWrapperDir} = {
fsType = "tmpfs";
···
]}"
'';
-
###### setcap activation script
system.activationScripts.wrappers =
lib.stringAfter [ "specialfs" "users" ]
''
-
# Look in the system path and in the default profile for
-
# programs to be wrapped.
-
WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin
-
chmod 755 "${parentWrapperDir}"
# We want to place the tmpdirs for the wrappers to the parent dir.
wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
-
chmod a+rx $wrapperDir
${lib.concatStringsSep "\n" mkWrappedPrograms}
···
# Atomically replace the symlink
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
old=$(readlink -f ${wrapperDir})
-
if [ -e ${wrapperDir}-tmp ]; then
-
rm --force --recursive ${wrapperDir}-tmp
fi
-
ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp
-
mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir}
-
rm --force --recursive $old
else
# For initial setup
-
ln --symbolic $wrapperDir ${wrapperDir}
fi
'';
};
}
···
parentWrapperDir = dirOf wrapperDir;
securityWrapper = pkgs.callPackage ./wrapper.nix {
inherit parentWrapperDir;
};
+
fileModeType =
+
let
+
# taken from the chmod(1) man page
+
symbolic = "[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+";
+
numeric = "[-+=]?[0-7]{0,4}";
+
mode = "((${symbolic})(,${symbolic})*)|(${numeric})";
+
in
+
lib.types.strMatching mode
+
// { description = "file mode string"; };
+
+
wrapperType = lib.types.submodule ({ name, config, ... }: {
+
options.source = lib.mkOption
+
{ type = lib.types.path;
+
description = "The absolute path to the program to be wrapped.";
+
};
+
options.program = lib.mkOption
+
{ type = with lib.types; nullOr str;
+
default = name;
+
description = ''
+
The name of the wrapper program. Defaults to the attribute name.
+
'';
+
};
+
options.owner = lib.mkOption
+
{ type = lib.types.str;
+
description = "The owner of the wrapper program.";
+
};
+
options.group = lib.mkOption
+
{ type = lib.types.str;
+
description = "The group of the wrapper program.";
+
};
+
options.permissions = lib.mkOption
+
{ type = fileModeType;
+
default = "u+rx,g+x,o+x";
+
example = "a+rx";
+
description = ''
+
The permissions of the wrapper program. The format is that of a
+
symbolic or numeric file mode understood by <command>chmod</command>.
+
'';
+
};
+
options.capabilities = lib.mkOption
+
{ type = lib.types.commas;
+
default = "";
+
description = ''
+
A comma-separated list of capabilities to be given to the wrapper
+
program. For capabilities supported by the system check the
+
<citerefentry>
+
<refentrytitle>capabilities</refentrytitle>
+
<manvolnum>7</manvolnum>
+
</citerefentry>
+
manual page.
+
+
<note><para>
+
<literal>cap_setpcap</literal>, which is required for the wrapper
+
program to be able to raise caps into the Ambient set is NOT raised
+
to the Ambient set so that the real program cannot modify its own
+
capabilities!! This may be too restrictive for cases in which the
+
real program needs cap_setpcap but it at least leans on the side
+
security paranoid vs. too relaxed.
+
</para></note>
+
'';
+
};
+
options.setuid = lib.mkOption
+
{ type = lib.types.bool;
+
default = false;
+
description = "Whether to add the setuid bit the wrapper program.";
+
};
+
options.setgid = lib.mkOption
+
{ type = lib.types.bool;
+
default = false;
+
description = "Whether to add the setgid bit the wrapper program.";
+
};
+
});
+
###### Activation script for the setcap wrappers
mkSetcapProgram =
{ program
, capabilities
, source
+
, owner
+
, group
+
, permissions
, ...
}:
assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
''
+
cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
+
echo -n "${source}" > "$wrapperDir/${program}.real"
# Prevent races
+
chmod 0000 "$wrapperDir/${program}"
+
chown ${owner}.${group} "$wrapperDir/${program}"
# Set desired capabilities on the file plus cap_setpcap so
# the wrapper program can elevate the capabilities set on
# its file into the Ambient set.
+
${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" "$wrapperDir/${program}"
# Set the executable bit
+
chmod ${permissions} "$wrapperDir/${program}"
'';
###### Activation script for the setuid wrappers
mkSetuidProgram =
{ program
, source
+
, owner
+
, group
+
, setuid
+
, setgid
+
, permissions
, ...
}:
''
+
cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}"
+
echo -n "${source}" > "$wrapperDir/${program}.real"
# Prevent races
+
chmod 0000 "$wrapperDir/${program}"
+
chown ${owner}.${group} "$wrapperDir/${program}"
+
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}"
'';
mkWrappedPrograms =
builtins.map
+
(opts:
+
if opts.capabilities != ""
+
then mkSetcapProgram opts
+
else mkSetuidProgram opts
+
) (lib.attrValues wrappers);
in
{
imports = [
···
options = {
security.wrappers = lib.mkOption {
+
type = lib.types.attrsOf wrapperType;
default = {};
example = lib.literalExample
''
+
{
+
# a setuid root program
+
doas =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "''${pkgs.doas}/bin/doas";
+
};
+
+
# a setgid program
+
locate =
+
{ setgid = true;
+
owner = "root";
+
group = "mlocate";
+
source = "''${pkgs.locate}/bin/locate";
+
};
+
+
# a program with the CAP_NET_RAW capability
+
ping =
+
{ owner = "root";
+
group = "root";
+
capabilities = "cap_net_raw+ep";
+
source = "''${pkgs.iputils.out}/bin/ping";
+
};
}
'';
description = ''
+
This option effectively allows adding setuid/setgid bits, capabilities,
+
changing file ownership and permissions of a program without directly
+
modifying it. This works by creating a wrapper program under the
+
<option>security.wrapperDir</option> directory, which is then added to
+
the shell <literal>PATH</literal>.
'';
};
···
###### implementation
config = {
+
assertions = lib.mapAttrsToList
+
(name: opts:
+
{ assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
+
message = ''
+
The security.wrappers.${name} wrapper is not valid:
+
setuid/setgid and capabilities are mutually exclusive.
+
'';
+
}
+
) wrappers;
+
+
security.wrappers =
+
let
+
mkSetuidRoot = source:
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
inherit source;
+
};
+
in
+
{ # These are mount related wrappers that require the +s permission.
+
fusermount = mkSetuidRoot "${pkgs.fuse}/bin/fusermount";
+
fusermount3 = mkSetuidRoot "${pkgs.fuse3}/bin/fusermount3";
+
mount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount";
+
umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount";
+
};
boot.specialFileSystems.${parentWrapperDir} = {
fsType = "tmpfs";
···
]}"
'';
+
###### wrappers activation script
system.activationScripts.wrappers =
lib.stringAfter [ "specialfs" "users" ]
''
chmod 755 "${parentWrapperDir}"
# We want to place the tmpdirs for the wrappers to the parent dir.
wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX)
+
chmod a+rx "$wrapperDir"
${lib.concatStringsSep "\n" mkWrappedPrograms}
···
# Atomically replace the symlink
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
old=$(readlink -f ${wrapperDir})
+
if [ -e "${wrapperDir}-tmp" ]; then
+
rm --force --recursive "${wrapperDir}-tmp"
fi
+
ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp"
+
mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}"
+
rm --force --recursive "$old"
else
# For initial setup
+
ln --symbolic "$wrapperDir" "${wrapperDir}"
fi
'';
+
+
###### wrappers consistency checks
+
system.extraDependencies = lib.singleton (pkgs.runCommandLocal
+
"ensure-all-wrappers-paths-exist" { }
+
''
+
# make sure we produce output
+
mkdir -p $out
+
+
echo -n "Checking that Nix store paths of all wrapped programs exist... "
+
+
declare -A wrappers
+
${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v:
+
"wrappers['${n}']='${v.source}'") wrappers)}
+
+
for name in "''${!wrappers[@]}"; do
+
path="''${wrappers[$name]}"
+
if [[ "$path" =~ /nix/store ]] && [ ! -e "$path" ]; then
+
test -t 1 && echo -ne '\033[1;31m'
+
echo "FAIL"
+
echo "The path $path does not exist!"
+
echo 'Please, check the value of `security.wrappers."'$name'".source`.'
+
test -t 1 && echo -ne '\033[0m'
+
exit 1
+
fi
+
done
+
+
echo "OK"
+
'');
};
}
+3 -1
nixos/modules/services/desktops/gnome/gnome-keyring.nix
···
security.pam.services.login.enableGnomeKeyring = true;
security.wrappers.gnome-keyring-daemon = {
-
source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon";
capabilities = "cap_ipc_lock=ep";
};
};
···
security.pam.services.login.enableGnomeKeyring = true;
security.wrappers.gnome-keyring-daemon = {
+
owner = "root";
+
group = "root";
capabilities = "cap_ipc_lock=ep";
+
source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon";
};
};
+6 -1
nixos/modules/services/mail/exim.nix
···
gid = config.ids.gids.exim;
};
-
security.wrappers.exim.source = "${cfg.package}/bin/exim";
systemd.services.exim = {
description = "Exim Mail Daemon";
···
gid = config.ids.gids.exim;
};
+
security.wrappers.exim =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${cfg.package}/bin/exim";
+
};
systemd.services.exim = {
description = "Exim Mail Daemon";
+2 -1
nixos/modules/services/mail/mail.nix
···
-
{ config, lib, ... }:
with lib;
···
services.mail = {
sendmailSetuidWrapper = mkOption {
default = null;
internal = true;
description = ''
···
+
{ config, options, lib, ... }:
with lib;
···
services.mail = {
sendmailSetuidWrapper = mkOption {
+
type = types.nullOr options.security.wrappers.type.nestedTypes.elemType;
default = null;
internal = true;
description = ''
+4 -1
nixos/modules/services/mail/opensmtpd.nix
···
};
security.wrappers.smtpctl = {
group = "smtpq";
setgid = true;
source = "${cfg.package}/bin/smtpctl";
};
-
services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail security.wrappers.smtpctl;
systemd.tmpfiles.rules = [
"d /var/spool/smtpd 711 root - - -"
···
};
security.wrappers.smtpctl = {
+
owner = "nobody";
group = "smtpq";
+
setuid = false;
setgid = true;
source = "${cfg.package}/bin/smtpctl";
};
+
services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail
+
security.wrappers.smtpctl // { program = "sendmail"; };
systemd.tmpfiles.rules = [
"d /var/spool/smtpd 711 root - - -"
+4
nixos/modules/services/mail/postfix.nix
···
services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
program = "sendmail";
source = "${pkgs.postfix}/bin/sendmail";
group = setgidGroup;
setuid = false;
setgid = true;
···
security.wrappers.mailq = {
program = "mailq";
source = "${pkgs.postfix}/bin/mailq";
group = setgidGroup;
setuid = false;
setgid = true;
···
security.wrappers.postqueue = {
program = "postqueue";
source = "${pkgs.postfix}/bin/postqueue";
group = setgidGroup;
setuid = false;
setgid = true;
···
security.wrappers.postdrop = {
program = "postdrop";
source = "${pkgs.postfix}/bin/postdrop";
group = setgidGroup;
setuid = false;
setgid = true;
···
services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail {
program = "sendmail";
source = "${pkgs.postfix}/bin/sendmail";
+
owner = "nobody";
group = setgidGroup;
setuid = false;
setgid = true;
···
security.wrappers.mailq = {
program = "mailq";
source = "${pkgs.postfix}/bin/mailq";
+
owner = "nobody";
group = setgidGroup;
setuid = false;
setgid = true;
···
security.wrappers.postqueue = {
program = "postqueue";
source = "${pkgs.postfix}/bin/postqueue";
+
owner = "nobody";
group = setgidGroup;
setuid = false;
setgid = true;
···
security.wrappers.postdrop = {
program = "postdrop";
source = "${pkgs.postfix}/bin/postdrop";
+
owner = "nobody";
group = setgidGroup;
setuid = false;
setgid = true;
+3 -1
nixos/modules/services/misc/mame.nix
···
environment.systemPackages = [ pkgs.mame ];
security.wrappers."${mame}" = {
-
source = "${pkgs.mame}/bin/${mame}";
capabilities = "cap_net_admin,cap_net_raw+eip";
};
systemd.services.mame = {
···
environment.systemPackages = [ pkgs.mame ];
security.wrappers."${mame}" = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_admin,cap_net_raw+eip";
+
source = "${pkgs.mame}/bin/${mame}";
};
systemd.services.mame = {
+6 -1
nixos/modules/services/misc/weechat.nix
···
wants = [ "network.target" ];
};
-
security.wrappers.screen.source = "${pkgs.screen}/bin/screen";
};
meta.doc = ./weechat.xml;
···
wants = [ "network.target" ];
};
+
security.wrappers.screen =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.screen}/bin/screen";
+
};
};
meta.doc = ./weechat.xml;
+6 -1
nixos/modules/services/monitoring/incron.nix
···
environment.systemPackages = [ pkgs.incron ];
-
security.wrappers.incrontab.source = "${pkgs.incron}/bin/incrontab";
# incron won't read symlinks
environment.etc."incron.d/system" = {
···
environment.systemPackages = [ pkgs.incron ];
+
security.wrappers.incrontab =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.incron}/bin/incrontab";
+
};
# incron won't read symlinks
environment.etc."incron.d/system" = {
+6 -1
nixos/modules/services/monitoring/zabbix-proxy.nix
···
};
security.wrappers = {
-
fping.source = "${pkgs.fping}/bin/fping";
};
systemd.services.zabbix-proxy = {
···
};
security.wrappers = {
+
fping =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.fping}/bin/fping";
+
};
};
systemd.services.zabbix-proxy = {
+12 -2
nixos/modules/services/networking/smokeping.nix
···
}
];
security.wrappers = {
-
fping.source = "${pkgs.fping}/bin/fping";
-
fping6.source = "${pkgs.fping}/bin/fping6";
};
environment.systemPackages = [ pkgs.fping ];
users.users.${cfg.user} = {
···
}
];
security.wrappers = {
+
fping =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.fping}/bin/fping";
+
};
+
fping6 =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.fping}/bin/fping6";
+
};
};
environment.systemPackages = [ pkgs.fping ];
users.users.${cfg.user} = {
+2
nixos/modules/services/networking/x2goserver.nix
···
source = "${pkgs.x2goserver}/lib/x2go/libx2go-server-db-sqlite3-wrapper.pl";
owner = "x2go";
group = "x2go";
setgid = true;
};
security.wrappers.x2goprintWrapper = {
source = "${pkgs.x2goserver}/bin/x2goprint";
owner = "x2go";
group = "x2go";
setgid = true;
};
···
source = "${pkgs.x2goserver}/lib/x2go/libx2go-server-db-sqlite3-wrapper.pl";
owner = "x2go";
group = "x2go";
+
setuid = false;
setgid = true;
};
security.wrappers.x2goprintWrapper = {
source = "${pkgs.x2goserver}/bin/x2goprint";
owner = "x2go";
group = "x2go";
+
setuid = false;
setgid = true;
};
+6 -1
nixos/modules/services/scheduling/cron.nix
···
{ services.cron.enable = mkDefault (allFiles != []); }
(mkIf (config.services.cron.enable) {
-
security.wrappers.crontab.source = "${cronNixosPkg}/bin/crontab";
environment.systemPackages = [ cronNixosPkg ];
environment.etc.crontab =
{ source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; }
···
{ services.cron.enable = mkDefault (allFiles != []); }
(mkIf (config.services.cron.enable) {
+
security.wrappers.crontab =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${cronNixosPkg}/bin/crontab";
+
};
environment.systemPackages = [ cronNixosPkg ];
environment.etc.crontab =
{ source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; }
+3
nixos/modules/services/scheduling/fcron.nix
···
owner = "fcron";
group = "fcron";
setgid = true;
};
fcronsighup = {
source = "${pkgs.fcron}/bin/fcronsighup";
group = "fcron";
};
};
systemd.services.fcron = {
···
owner = "fcron";
group = "fcron";
setgid = true;
+
setuid = false;
};
fcronsighup = {
source = "${pkgs.fcron}/bin/fcronsighup";
+
owner = "root";
group = "fcron";
+
setuid = true;
};
};
systemd.services.fcron = {
+3 -1
nixos/modules/services/video/replay-sorcery.nix
···
security.wrappers = mkIf cfg.enableSysAdminCapability {
replay-sorcery = {
-
source = "${pkgs.replay-sorcery}/bin/replay-sorcery";
capabilities = "cap_sys_admin+ep";
};
};
···
security.wrappers = mkIf cfg.enableSysAdminCapability {
replay-sorcery = {
+
owner = "root";
+
group = "root";
capabilities = "cap_sys_admin+ep";
+
source = "${pkgs.replay-sorcery}/bin/replay-sorcery";
};
};
+3 -2
nixos/modules/services/x11/desktop-managers/cde.nix
···
users.groups.mail = {};
security.wrappers = {
dtmail = {
source = "${pkgs.cdesktopenv}/bin/dtmail";
-
group = "mail";
-
setgid = true;
};
};
···
users.groups.mail = {};
security.wrappers = {
dtmail = {
+
setgid = true;
+
owner = "nobody";
+
group = "mail";
source = "${pkgs.cdesktopenv}/bin/dtmail";
};
};
+18 -3
nixos/modules/services/x11/desktop-managers/enlightenment.nix
···
# Wrappers for programs installed by enlightenment that should be setuid
security.wrappers = {
-
enlightenment_ckpasswd.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd";
-
enlightenment_sys.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys";
-
enlightenment_system.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system";
};
environment.etc."X11/xkb".source = xcfg.xkbDir;
···
# Wrappers for programs installed by enlightenment that should be setuid
security.wrappers = {
+
enlightenment_ckpasswd =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd";
+
};
+
enlightenment_sys =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys";
+
};
+
enlightenment_system =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system";
+
};
};
environment.etc."X11/xkb".source = xcfg.xkbDir;
+18 -6
nixos/modules/services/x11/desktop-managers/plasma5.nix
···
};
security.wrappers = {
-
kcheckpass.source = "${lib.getBin libsForQt5.kscreenlocker}/libexec/kcheckpass";
-
start_kdeinit.source = "${lib.getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
-
kwin_wayland = {
-
source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
-
capabilities = "cap_sys_nice+ep";
-
};
};
# DDC support
···
};
security.wrappers = {
+
kcheckpass =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${lib.getBin libsForQt5.kscreenlocker}/libexec/kcheckpass";
+
};
+
start_kdeinit =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${lib.getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit";
+
};
+
kwin_wayland =
+
{ owner = "root";
+
group = "root";
+
capabilities = "cap_sys_nice+ep";
+
source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland";
+
};
};
# DDC support
+12 -2
nixos/modules/tasks/filesystems/ecryptfs.nix
···
config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
system.fsPackages = [ pkgs.ecryptfs ];
security.wrappers = {
-
"mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
-
"umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
};
};
}
···
config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) {
system.fsPackages = [ pkgs.ecryptfs ];
security.wrappers = {
+
"mount.ecryptfs_private" =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private";
+
};
+
"umount.ecryptfs_private" =
+
{ setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private";
+
};
};
};
}
+7 -2
nixos/modules/tasks/network-interfaces.nix
···
# kernel because we need the ambient capability
security.wrappers = if (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") then {
ping = {
-
source = "${pkgs.iputils.out}/bin/ping";
capabilities = "cap_net_raw+p";
};
} else {
-
ping.source = "${pkgs.iputils.out}/bin/ping";
};
security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
/run/wrappers/bin/ping {
···
# kernel because we need the ambient capability
security.wrappers = if (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") then {
ping = {
+
owner = "root";
+
group = "root";
capabilities = "cap_net_raw+p";
+
source = "${pkgs.iputils.out}/bin/ping";
};
} else {
+
setuid = true;
+
owner = "root";
+
group = "root";
+
source = "${pkgs.iputils.out}/bin/ping";
};
security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter ''
/run/wrappers/bin/ping {
+3
nixos/modules/virtualisation/libvirtd.nix
···
};
security.wrappers.qemu-bridge-helper = {
source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
};
···
};
security.wrappers.qemu-bridge-helper = {
+
setuid = true;
+
owner = "root";
+
group = "root";
source = "/run/${dirName}/nix-helpers/qemu-bridge-helper";
};
+4 -2
nixos/modules/virtualisation/spice-usb-redirection.nix
···
config = lib.mkIf config.virtualisation.spiceUSBRedirection.enable {
environment.systemPackages = [ pkgs.spice-gtk ]; # For polkit actions
-
security.wrappers.spice-client-glib-usb-acl-helper ={
-
source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper";
capabilities = "cap_fowner+ep";
};
};
···
config = lib.mkIf config.virtualisation.spiceUSBRedirection.enable {
environment.systemPackages = [ pkgs.spice-gtk ]; # For polkit actions
+
security.wrappers.spice-client-glib-usb-acl-helper = {
+
owner = "root";
+
group = "root";
capabilities = "cap_fowner+ep";
+
source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper";
};
};