nixos/dhcpcd: harden and run as unprivileged user (#336988)

This commit is contained in:
Michele Guerini Rocco
2024-09-17 19:21:11 +02:00
committed by GitHub
9 changed files with 90 additions and 66 deletions
@@ -189,6 +189,11 @@
- The nvidia driver no longer defaults to the proprietary driver starting with version 560. You will need to manually set `hardware.nvidia.open` to select the proprietary or open driver.
- The dhcpcd service (`networking.useDHCP`) has been hardened and now runs exclusively as the "dhcpcd" user.
Users that were relying on the root privileges in `networking.dhcpcd.runHook` will have to write specific [sudo](security.sudo.extraRules) or [polkit](security.polkit.extraConfig) rules to allow dhcpcd to perform privileged actions.
As part of these changes, the DHCP lease files directory has also been moved from `/var/db/dhcpcd` to `/var/lib/dhcpcd`. This migration is performed automatically, but users may have to update their backup configuration.
- `singularity-tools` have the `storeDir` argument removed from its override interface and use `builtins.storeDir` instead.
- Two build helpers in `singularity-tools`, i.e., `mkLayer` and `shellScript`, are deprecated, as they are no longer involved in image-building. Maintainers will remove them in future releases.
+8 -5
View File
@@ -132,6 +132,8 @@ in
}
(lib.mkIf cfg.enable {
users.groups.resolvconf = {};
networking.resolvconf.package = pkgs.openresolv;
environment.systemPackages = [ cfg.package ];
@@ -143,12 +145,13 @@ in
wants = [ "network-pre.target" ];
wantedBy = [ "multi-user.target" ];
restartTriggers = [ config.environment.etc."resolvconf.conf".source ];
serviceConfig.RemainAfterExit = true;
serviceConfig = {
Type = "oneshot";
ExecStart = "${cfg.package}/bin/resolvconf -u";
RemainAfterExit = true;
};
script = ''
${lib.getExe cfg.package} -u
chgrp -R resolvconf /etc/resolv.conf /run/resolvconf
chmod -R g=u /etc/resolv.conf /run/resolvconf
'';
};
})
+65 -38
View File
@@ -10,7 +10,7 @@ let
enableDHCP = config.networking.dhcpcd.enable &&
(config.networking.useDHCP || lib.any (i: i.useDHCP == true) interfaces);
enableNTPService = (config.services.ntp.enable || config.services.ntpd-rs.enable || config.services.openntpd.enable || config.services.chrony.enable);
useResolvConf = config.networking.resolvconf.enable;
# Don't start dhcpcd on explicitly configured interfaces or on
# interfaces that are part of a bridge, bond or sit device.
@@ -88,23 +88,6 @@ let
${cfg.extraConfig}
'';
exitHook = pkgs.writeText "dhcpcd.exit-hook" ''
${lib.optionalString enableNTPService ''
if [ "$reason" = BOUND -o "$reason" = REBOOT ]; then
# Restart ntpd. We need to restart it to make sure that it will actually do something:
# if ntpd cannot resolve the server hostnames in its config file, then it will never do
# anything ever again ("couldn't resolve ..., giving up on it"), so we silently lose
# time synchronisation. This also applies to openntpd.
${lib.optionalString config.services.ntp.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service || true"}
${lib.optionalString config.services.ntpd-rs.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd-rs.service || true"}
${lib.optionalString config.services.openntpd.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart openntpd.service || true"}
${lib.optionalString config.services.chrony.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart chronyd.service || true"}
fi
''}
${cfg.runHook}
'';
in
{
@@ -181,6 +164,19 @@ in
description = ''
Shell code that will be run after all other hooks. See
`man dhcpcd-run-hooks` for details on what is possible.
::: {.note}
To use sudo or similar tools in your script you may have to set:
systemd.services.dhcpcd.serviceConfig.NoNewPrivileges = false;
In addition, as most of the filesystem is inaccessible to dhcpcd
by default, you may want to define some exceptions, e.g.
systemd.services.dhcpcd.serviceConfig.ReadOnlyPaths = [
"/run/user/1000/bus" # to send desktop notifications
];
:::
'';
};
@@ -206,22 +202,6 @@ in
config = lib.mkIf enableDHCP {
assertions = [ {
# dhcpcd doesn't start properly with malloc ∉ [ libc scudo ]
# see https://github.com/NixOS/nixpkgs/issues/151696
assertion =
dhcpcd.enablePrivSep
-> lib.elem config.environment.memoryAllocator.provider [ "libc" "scudo" ];
message = ''
dhcpcd with privilege separation is incompatible with chosen system malloc.
Currently only the `libc` and `scudo` allocators are known to work.
To disable dhcpcd's privilege separation, overlay Nixpkgs and override dhcpcd
to set `enablePrivSep = false`.
'';
} ];
environment.etc."dhcpcd.conf".source = dhcpcdConf;
systemd.services.dhcpcd = let
cfgN = config.networking;
hasDefaultGatewaySet = (cfgN.defaultGateway != null && cfgN.defaultGateway.address != "")
@@ -233,7 +213,7 @@ in
wants = [ "network.target" ];
before = [ "network-online.target" ];
restartTriggers = lib.optional (enableNTPService || cfg.runHook != "") [ exitHook ];
restartTriggers = [ cfg.runHook ];
# Stopping dhcpcd during a reconfiguration is undesirable
# because it brings down the network interfaces configured by
@@ -247,13 +227,62 @@ in
serviceConfig =
{ Type = "forking";
PIDFile = "/run/dhcpcd/pid";
SupplementaryGroups = lib.optional useResolvConf "resolvconf";
User = "dhcpcd";
Group = "dhcpcd";
StateDirectory = "dhcpcd";
RuntimeDirectory = "dhcpcd";
ExecStartPre = "+${pkgs.writeShellScript "migrate-dhcpcd" ''
# migrate from old database directory
if test -f /var/db/dhcpcd/duid; then
echo 'migrating DHCP leases from /var/db/dhcpcd to /var/lib/dhcpcd ...'
mv /var/db/dhcpcd/* -t /var/lib/dhcpcd
chown dhcpcd:dhcpcd /var/lib/dhcpcd/*
rmdir /var/db/dhcpcd || true
echo done
fi
''}";
ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${lib.optionalString cfg.persistent "--persistent"} --config ${dhcpcdConf}";
ExecReload = "${dhcpcd}/sbin/dhcpcd --rebind";
Restart = "always";
AmbientCapabilities = [ "CAP_NET_ADMIN" "CAP_NET_RAW" "CAP_NET_BIND_SERVICE" ];
ReadWritePaths = [ "/proc/sys/net/ipv6" ]
++ lib.optionals useResolvConf [ "/etc/resolv.conf" "/run/resolvconf" ];
DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = lib.mkDefault true; # may be disabled for sudo in runHook
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = "tmpfs"; # allow exceptions to be added to ReadOnlyPaths, etc.
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" "AF_PACKET" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallFilter = [
"@system-service"
"~@aio" "~@chown" "~@keyring" "~@memlock"
];
SystemCallArchitectures = "native";
};
};
# Note: the service could run with `DynamicUser`, however that makes
# impossible (for no good reason, see systemd issue #20495) to disable
# `NoNewPrivileges` or `ProtectHome`, which users may want to in order
# to run certain scripts in `networking.dhcpcd.runHook`.
users.users.dhcpcd = {
isSystemUser = true;
group = "dhcpcd";
@@ -262,9 +291,7 @@ in
environment.systemPackages = [ dhcpcd ];
environment.etc."dhcpcd.exit-hook" = lib.mkIf (enableNTPService || cfg.runHook != "") {
source = exitHook;
};
environment.etc."dhcpcd.exit-hook".text = cfg.runHook;
powerManagement.resumeCommands = lib.mkIf config.systemd.services.dhcpcd.enable
''
-2
View File
@@ -13,8 +13,6 @@ import ./make-test-python.nix ({ lib, ... }:
specialisation.hardened.configuration = {
services.chrony.enableMemoryLocking = true;
environment.memoryAllocator.provider = "graphene-hardened";
# dhcpcd privsep is incompatible with graphene-hardened
networking.useNetworkd = true;
};
};
};
-5
View File
@@ -11,11 +11,6 @@ import ./make-test-python.nix ({ pkgs, ... } : {
imports = [ ../modules/profiles/hardened.nix ];
environment.memoryAllocator.provider = "graphene-hardened";
nix.settings.sandbox = false;
nixpkgs.overlays = [
(final: super: {
dhcpcd = super.dhcpcd.override { enablePrivSep = false; };
})
];
virtualisation.emptyDiskImages = [ 4096 ];
boot.initrd.postDeviceCommands = ''
${pkgs.dosfstools}/bin/mkfs.vfat -n EFISYS /dev/vdb
@@ -132,6 +132,10 @@ let
client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q '192.168.2'")
client.wait_until_succeeds("ip addr show dev enp2s0 | grep -q 'fd00:1234:5678:2:'")
with subtest("Wait until we have received the nameservers"):
client.wait_until_succeeds("grep -q 2001:db8::1 /etc/resolv.conf")
client.wait_until_succeeds("grep -q 192.168.2.1 /etc/resolv.conf")
with subtest("Test vlan 1"):
client.wait_until_succeeds("ping -c 1 192.168.1.1")
client.wait_until_succeeds("ping -c 1 fd00:1234:5678:1::1")
@@ -121,6 +121,7 @@ let
static.wait_for_unit("NetworkManager.service")
dynamic.wait_until_succeeds("cat /etc/resolv.conf | grep -q '192.168.1.1'")
dynamic.wait_until_succeeds("cat /etc/resolv.conf | grep -q '2001:db8::1'")
static.wait_until_succeeds("cat /etc/resolv.conf | grep -q '10.10.10.10'")
static.wait_until_fails("cat /etc/resolv.conf | grep -q '192.168.1.1'")
'';
+1
View File
@@ -72,6 +72,7 @@
AdvSendAdvert on;
AdvManagedFlag on;
AdvOtherConfigFlag on;
RDNSS 2001:db8::1 {};
prefix fd00:1234:5678:${toString n}::/64 {
AdvAutonomous off;
+6 -16
View File
@@ -7,7 +7,6 @@
, runtimeShellPackage
, runtimeShell
, nixosTests
, enablePrivSep ? true
}:
stdenv.mkDerivation rec {
@@ -38,30 +37,21 @@ stdenv.mkDerivation rec {
configureFlags = [
"--sysconfdir=/etc"
"--localstatedir=/var"
]
++ (
if ! enablePrivSep
then [ "--disable-privsep" ]
else [
"--enable-privsep"
# dhcpcd disables privsep if it can't find the default user,
# so we explicitly specify a user.
"--privsepuser=dhcpcd"
]
);
"--disable-privsep"
"--dbdir=/var/lib/dhcpcd"
];
makeFlags = [ "PREFIX=${placeholder "out"}" ];
# Hack to make installation succeed. dhcpcd will still use /var/db
# Hack to make installation succeed. dhcpcd will still use /var/lib
# at runtime.
installFlags = [ "DBDIR=$(TMPDIR)/db" "SYSCONFDIR=${placeholder "out"}/etc" ];
# Check that the udev plugin got built.
postInstall = lib.optionalString (udev != null && stdenv.isLinux) "[ -e ${placeholder "out"}/lib/dhcpcd/dev/udev.so ]";
passthru = {
inherit enablePrivSep;
tests = { inherit (nixosTests.networking.scripted) macvlan dhcpSimple dhcpOneIf; };
passthru.tests = {
inherit (nixosTests.networking.scripted) macvlan dhcpSimple dhcpOneIf;
};
meta = with lib; {