Merge pull request #126289 from rnhmjoj/wrappers

nixos/security/wrappers: make well-typed

authored by

Guillaume Girol and committed by
GitHub
ceb2e666 a92dd171

+462 -155
+10
nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
··· 280 280 <itemizedlist> 281 281 <listitem> 282 282 <para> 283 + The <literal>security.wrappers</literal> option now requires 284 + to always specify an owner, group and whether the 285 + setuid/setgid bit should be set. This is motivated by the fact 286 + that before NixOS 21.11, specifying either setuid or setgid 287 + but not owner/group resulted in wrappers owned by 288 + nobody/nogroup, which is unsafe. 289 + </para> 290 + </listitem> 291 + <listitem> 292 + <para> 283 293 The <literal>paperless</literal> module and package have been 284 294 removed. All users should migrate to the successor 285 295 <literal>paperless-ng</literal> instead. The Paperless project
+2
nixos/doc/manual/release-notes/rl-2111.section.md
··· 88 88 89 89 ## Backward Incompatibilities {#sec-release-21.11-incompatibilities} 90 90 91 + - The `security.wrappers` option now requires to always specify an owner, group and whether the setuid/setgid bit should be set. 92 + 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. 91 93 92 94 - The `paperless` module and package have been removed. All users should migrate to the 93 95 successor `paperless-ng` instead. The Paperless project [has been
+3 -1
nixos/modules/programs/bandwhich.nix
··· 22 22 config = mkIf cfg.enable { 23 23 environment.systemPackages = with pkgs; [ bandwhich ]; 24 24 security.wrappers.bandwhich = { 25 - source = "${pkgs.bandwhich}/bin/bandwhich"; 25 + owner = "root"; 26 + group = "root"; 26 27 capabilities = "cap_net_raw,cap_net_admin+ep"; 28 + source = "${pkgs.bandwhich}/bin/bandwhich"; 27 29 }; 28 30 }; 29 31 }
+4
nixos/modules/programs/captive-browser.nix
··· 105 105 ); 106 106 107 107 security.wrappers.udhcpc = { 108 + owner = "root"; 109 + group = "root"; 108 110 capabilities = "cap_net_raw+p"; 109 111 source = "${pkgs.busybox}/bin/udhcpc"; 110 112 }; 111 113 112 114 security.wrappers.captive-browser = { 115 + owner = "root"; 116 + group = "root"; 113 117 capabilities = "cap_net_raw+p"; 114 118 source = pkgs.writeShellScript "captive-browser" '' 115 119 export PREV_CONFIG_HOME="$XDG_CONFIG_HOME"
+2
nixos/modules/programs/ccache.nix
··· 28 28 29 29 # "nix-ccache --show-stats" and "nix-ccache --clear" 30 30 security.wrappers.nix-ccache = { 31 + owner = "nobody"; 31 32 group = "nixbld"; 33 + setuid = false; 32 34 setgid = true; 33 35 source = pkgs.writeScript "nix-ccache.pl" '' 34 36 #!${pkgs.perl}/bin/perl
+6 -1
nixos/modules/programs/firejail.nix
··· 81 81 }; 82 82 83 83 config = mkIf cfg.enable { 84 - security.wrappers.firejail.source = "${lib.getBin pkgs.firejail}/bin/firejail"; 84 + security.wrappers.firejail = 85 + { setuid = true; 86 + owner = "root"; 87 + group = "root"; 88 + source = "${lib.getBin pkgs.firejail}/bin/firejail"; 89 + }; 85 90 86 91 environment.systemPackages = [ pkgs.firejail ] ++ [ wrappedBins ]; 87 92 };
+2
nixos/modules/programs/gamemode.nix
··· 56 56 polkit.enable = true; 57 57 wrappers = mkIf cfg.enableRenice { 58 58 gamemoded = { 59 + owner = "root"; 60 + group = "root"; 59 61 source = "${pkgs.gamemode}/bin/gamemoded"; 60 62 capabilities = "cap_sys_nice+ep"; 61 63 };
+3 -1
nixos/modules/programs/iftop.nix
··· 11 11 config = mkIf cfg.enable { 12 12 environment.systemPackages = [ pkgs.iftop ]; 13 13 security.wrappers.iftop = { 14 - source = "${pkgs.iftop}/bin/iftop"; 14 + owner = "root"; 15 + group = "root"; 15 16 capabilities = "cap_net_raw+p"; 17 + source = "${pkgs.iftop}/bin/iftop"; 16 18 }; 17 19 }; 18 20 }
+3 -1
nixos/modules/programs/iotop.nix
··· 10 10 }; 11 11 config = mkIf cfg.enable { 12 12 security.wrappers.iotop = { 13 - source = "${pkgs.iotop}/bin/iotop"; 13 + owner = "root"; 14 + group = "root"; 14 15 capabilities = "cap_net_admin+p"; 16 + source = "${pkgs.iotop}/bin/iotop"; 15 17 }; 16 18 }; 17 19 }
+6 -1
nixos/modules/programs/kbdlight.nix
··· 11 11 12 12 config = mkIf cfg.enable { 13 13 environment.systemPackages = [ pkgs.kbdlight ]; 14 - security.wrappers.kbdlight.source = "${pkgs.kbdlight.out}/bin/kbdlight"; 14 + security.wrappers.kbdlight = 15 + { setuid = true; 16 + owner = "root"; 17 + group = "root"; 18 + source = "${pkgs.kbdlight.out}/bin/kbdlight"; 19 + }; 15 20 }; 16 21 }
+3 -1
nixos/modules/programs/liboping.nix
··· 13 13 security.wrappers = mkMerge (map ( 14 14 exec: { 15 15 "${exec}" = { 16 - source = "${pkgs.liboping}/bin/${exec}"; 16 + owner = "root"; 17 + group = "root"; 17 18 capabilities = "cap_net_raw+p"; 19 + source = "${pkgs.liboping}/bin/${exec}"; 18 20 }; 19 21 } 20 22 ) [ "oping" "noping" ]);
+2
nixos/modules/programs/msmtp.nix
··· 78 78 source = "${pkgs.msmtp}/bin/sendmail"; 79 79 setuid = false; 80 80 setgid = false; 81 + owner = "root"; 82 + group = "root"; 81 83 }; 82 84 83 85 environment.etc."msmtprc".text = let
+3 -1
nixos/modules/programs/mtr.nix
··· 31 31 environment.systemPackages = with pkgs; [ cfg.package ]; 32 32 33 33 security.wrappers.mtr-packet = { 34 - source = "${cfg.package}/bin/mtr-packet"; 34 + owner = "root"; 35 + group = "root"; 35 36 capabilities = "cap_net_raw+p"; 37 + source = "${cfg.package}/bin/mtr-packet"; 36 38 }; 37 39 }; 38 40 }
+3 -1
nixos/modules/programs/noisetorch.nix
··· 18 18 19 19 config = mkIf cfg.enable { 20 20 security.wrappers.noisetorch = { 21 - source = "${cfg.package}/bin/noisetorch"; 21 + owner = "root"; 22 + group = "root"; 22 23 capabilities = "cap_sys_resource=+ep"; 24 + source = "${cfg.package}/bin/noisetorch"; 23 25 }; 24 26 }; 25 27 }
+14 -7
nixos/modules/programs/shadow.nix
··· 43 43 44 44 ''; 45 45 46 + mkSetuidRoot = source: 47 + { setuid = true; 48 + owner = "root"; 49 + group = "root"; 50 + inherit source; 51 + }; 52 + 46 53 in 47 54 48 55 { ··· 109 116 }; 110 117 111 118 security.wrappers = { 112 - su.source = "${pkgs.shadow.su}/bin/su"; 113 - sg.source = "${pkgs.shadow.out}/bin/sg"; 114 - newgrp.source = "${pkgs.shadow.out}/bin/newgrp"; 115 - newuidmap.source = "${pkgs.shadow.out}/bin/newuidmap"; 116 - newgidmap.source = "${pkgs.shadow.out}/bin/newgidmap"; 119 + su = mkSetuidRoot "${pkgs.shadow.su}/bin/su"; 120 + sg = mkSetuidRoot "${pkgs.shadow.out}/bin/sg"; 121 + newgrp = mkSetuidRoot "${pkgs.shadow.out}/bin/newgrp"; 122 + newuidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newuidmap"; 123 + newgidmap = mkSetuidRoot "${pkgs.shadow.out}/bin/newgidmap"; 117 124 } // lib.optionalAttrs config.users.mutableUsers { 118 - chsh.source = "${pkgs.shadow.out}/bin/chsh"; 119 - passwd.source = "${pkgs.shadow.out}/bin/passwd"; 125 + chsh = mkSetuidRoot "${pkgs.shadow.out}/bin/chsh"; 126 + passwd = mkSetuidRoot "${pkgs.shadow.out}/bin/passwd"; 120 127 }; 121 128 }; 122 129 }
+6 -1
nixos/modules/programs/singularity.nix
··· 16 16 17 17 config = mkIf cfg.enable { 18 18 environment.systemPackages = [ singularity ]; 19 - security.wrappers.singularity-suid.source = "${singularity}/libexec/singularity/bin/starter-suid.orig"; 19 + security.wrappers.singularity-suid = 20 + { setuid = true; 21 + owner = "root"; 22 + group = "root"; 23 + source = "${singularity}/libexec/singularity/bin/starter-suid.orig"; 24 + }; 20 25 systemd.tmpfiles.rules = [ 21 26 "d /var/singularity/mnt/session 0770 root root -" 22 27 "d /var/singularity/mnt/final 0770 root root -"
+6 -1
nixos/modules/programs/slock.nix
··· 21 21 22 22 config = mkIf cfg.enable { 23 23 environment.systemPackages = [ pkgs.slock ]; 24 - security.wrappers.slock.source = "${pkgs.slock.out}/bin/slock"; 24 + security.wrappers.slock = 25 + { setuid = true; 26 + owner = "root"; 27 + group = "root"; 28 + source = "${pkgs.slock.out}/bin/slock"; 29 + }; 25 30 }; 26 31 }
+2
nixos/modules/programs/ssmtp.nix
··· 181 181 source = "${pkgs.ssmtp}/bin/sendmail"; 182 182 setuid = false; 183 183 setgid = false; 184 + owner = "root"; 185 + group = "root"; 184 186 }; 185 187 186 188 };
+3 -1
nixos/modules/programs/traceroute.nix
··· 19 19 20 20 config = mkIf cfg.enable { 21 21 security.wrappers.traceroute = { 22 - source = "${pkgs.traceroute}/bin/traceroute"; 22 + owner = "root"; 23 + group = "root"; 23 24 capabilities = "cap_net_raw+p"; 25 + source = "${pkgs.traceroute}/bin/traceroute"; 24 26 }; 25 27 }; 26 28 }
+6 -1
nixos/modules/programs/udevil.nix
··· 9 9 options.programs.udevil.enable = mkEnableOption "udevil"; 10 10 11 11 config = mkIf cfg.enable { 12 - security.wrappers.udevil.source = "${lib.getBin pkgs.udevil}/bin/udevil"; 12 + security.wrappers.udevil = 13 + { setuid = true; 14 + owner = "root"; 15 + group = "root"; 16 + source = "${lib.getBin pkgs.udevil}/bin/udevil"; 17 + }; 13 18 }; 14 19 }
+3 -1
nixos/modules/programs/wavemon.nix
··· 21 21 config = mkIf cfg.enable { 22 22 environment.systemPackages = with pkgs; [ wavemon ]; 23 23 security.wrappers.wavemon = { 24 - source = "${pkgs.wavemon}/bin/wavemon"; 24 + owner = "root"; 25 + group = "root"; 25 26 capabilities = "cap_net_admin+ep"; 27 + source = "${pkgs.wavemon}/bin/wavemon"; 26 28 }; 27 29 }; 28 30 }
+6 -1
nixos/modules/programs/wshowkeys.nix
··· 17 17 }; 18 18 19 19 config = mkIf cfg.enable { 20 - security.wrappers.wshowkeys.source = "${pkgs.wshowkeys}/bin/wshowkeys"; 20 + security.wrappers.wshowkeys = 21 + { setuid = true; 22 + owner = "root"; 23 + group = "root"; 24 + source = "${pkgs.wshowkeys}/bin/wshowkeys"; 25 + }; 21 26 }; 22 27 }
+6 -1
nixos/modules/security/chromium-suid-sandbox.nix
··· 28 28 29 29 config = mkIf cfg.enable { 30 30 environment.systemPackages = [ sandbox ]; 31 - security.wrappers.${sandbox.passthru.sandboxExecutableName}.source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}"; 31 + security.wrappers.${sandbox.passthru.sandboxExecutableName} = 32 + { setuid = true; 33 + owner = "root"; 34 + group = "root"; 35 + source = "${sandbox}/bin/${sandbox.passthru.sandboxExecutableName}"; 36 + }; 32 37 }; 33 38 }
+6 -3
nixos/modules/security/doas.nix
··· 241 241 } 242 242 ]; 243 243 244 - security.wrappers = { 245 - doas.source = "${doas}/bin/doas"; 246 - }; 244 + security.wrappers.doas = 245 + { setuid = true; 246 + owner = "root"; 247 + group = "root"; 248 + source = "${doas}/bin/doas"; 249 + }; 247 250 248 251 environment.systemPackages = [ 249 252 doas
+6 -1
nixos/modules/security/duosec.nix
··· 186 186 config = mkIf (cfg.ssh.enable || cfg.pam.enable) { 187 187 environment.systemPackages = [ pkgs.duo-unix ]; 188 188 189 - security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo"; 189 + security.wrappers.login_duo = 190 + { setuid = true; 191 + owner = "root"; 192 + group = "root"; 193 + source = "${pkgs.duo-unix.out}/bin/login_duo"; 194 + }; 190 195 191 196 system.activationScripts = { 192 197 login_duo = mkIf cfg.ssh.enable ''
+3 -2
nixos/modules/security/pam.nix
··· 869 869 870 870 security.wrappers = { 871 871 unix_chkpwd = { 872 + setuid = true; 873 + owner = "root"; 874 + group = "root"; 872 875 source = "${pkgs.pam}/sbin/unix_chkpwd.orig"; 873 - owner = "root"; 874 - setuid = true; 875 876 }; 876 877 }; 877 878
+12 -2
nixos/modules/security/pam_usb.nix
··· 32 32 33 33 # Make sure pmount and pumount are setuid wrapped. 34 34 security.wrappers = { 35 - pmount.source = "${pkgs.pmount.out}/bin/pmount"; 36 - pumount.source = "${pkgs.pmount.out}/bin/pumount"; 35 + pmount = 36 + { setuid = true; 37 + owner = "root"; 38 + group = "root"; 39 + source = "${pkgs.pmount.out}/bin/pmount"; 40 + }; 41 + pumount = 42 + { setuid = true; 43 + owner = "root"; 44 + group = "root"; 45 + source = "${pkgs.pmount.out}/bin/pumount"; 46 + }; 37 47 }; 38 48 39 49 environment.systemPackages = [ pkgs.pmount ];
+12 -2
nixos/modules/security/polkit.nix
··· 83 83 security.pam.services.polkit-1 = {}; 84 84 85 85 security.wrappers = { 86 - pkexec.source = "${pkgs.polkit.bin}/bin/pkexec"; 87 - polkit-agent-helper-1.source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1"; 86 + pkexec = 87 + { setuid = true; 88 + owner = "root"; 89 + group = "root"; 90 + source = "${pkgs.polkit.bin}/bin/pkexec"; 91 + }; 92 + polkit-agent-helper-1 = 93 + { setuid = true; 94 + owner = "root"; 95 + group = "root"; 96 + source = "${pkgs.polkit.out}/lib/polkit-1/polkit-agent-helper-1"; 97 + }; 88 98 }; 89 99 90 100 systemd.tmpfiles.rules = [
+188 -94
nixos/modules/security/wrappers/default.nix
··· 5 5 6 6 parentWrapperDir = dirOf wrapperDir; 7 7 8 - programs = 9 - (lib.mapAttrsToList 10 - (n: v: (if v ? program then v else v // {program=n;})) 11 - wrappers); 12 - 13 8 securityWrapper = pkgs.callPackage ./wrapper.nix { 14 9 inherit parentWrapperDir; 15 10 }; 16 11 12 + fileModeType = 13 + let 14 + # taken from the chmod(1) man page 15 + symbolic = "[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+"; 16 + numeric = "[-+=]?[0-7]{0,4}"; 17 + mode = "((${symbolic})(,${symbolic})*)|(${numeric})"; 18 + in 19 + lib.types.strMatching mode 20 + // { description = "file mode string"; }; 21 + 22 + wrapperType = lib.types.submodule ({ name, config, ... }: { 23 + options.source = lib.mkOption 24 + { type = lib.types.path; 25 + description = "The absolute path to the program to be wrapped."; 26 + }; 27 + options.program = lib.mkOption 28 + { type = with lib.types; nullOr str; 29 + default = name; 30 + description = '' 31 + The name of the wrapper program. Defaults to the attribute name. 32 + ''; 33 + }; 34 + options.owner = lib.mkOption 35 + { type = lib.types.str; 36 + description = "The owner of the wrapper program."; 37 + }; 38 + options.group = lib.mkOption 39 + { type = lib.types.str; 40 + description = "The group of the wrapper program."; 41 + }; 42 + options.permissions = lib.mkOption 43 + { type = fileModeType; 44 + default = "u+rx,g+x,o+x"; 45 + example = "a+rx"; 46 + description = '' 47 + The permissions of the wrapper program. The format is that of a 48 + symbolic or numeric file mode understood by <command>chmod</command>. 49 + ''; 50 + }; 51 + options.capabilities = lib.mkOption 52 + { type = lib.types.commas; 53 + default = ""; 54 + description = '' 55 + A comma-separated list of capabilities to be given to the wrapper 56 + program. For capabilities supported by the system check the 57 + <citerefentry> 58 + <refentrytitle>capabilities</refentrytitle> 59 + <manvolnum>7</manvolnum> 60 + </citerefentry> 61 + manual page. 62 + 63 + <note><para> 64 + <literal>cap_setpcap</literal>, which is required for the wrapper 65 + program to be able to raise caps into the Ambient set is NOT raised 66 + to the Ambient set so that the real program cannot modify its own 67 + capabilities!! This may be too restrictive for cases in which the 68 + real program needs cap_setpcap but it at least leans on the side 69 + security paranoid vs. too relaxed. 70 + </para></note> 71 + ''; 72 + }; 73 + options.setuid = lib.mkOption 74 + { type = lib.types.bool; 75 + default = false; 76 + description = "Whether to add the setuid bit the wrapper program."; 77 + }; 78 + options.setgid = lib.mkOption 79 + { type = lib.types.bool; 80 + default = false; 81 + description = "Whether to add the setgid bit the wrapper program."; 82 + }; 83 + }); 84 + 17 85 ###### Activation script for the setcap wrappers 18 86 mkSetcapProgram = 19 87 { program 20 88 , capabilities 21 89 , source 22 - , owner ? "nobody" 23 - , group ? "nogroup" 24 - , permissions ? "u+rx,g+x,o+x" 90 + , owner 91 + , group 92 + , permissions 25 93 , ... 26 94 }: 27 95 assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3"); 28 96 '' 29 - cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program} 30 - echo -n "${source}" > $wrapperDir/${program}.real 97 + cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" 98 + echo -n "${source}" > "$wrapperDir/${program}.real" 31 99 32 100 # Prevent races 33 - chmod 0000 $wrapperDir/${program} 34 - chown ${owner}.${group} $wrapperDir/${program} 101 + chmod 0000 "$wrapperDir/${program}" 102 + chown ${owner}.${group} "$wrapperDir/${program}" 35 103 36 104 # Set desired capabilities on the file plus cap_setpcap so 37 105 # the wrapper program can elevate the capabilities set on 38 106 # its file into the Ambient set. 39 - ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program} 107 + ${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" "$wrapperDir/${program}" 40 108 41 109 # Set the executable bit 42 - chmod ${permissions} $wrapperDir/${program} 110 + chmod ${permissions} "$wrapperDir/${program}" 43 111 ''; 44 112 45 113 ###### Activation script for the setuid wrappers 46 114 mkSetuidProgram = 47 115 { program 48 116 , source 49 - , owner ? "nobody" 50 - , group ? "nogroup" 51 - , setuid ? false 52 - , setgid ? false 53 - , permissions ? "u+rx,g+x,o+x" 117 + , owner 118 + , group 119 + , setuid 120 + , setgid 121 + , permissions 54 122 , ... 55 123 }: 56 124 '' 57 - cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program} 58 - echo -n "${source}" > $wrapperDir/${program}.real 125 + cp ${securityWrapper}/bin/security-wrapper "$wrapperDir/${program}" 126 + echo -n "${source}" > "$wrapperDir/${program}.real" 59 127 60 128 # Prevent races 61 - chmod 0000 $wrapperDir/${program} 62 - chown ${owner}.${group} $wrapperDir/${program} 129 + chmod 0000 "$wrapperDir/${program}" 130 + chown ${owner}.${group} "$wrapperDir/${program}" 63 131 64 - chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program} 132 + chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" "$wrapperDir/${program}" 65 133 ''; 66 134 67 135 mkWrappedPrograms = 68 136 builtins.map 69 - (s: if (s ? capabilities) 70 - then mkSetcapProgram 71 - ({ owner = "root"; 72 - group = "root"; 73 - } // s) 74 - else if 75 - (s ? setuid && s.setuid) || 76 - (s ? setgid && s.setgid) || 77 - (s ? permissions) 78 - then mkSetuidProgram s 79 - else mkSetuidProgram 80 - ({ owner = "root"; 81 - group = "root"; 82 - setuid = true; 83 - setgid = false; 84 - permissions = "u+rx,g+x,o+x"; 85 - } // s) 86 - ) programs; 137 + (opts: 138 + if opts.capabilities != "" 139 + then mkSetcapProgram opts 140 + else mkSetuidProgram opts 141 + ) (lib.attrValues wrappers); 87 142 in 88 143 { 89 144 imports = [ ··· 95 150 96 151 options = { 97 152 security.wrappers = lib.mkOption { 98 - type = lib.types.attrs; 153 + type = lib.types.attrsOf wrapperType; 99 154 default = {}; 100 155 example = lib.literalExample 101 156 '' 102 - { sendmail.source = "/nix/store/.../bin/sendmail"; 103 - ping = { 104 - source = "${pkgs.iputils.out}/bin/ping"; 105 - owner = "nobody"; 106 - group = "nogroup"; 107 - capabilities = "cap_net_raw+ep"; 108 - }; 157 + { 158 + # a setuid root program 159 + doas = 160 + { setuid = true; 161 + owner = "root"; 162 + group = "root"; 163 + source = "''${pkgs.doas}/bin/doas"; 164 + }; 165 + 166 + # a setgid program 167 + locate = 168 + { setgid = true; 169 + owner = "root"; 170 + group = "mlocate"; 171 + source = "''${pkgs.locate}/bin/locate"; 172 + }; 173 + 174 + # a program with the CAP_NET_RAW capability 175 + ping = 176 + { owner = "root"; 177 + group = "root"; 178 + capabilities = "cap_net_raw+ep"; 179 + source = "''${pkgs.iputils.out}/bin/ping"; 180 + }; 109 181 } 110 182 ''; 111 183 description = '' 112 - This option allows the ownership and permissions on the setuid 113 - wrappers for specific programs to be overridden from the 114 - default (setuid root, but not setgid root). 115 - 116 - <note> 117 - <para>The sub-attribute <literal>source</literal> is mandatory, 118 - it must be the absolute path to the program to be wrapped. 119 - </para> 120 - 121 - <para>The sub-attribute <literal>program</literal> is optional and 122 - can give the wrapper program a new name. The default name is the same 123 - as the attribute name itself.</para> 124 - 125 - <para>Additionally, this option can set capabilities on a 126 - wrapper program that propagates those capabilities down to the 127 - wrapped, real program.</para> 128 - 129 - <para>NOTE: cap_setpcap, which is required for the wrapper 130 - program to be able to raise caps into the Ambient set is NOT 131 - raised to the Ambient set so that the real program cannot 132 - modify its own capabilities!! This may be too restrictive for 133 - cases in which the real program needs cap_setpcap but it at 134 - least leans on the side security paranoid vs. too 135 - relaxed.</para> 136 - </note> 184 + This option effectively allows adding setuid/setgid bits, capabilities, 185 + changing file ownership and permissions of a program without directly 186 + modifying it. This works by creating a wrapper program under the 187 + <option>security.wrapperDir</option> directory, which is then added to 188 + the shell <literal>PATH</literal>. 137 189 ''; 138 190 }; 139 191 ··· 151 203 ###### implementation 152 204 config = { 153 205 154 - security.wrappers = { 155 - # These are mount related wrappers that require the +s permission. 156 - fusermount.source = "${pkgs.fuse}/bin/fusermount"; 157 - fusermount3.source = "${pkgs.fuse3}/bin/fusermount3"; 158 - mount.source = "${lib.getBin pkgs.util-linux}/bin/mount"; 159 - umount.source = "${lib.getBin pkgs.util-linux}/bin/umount"; 160 - }; 206 + assertions = lib.mapAttrsToList 207 + (name: opts: 208 + { assertion = opts.setuid || opts.setgid -> opts.capabilities == ""; 209 + message = '' 210 + The security.wrappers.${name} wrapper is not valid: 211 + setuid/setgid and capabilities are mutually exclusive. 212 + ''; 213 + } 214 + ) wrappers; 215 + 216 + security.wrappers = 217 + let 218 + mkSetuidRoot = source: 219 + { setuid = true; 220 + owner = "root"; 221 + group = "root"; 222 + inherit source; 223 + }; 224 + in 225 + { # These are mount related wrappers that require the +s permission. 226 + fusermount = mkSetuidRoot "${pkgs.fuse}/bin/fusermount"; 227 + fusermount3 = mkSetuidRoot "${pkgs.fuse3}/bin/fusermount3"; 228 + mount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/mount"; 229 + umount = mkSetuidRoot "${lib.getBin pkgs.util-linux}/bin/umount"; 230 + }; 161 231 162 232 boot.specialFileSystems.${parentWrapperDir} = { 163 233 fsType = "tmpfs"; ··· 179 249 ]}" 180 250 ''; 181 251 182 - ###### setcap activation script 252 + ###### wrappers activation script 183 253 system.activationScripts.wrappers = 184 254 lib.stringAfter [ "specialfs" "users" ] 185 255 '' 186 - # Look in the system path and in the default profile for 187 - # programs to be wrapped. 188 - WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin 189 - 190 256 chmod 755 "${parentWrapperDir}" 191 257 192 258 # We want to place the tmpdirs for the wrappers to the parent dir. 193 259 wrapperDir=$(mktemp --directory --tmpdir="${parentWrapperDir}" wrappers.XXXXXXXXXX) 194 - chmod a+rx $wrapperDir 260 + chmod a+rx "$wrapperDir" 195 261 196 262 ${lib.concatStringsSep "\n" mkWrappedPrograms} 197 263 ··· 199 265 # Atomically replace the symlink 200 266 # See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ 201 267 old=$(readlink -f ${wrapperDir}) 202 - if [ -e ${wrapperDir}-tmp ]; then 203 - rm --force --recursive ${wrapperDir}-tmp 268 + if [ -e "${wrapperDir}-tmp" ]; then 269 + rm --force --recursive "${wrapperDir}-tmp" 204 270 fi 205 - ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp 206 - mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir} 207 - rm --force --recursive $old 271 + ln --symbolic --force --no-dereference "$wrapperDir" "${wrapperDir}-tmp" 272 + mv --no-target-directory "${wrapperDir}-tmp" "${wrapperDir}" 273 + rm --force --recursive "$old" 208 274 else 209 275 # For initial setup 210 - ln --symbolic $wrapperDir ${wrapperDir} 276 + ln --symbolic "$wrapperDir" "${wrapperDir}" 211 277 fi 212 278 ''; 279 + 280 + ###### wrappers consistency checks 281 + system.extraDependencies = lib.singleton (pkgs.runCommandLocal 282 + "ensure-all-wrappers-paths-exist" { } 283 + '' 284 + # make sure we produce output 285 + mkdir -p $out 286 + 287 + echo -n "Checking that Nix store paths of all wrapped programs exist... " 288 + 289 + declare -A wrappers 290 + ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: v: 291 + "wrappers['${n}']='${v.source}'") wrappers)} 292 + 293 + for name in "''${!wrappers[@]}"; do 294 + path="''${wrappers[$name]}" 295 + if [[ "$path" =~ /nix/store ]] && [ ! -e "$path" ]; then 296 + test -t 1 && echo -ne '\033[1;31m' 297 + echo "FAIL" 298 + echo "The path $path does not exist!" 299 + echo 'Please, check the value of `security.wrappers."'$name'".source`.' 300 + test -t 1 && echo -ne '\033[0m' 301 + exit 1 302 + fi 303 + done 304 + 305 + echo "OK" 306 + ''); 213 307 }; 214 308 }
+3 -1
nixos/modules/services/desktops/gnome/gnome-keyring.nix
··· 52 52 security.pam.services.login.enableGnomeKeyring = true; 53 53 54 54 security.wrappers.gnome-keyring-daemon = { 55 - source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon"; 55 + owner = "root"; 56 + group = "root"; 56 57 capabilities = "cap_ipc_lock=ep"; 58 + source = "${pkgs.gnome.gnome-keyring}/bin/gnome-keyring-daemon"; 57 59 }; 58 60 59 61 };
+6 -1
nixos/modules/services/mail/exim.nix
··· 104 104 gid = config.ids.gids.exim; 105 105 }; 106 106 107 - security.wrappers.exim.source = "${cfg.package}/bin/exim"; 107 + security.wrappers.exim = 108 + { setuid = true; 109 + owner = "root"; 110 + group = "root"; 111 + source = "${cfg.package}/bin/exim"; 112 + }; 108 113 109 114 systemd.services.exim = { 110 115 description = "Exim Mail Daemon";
+2 -1
nixos/modules/services/mail/mail.nix
··· 1 - { config, lib, ... }: 1 + { config, options, lib, ... }: 2 2 3 3 with lib; 4 4 ··· 11 11 services.mail = { 12 12 13 13 sendmailSetuidWrapper = mkOption { 14 + type = types.nullOr options.security.wrappers.type.nestedTypes.elemType; 14 15 default = null; 15 16 internal = true; 16 17 description = ''
+4 -1
nixos/modules/services/mail/opensmtpd.nix
··· 103 103 }; 104 104 105 105 security.wrappers.smtpctl = { 106 + owner = "nobody"; 106 107 group = "smtpq"; 108 + setuid = false; 107 109 setgid = true; 108 110 source = "${cfg.package}/bin/smtpctl"; 109 111 }; 110 112 111 - services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail security.wrappers.smtpctl; 113 + services.mail.sendmailSetuidWrapper = mkIf cfg.setSendmail 114 + security.wrappers.smtpctl // { program = "sendmail"; }; 112 115 113 116 systemd.tmpfiles.rules = [ 114 117 "d /var/spool/smtpd 711 root - - -"
+4
nixos/modules/services/mail/postfix.nix
··· 673 673 services.mail.sendmailSetuidWrapper = mkIf config.services.postfix.setSendmail { 674 674 program = "sendmail"; 675 675 source = "${pkgs.postfix}/bin/sendmail"; 676 + owner = "nobody"; 676 677 group = setgidGroup; 677 678 setuid = false; 678 679 setgid = true; ··· 681 682 security.wrappers.mailq = { 682 683 program = "mailq"; 683 684 source = "${pkgs.postfix}/bin/mailq"; 685 + owner = "nobody"; 684 686 group = setgidGroup; 685 687 setuid = false; 686 688 setgid = true; ··· 689 691 security.wrappers.postqueue = { 690 692 program = "postqueue"; 691 693 source = "${pkgs.postfix}/bin/postqueue"; 694 + owner = "nobody"; 692 695 group = setgidGroup; 693 696 setuid = false; 694 697 setgid = true; ··· 697 700 security.wrappers.postdrop = { 698 701 program = "postdrop"; 699 702 source = "${pkgs.postfix}/bin/postdrop"; 703 + owner = "nobody"; 700 704 group = setgidGroup; 701 705 setuid = false; 702 706 setgid = true;
+3 -1
nixos/modules/services/misc/mame.nix
··· 45 45 environment.systemPackages = [ pkgs.mame ]; 46 46 47 47 security.wrappers."${mame}" = { 48 - source = "${pkgs.mame}/bin/${mame}"; 48 + owner = "root"; 49 + group = "root"; 49 50 capabilities = "cap_net_admin,cap_net_raw+eip"; 51 + source = "${pkgs.mame}/bin/${mame}"; 50 52 }; 51 53 52 54 systemd.services.mame = {
+6 -1
nixos/modules/services/misc/weechat.nix
··· 52 52 wants = [ "network.target" ]; 53 53 }; 54 54 55 - security.wrappers.screen.source = "${pkgs.screen}/bin/screen"; 55 + security.wrappers.screen = 56 + { setuid = true; 57 + owner = "root"; 58 + group = "root"; 59 + source = "${pkgs.screen}/bin/screen"; 60 + }; 56 61 }; 57 62 58 63 meta.doc = ./weechat.xml;
+6 -1
nixos/modules/services/monitoring/incron.nix
··· 71 71 72 72 environment.systemPackages = [ pkgs.incron ]; 73 73 74 - security.wrappers.incrontab.source = "${pkgs.incron}/bin/incrontab"; 74 + security.wrappers.incrontab = 75 + { setuid = true; 76 + owner = "root"; 77 + group = "root"; 78 + source = "${pkgs.incron}/bin/incrontab"; 79 + }; 75 80 76 81 # incron won't read symlinks 77 82 environment.etc."incron.d/system" = {
+6 -1
nixos/modules/services/monitoring/zabbix-proxy.nix
··· 262 262 }; 263 263 264 264 security.wrappers = { 265 - fping.source = "${pkgs.fping}/bin/fping"; 265 + fping = 266 + { setuid = true; 267 + owner = "root"; 268 + group = "root"; 269 + source = "${pkgs.fping}/bin/fping"; 270 + }; 266 271 }; 267 272 268 273 systemd.services.zabbix-proxy = {
+12 -2
nixos/modules/services/networking/smokeping.nix
··· 278 278 } 279 279 ]; 280 280 security.wrappers = { 281 - fping.source = "${pkgs.fping}/bin/fping"; 282 - fping6.source = "${pkgs.fping}/bin/fping6"; 281 + fping = 282 + { setuid = true; 283 + owner = "root"; 284 + group = "root"; 285 + source = "${pkgs.fping}/bin/fping"; 286 + }; 287 + fping6 = 288 + { setuid = true; 289 + owner = "root"; 290 + group = "root"; 291 + source = "${pkgs.fping}/bin/fping6"; 292 + }; 283 293 }; 284 294 environment.systemPackages = [ pkgs.fping ]; 285 295 users.users.${cfg.user} = {
+2
nixos/modules/services/networking/x2goserver.nix
··· 88 88 source = "${pkgs.x2goserver}/lib/x2go/libx2go-server-db-sqlite3-wrapper.pl"; 89 89 owner = "x2go"; 90 90 group = "x2go"; 91 + setuid = false; 91 92 setgid = true; 92 93 }; 93 94 security.wrappers.x2goprintWrapper = { 94 95 source = "${pkgs.x2goserver}/bin/x2goprint"; 95 96 owner = "x2go"; 96 97 group = "x2go"; 98 + setuid = false; 97 99 setgid = true; 98 100 }; 99 101
+6 -1
nixos/modules/services/scheduling/cron.nix
··· 93 93 94 94 { services.cron.enable = mkDefault (allFiles != []); } 95 95 (mkIf (config.services.cron.enable) { 96 - security.wrappers.crontab.source = "${cronNixosPkg}/bin/crontab"; 96 + security.wrappers.crontab = 97 + { setuid = true; 98 + owner = "root"; 99 + group = "root"; 100 + source = "${cronNixosPkg}/bin/crontab"; 101 + }; 97 102 environment.systemPackages = [ cronNixosPkg ]; 98 103 environment.etc.crontab = 99 104 { source = pkgs.runCommand "crontabs" { inherit allFiles; preferLocalBuild = true; }
+3
nixos/modules/services/scheduling/fcron.nix
··· 136 136 owner = "fcron"; 137 137 group = "fcron"; 138 138 setgid = true; 139 + setuid = false; 139 140 }; 140 141 fcronsighup = { 141 142 source = "${pkgs.fcron}/bin/fcronsighup"; 143 + owner = "root"; 142 144 group = "fcron"; 145 + setuid = true; 143 146 }; 144 147 }; 145 148 systemd.services.fcron = {
+3 -1
nixos/modules/services/video/replay-sorcery.nix
··· 44 44 45 45 security.wrappers = mkIf cfg.enableSysAdminCapability { 46 46 replay-sorcery = { 47 - source = "${pkgs.replay-sorcery}/bin/replay-sorcery"; 47 + owner = "root"; 48 + group = "root"; 48 49 capabilities = "cap_sys_admin+ep"; 50 + source = "${pkgs.replay-sorcery}/bin/replay-sorcery"; 49 51 }; 50 52 }; 51 53
+3 -2
nixos/modules/services/x11/desktop-managers/cde.nix
··· 49 49 users.groups.mail = {}; 50 50 security.wrappers = { 51 51 dtmail = { 52 + setgid = true; 53 + owner = "nobody"; 54 + group = "mail"; 52 55 source = "${pkgs.cdesktopenv}/bin/dtmail"; 53 - group = "mail"; 54 - setgid = true; 55 56 }; 56 57 }; 57 58
+18 -3
nixos/modules/services/x11/desktop-managers/enlightenment.nix
··· 65 65 66 66 # Wrappers for programs installed by enlightenment that should be setuid 67 67 security.wrappers = { 68 - enlightenment_ckpasswd.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd"; 69 - enlightenment_sys.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys"; 70 - enlightenment_system.source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system"; 68 + enlightenment_ckpasswd = 69 + { setuid = true; 70 + owner = "root"; 71 + group = "root"; 72 + source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_ckpasswd"; 73 + }; 74 + enlightenment_sys = 75 + { setuid = true; 76 + owner = "root"; 77 + group = "root"; 78 + source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_sys"; 79 + }; 80 + enlightenment_system = 81 + { setuid = true; 82 + owner = "root"; 83 + group = "root"; 84 + source = "${pkgs.enlightenment.enlightenment}/lib/enlightenment/utils/enlightenment_system"; 85 + }; 71 86 }; 72 87 73 88 environment.etc."X11/xkb".source = xcfg.xkbDir;
+18 -6
nixos/modules/services/x11/desktop-managers/plasma5.nix
··· 197 197 }; 198 198 199 199 security.wrappers = { 200 - kcheckpass.source = "${lib.getBin libsForQt5.kscreenlocker}/libexec/kcheckpass"; 201 - start_kdeinit.source = "${lib.getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit"; 202 - kwin_wayland = { 203 - source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland"; 204 - capabilities = "cap_sys_nice+ep"; 205 - }; 200 + kcheckpass = 201 + { setuid = true; 202 + owner = "root"; 203 + group = "root"; 204 + source = "${lib.getBin libsForQt5.kscreenlocker}/libexec/kcheckpass"; 205 + }; 206 + start_kdeinit = 207 + { setuid = true; 208 + owner = "root"; 209 + group = "root"; 210 + source = "${lib.getBin libsForQt5.kinit}/libexec/kf5/start_kdeinit"; 211 + }; 212 + kwin_wayland = 213 + { owner = "root"; 214 + group = "root"; 215 + capabilities = "cap_sys_nice+ep"; 216 + source = "${lib.getBin plasma5.kwin}/bin/kwin_wayland"; 217 + }; 206 218 }; 207 219 208 220 # DDC support
+12 -2
nixos/modules/tasks/filesystems/ecryptfs.nix
··· 7 7 config = mkIf (any (fs: fs == "ecryptfs") config.boot.supportedFilesystems) { 8 8 system.fsPackages = [ pkgs.ecryptfs ]; 9 9 security.wrappers = { 10 - "mount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private"; 11 - "umount.ecryptfs_private".source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private"; 10 + "mount.ecryptfs_private" = 11 + { setuid = true; 12 + owner = "root"; 13 + group = "root"; 14 + source = "${pkgs.ecryptfs.out}/bin/mount.ecryptfs_private"; 15 + }; 16 + "umount.ecryptfs_private" = 17 + { setuid = true; 18 + owner = "root"; 19 + group = "root"; 20 + source = "${pkgs.ecryptfs.out}/bin/umount.ecryptfs_private"; 21 + }; 12 22 }; 13 23 }; 14 24 }
+7 -2
nixos/modules/tasks/network-interfaces.nix
··· 1133 1133 # kernel because we need the ambient capability 1134 1134 security.wrappers = if (versionAtLeast (getVersion config.boot.kernelPackages.kernel) "4.3") then { 1135 1135 ping = { 1136 - source = "${pkgs.iputils.out}/bin/ping"; 1136 + owner = "root"; 1137 + group = "root"; 1137 1138 capabilities = "cap_net_raw+p"; 1139 + source = "${pkgs.iputils.out}/bin/ping"; 1138 1140 }; 1139 1141 } else { 1140 - ping.source = "${pkgs.iputils.out}/bin/ping"; 1142 + setuid = true; 1143 + owner = "root"; 1144 + group = "root"; 1145 + source = "${pkgs.iputils.out}/bin/ping"; 1141 1146 }; 1142 1147 security.apparmor.policies."bin.ping".profile = lib.mkIf config.security.apparmor.policies."bin.ping".enable (lib.mkAfter '' 1143 1148 /run/wrappers/bin/ping {
+3
nixos/modules/virtualisation/libvirtd.nix
··· 183 183 }; 184 184 185 185 security.wrappers.qemu-bridge-helper = { 186 + setuid = true; 187 + owner = "root"; 188 + group = "root"; 186 189 source = "/run/${dirName}/nix-helpers/qemu-bridge-helper"; 187 190 }; 188 191
+4 -2
nixos/modules/virtualisation/spice-usb-redirection.nix
··· 14 14 15 15 config = lib.mkIf config.virtualisation.spiceUSBRedirection.enable { 16 16 environment.systemPackages = [ pkgs.spice-gtk ]; # For polkit actions 17 - security.wrappers.spice-client-glib-usb-acl-helper ={ 18 - source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper"; 17 + security.wrappers.spice-client-glib-usb-acl-helper = { 18 + owner = "root"; 19 + group = "root"; 19 20 capabilities = "cap_fowner+ep"; 21 + source = "${pkgs.spice-gtk}/bin/spice-client-glib-usb-acl-helper"; 20 22 }; 21 23 }; 22 24