diff --git a/nixos/modules/security/setuid-swap.c b/nixos/modules/security/setuid-swap.c new file mode 100644 index 0000000000000..e184096b6f3f2 --- /dev/null +++ b/nixos/modules/security/setuid-swap.c @@ -0,0 +1,43 @@ +/* Try to atomically swap two files with renameat2/RENAME_EXCHANGE, + * falling back to swapping through a third temporary path. + * + * We use this for atomically (or almost atomically) updating the + * setuid-wrapper dir, rather than using the always-atomic symlink + * updating pattern, to avoid any potential issues with setuid + * binaries and symlinks. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +static void move(const char * old, const char * new) { + if (rename(old, new) == -1) { + fprintf(stderr, "moving %s to %s: %s\n", old, new, strerror(errno)); + exit(1); + } +} + +int main(int argc, char ** argv) { + if (argc != 4) { + fprintf(stderr, "USAGE: %s OLD NEW TMP\n", argv[0]); + return 1; + } + + if (syscall(SYS_renameat2, AT_FDCWD, argv[1], AT_FDCWD, argv[2], RENAME_EXCHANGE) == -1) { + fprintf(stderr, "directly swapping %s and %s failed: %s\nfalling back to swapping through %s\n", + argv[1], argv[2], strerror(errno), argv[3]); + + move(argv[1], argv[3]); + move(argv[2], argv[1]); + move(argv[3], argv[2]); + } + + return 0; +} diff --git a/nixos/modules/security/setuid-wrappers.nix b/nixos/modules/security/setuid-wrappers.nix index 99dd514feea3f..834e6ccf59dcd 100644 --- a/nixos/modules/security/setuid-wrappers.nix +++ b/nixos/modules/security/setuid-wrappers.nix @@ -17,6 +17,12 @@ let ''; }; + setuid-swap = pkgs.runCommand "setuid-swap" {} '' + mkdir -p $out/bin + gcc -Wall -O2 ${./setuid-swap.c} -o $out/bin/setuid-swap + fixupPhase + ''; + in { @@ -102,11 +108,11 @@ in 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} + cp ${setuidWrapper}/bin/setuid-wrapper ${wrapperDir}-new/${program} + echo -n "$source" > ${wrapperDir}-new/${program}.real + chmod 0000 ${wrapperDir}-new/${program} # to prevent races + chown ${owner}.${group} ${wrapperDir}-new/${program} + chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" ${wrapperDir}-new/${program} ''; in stringAfter [ "users" ] @@ -115,9 +121,11 @@ in # programs to be wrapped. SETUID_PATH=${config.system.path}/bin:${config.system.path}/sbin - rm -f ${wrapperDir}/* # */ - + mkdir -p ${wrapperDir}-new ${concatMapStrings makeSetuidWrapper setuidPrograms} + + ${setuid-swap}/bin/setuid-swap ${wrapperDir}-new ${wrapperDir} ${wrapperDir}-tmp + rm -fR ${wrapperDir}-new ''; };