nixos/wpa_supplicant: harden and run as unprivileged user (#427528)
This commit is contained in:
@@ -61,6 +61,16 @@ of pulling the upstream container image from Docker Hub. If you want the old beh
|
||||
|
||||
- The Bash implementation of the `nixos-rebuild` program is removed. All switchable systems now use the Python rewrite. Any prior usage of `system.rebuild.enableNg` must now be removed. If you have any outstanding issues with the new implementation, please open an issue on GitHub.
|
||||
|
||||
- The `networking.wireless` module has been security hardened: the `wpa_supplicant` daemon now runs under an unprivileged user with restricted access to the system.
|
||||
|
||||
As part of these changes, `/etc/wpa_supplicant.conf` has been deprecated: the NixOS-generated configuration file is now linked to `/etc/wpa_supplicant/nixos.conf` and `/etc/wpa_supplicant/imperative.conf` has been added for imperatively configuring `wpa_supplicant` or when using [allowAuxiliaryImperativeNetworks](#opt-networking.wireless.allowAuxiliaryImperativeNetworks).
|
||||
|
||||
If client certificates, keys or other files are needed, these should be stored under `/etc/wpa_supplicant` and owned by `wpa_supplicant` to ensure the daemon can read them.
|
||||
|
||||
Also, the {option}`networking.wireless.userControlled.group` option has been removed since there is now a dedicated `wpa_supplicant` group to control the daemon, and {option}`networking.wireless.userControlled.enable` has been renamed to [](#opt-networking.wireless.userControlled).
|
||||
|
||||
No functionality should have been impacted by these changes (including controlling via `wpa_cli`, integration with NetworkManager or connman), but if you find any problems, please open an issue on GitHub.
|
||||
|
||||
- `services.angrr` now uses TOML for configuration. Define policies with `services.angrr.settings` (generate TOML file) or point to a file using `services.angrr.configFile`. The legacy options `services.angrr.period`, `services.angrr.ownedOnly`, and `services.angrr.removeRoot` have been removed. See `man 5 angrr` and the description of `services.angrr.settings` options for examples and details.
|
||||
|
||||
- `services.pingvin-share` has been removed as the `pingvin-share.backend` package was broken and the project was archived upstream.
|
||||
|
||||
@@ -165,6 +165,7 @@ in
|
||||
wireless = {
|
||||
enable = lib.mkIf (!enableIwd) true;
|
||||
dbusControlled = true;
|
||||
autoDetectInterfaces = false;
|
||||
iwd = lib.mkIf enableIwd {
|
||||
enable = true;
|
||||
};
|
||||
|
||||
@@ -11,7 +11,8 @@ let
|
||||
cfg = config.networking.networkmanager;
|
||||
ini = pkgs.formats.ini { };
|
||||
|
||||
delegateWireless = config.networking.wireless.enable == true && cfg.unmanaged != [ ];
|
||||
# Whether wpa_supplicant is managed independently
|
||||
delegateWireless = config.networking.wireless.networks != { } && cfg.unmanaged != [ ];
|
||||
|
||||
enableIwd = cfg.wifi.backend == "iwd";
|
||||
|
||||
@@ -136,10 +137,7 @@ let
|
||||
cfg.package
|
||||
]
|
||||
++ cfg.plugins
|
||||
++ pluginRuntimeDeps
|
||||
++ lib.optionals (!delegateWireless && !enableIwd) [
|
||||
pkgs.wpa_supplicant
|
||||
];
|
||||
++ pluginRuntimeDeps;
|
||||
in
|
||||
{
|
||||
|
||||
@@ -541,9 +539,9 @@ in
|
||||
|
||||
assertions = [
|
||||
{
|
||||
assertion = config.networking.wireless.enable == true -> cfg.unmanaged != [ ];
|
||||
assertion = config.networking.wireless.networks != { } -> cfg.unmanaged != [ ];
|
||||
message = ''
|
||||
You can not use networking.networkmanager with networking.wireless.
|
||||
You can not use networking.networkmanager with networking.wireless.networks.
|
||||
Except if you mark some interfaces as <literal>unmanaged</literal> by NetworkManager.
|
||||
'';
|
||||
}
|
||||
@@ -676,6 +674,13 @@ in
|
||||
useDHCP = false;
|
||||
})
|
||||
|
||||
(mkIf (!delegateWireless && !enableIwd) {
|
||||
# Enable wpa_supplicant but fully control it over DBus
|
||||
wireless.enable = true;
|
||||
wireless.autoDetectInterfaces = false;
|
||||
wireless.dbusControlled = true;
|
||||
})
|
||||
|
||||
(mkIf enableIwd {
|
||||
wireless.iwd.enable = true;
|
||||
})
|
||||
|
||||
@@ -48,31 +48,6 @@ let
|
||||
else
|
||||
networkList;
|
||||
|
||||
# Content of wpa_supplicant.conf
|
||||
generatedConfig = concatStringsSep "\n" (
|
||||
(map mkNetwork allNetworks)
|
||||
++ optional cfg.userControlled.enable (
|
||||
concatStringsSep "\n" [
|
||||
"ctrl_interface=/run/wpa_supplicant"
|
||||
"ctrl_interface_group=${cfg.userControlled.group}"
|
||||
"update_config=1"
|
||||
]
|
||||
)
|
||||
++ [ "pmf=1" ]
|
||||
++ optional (cfg.secretsFile != null) "ext_password_backend=file:${cfg.secretsFile}"
|
||||
++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"''
|
||||
++ optional (cfg.extraConfig != "") cfg.extraConfig
|
||||
);
|
||||
|
||||
configIsGenerated = with cfg; networks != { } || extraConfig != "" || userControlled.enable;
|
||||
|
||||
# the original configuration file
|
||||
configFile =
|
||||
if configIsGenerated then
|
||||
pkgs.writeText "wpa_supplicant.conf" generatedConfig
|
||||
else
|
||||
"/etc/wpa_supplicant.conf";
|
||||
|
||||
# Creates a network block for wpa_supplicant.conf
|
||||
mkNetwork =
|
||||
opts:
|
||||
@@ -104,6 +79,12 @@ let
|
||||
}
|
||||
'';
|
||||
|
||||
hasDeclarative = lib.any id [
|
||||
(cfg.networks != { })
|
||||
(cfg.extraConfig != "")
|
||||
cfg.userControlled
|
||||
];
|
||||
|
||||
# Creates a systemd unit for wpa_supplicant bound to a given (or any) interface
|
||||
mkUnit =
|
||||
iface:
|
||||
@@ -114,9 +95,11 @@ let
|
||||
configStr =
|
||||
(
|
||||
if cfg.allowAuxiliaryImperativeNetworks then
|
||||
"-c /etc/wpa_supplicant.conf -I ${configFile}"
|
||||
"-c /etc/wpa_supplicant/imperative.conf -I /etc/wpa_supplicant/nixos.conf"
|
||||
else if hasDeclarative then
|
||||
"-c /etc/wpa_supplicant/nixos.conf"
|
||||
else
|
||||
"-c ${configFile}"
|
||||
"-c /etc/wpa_supplicant/imperative.conf"
|
||||
)
|
||||
+ lib.concatMapStrings (p: " -I " + p) cfg.extraConfigFiles;
|
||||
in
|
||||
@@ -128,32 +111,100 @@ let
|
||||
wants = [ "network.target" ];
|
||||
requires = deviceUnit;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
stopIfChanged = false;
|
||||
restartTriggers = [ config.environment.etc."wpa_supplicant/nixos.conf".source ];
|
||||
|
||||
path = [ pkgs.wpa_supplicant ];
|
||||
# if `userControl.enable`, the supplicant automatically changes the permissions
|
||||
# and owning group of the runtime dir; setting `umask` ensures the generated
|
||||
# config file isn't readable (except to root); see nixpkgs#267693
|
||||
serviceConfig.UMask = "066";
|
||||
serviceConfig.RuntimeDirectory = "wpa_supplicant";
|
||||
serviceConfig.RuntimeDirectoryMode = "700";
|
||||
serviceConfig = {
|
||||
User = "wpa_supplicant";
|
||||
Group = "wpa_supplicant";
|
||||
RuntimeDirectory = "wpa_supplicant";
|
||||
AmbientCapabilities = [
|
||||
"CAP_NET_ADMIN"
|
||||
"CAP_NET_RAW"
|
||||
];
|
||||
CapabilityBoundingSet = [
|
||||
"CAP_NET_ADMIN"
|
||||
"CAP_NET_RAW"
|
||||
];
|
||||
RootDirectory = "/run/wpa_supplicant";
|
||||
RootDirectoryStartOnly = true;
|
||||
BindPaths = [
|
||||
"/etc/wpa_supplicant" # to write wpa_supplicant.conf{,.tmp}
|
||||
"/run/wpa_supplicant" # to make control sockets
|
||||
# to set up interfaces
|
||||
"/proc/sys/net"
|
||||
"/dev/rfkill"
|
||||
]
|
||||
++ lib.optional cfg.dbusControlled "/run/dbus"
|
||||
++ lib.optional cfg.allowAuxiliaryImperativeNetworks "/etc/wpa_supplicant";
|
||||
BindReadOnlyPaths = [
|
||||
builtins.storeDir
|
||||
"/etc/"
|
||||
]
|
||||
++ lib.optional (cfg.secretsFile != null) cfg.secretsFile;
|
||||
DeviceAllow = "/dev/rfkill rw";
|
||||
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;
|
||||
ProtectProc = "invisible";
|
||||
ProtectSystem = "strict";
|
||||
IPAddressDeny = "any";
|
||||
RemoveIPC = true;
|
||||
RestrictAddressFamilies = [
|
||||
"AF_UNIX"
|
||||
"AF_INET"
|
||||
"AF_INET6"
|
||||
"AF_NETLINK"
|
||||
"AF_PACKET"
|
||||
];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
"~@keyring"
|
||||
"~@resources"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
UMask = "0077";
|
||||
|
||||
ExecStartPre =
|
||||
lib.optionals (cfg.allowAuxiliaryImperativeNetworks || !hasDeclarative) [
|
||||
# set up imperative config file
|
||||
"+${pkgs.coreutils}/bin/touch /etc/wpa_supplicant/imperative.conf"
|
||||
"+${pkgs.coreutils}/bin/chmod 664 /etc/wpa_supplicant/imperative.conf"
|
||||
"+${pkgs.coreutils}/bin/chown -R wpa_supplicant:wpa_supplicant /etc/wpa_supplicant"
|
||||
]
|
||||
++ lib.optionals cfg.userControlled [
|
||||
# set up client sockets directory
|
||||
"+${pkgs.coreutils}/bin/mkdir /run/wpa_supplicant/client"
|
||||
"+${pkgs.coreutils}/bin/chown wpa_supplicant:wpa_supplicant /run/wpa_supplicant/client"
|
||||
"+${pkgs.coreutils}/bin/chmod g=u /run/wpa_supplicant/client"
|
||||
];
|
||||
};
|
||||
|
||||
script = ''
|
||||
${optionalString (configIsGenerated && !cfg.allowAuxiliaryImperativeNetworks) ''
|
||||
if [ -f /etc/wpa_supplicant.conf ]; then
|
||||
echo >&2 "<3>/etc/wpa_supplicant.conf present but ignored. Generated ${configFile} is used instead."
|
||||
fi
|
||||
''}
|
||||
|
||||
# ensure wpa_supplicant.conf exists, or the daemon will fail to start
|
||||
${optionalString cfg.allowAuxiliaryImperativeNetworks ''
|
||||
touch /etc/wpa_supplicant.conf
|
||||
''}
|
||||
|
||||
iface_args="-s ${optionalString cfg.dbusControlled "-u"} -D${cfg.driver} ${configStr}"
|
||||
|
||||
${
|
||||
if iface == null then
|
||||
if iface != null then
|
||||
''
|
||||
# add known interface to the daemon arguments
|
||||
args="-i${iface} $iface_args"
|
||||
''
|
||||
else if cfg.autoDetectInterfaces then
|
||||
''
|
||||
# detect interfaces automatically
|
||||
|
||||
@@ -176,10 +227,7 @@ let
|
||||
done
|
||||
''
|
||||
else
|
||||
''
|
||||
# add known interface to the daemon arguments
|
||||
args="-i${iface} $iface_args"
|
||||
''
|
||||
"args=$iface_args"
|
||||
}
|
||||
|
||||
# finally start daemon
|
||||
@@ -205,7 +253,8 @@ in
|
||||
"wlan1"
|
||||
];
|
||||
description = ''
|
||||
The interfaces {command}`wpa_supplicant` will use. If empty, it will
|
||||
The interfaces {command}`wpa_supplicant` will use. If empty and
|
||||
[](#opt-networking.wireless.autoDetectInterfaces) is true it will
|
||||
automatically use all wireless interfaces.
|
||||
|
||||
::: {.note}
|
||||
@@ -214,6 +263,10 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
autoDetectInterfaces = mkEnableOption "automatic detection of wireless interfaces" // {
|
||||
default = true;
|
||||
};
|
||||
|
||||
driver = mkOption {
|
||||
type = types.str;
|
||||
default = "nl80211,wext";
|
||||
@@ -503,27 +556,36 @@ in
|
||||
'';
|
||||
};
|
||||
|
||||
userControlled = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Allow normal users to control wpa_supplicant through wpa_gui or wpa_cli.
|
||||
This is useful for laptop users that switch networks a lot and don't want
|
||||
to depend on a large package such as NetworkManager just to pick nearby
|
||||
access points.
|
||||
userControlled = mkOption {
|
||||
type =
|
||||
with types;
|
||||
coercedTo attrs (
|
||||
val:
|
||||
if builtins.isAttrs val && val ? enable then
|
||||
trace "Obsolete option `networking.wireless.userControlled.enable' is used. It was renamed to networking.wireless.userControlled" val.enable
|
||||
else if builtins.isAttrs val && val ? group then
|
||||
trace
|
||||
"The option definition `networking.wireless.userControlled.group' no longer has any effect. The group is now fixed to `wpa_supplicant'."
|
||||
(val.enable or false)
|
||||
else if builtins.isBool val then
|
||||
val
|
||||
else
|
||||
false
|
||||
) bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Allow users of the `wpa_supplicant` group to control wpa_supplicant
|
||||
through wpa_gui or wpa_cli.
|
||||
This is useful for laptop users that switch networks a lot and don't want
|
||||
to depend on a large package such as NetworkManager just to pick nearby
|
||||
access points.
|
||||
|
||||
When using a declarative network specification you cannot persist any
|
||||
settings via wpa_gui or wpa_cli.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "wheel";
|
||||
example = "network";
|
||||
description = "Members of this group can control wpa_supplicant.";
|
||||
};
|
||||
::: {.note}
|
||||
When networks are configured declaratively, you cannot persist any settings
|
||||
via wpa_gui or wpa_cli, unless {option}`allowAuxiliaryImperativeNetworks`
|
||||
is used.
|
||||
:::
|
||||
'';
|
||||
};
|
||||
|
||||
dbusControlled = mkOption {
|
||||
@@ -624,9 +686,33 @@ in
|
||||
}
|
||||
];
|
||||
|
||||
users.groups.wpa_supplicant = { };
|
||||
users.users.wpa_supplicant = {
|
||||
isSystemUser = true;
|
||||
group = "wpa_supplicant";
|
||||
description = "WPA Supplicant user";
|
||||
};
|
||||
|
||||
hardware.wirelessRegulatoryDatabase = true;
|
||||
|
||||
environment.systemPackages = [ pkgs.wpa_supplicant ];
|
||||
|
||||
# NixOS-generated configuration files
|
||||
environment.etc."wpa_supplicant/nixos.conf".text = concatStringsSep "\n" (
|
||||
(map mkNetwork allNetworks)
|
||||
++ optional cfg.userControlled (
|
||||
concatStringsSep "\n" [
|
||||
"ctrl_interface=/run/wpa_supplicant/control"
|
||||
"ctrl_interface_group=wpa_supplicant"
|
||||
"update_config=1"
|
||||
]
|
||||
)
|
||||
++ [ "pmf=1" ]
|
||||
++ optional (cfg.secretsFile != null) "ext_password_backend=file:${cfg.secretsFile}"
|
||||
++ optional cfg.scanOnLowSignal ''bgscan="simple:30:-70:3600"''
|
||||
++ optional (cfg.extraConfig != "") cfg.extraConfig
|
||||
);
|
||||
|
||||
services.dbus.packages = optional cfg.dbusControlled pkgs.wpa_supplicant;
|
||||
|
||||
systemd.services =
|
||||
|
||||
@@ -94,20 +94,30 @@ let
|
||||
};
|
||||
};
|
||||
|
||||
# Note: secrets are stored outside /etc/ and /nix/store to
|
||||
# test for accessibility of these paths
|
||||
system.activationScripts.wpa-secrets = {
|
||||
deps = [
|
||||
"users"
|
||||
"specialfs"
|
||||
];
|
||||
text = ''
|
||||
install -Dm600 -o wpa_supplicant ${pkgs.writeText "wpa" ''
|
||||
psk_nixos_test=${naughtyPassphrase}
|
||||
''} /var/lib/secrets/wpa
|
||||
'';
|
||||
};
|
||||
|
||||
# wireless client
|
||||
networking.wireless = lib.mkMerge [
|
||||
{
|
||||
# the override is needed because the wifi is
|
||||
# disabled with mkVMOverride in qemu-vm.nix.
|
||||
enable = lib.mkOverride 0 true;
|
||||
userControlled.enable = true;
|
||||
userControlled = true;
|
||||
interfaces = [ "wlan1" ];
|
||||
fallbackToWPA2 = lib.mkDefault true;
|
||||
|
||||
# secrets
|
||||
secretsFile = pkgs.writeText "wpa-secrets" ''
|
||||
psk_nixos_test=${naughtyPassphrase}
|
||||
'';
|
||||
secretsFile = "/var/lib/secrets/wpa";
|
||||
}
|
||||
extraConfig
|
||||
];
|
||||
@@ -142,7 +152,8 @@ in
|
||||
# the override is needed because the wifi is
|
||||
# disabled with mkVMOverride in qemu-vm.nix.
|
||||
enable = lib.mkOverride 0 true;
|
||||
userControlled.enable = true;
|
||||
userControlled = true;
|
||||
dbusControlled = true;
|
||||
fallbackToWPA2 = true;
|
||||
|
||||
networks = {
|
||||
@@ -198,9 +209,14 @@ in
|
||||
assert "Failed to connect" not in status, \
|
||||
"Failed to connect to the daemon"
|
||||
|
||||
# get the configuration file
|
||||
cmdline = machine.succeed("cat /proc/$(pgrep wpa)/cmdline").split('\x00')
|
||||
config_file = cmdline[cmdline.index("-c") + 1]
|
||||
with subtest("D-Bus interface is working"):
|
||||
dbus_command = "dbus-send --system --print-reply --dest=fi.w1.wpa_supplicant1 " \
|
||||
"/fi/w1/wpa_supplicant1 fi.w1.wpa_supplicant1.GetInterface string:wlan0"
|
||||
machine.succeed(dbus_command) # as root
|
||||
machine.succeed(f"sudo -g wpa_supplicant {dbus_command}") # as wpa_supplicant group
|
||||
|
||||
# generated configuration file
|
||||
config_file = "/etc/static/wpa_supplicant/nixos.conf"
|
||||
|
||||
with subtest("WPA2 fallbacks have been generated"):
|
||||
assert int(machine.succeed(f"grep -c sae-only {config_file}")) == 1
|
||||
@@ -218,6 +234,9 @@ in
|
||||
|
||||
# save file for manual inspection
|
||||
machine.copy_from_vm(config_file)
|
||||
|
||||
# check hardening options
|
||||
machine.succeed("systemd-analyze security wpa_supplicant >&2")
|
||||
'';
|
||||
};
|
||||
|
||||
@@ -233,25 +252,27 @@ in
|
||||
# wireless client
|
||||
networking.wireless = {
|
||||
enable = lib.mkOverride 0 true;
|
||||
userControlled.enable = true;
|
||||
userControlled = true;
|
||||
allowAuxiliaryImperativeNetworks = true;
|
||||
interfaces = [ "wlan1" ];
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
wpa_cli = "sudo -u nobody -g wpa_supplicant wpa_cli"
|
||||
|
||||
with subtest("Daemon is running and accepting connections"):
|
||||
machine.wait_for_unit("wpa_supplicant-wlan1.service")
|
||||
status = machine.wait_until_succeeds("wpa_cli -i wlan1 status")
|
||||
status = machine.wait_until_succeeds(f"{wpa_cli} -i wlan1 status")
|
||||
assert "Failed to connect" not in status, \
|
||||
"Failed to connect to the daemon"
|
||||
|
||||
with subtest("Daemon can be configured imperatively"):
|
||||
machine.succeed("wpa_cli -i wlan1 add_network")
|
||||
machine.succeed("wpa_cli -i wlan1 set_network 0 ssid '\"nixos-test\"'")
|
||||
machine.succeed("wpa_cli -i wlan1 set_network 0 psk '\"reproducibility\"'")
|
||||
machine.succeed("wpa_cli -i wlan1 save_config")
|
||||
machine.succeed("grep -q nixos-test /etc/wpa_supplicant.conf")
|
||||
machine.succeed(f"{wpa_cli} -i wlan1 add_network")
|
||||
machine.succeed(f"{wpa_cli} -i wlan1 set_network 0 ssid '\"nixos-test\"'")
|
||||
machine.succeed(f"{wpa_cli} -i wlan1 set_network 0 psk '\"reproducibility\"'")
|
||||
machine.succeed(f"{wpa_cli} -i wlan1 save_config")
|
||||
machine.succeed("grep -q nixos-test /etc/wpa_supplicant/imperative.conf")
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
readline,
|
||||
withPcsclite ? !stdenv.hostPlatform.isStatic,
|
||||
pcsclite,
|
||||
unprivileged ? true,
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation rec {
|
||||
@@ -33,8 +34,6 @@ stdenv.mkDerivation rec {
|
||||
hash = "sha256-X6mBbj7BkW66aYeSCiI3JKBJv10etLQxaTRfRgwsFmM=";
|
||||
revert = true;
|
||||
})
|
||||
./unsurprising-ext-password.patch
|
||||
./multiple-configs.patch
|
||||
(fetchpatch {
|
||||
name = "suppress-ctrl-event-signal-change.patch";
|
||||
url = "https://w1.fi/cgit/hostap/patch/?id=c330b5820eefa8e703dbce7278c2a62d9c69166a";
|
||||
@@ -45,7 +44,10 @@ stdenv.mkDerivation rec {
|
||||
url = "https://git.w1.fi/cgit/hostap/patch/?id=1ce37105da371c8b9cf3f349f78f5aac77d40836";
|
||||
hash = "sha256-leCk0oexNBZyVK5Q5gR4ZcgWxa0/xt/aU+DssTa0UwE=";
|
||||
})
|
||||
];
|
||||
./unsurprising-ext-password.patch
|
||||
./multiple-configs.patch
|
||||
]
|
||||
++ lib.optional unprivileged ./unprivileged-daemon.patch;
|
||||
|
||||
# TODO: Patch epoll so that the dbus actually responds
|
||||
# TODO: Figure out how to get privsep working, currently getting SIGBUS
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
commit 24e932357ee3041763135b931206dfc0bbe0441e
|
||||
Author: rnhmjoj <rnhmjoj@inventati.org>
|
||||
Date: Wed Jul 23 10:18:55 2025 +0200
|
||||
|
||||
Fixes for running wpa_supplicant unprivileged
|
||||
|
||||
1. Change the dbus service user to "wpa_supplicant"
|
||||
|
||||
2. Ensure appropriate group ownership and permissions on the client sockets.
|
||||
Motivation: clients communicate with the daemon by creating "client"
|
||||
sockets; by default this is owned by the user running the client,
|
||||
so it may be inaccessible by the daemon.
|
||||
|
||||
3. Move the "control" sockets under a subdirectory of /run/wpa_supplicant.
|
||||
Motivation: wpa_supplicant will try to adjust the ownership of the
|
||||
sockets directory, even if they are fine, and fail.
|
||||
|
||||
4. Move the "client" under a subdirectory of /run/wpa_supplicant instead
|
||||
of tmp. Motivation: this allows to unshare /tmp
|
||||
|
||||
diff --git a/src/common/wpa_ctrl.c b/src/common/wpa_ctrl.c
|
||||
index 7e197f0..6bfb091 100644
|
||||
--- a/src/common/wpa_ctrl.c
|
||||
+++ b/src/common/wpa_ctrl.c
|
||||
@@ -15,6 +15,8 @@
|
||||
#include <fcntl.h>
|
||||
#include <sys/un.h>
|
||||
#include <unistd.h>
|
||||
+#include <sys/types.h>
|
||||
+#include <grp.h>
|
||||
#include <fcntl.h>
|
||||
#endif /* CONFIG_CTRL_IFACE_UNIX */
|
||||
#ifdef CONFIG_CTRL_IFACE_UDP_REMOTE
|
||||
@@ -165,6 +167,14 @@ try_again:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+ /* Set the client socket owner group to "wpa_supplicant"
|
||||
+ * and ensure group and user permissions are the same */
|
||||
+ struct group *grp = getgrnam("wpa_supplicant");
|
||||
+ if (grp != NULL) {
|
||||
+ lchown(ctrl->local.sun_path, -1, grp->gr_gid);
|
||||
+ chmod(ctrl->local.sun_path, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
|
||||
+ }
|
||||
+
|
||||
#ifdef ANDROID
|
||||
/* Set group even if we do not have privileges to change owner */
|
||||
lchown(ctrl->local.sun_path, -1, AID_WIFI);
|
||||
--- a/wpa_supplicant/dbus/dbus-wpa_supplicant.conf
|
||||
+++ b/wpa_supplicant/dbus/dbus-wpa_supplicant.conf
|
||||
@@ -2,9 +2,15 @@
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
- <policy user="root">
|
||||
+ <policy user="wpa_supplicant">
|
||||
<allow own="fi.w1.wpa_supplicant1"/>
|
||||
-
|
||||
+ </policy>
|
||||
+ <policy user="root">
|
||||
+ <allow send_destination="fi.w1.wpa_supplicant1"/>
|
||||
+ <allow send_interface="fi.w1.wpa_supplicant1"/>
|
||||
+ <allow receive_sender="fi.w1.wpa_supplicant1" receive_type="signal"/>
|
||||
+ </policy>
|
||||
+ <policy group="wpa_supplicant">
|
||||
<allow send_destination="fi.w1.wpa_supplicant1"/>
|
||||
<allow send_interface="fi.w1.wpa_supplicant1"/>
|
||||
<allow receive_sender="fi.w1.wpa_supplicant1" receive_type="signal"/>
|
||||
diff --git a/wpa_supplicant/dbus/fi.w1.wpa_supplicant1.service.in b/wpa_supplicant/dbus/fi.w1.wpa_supplicant1.service.in
|
||||
index d97ff39..367a7c6 100644
|
||||
--- a/wpa_supplicant/dbus/fi.w1.wpa_supplicant1.service.in
|
||||
+++ b/wpa_supplicant/dbus/fi.w1.wpa_supplicant1.service.in
|
||||
@@ -1,5 +1,5 @@
|
||||
[D-BUS Service]
|
||||
Name=fi.w1.wpa_supplicant1
|
||||
Exec=@BINDIR@/wpa_supplicant -u
|
||||
-User=root
|
||||
+User=wpa_supplicant
|
||||
SystemdService=wpa_supplicant.service
|
||||
diff --git a/wpa_supplicant/wpa_cli.c b/wpa_supplicant/wpa_cli.c
|
||||
index af00e79..840b307 100644
|
||||
--- a/wpa_supplicant/wpa_cli.c
|
||||
+++ b/wpa_supplicant/wpa_cli.c
|
||||
@@ -44,10 +44,10 @@ static int wpa_cli_attached = 0;
|
||||
static int wpa_cli_connected = -1;
|
||||
static int wpa_cli_last_id = 0;
|
||||
#ifndef CONFIG_CTRL_IFACE_DIR
|
||||
-#define CONFIG_CTRL_IFACE_DIR "/var/run/wpa_supplicant"
|
||||
+#define CONFIG_CTRL_IFACE_DIR "/run/wpa_supplicant/control"
|
||||
#endif /* CONFIG_CTRL_IFACE_DIR */
|
||||
static const char *ctrl_iface_dir = CONFIG_CTRL_IFACE_DIR;
|
||||
-static const char *client_socket_dir = NULL;
|
||||
+static const char *client_socket_dir = "/run/wpa_supplicant/client";
|
||||
static char *ctrl_ifname = NULL;
|
||||
static const char *global = NULL;
|
||||
static const char *pid_file = NULL;
|
||||
Reference in New Issue
Block a user