Please see and leave feedback:
Specifically in context of https://github.com/QubesOS/qubes-issues/issues/5764, that (supposedly) was about MAC address. In Qubes every VM (inter-VM interfaces) use the same MAC address (
00:16:3e:5e:6c:00). This means having ability to read local MAC address, one can easily say it is Qubes. Also, attacker can think they correlated two VMs to the same pseudonym when finding the same MAC elsewhere, but in practice this means the other VM is also a Qubes VM (possibly on a totally different machine).
The MAC address we use if of “Xensource, Inc” vendor - default value that Xen uses. Standard Xen behavior is to choose random device part (or hash of VM name, depending on version) - we use constant device part.
Now we can consider two scenarios:
- Attacker knowing about Qubes, trying to de-anonymize user: here I think constant MAC doesn’t change much - there are already quite a lot of methods to detect it is Qubes, but otherwise it doesn’t give much more info. This may affect a case where attacker has very limited access and can for example extract only MAC address - in this case, our current approach gives some info - that it is Qubes.
- Attacker not knowing about Qubes, using generic techniques like checking MAC address (Zoom case). Here, using constant MAC across all Qubes installations, gives false sense of de-anonymization, while in fact it is only about identifying Qubes. Here any kind of randomization (like - just device part, keeping Xensource vendor) would help.
There is also another argument: any kind of MAC randomization (in opposition to use the same across large user base) allows to identify (with some probability) a specific instance of a DisposableVM. If an attacker can only probe MAC address from time to time, with randomization they can say “I have seen this particular DisposableVM recently (with some probability)” - until that DisposableVM is stopped. I think it isn’t a big concern given the threat model of a DisposableVM (a method to unlink identifies is to restart it, other methods aren’t guaranteed to work), but still something to consider. Also, likely if one can extract MAC address, then can access also various other information with similar impact (like system uptime, VM IP address etc).
In any case, it may be desirable to hide the fact of using Qubes even if only from some attackers (by using different vendor parts in MAC), because this is also a bit of information. But as @Patrick said elsewhere, it is complex problem.
Excellent writeup. I am not sure hiding Whonix is a feasible goal, same with hiding Tor. A really determined remote service can implement ways to fingerprint an environment especially if code is run locally.
Some low hanging fruit can be fixed with sandbox restrictions to files that have uniquely generated data, but besides that it’s impossible.
/proc/cpuinfo(Related: Restrict Hardware Information to Root)
To hide hardware identifiers, we could add a hardware info permission to sandbox-app-launcher. Then, only apps users have chosen can access hardware identifiers like CPU information.
Perhaps such a feature could be implemented in sandbox-app-launcher [archive] (development discussion [archive]) which would be useful in case of buggy / misbehaving applications not accidentally DDOS’ing the host as well as compromised applications trying to benchmark the VM.
Apparmor supports rlimit rules we can set.
In sandbox-app-launcher, currently it only sets a 200 process limit to prevent fork bombs but we can restrict it further.
rlimit supports many types of restrictions.
Another option to defeat benchmarking. (And also maybe a nice way to prevent crashes, i.e. buggy applications using all CPU / RAM / IO thereby freezing the system.)
systemd-run supports system resource limits. An easy to use wrapper to use cgroups.
sudo apt update sudo apt install stress
Example as root:
sudo systemd-run \ --same-dir \ --collect \ --nice=19 \ --property=MemoryMax=2G \ --property=CPUAccounting=yes \ --property=CPUQuota=20% \ --property=CPUWeight=1 \ --property=CPUShares=2 \ --property=TasksAccounting=yes \ --property=TasksMax=200 \ --property=IOAccounting=yes \ --property=IOWeight=1 \ --scope \ -- \ stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 10s
Does not work as non-root, user as of Debian
buster. But does work in Debian
bullseye. (Newer systemd version.)
Example as non-root:
systemd-run \ --user \ --same-dir \ --collect \ --nice=19 \ --property=MemoryMax=2G \ --property=CPUAccounting=yes \ --property=CPUQuota=20% \ --property=CPUWeight=1 \ --property=CPUShares=2 \ --property=TasksAccounting=yes \ --property=TasksMax=200 \ --property=IOAccounting=yes \ --property=IOWeight=1 \ --scope \ --unit=testtest \ -- \ stress --cpu 8 --io 4 --vm 2 --vm-bytes 128M --timeout 10s