Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nixos/dhcpcd: harden and run as unprivileged user #276919

Open
wants to merge 7 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions nixos/doc/manual/release-notes/rl-2405.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ The pre-existing [services.ankisyncd](#opt-services.ankisyncd.enable) has been m

- `services.zope2` has been removed as `zope2` is unmaintained and was relying on Python2.

- The dhcpcd service (`networking.useDHCP`) has been hardened and now runs exclusively as the "dhcpcd" user.
rnhmjoj marked this conversation as resolved.
Show resolved Hide resolved
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.

- `services.avahi.nssmdns` got split into `services.avahi.nssmdns4` and `services.avahi.nssmdns6` which enable the mDNS NSS switch for IPv4 and IPv6 respectively.
Since most mDNS responders only register IPv4 addresses, most users want to keep the IPv6 support disabled to avoid long timeouts.

Expand Down
13 changes: 8 additions & 5 deletions nixos/modules/config/resolvconf.nix
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ in
}

(mkIf cfg.enable {
users.groups.resolvconf = {};

networking.resolvconf.package = pkgs.openresolv;

environment.systemPackages = [ cfg.package ];
Expand All @@ -147,12 +149,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
'';
};

})
Expand Down
92 changes: 46 additions & 46 deletions nixos/modules/services/networking/dhcpcd.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ let
enableDHCP = config.networking.dhcpcd.enable &&
(config.networking.useDHCP || 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.
Expand Down Expand Up @@ -91,23 +91,6 @@ let
${cfg.extraConfig}
'';

exitHook = pkgs.writeText "dhcpcd.exit-hook" ''
${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.
${optionalString config.services.ntp.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd.service || true"}
${optionalString config.services.ntpd-rs.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart ntpd-rs.service || true"}
${optionalString config.services.openntpd.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart openntpd.service || true"}
${optionalString config.services.chrony.enable "/run/current-system/systemd/bin/systemctl try-reload-or-restart chronyd.service || true"}
fi
''}

${cfg.runHook}
'';

in

{
Expand Down Expand Up @@ -209,22 +192,6 @@ in

config = mkIf enableDHCP {

assertions = [ {
# dhcpcd doesn't start properly with malloc ∉ [ libc scudo ]
# see https://github.com/NixOS/nixpkgs/issues/151696
assertion =
dhcpcd.enablePrivSep
-> 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 != "")
Expand All @@ -236,8 +203,6 @@ in
wants = [ "network.target" ];
before = [ "network-online.target" ];

restartTriggers = optional (enableNTPService || cfg.runHook != "") [ exitHook ];

# Stopping dhcpcd during a reconfiguration is undesirable
# because it brings down the network interfaces configured by
# dhcpcd. So do a "systemctl restart" instead.
Expand All @@ -250,25 +215,60 @@ in
serviceConfig =
{ Type = "forking";
PIDFile = "/run/dhcpcd/pid";
DynamicUser = true;
SupplementaryGroups = optional useResolvConf "resolvconf";
User = "dhcpcd";
Group = "dhcpcd";
StateDirectory = "dhcpcd";
RuntimeDirectory = "dhcpcd";

ExecStartPre = "+${pkgs.writeShellScript "migrate-dhcpcd" ''
# migrate from old database directory
if test -d /var/db/dhcpcd; then
mv /var/db/dhcpcd/* -t /var/lib/dhcpcd
rmdir /var/db/dhcpcd
chown -R dhcpcd:dhcpcd /var/lib/dhcpcd
rnhmjoj marked this conversation as resolved.
Show resolved Hide resolved
fi
''}";

ExecStart = "@${dhcpcd}/sbin/dhcpcd dhcpcd --quiet ${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" ]
++ optionals useResolvConf [ "/etc/resolv.conf" "/run/resolvconf" ];
DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateTmp = true;
PrivateUsers = false;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
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"
"~@resources" "~@setuid" "~@timer"
];
SystemCallArchitectures = "native";
};
};

users.users.dhcpcd = {
isSystemUser = true;
group = "dhcpcd";
};
users.groups.dhcpcd = {};

environment.systemPackages = [ dhcpcd ];

environment.etc."dhcpcd.exit-hook" = mkIf (enableNTPService || cfg.runHook != "") {
source = exitHook;
};

powerManagement.resumeCommands = mkIf config.systemd.services.dhcpcd.enable
''
# Tell dhcpcd to rebind its interfaces if it's running.
Expand Down
2 changes: 0 additions & 2 deletions nixos/tests/chrony.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import ./make-test-python.nix ({ lib, ... }:
services.chrony.enable = true;
services.chrony.enableMemoryLocking = true;
environment.memoryAllocator.provider = "graphene-hardened";
# dhcpcd privsep is incompatible with graphene-hardened
networking.useNetworkd = true;
};
};

Expand Down
5 changes: 0 additions & 5 deletions nixos/tests/hardened.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions nixos/tests/networking/networkd-and-scripted.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
1 change: 1 addition & 0 deletions nixos/tests/networking/router.nix
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
AdvSendAdvert on;
AdvManagedFlag on;
AdvOtherConfigFlag on;
RDNSS 2001:db8::1 {};

prefix fd00:1234:5678:${toString n}::/64 {
AdvAutonomous off;
Expand Down
22 changes: 6 additions & 16 deletions pkgs/tools/networking/dhcpcd/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
, runtimeShellPackage
, runtimeShell
, nixosTests
, enablePrivSep ? true
}:

stdenv.mkDerivation rec {
Expand All @@ -33,30 +32,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) "[ -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; {
Expand Down