The issue had also been reported one day earlier, 20 July 2026, by
Andres Berbescu <[email protected]>. Original report follows:

# Private security report: wpa_supplicant D-Bus SaveConfig root file
overwrite and LPE

Report date: 2026-07-19  
Status: privately prepared; vendor duplicate assessment pending  
Confirmed target: Debian 13, `wpasupplicant 2:2.10-24`  
Suggested severity: High, CVSS 3.1 `7.8` 
(`AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H`)  
Suggested weakness: CWE-59, improper link resolution before file access

## Executive summary

On Debian, a local member of the default `netdev` group can ask the root
`wpa_supplicant` D-Bus service to load an attacker-owned configuration path and
then invoke `SaveConfig`. The writer uses the predictable pathname
`<ConfigFile>.tmp`, opens it with `fopen(path, "w")`, and later renames it over
the original configuration.

An attacker can create `<ConfigFile>.tmp` as a symbolic link to any file 
writable
by root before calling `SaveConfig`. The root process follows that link, 
truncates
the target, and writes attacker-influenced serialized configuration into it. 
This
is deterministic: there is no race loop or timing window. The target retains its
inode, ownership, and mode.

The primitive was converted to EUID 0 in a disposable lab by targeting a
synthetic passwd file. A serialized `driver_param` value formed a valid UID-0
passwd entry; NSS accepted it, and `su` invoked by an unprivileged UID created a
root-owned marker. No host file is modified by the supplied reproducer.

The minimum confirmed path uses `Ifname=lo` and `Driver=none`. It does not need
Wi-Fi hardware, interface creation, `CAP_NET_ADMIN`, sudo, user interaction, or
an already-running supplicant. On Debian 13, the attacker's first D-Bus call
auto-activated `/usr/sbin/wpa_supplicant -u -s -O /run/wpa_supplicant` as root.

## Security boundary and exposure

Debian's system-bus policy allows members of `netdev` to send method calls to
`fi.w1.wpa_supplicant1`, while a plain local user is denied. Debian Installer's
`passwd/user-default-groups` list includes `netdev`, and `user-setup-apply` adds
the first created user to every group in that list. Thus this is not reachable
by every arbitrary UID, but it is reachable by the standard first-user profile
created by the Debian installer and by any other account granted `netdev`.

`wpasupplicant` is also a normal part of desktop/laptop installations. In Debian
13 and Debian 12, NetworkManager recommends it; Debian's normal installation
with recommended packages installs it. Debian 11 NetworkManager directly
depends on it.

Confirmed caller control:

```text
uid=1000(plain) groups=1000(plain)
  -> org.freedesktop.DBus.Error.AccessDenied

uid=1001(member) groups=1001(member),102(netdev)
  -> CreateInterface and SaveConfig allowed
```

## Deterministic exploitation sequence

1. The `netdev` caller creates a private directory and a valid configuration
   containing `update_config=1` and attacker-selected global values.
2. The caller creates `<configuration>.tmp` as a symlink to a root-owned target.
3. The caller invokes `CreateInterface` with:

   ```text
   Ifname=lo
   Driver=none
   ConfigFile=/path/controlled/by/caller.conf
   ```

4. The caller invokes `fi.w1.wpa_supplicant1.Interface.SaveConfig`.
5. Root `wpa_supplicant` executes `fopen("...caller.conf.tmp", "w")`, follows
   the already-present link, and truncates/writes the target.
6. `rename("...caller.conf.tmp", "...caller.conf")` moves the symlink, but the
   target has already been modified.

The EUID-0 demonstration used this valid configuration:

```text
driver_param=wpa-root::0:0:wpa root:/root:/bin/sh
update_config=1
```

The writer preserved the first line in a form parsed as the passwd account
`driver_param=wpa-root`, UID 0. The supplied proof uses a synthetic chroot and a
disposable container, not the container's real `/etc/passwd`.

## End-to-end evidence

Stock Debian 13 packages:

```text
dbus=1.16.2-2
wpasupplicant=2:2.10-24
```

Relevant output:

```text
victim owner=0:0 mode=644 inode=24903758 size=21
VICTIM_PASSWD_BEFORE

SAVE_ALLOWED /fi/w1/wpa_supplicant1/Interfaces/0
victim owner=0:0 mode=644 inode=24903758 size=66
driver_param=wpa-root::0:0:wpa root:/root:/bin/sh
update_config=1

uid=0(driver_param=wpa-root) gid=0(root) groups=0(root)
euid0 marker owner=0:0 mode=644 size=6
EUID0
```

The unchanged inode, owner, and mode distinguish this from merely replacing a
file inside an attacker-owned directory.

Auto-activation was separately confirmed:

```text
wpa_process=absent
SAVE_ALLOWED /fi/w1/wpa_supplicant1/Interfaces/0
root  0  549 /usr/sbin/wpa_supplicant -u -s -O /run/wpa_supplicant
VULNERABLE: D-Bus activation alone was sufficient
```

## Root cause

The vulnerable data flow in current upstream is:

- `wpas_dbus_handler_create_interface()` accepts the caller's `ConfigFile` and
  passes it into `wpa_supplicant_add_iface()`.
- `wpa_supplicant_init_iface()` converts it to a textual absolute path and
  reads it, but does not pin the file or parent directory identity.
- `wpas_dbus_handler_save_config()` checks only `update_config` and calls
  `wpa_config_write(wpa_s->confname, wpa_s->conf)`.
- `wpa_config_write()` appends the predictable `.tmp` suffix, calls
  `fopen(name, "w")`, and later calls `rename(tmp_name, orig_name)`.

There are two related, independently reproduced path attacks:

1. Pre-positioned `.tmp` symlink: deterministic and requires only one
   `SaveConfig` call after setup.
2. Parent-component substitution: load `switch/passwd` while `switch` points
   to the attacker directory, change `switch` to the synthetic victim tree
   between the separate D-Bus calls, then invoke `SaveConfig`.

The second control shows why adding only `O_NOFOLLOW` to the final temporary
file open is not a complete fix: parent path components also need to be pinned,
resolved without links, or excluded from unprivileged control.

## Affected-version matrix

| Target | Version/revision | Result |
|---|---|---|
| Debian 11 | `2:2.9.0-21+deb11u3` | root-owned `0600` target overwritten; 
inode preserved |
| Debian 12 | `2:2.10-12+deb12u3` | root-owned `0600` target overwritten; inode 
preserved |
| Debian 13 | `2:2.10-24` | overwrite and synthetic-chroot EUID 0 confirmed |
| upstream 2.10 | `cff80b4f7d3c0a47c052e8187d671710f48939e4` | vulnerable 
source pattern present |
| upstream 2.11 | `d945ddd368085f255e68328f2d3b020ceea359af` | vulnerable 
source pattern present |
| upstream `2_12` | `f08f2749aa696c4e47c5c0f591dda99951bf9fac` | vulnerable 
source pattern present |
| upstream main | `74718c75f668f3436b95122bb99bfc71f40e63ce`, 2026-07-10 | 
vulnerable source pattern present |

The two relevant historical commits are:

- `663ae2f417bc1896cbe1fef917b9ade06bfd4071` (2015-02-07), which introduced
  `<configuration>.tmp` followed by `rename()`;
- `2d0fe6bc4eabdc2396ba8eed1a5dec6a2595bb14` (2015-11-15), which exposed
  `SaveConfig` over D-Bus.

Both are present from hostap 2.6 onward. Actual reachability depends on the
distribution's local D-Bus/control-interface policy and daemon privileges.

## Duplicate analysis

### CVE-2024-5290

This report shares the same Debian `netdev`/D-Bus trust boundary, but it is not
the same causal flaw as CVE-2024-5290:

- CVE-2024-5290 is arbitrary shared-object loading through OpenSC/PKCS#11
  engine/module paths (published as CWE-427).
- Debian's fix, `CVE-2024-5290-lib_engine_trusted_path.patch`, changes only
  `src/crypto/tls_openssl.c` and restricts resolved module paths to `/usr/lib/`.
- The SaveConfig issue is in `wpa_supplicant/config_file.c`; it does not load a
  library or use a TLS engine.
- Debian marks CVE-2024-5290 fixed in all three tested package versions, yet the
  overwrite and EUID-0 chain remain reproducible.

The vendor may choose to extend CVE-2024-5290 because of the shared exposed API,
but the vulnerable sink, weakness class, patch, and exploit primitive are
distinct. I recommend treating this as a sibling variant/incomplete hardening
of the same boundary and making the CVE assignment decision during private
triage.

### 2021 configuration-symlink thread

The closest public prior art is the 2021 hostap thread “Use wpa conf file link
target for writing.” It addressed the functional problem that atomic rename
replaces an original configuration symlink. The maintainer noted a possible
race if that proposed link resolution were added and assumed the target lived
in a directory with restricted write access.

That thread did not describe a pre-positioned `<ConfigFile>.tmp` link, an
untrusted D-Bus caller, root-file overwrite, or LPE, and the proposal was not
merged. It should be considered functional prior art, not an exact public
security duplicate.

No advisory or CVE combining `CreateInterface(ConfigFile)`, the predictable
temporary path, and local root escalation was found as of the report date.
Administrative novelty remains subject to confidential vendor databases.

## Fix validation

The included prototype patch is intentionally conservative on Linux:

1. It opens each parent component with `openat(..., O_DIRECTORY|O_NOFOLLOW)`
   and keeps the resulting directory descriptor pinned.
2. It creates the temporary file relative to that descriptor with
   `O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC`, mode `0600`.
3. It publishes with `renameat()` relative to the same pinned descriptor.

The patched Debian 13 build produced these controls:

```text
pre-existing .tmp symlink: blocked; victim hash unchanged
parent-component swap:     blocked; victim hash unchanged
normal root-owned config:  SaveConfig succeeds
```

The patch is a verified prototype, not a final portability proposal. A
production implementation should use a random temporary name within the pinned
directory (or an equivalent unnamed temporary file) to avoid stale-temp DoS.
If configuration-directory symlinks must be supported, the most robust design
is to anchor the directory when the configuration is initially loaded and use
that descriptor for all later reads and saves.

As defense in depth, unprivileged D-Bus callers should not be permitted to both
select a root daemon's `ConfigFile` and persist it. The included Debian policy
prototype denies `CreateInterface` and `SaveConfig` only to `group="netdev"`.
It blocked the attacker's call with `AccessDenied`, while the same
`CreateInterface` call as root continued to succeed.

## Safe reproduction

From this submission directory, on a system with Docker:

```sh
./run-disposable-docker.sh
```

The wrapper:

- starts a fresh stock `debian:trixie` container;
- does not request `--privileged` or extra capabilities;
- bind-mounts the submission read-only;
- writes only below `/var/tmp` inside the disposable container;
- constructs a synthetic chroot for the EUID-0 proof; and
- removes the container when the run finishes.

Expected terminal markers are:

```text
plain_create_exit=1 (expected AccessDenied)
SAVE_ALLOWED /fi/w1/wpa_supplicant1/Interfaces/0
uid=0(driver_param=wpa-root) gid=0(root) groups=0(root)
euid0 marker owner=0:0 mode=644 size=6
EUID0
```

For the auto-activation-only control, run the same image with
`poc/wpa_autoactivation_lab.sh`; its recorded output is included under
`evidence/`.

## References

- Debian CVE-2024-5290 tracker:
  <https://security-tracker.debian.org/tracker/CVE-2024-5290>
- Debian CVE-2024-5290 patch:
  
<https://sources.debian.org/src/wpa/2%3A2.10-24/debian/patches/CVE-2024-5290-lib_engine_trusted_path.patch/>
- Original D-Bus SaveConfig submission:
  <https://lists.infradead.org/pipermail/hostap/2015-November/034113.html>
- 2021 symlink prior-art thread:
  <https://www.spinics.net/lists/hostap/msg09376.html>
- Debian installer default groups source:
  <https://salsa.debian.org/installer-team/user-setup>

** CVE added: https://cve.org/CVERecord?id=CVE-2024-5290

-- 
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

Reply via email to