** Description changed: Short description: local privilege escalation from membership of the netdev group to root can be achieved by using the wpa_supplicant's D-Bus interface. wpa_supplicant reads and writes to a user-controlled path. - Original report by Vivek Parikh <[email protected]>: + Original report by Vivek Parikh <[email protected]> on 21 July + 2026: Tooling and validation ---------------------- This issue was found with AI-assisted source review. Every stated fact was validated by hand before sending: the source paths/line numbers were read directly, the reachability was confirmed against the shipped D-Bus policy and running process on a stock system, and the end-to-end privilege escalation was reproduced at runtime on Ubuntu 24.04.4 LTS (details below). Where the impact is conditional, that is stated explicitly rather than overclaimed. Summary ------- wpa_supplicant's wpa_config_write() (wpa_supplicant/config_file.c) writes the configuration by opening "<confname>.tmp" with fopen(name, "w") and no O_NOFOLLOW / O_EXCL, then renaming it over <confname>. Because the open follows symlinks, a caller who can (a) choose the config path and (b) trigger a config save can pre-plant "<confname>.tmp" as a symlink to any file and have wpa_supplicant (running as root) truncate and overwrite that file with config-formatted content. An attacker with control over one job-template-style string field can write a line of their choosing into the target. Overwriting /etc/passwd with an injected UID-0 account yields root (demonstrated end-to-end, see PoC). This is a sibling of CVE-2024-5290. That issue used the same netdev-reachable D-Bus CreateInterface(ConfigFile=...) surface, but the sink was loading an attacker .so via opensc_engine_path; it was fixed by restricting loaded module paths to /usr/lib. That fix does NOT cover wpa_config_write(), so the SaveConfig -> wpa_config_write() symlink-follow sink remains open on current hostap (HEAD 2026-07-20 still uses fopen(name, "w") with no O_NOFOLLOW). Affected code ------------- wpa_supplicant/config_file.c, wpa_config_write(): tmp_name = "<name>.tmp"; ... f = fopen(name, "w"); /* <- follows symlinks; no O_NOFOLLOW/O_EXCL */ Reachable sinks that call wpa_config_write(wpa_s->confname, ...): - D-Bus: Interface.SaveConfig -> wpas_dbus_handler_save_config() (wpa_supplicant/dbus/dbus_new_handlers.c). The confname is set from the client-supplied ConfigFile in CreateInterface() (dbus_new_handlers.c, "ConfigFile" key). - Control interface: SAVE_CONFIG on an interface added via INTERFACE_ADD with an attacker-chosen confname (wpa_supplicant/ctrl_iface.c), where a global control socket (-g) is group-accessible. Reachability / who can do this ------------------------------ The D-Bus path is reachable by any member of the "netdev" group on Debian/Ubuntu: the shipped /usr/share/dbus-1/system.d/wpa_supplicant.conf grants <policy group="netdev"> <allow send_destination="fi.w1.wpa_supplicant1"/> ... (On Fedora/RHEL the D-Bus policy is root-only and there is no -g socket, so those are not affected by default.) Scope, stated honestly: - This is a netdev-group -> root escalation, not any-local-user -> root. A non-netdev user is denied by the D-Bus policy ("Access denied"), and on a default Ubuntu desktop no human user is in netdev (NetworkManager uses polkit, not netdev membership). - It matters where an administrator has placed a user in netdev (e.g. direct wpa_cli/wpa_gui users, appliance/kiosk images, some legacy configurations), and anywhere a group-accessible global control socket (-g) is configured. - netdev is intended for network configuration, not for arbitrary root file overwrite; that is the privilege boundary being crossed. Proof of concept (reproduced end-to-end, Ubuntu 24.04.4 LTS, wpasupplicant 2.10) -------------------------------------------------------------------------------- As an unprivileged user "lowpriv" whose only extra group is "netdev" (no sudo): 1. Pre-plant an executable at a path ending in a double-quote, e.g. /tmp/x" : printf '#!/bin/sh\nid\n' > '/tmp/x"'; chmod 755 '/tmp/x"' 2. Write evil.conf (update_config=1) with a network whose EAP identity encodes a passwd UID-0 line; wpa appends a closing quote which the passwd shell field absorbs, so the shell becomes /tmp/x": identity="x:<openssl passwd -1 output>:0:0::/root:/tmp/x" 3. Symlink evil.conf.tmp -> /etc/passwd 4. Over D-Bus (as netdev): CreateInterface({Ifname: lo, Driver: wired, ConfigFile: /home/lowpriv/evil.conf}) Interface.SaveConfig() # root wpa_supplicant writes through the symlink -> /etc/passwd is overwritten and now contains the UID-0 line. 5. su 'identity="x' (password from step 2) -> uid=0(...) gid=0(root): root. The su/auth step was verified in an isolated mount namespace (unshare -m, bind of the wpa-written file over /etc/passwd) so the live system was not modified. Note: a live overwrite truncates /etc/passwd (fopen "w"), which is itself a full authentication-lockout DoS. Suggested fix ------------- Open the temp file without following symlinks and without reusing an existing file, e.g.: int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0600); f = (fd >= 0) ? fdopen(fd, "w") : NULL; (Consider O_EXCL after unlinking a stale <name>.tmp, and fstat()-verifying the result is a regular file owned as expected.) A patch is attached (wpa-fix-O_NOFOLLOW.patch). Optionally, gate the config-writing control paths (SaveConfig / SAVE_CONFIG with a caller-supplied ConfigFile) more tightly than plain netdev access. A working reproducer is available on request.
-- You received this bug notification because you are a member of Ubuntu Bugs, which is subscribed to Ubuntu. https://bugs.launchpad.net/bugs/2163513 Title: wpa_config_write() follows symlinks (no O_NOFOLLOW) -> arbitrary root file overwrite by a netdev-group user To manage notifications about this bug go to: https://bugs.launchpad.net/ubuntu/+source/wpa/+bug/2163513/+subscriptions -- ubuntu-bugs mailing list [email protected] https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs
