1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 inherit (config.security) wrapperDir;
8
9 setuidWrapper = pkgs.stdenv.mkDerivation {
10 name = "setuid-wrapper";
11 unpackPhase = "true";
12 installPhase = ''
13 mkdir -p $out/bin
14 cp ${./setuid-wrapper.c} setuid-wrapper.c
15 gcc -Wall -O2 -DWRAPPER_DIR=\"/run/setuid-wrapper-dirs\" \
16 setuid-wrapper.c -o $out/bin/setuid-wrapper
17 '';
18 };
19
20in
21
22{
23
24 ###### interface
25
26 options = {
27
28 security.setuidPrograms = mkOption {
29 type = types.listOf types.str;
30 default = [];
31 example = ["passwd"];
32 description = ''
33 The Nix store cannot contain setuid/setgid programs directly.
34 For this reason, NixOS can automatically generate wrapper
35 programs that have the necessary privileges. This option
36 lists the names of programs in the system environment for
37 which setuid root wrappers should be created.
38 '';
39 };
40
41 security.setuidOwners = mkOption {
42 type = types.listOf types.attrs;
43 default = [];
44 example =
45 [ { program = "sendmail";
46 owner = "nobody";
47 group = "postdrop";
48 setuid = false;
49 setgid = true;
50 permissions = "u+rx,g+x,o+x";
51 }
52 ];
53 description = ''
54 This option allows the ownership and permissions on the setuid
55 wrappers for specific programs to be overridden from the
56 default (setuid root, but not setgid root).
57 '';
58 };
59
60 security.wrapperDir = mkOption {
61 internal = true;
62 type = types.path;
63 default = "/var/setuid-wrappers";
64 description = ''
65 This option defines the path to the setuid wrappers. It
66 should generally not be overriden. Some packages in Nixpkgs
67 expect that <option>wrapperDir</option> is
68 <filename>/var/setuid-wrappers</filename>.
69 '';
70 };
71
72 };
73
74
75 ###### implementation
76
77 config = {
78
79 security.setuidPrograms = [ "fusermount" ];
80
81 system.activationScripts.setuid =
82 let
83 setuidPrograms =
84 (map (x: { program = x; owner = "root"; group = "root"; setuid = true; })
85 config.security.setuidPrograms)
86 ++ config.security.setuidOwners;
87
88 makeSetuidWrapper =
89 { program
90 , source ? ""
91 , owner ? "nobody"
92 , group ? "nogroup"
93 , setuid ? false
94 , setgid ? false
95 , permissions ? "u+rx,g+x,o+x"
96 }:
97
98 ''
99 if ! source=${if source != "" then source else "$(readlink -f $(PATH=$SETUID_PATH type -tP ${program}))"}; then
100 # If we can't find the program, fall back to the
101 # system profile.
102 source=/nix/var/nix/profiles/default/bin/${program}
103 fi
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}
110 '';
111
112 in stringAfter [ "users" ]
113 ''
114 # Look in the system path and in the default profile for
115 # programs to be wrapped.
116 SETUID_PATH=${config.system.path}/bin:${config.system.path}/sbin
117
118 mkdir -p /run/setuid-wrapper-dirs
119 wrapperDir=$(mktemp --directory --tmpdir=/run/setuid-wrapper-dirs setuid-wrappers.XXXXXXXXXX)
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
141 '';
142
143 };
144
145}