nixpkgs/nixos/modules/security/setuid-wrappers.nix
Domen Kožar a6670c1a0b Fixes #18124: atomically replace /var/setuid-wrappers/ (#18186)
Before this commit updating /var/setuid-wrappers/ folder introduced
a small window where NixOS activation scripts could be terminated
and resulted into empty /var/setuid-wrappers/ folder.

That's very unfortunate because one might lose sudo binary.

Instead we use two atomic operations mv and ln (as described in
https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/)
to achieve atomicity.

Since /var/setuid-wrappers is not a directory anymore, tmpfs mountpoints
were removed in installation scripts and in boot process.

Tested:

- upgrade /var/setuid-wrappers/ from folder to a symlink
- make sure /run/setuid-wrappers-dirs/ legacy symlink is really deleted
2016-09-01 20:57:51 +02:00

145 lines
4.4 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
inherit (config.security) wrapperDir;
setuidWrapper = pkgs.stdenv.mkDerivation {
name = "setuid-wrapper";
unpackPhase = "true";
installPhase = ''
mkdir -p $out/bin
cp ${./setuid-wrapper.c} setuid-wrapper.c
gcc -Wall -O2 -DWRAPPER_DIR=\"/run/setuid-wrapper-dirs\" \
setuid-wrapper.c -o $out/bin/setuid-wrapper
'';
};
in
{
###### interface
options = {
security.setuidPrograms = mkOption {
type = types.listOf types.str;
default = [];
example = ["passwd"];
description = ''
The Nix store cannot contain setuid/setgid programs directly.
For this reason, NixOS can automatically generate wrapper
programs that have the necessary privileges. This option
lists the names of programs in the system environment for
which setuid root wrappers should be created.
'';
};
security.setuidOwners = mkOption {
type = types.listOf types.attrs;
default = [];
example =
[ { program = "sendmail";
owner = "nobody";
group = "postdrop";
setuid = false;
setgid = true;
permissions = "u+rx,g+x,o+x";
}
];
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).
'';
};
security.wrapperDir = mkOption {
internal = true;
type = types.path;
default = "/var/setuid-wrappers";
description = ''
This option defines the path to the setuid wrappers. It
should generally not be overriden. Some packages in Nixpkgs
expect that <option>wrapperDir</option> is
<filename>/var/setuid-wrappers</filename>.
'';
};
};
###### implementation
config = {
security.setuidPrograms = [ "fusermount" ];
system.activationScripts.setuid =
let
setuidPrograms =
(map (x: { program = x; owner = "root"; group = "root"; setuid = true; })
config.security.setuidPrograms)
++ config.security.setuidOwners;
makeSetuidWrapper =
{ program
, source ? ""
, owner ? "nobody"
, group ? "nogroup"
, setuid ? false
, setgid ? false
, permissions ? "u+rx,g+x,o+x"
}:
''
if ! source=${if source != "" then source else "$(readlink -f $(PATH=$SETUID_PATH type -tP ${program}))"}; then
# If we can't find the program, fall back to the
# system profile.
source=/nix/var/nix/profiles/default/bin/${program}
fi
cp ${setuidWrapper}/bin/setuid-wrapper $wrapperDir/${program}
echo -n "$source" > $wrapperDir/${program}.real
chmod 0000 $wrapperDir/${program} # to prevent races
chown ${owner}.${group} $wrapperDir/${program}
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program}
'';
in stringAfter [ "users" ]
''
# Look in the system path and in the default profile for
# programs to be wrapped.
SETUID_PATH=${config.system.path}/bin:${config.system.path}/sbin
mkdir -p /run/setuid-wrapper-dirs
wrapperDir=$(mktemp --directory --tmpdir=/run/setuid-wrapper-dirs setuid-wrappers.XXXXXXXXXX)
${concatMapStrings makeSetuidWrapper setuidPrograms}
if [ -L ${wrapperDir} ]; then
# Atomically replace the symlink
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
old=$(readlink ${wrapperDir})
ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp
mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir}
rm --force --recursive $old
elif [ -d ${wrapperDir} ]; then
# Compatibility with old state, just remove the folder and symlink
rm -f ${wrapperDir}/*
# if it happens to be a tmpfs
umount ${wrapperDir} || true
rm -d ${wrapperDir}
ln -d --symbolic $wrapperDir ${wrapperDir}
else
# For initial setup
ln --symbolic $wrapperDir ${wrapperDir}
fi
'';
};
}