TLDR: NetworkManager leaks MAC address. Can work around this by modifying drivers, which is feasible but requires infrastructure support from Qubes (QubesOS Contrib).
I recently set out to verify for myself that NetworkManager’s MAC randomization is implemented properly. I did this by reviewing NetworkManager, wpa_supplicant, macchanger, udev, iwlwifi, Linux kernel (cfg80211, mac80211, wired/wireless network driver implementation), iproute2, Tails, systemd and other codebases. I also read every Tails/Qubes/NetworkManager/other GitHub/GitLab issue that I could find on the topic.
My conclusion is that we can’t rely solely on either udev (Tails approach) or NetworkManager (Qubes approach) to ensure the true MAC address is never leaked. We must individually patch drivers to generate random MAC addresses instead of reading them from EEPROM. I agree this is not ideal, I wish we could’ve relied on one of the two existing solutions. With that said, it’s not as difficult as it might sound. Modifying the drivers to do what we need is quite easy, I have done it myself for a couple of obscure drivers already.
We can’t use the udev solution because it does not reliably handle suspend/sleep. Some drivers reset to the permanent (what I am referring to as true) MAC address during suspend. I can’t see that the Tails solution sets the random MAC again when exiting the suspend state. I can’t see an easy way to modify their solution to handle suspend, especially without race conditions. If there was an easy way to address this, it would be a better solution than NetworkManager although still slightly worse than modifying drivers because the true MAC is still accessible/readable.
The main problem with NetworkManager is that it brings the NIC into the ‘up’ state with the true MAC address prior to associating with a network. I reported this. You can read more details about why this is problematic in the report. They could change the code to not do this, but even if they make the change I would be hesitant to rely on NetworkManager for the purpose of MAC randomization. NM is a large and complex codebase, which does not put privacy first. Modifying the drivers, although tedious due to the large number of drivers, actually allows us to very easily verify that the MAC is not leaking due to the simplicity of the code that reads EEPROM and sets the permanent MAC.
An additional issue with NetworkManager is that it restores the true MAC address in many instances, such as when the device sleeps and when NM is restarted (typically done to apply an updated config). In the latter case a leak is guaranteed if the user is associated with an AP. With the modified drivers these issues would not exist.
I have managed to build a kernel with a modified driver using qubesbuilderv2, but this process required me to read (and modify!) portions of qubesbuilderv2. It also took a long time to run on my hardware. It’s definitely not something ends users should be expected to do, especially with each update of the kernel.
My proposal is that we come to an agreement on how we would like to structure all of this, and start supplying it to end users as a contrib package. The way I was intending to do it for my own personal use was to build the modified kernel and then set that only for my NetVM (setting it for all VMs would be bad as the modified driver could act as a fingerprint). If we are publishing it through the contrib system, there are a few other ways we could structure this. Deciding which of these is best needs to be discussed with the Qubes developers. Ideally whatever solution we choose should “fail closed”. So, if a driver hasn’t been patched then the unpatched driver should not load.
To clarify, the proposed solution is opt-in and users that want to rely on the existing NetworkManager-only solution would simply not use the modified kernel/drivers. In both cases users will still use NetworkManager’s randomization, just that one group is relying on it whereas for the other group it is a supplement.
Tails developers have recently been discussing MAC randomization and moving to NetworkManager. I have filled them in on my findings and proposed they get involved in the driver modification process. If the two projects and their communities are working together on this, we can probably cover the common drivers quite quickly.
For good measure, here’s a reference to Qubes’ main prior discussion on this topic
What do we think?