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

authored by Domen Kožar and committed by GitHub a6670c1a 78cd9f8e

Changed files
+35 -16
nixos
doc
manual
release-notes
modules
installer
security
system
+8
nixos/doc/manual/release-notes/rl-1609.xml
··· 58 58 </listitem> 59 59 60 60 <listitem> 61 + <para>/var/setuid-wrappers/ 62 + <link xlink:href="https://github.com/NixOS/nixpkgs/pull/18124">is now a symlink so 63 + it can be atomically updated</link> 64 + and it's not mounted as tmpfs anymore since setuid binaries are located on /run/ as tmpfs. 65 + </para> 66 + </listitem> 67 + 68 + <listitem> 61 69 <para>Gitlab's maintainence script gitlab-runner was removed and split up into the more clearer 62 70 gitlab-run and gitlab-rake scripts because gitlab-runner is a component of Gitlab CI.</para> 63 71 </listitem>
-2
nixos/modules/installer/tools/nixos-install.sh
··· 92 92 mkdir -m 0755 -p $mountPoint/dev $mountPoint/proc $mountPoint/sys $mountPoint/etc $mountPoint/run $mountPoint/home 93 93 mkdir -m 01777 -p $mountPoint/tmp 94 94 mkdir -m 0755 -p $mountPoint/tmp/root 95 - mkdir -m 0755 -p $mountPoint/var/setuid-wrappers 96 95 mkdir -m 0700 -p $mountPoint/root 97 96 mount --rbind /dev $mountPoint/dev 98 97 mount --rbind /proc $mountPoint/proc 99 98 mount --rbind /sys $mountPoint/sys 100 99 mount --rbind / $mountPoint/tmp/root 101 100 mount -t tmpfs -o "mode=0755" none $mountPoint/run 102 - mount -t tmpfs -o "mode=0755" none $mountPoint/var/setuid-wrappers 103 101 rm -rf $mountPoint/var/run 104 102 ln -s /run $mountPoint/var/run 105 103 for f in /etc/resolv.conf /etc/hosts; do rm -f $mountPoint/$f; [ -f "$f" ] && cp -Lf $f $mountPoint/etc/; done
+27 -7
nixos/modules/security/setuid-wrappers.nix
··· 12 12 installPhase = '' 13 13 mkdir -p $out/bin 14 14 cp ${./setuid-wrapper.c} setuid-wrapper.c 15 - gcc -Wall -O2 -DWRAPPER_DIR=\"${wrapperDir}\" \ 15 + gcc -Wall -O2 -DWRAPPER_DIR=\"/run/setuid-wrapper-dirs\" \ 16 16 setuid-wrapper.c -o $out/bin/setuid-wrapper 17 17 ''; 18 18 }; ··· 102 102 source=/nix/var/nix/profiles/default/bin/${program} 103 103 fi 104 104 105 - cp ${setuidWrapper}/bin/setuid-wrapper ${wrapperDir}/${program} 106 - echo -n "$source" > ${wrapperDir}/${program}.real 107 - chmod 0000 ${wrapperDir}/${program} # to prevent races 108 - chown ${owner}.${group} ${wrapperDir}/${program} 109 - chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" ${wrapperDir}/${program} 105 + cp ${setuidWrapper}/bin/setuid-wrapper $wrapperDir/${program} 106 + echo -n "$source" > $wrapperDir/${program}.real 107 + chmod 0000 $wrapperDir/${program} # to prevent races 108 + chown ${owner}.${group} $wrapperDir/${program} 109 + chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program} 110 110 ''; 111 111 112 112 in stringAfter [ "users" ] ··· 115 115 # programs to be wrapped. 116 116 SETUID_PATH=${config.system.path}/bin:${config.system.path}/sbin 117 117 118 - rm -f ${wrapperDir}/* # */ 118 + mkdir -p /run/setuid-wrapper-dirs 119 + wrapperDir=$(mktemp --directory --tmpdir=/run/setuid-wrapper-dirs setuid-wrappers.XXXXXXXXXX) 119 120 120 121 ${concatMapStrings makeSetuidWrapper setuidPrograms} 122 + 123 + if [ -L ${wrapperDir} ]; then 124 + # Atomically replace the symlink 125 + # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ 126 + old=$(readlink ${wrapperDir}) 127 + ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp 128 + mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir} 129 + rm --force --recursive $old 130 + elif [ -d ${wrapperDir} ]; then 131 + # Compatibility with old state, just remove the folder and symlink 132 + rm -f ${wrapperDir}/* 133 + # if it happens to be a tmpfs 134 + umount ${wrapperDir} || true 135 + rm -d ${wrapperDir} 136 + ln -d --symbolic $wrapperDir ${wrapperDir} 137 + else 138 + # For initial setup 139 + ln --symbolic $wrapperDir ${wrapperDir} 140 + fi 121 141 ''; 122 142 123 143 };
-7
nixos/modules/system/boot/stage-2-init.sh
··· 141 141 cat /etc/resolv.conf | resolvconf -m 1000 -a host 142 142 fi 143 143 144 - 145 - # Create /var/setuid-wrappers as a tmpfs. 146 - rm -rf /var/setuid-wrappers 147 - mkdir -m 0755 -p /var/setuid-wrappers 148 - mount -t tmpfs -o "mode=0755" tmpfs /var/setuid-wrappers 149 - 150 - 151 144 # Log the script output to /dev/kmsg or /run/log/stage-2-init.log. 152 145 # Only at this point are all the necessary prerequisites ready for these commands. 153 146 exec {logOutFd}>&1 {logErrFd}>&2