State of Wi-Fi on Linux

2026-11-13 #design #linux #rant #wi-fi

iwd

A few months ago, a pile of security issues were found in the kernel’s AF_ALG implementation, and it now seems to be on its way to deprecation.

iwd, the mature, working Wi-Fi implementation on Linux relies on AF_ALG extensively. It’s also somewhat unmaintained, so it’s unclear if it’ll be updated or simply die — its dependency is quite foundational, so it’s not a trivial fix either.

iwd is also really well positioned in terms of UIs, with a few pretty okay ones available (in TUI or GUI forms).

wpa_supplicant

With iwd’s future being uncertain, I’ve been experimenting with the other major implementation: wpa_supplicant. Setting it up was a bit under-documented, and I had to patch its service file so it runs as a dedicated user (with capabilities="^cap_net_raw,^cap_net_admin") instead of as root (with infinite permissions).

It’s unclear to me why it even ran as root: I suspect some edge case requires this, but I can’t find which one. It also runs as root on other distros.

UIs are rather lacking. wpa_cli works, but requires lots of ceremony to simply add a network and connect to it — not something I want to do while travelling and quickly connecting to Wi-Fi. wpa_gui doesn’t support WPA3, and is not very practical for casual usage.

I considered writing my own UI, which led me to inspect how wpa_supplicant is controlled via the socket it exposes.

Without the -g flag, wpa_supplicant automatically exits if no interfaces are found at start-up — so hot-plugging won’t work. But -g exposes a global control socket, which exposes functionality like arbitrary code execution (via SET load_dynamic_eap= or SET pkcs11_engine_path=), raw vendor commands, calibration tweaks, firmware fiddling, creating bridge networks, etc.

I can make the control socket 0700, so that only root can connect to it, and the per-interface sockets 0770, so that members of the wpa_supplicant group (e.g.: regular users) can access these. An mdevd/udev rule can talk to the control socket and add/remove new interfaces in response to kernel uevents.

But even the per-interface sockets a bit too broad in terms of what they allow: it’s still possible to trigger arbitrary code execution, send vendor commands or (gracefully) terminate the daemon.

At this point, the “rewrite it in Rust” memes start to eco in the back of my head. Not becase of the “memory safety” narrative, but because a rewrite is a good opportunity to fix API design, move hard-coded policy (network reconnection heuristics, background scanning, roaming decisions, etc) out of the daemon itself, and also split the “highly privileged” functionality out of the regular socket interface.

I likely won’t do this though: iwd has existed for years with a better design that wpa_supplicant but has failed to displace it regardless. Plus, I’d still need a nice UI, so might as well focus on one for the daemon we have.

I’m still pissed at the sad state of affairs.

Have comments or want to discuss this topic?
Send an email to my public inbox: ~whynothugo/public-inbox@lists.sr.ht.
Reply privately by email: hugo@whynothugo.nl.

— § —