From 19a494135aef450171149a6c328375fb3d96947a Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Sun, 24 Dec 2023 23:40:36 +0100 Subject: [PATCH 1/7] nixos/resolvconf: add a resolvconf group This group is useful to allow specific users to run resolvconf and (and this modify /etc/resolv.conf) without root privileges. --- nixos/modules/config/resolvconf.nix | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/nixos/modules/config/resolvconf.nix b/nixos/modules/config/resolvconf.nix index 3609b1ba475b8..6ffcbcabe5192 100644 --- a/nixos/modules/config/resolvconf.nix +++ b/nixos/modules/config/resolvconf.nix @@ -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 + ''; }; }) From a432668acf9db69fb9123034a56f81eb4d437b53 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Sun, 24 Dec 2023 23:58:43 +0100 Subject: [PATCH 2/7] dhcpcd: disable privsep by default The priviledge separation mode has several downsides: - it's incompatible with alternative memory allocators, including graphene-hardened; - it needs an unreleased patch to fix a crash; - it results in none less than 6 subprocesses running at any time, increasing the memory usage; - the privileged process (albeit not doing any networking related tasks) is still running as root, so it has complete access to the system. Let's disable this by default and instead run dhcpcd as an unpriviledge user with only the necessary capabilities. --- nixos/modules/services/networking/dhcpcd.nix | 16 ---------------- nixos/tests/chrony.nix | 2 -- nixos/tests/hardened.nix | 5 ----- pkgs/tools/networking/dhcpcd/default.nix | 19 ++++--------------- 4 files changed, 4 insertions(+), 38 deletions(-) diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 9b3269e965f5b..97b9680ed9516 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -206,22 +206,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 != "") diff --git a/nixos/tests/chrony.nix b/nixos/tests/chrony.nix index 2dcc363728beb..9582ab14bb8f1 100644 --- a/nixos/tests/chrony.nix +++ b/nixos/tests/chrony.nix @@ -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; }; }; }; diff --git a/nixos/tests/hardened.nix b/nixos/tests/hardened.nix index e38834961e13a..0c43e3523dacf 100644 --- a/nixos/tests/hardened.nix +++ b/nixos/tests/hardened.nix @@ -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 diff --git a/pkgs/tools/networking/dhcpcd/default.nix b/pkgs/tools/networking/dhcpcd/default.nix index 6221034dc2857..368319195df2c 100644 --- a/pkgs/tools/networking/dhcpcd/default.nix +++ b/pkgs/tools/networking/dhcpcd/default.nix @@ -7,7 +7,6 @@ , runtimeShellPackage , runtimeShell , nixosTests -, enablePrivSep ? true }: stdenv.mkDerivation rec { @@ -38,17 +37,8 @@ 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" + ]; makeFlags = [ "PREFIX=${placeholder "out"}" ]; @@ -59,9 +49,8 @@ stdenv.mkDerivation rec { # 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; { From aff5d1d523225d86027dbebfb95c321d7760a1fa Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 25 Dec 2023 00:29:48 +0100 Subject: [PATCH 3/7] nixos/dhcpcd: remove ntpd workaround This workaround for NTP daemons has been there for 12 years and is most likely not needed anymore. --- nixos/modules/services/networking/dhcpcd.nix | 25 ++------------------ 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 97b9680ed9516..502289d8820a5 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -10,8 +10,6 @@ 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); - # Don't start dhcpcd on explicitly configured interfaces or on # interfaces that are part of a bridge, bond or sit device. ignoredInterfaces = @@ -88,23 +86,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 { @@ -217,7 +198,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 @@ -246,9 +227,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 '' From bad5251e874bec27438cb8a613ec87e845ed437e Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 25 Dec 2023 00:54:46 +0100 Subject: [PATCH 4/7] nixos/tests/networking: test nameservers via DHCP --- nixos/tests/networking/networkd-and-scripted.nix | 4 ++++ nixos/tests/networking/networkmanager.nix | 1 + nixos/tests/networking/router.nix | 1 + 3 files changed, 6 insertions(+) diff --git a/nixos/tests/networking/networkd-and-scripted.nix b/nixos/tests/networking/networkd-and-scripted.nix index 777c00f74e228..6b8ed50a2f19e 100644 --- a/nixos/tests/networking/networkd-and-scripted.nix +++ b/nixos/tests/networking/networkd-and-scripted.nix @@ -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") diff --git a/nixos/tests/networking/networkmanager.nix b/nixos/tests/networking/networkmanager.nix index bd989408df8a1..70d28fe96380b 100644 --- a/nixos/tests/networking/networkmanager.nix +++ b/nixos/tests/networking/networkmanager.nix @@ -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'") ''; diff --git a/nixos/tests/networking/router.nix b/nixos/tests/networking/router.nix index e0ad7fa01591a..fab21c9e78624 100644 --- a/nixos/tests/networking/router.nix +++ b/nixos/tests/networking/router.nix @@ -72,6 +72,7 @@ AdvSendAdvert on; AdvManagedFlag on; AdvOtherConfigFlag on; + RDNSS 2001:db8::1 {}; prefix fd00:1234:5678:${toString n}::/64 { AdvAutonomous off; From b447fd58c7921b4c331760c6eebfad8d8188a19c Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 25 Dec 2023 01:09:29 +0100 Subject: [PATCH 5/7] nixos/dhcpcd: harden and run as unprivileged user --- nixos/modules/services/networking/dhcpcd.nix | 52 ++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 502289d8820a5..572801876eab3 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -10,6 +10,8 @@ let enableDHCP = config.networking.dhcpcd.enable && (config.networking.useDHCP || lib.any (i: i.useDHCP == true) interfaces); + 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. ignoredInterfaces = @@ -162,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 + ]; + ::: ''; }; @@ -212,13 +227,50 @@ in serviceConfig = { Type = "forking"; PIDFile = "/run/dhcpcd/pid"; + SupplementaryGroups = lib.optional useResolvConf "resolvconf"; + User = "dhcpcd"; + Group = "dhcpcd"; + StateDirectory = "dhcpcd"; RuntimeDirectory = "dhcpcd"; 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"; From 234b7541be87635c14c358c7a254633f5d9e3af4 Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Mon, 25 Dec 2023 01:07:24 +0100 Subject: [PATCH 6/7] dhcpcd: move database to /var/lib --- nixos/modules/services/networking/dhcpcd.nix | 12 ++++++++++++ pkgs/tools/networking/dhcpcd/default.nix | 3 ++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/nixos/modules/services/networking/dhcpcd.nix b/nixos/modules/services/networking/dhcpcd.nix index 572801876eab3..39c8930eea133 100644 --- a/nixos/modules/services/networking/dhcpcd.nix +++ b/nixos/modules/services/networking/dhcpcd.nix @@ -232,6 +232,18 @@ in 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"; diff --git a/pkgs/tools/networking/dhcpcd/default.nix b/pkgs/tools/networking/dhcpcd/default.nix index 368319195df2c..324ac174513b0 100644 --- a/pkgs/tools/networking/dhcpcd/default.nix +++ b/pkgs/tools/networking/dhcpcd/default.nix @@ -38,11 +38,12 @@ stdenv.mkDerivation rec { "--sysconfdir=/etc" "--localstatedir=/var" "--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" ]; From 67700c521eb7f65b734e70e5509056f46973b92d Mon Sep 17 00:00:00 2001 From: rnhmjoj Date: Tue, 26 Dec 2023 10:52:34 +0100 Subject: [PATCH 7/7] nixos/release-notes: mention dhcpcd changes --- nixos/doc/manual/release-notes/rl-2411.section.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nixos/doc/manual/release-notes/rl-2411.section.md b/nixos/doc/manual/release-notes/rl-2411.section.md index 82cadca537f04..5d82fd196a60d 100644 --- a/nixos/doc/manual/release-notes/rl-2411.section.md +++ b/nixos/doc/manual/release-notes/rl-2411.section.md @@ -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.