MAC Randomization Is Flawed: Proposed New Solution

Could MAC address randomization be implemented as (a) kernel module(s)? This might be possible using Linux kernel live patching? This would avoid the need to compile a custom kernel.

For reference only - unrelated to MAC address randomization - see this small and probably by comparison maybe “easier” [1] kernel module which is using Linux kernel live patching to implement a different security feature:

Issues with Linux kernel live patching:


[1] Was not easy to invent but since the code has been invented already, the code is relatively small and therefore probably very suitable as an example.


EDIT:

I am doubling down on my kernel module feature request (maybe using Kernel live patching) based MAC randomization. If simple enough, the Kicksecure project might be able to review the code for non-maliciousness, and to make Debian packages available from deb.kicksecure.com.

(Custom kernel compilation / functionality review / MAC address leak-proof testing will most likely be either very limited or more likely not be included at all. Review by others will still be required. What I can offer is a bit of infrastructure.)

Why do I prefer a kernel module based solution?

  • Kicksecure doesn’t have the resources to provide its own compiled hardened-kernel at time of writing.
  • The solution would available to Debian, Tails, Kicksecure, Whonix and perhaps Debian derivatives, not just only Qubes.
    • This might lead to more users, testers, reviews.
  • Might even lead the way to Linux kernel upstream inclusion one day.
3 Likes

I proposed compiling the kernel separately as that was the path of least resistance for doing this without any support from Qubes. It’s possible that it’s not at all the best way to do this.

I was told in another thread that until this is moved to the mailing list we won’t be getting anything back from Qubes internally. I’ve submitted a mailing list post which is pending approval.

2 Likes

I think this could be done entirely in userspace. udev calls modprobe which searches in /lib/modules/uname -r. We could ship the modified wireless drivers and, based on whether “driver MAC randomization” is enabled, put the modified drivers earlier in the module search path. As long as we do this before the first udev event is processed then it should work as we want.

To prevent unmodified drivers from loading if “driver MAC randomization” is enabled, we could blacklist all unmodified drivers at the same time we add the modified drivers to the search path.

1 Like

Ran some quick tests and this is not working as expected. I will see if I can get it to work with some small changes, but let me know if this isn’t the direction you were thinking of taking this @adrelanos.

1 Like

Sounds complex, best to avoid the complexities of udev? I don’t think anyone is using kernel modules in this way?

modprobe is used inside initrd. And this is initramfs generator specific, adding further complexity. Different for initramfs-tools (used by Debian) versus dracut (used by Kicksecure, Fedora) I just now checked. dracut(-ng) has a lot of mentions of modprobe.


If you wish to support an on/off toogle, this could be implemented cleanly through a sysctl?


Could you please show a patch for a kernel module implementing MAC randomization? If it’s just a limited amount of lines, it could be pasted here.


Do you want to use,

  • macchanger style random MAC or
  • random Macchiato style MAC?

See Dev/MAC - Whonix for discussion and references about these.

1 Like

I don’t have the 2 patches that I’ve already made with me. I’ve just looked at iwlwifi and would add the randomization to the top of this function. All that’s needed there is eth_random_addr(data->hw_addr);. It would need to be added to the top of that if statement with a condition that is always true.

That randomization function is provided for us and can be used in any driver. I think that’s the same as what NetworkManager does, which I think we should use.

Sounds complex, best to avoid the complexities of udev? I don’t think anyone is using kernel modules in this way?

Ignore my suggestion, apparently I am not up to date with how this is handled.

If you wish to support an on/off toogle, this could be implemented cleanly through a sysctl?

We probably don’t need to worry about doing the on/off toggle initially, I was thinking about something incorrectly.

1 Like

That iwlwifi change was put together in a very short period of time while distracted, it might need changing but in general represents the complexity of these patches.

1 Like

All my research is indicating that udev is involved in driver loading.

https://www.debian.org/doc/manuals/debian-kernel-handbook/ch-modules.html

Can verify this on a Debian system by commenting out the first kmod load in /etc/lib/udev/rules.d/80-drivers.rules. After doing this and restarting, lsmod will not return the wireless driver.

1 Like

I’ve come up with a handful of ways to implement this. There’s not much point in me speculating which would be the best, just need to wait to hear back from a Qubes dev.

Hopefully will get a response on the mailing list.

1 Like

this was awfully unnecessary in a technical convo. @solene was just asking you if you measured the thing you suggested yourself. I don’t think she was trying to throw a shade.

3 Likes

Right. Keep that as is but my recommendation is to avoid the following:


Imagine there was a project called mac-randomization-kmod and it was available in packages.debian.org and Fedora package repository. (Should think more about the name keep following naming conventions for kernel modules, if any.)

In this case, a Qubes feature request could be as simple as “please install mac-randomization-kmod by default”. Perhaps no further work required from Qubes developers.

And even without any reply from Qubes developers whatsoever, users could easily opt-in to install such as package. Plus non-Qubes users could also benefit from it. (Anonymity Loves Company: Usability and the Network Effect)

4 Likes

Right. Keep that as is but my recommendation is to avoid the following:

I agree with you. That’s the wrong way to do it.

But none of
those pieces are qubes-specific.

If driver maintainers would accept such patch, then maybe. We are not
going to carry such patch ourselves.

The above are from the mailing list. As you’ve suggested, this will have to be a separate project with no support from Qubes.

In this case, a Qubes feature request could be as simple as “please install mac-randomization-kmod by default”. Perhaps no further work required from Qubes developers.

I don’t have much experience shipping creating/shipping Debian packages, but can easily learn.

How much does it matter that Qubes ships a modified kernel?

Do you have some time available to work with me on this, or are you busy elsewhere?

If I’m not mistaken, the only thing we haven’t already solved is how to switch between using the drivers shipped with the user’s selected kernel image and the drivers we will ship in our “mac-randomization-kmod” package, but I don’t think that’s going to be difficult to overcome.

following naming conventions for kernel modules

I still don’t think this needs to be a kernel module. Which part of this do you think can’t be done in userspace?

@adrelanos tagging because I forgot to reply and can’t update it.

1 Like

https://groups.google.com/g/qubes-devel/c/B5DtFt5crAE

Probably does not matter because the kernel live patching kernel module would not interact with any kernel code that Qubes is modifying. There could be an issue if two similar kernel modules would attempt to modify similar parts of the kernel. This is not applicable here.

You could emulate what the tirdad package is doing. Debian packaging will be the easy part. Aaaron probably and me can help.

It’s not commonplace (for me) to make promises of availability in Open Source.

Using the same kernel image. Unmodified. But a kernel module can overwrite kernel code. Example kernel module:

Maybe I am confused. If you want to enforce MAC address randomization at the kernel level there’s only 4 options:

  • A) patch Linux kernel upstream
  • B) custom compiled kernel
  • C) a kernel module using kernel hooks
  • D) a kernel module using kernel live patching

Use the same driver. But using kernel live patching API there can be a sysctl to enable/disable this?


Next suggested steps:

  1. Post (the diff) of 1 patched kernel driver.
  2. Hello world kernel module on Debian (non-Qubes).
  3. Hello world kernel module using Linux live patching API (non-Qubes).
  4. Same as above using Qubes.
  5. MAC randomization using kernel module using Linux live patching API.
1 Like

It’s not commonplace (for me) to make promises of availability in Open Source.

I’m more asking if this is something you will continue to have an interest in and will be able to answer questions/provide infrastructure as you had mentioned previously? I can solve this for my personal use without going to the additional trouble of creating a package, but I will make the package if I know I’ve got support.

Maybe I am confused. If you want to enforce MAC address randomization at the kernel level there’s only 4 options:

Sorry, I’ve caused some confusion. I was taking the approach that we would compile just the additional kernel modules, but not the entire kernel. This is why I thought we could do it in userspace without a kernel module. I don’t know if this is technically possible, do you?

D) a kernel module using kernel live patching

If it were possible to compile just the additional modules without the entire kernel, would you still recommend this approach? If so, and depending your answer about support, I can get started on the next steps.

1 Like

It’s certainly something useful in context of Kicksecure, Whonix, so yes.

Yes. No need to compile whole kernel. Compile kernel module only. It’s fast. Building the tirdad kernel module including packaging and lintian it still only takes less than 15 seconds. Installation in less than 30 seconds. Speed most likely won’t be the issue. Without packaging/lintian, simply code compilation outside of packaging will be even faster.

The kernel module can be developed, compiled, installed from user space.
The kernel module itself runs in kernel space.
I don’t think user space / kernel space definitions will matter much in this context.

Yes. This seems for now the best way.

1 Like

Sorry, there’s additional confusion.

In my previous post I am referring to compiling the patched driver modules, not the kernel module that you’ve proposed.

I am in support of the live patching, I just want to be certain we’re not overlooking any other solutions.

1 Like

For someone who is not a kernel hacker, compiling, forking, and installing a patched module seems more complex compared to using kernel live patching.

A kernel module might be replaced during operating system upgrades. Therefore, some form of dpkg-divert, config-package-dev replace, or a similar mechanism may be required—if it’s even possible.

Kernel modules with live patching that modify only 1-2 lines of kernel code seem to be the least invasive solution. Additional lines may be required for sysctl configurations and informational output, but the core modification remains minimal.

When forking an entire kernel module, any upstream changes (such as security fixes) will require backporting those changes into the forked module.

By contrast, when using the kernel live patching API, updates might only break if the specific lines being patched or hooked undergo significant changes.

Instead of patching all the drivers, could this be achieved at a higher level in the kernel? Kernel drivers operate at a low level and serve as hardware abstractions. The Linux kernel likely implements it this way.

Python-style Pseudocode:

def driver_1():
    """Vendor-specific initialization for driver 1"""
    return get_built_in_mac_address()

def driver_2():
    """Vendor-specific initialization for driver 2"""
    return get_built_in_mac_address()

def handle_networking(vendor):
    """Determine the appropriate driver and retrieve the MAC address."""
    if vendor == "vendor_1":
        mac_address = driver_1()
    elif vendor == "vendor_2":
        mac_address = driver_2()
    else:
        mac_address = get_mac_address()  # Generic method to get MAC address
    return mac_address

Then hook it somehow. Instead simple kernel default return mac_address have a hook which modifies that variable.

I cannot imagine the kernel does not have that kind of abstraction? If it has, if you can find that code and hook/patch that, then no driver specific modifications will be required. Then only 1 kernel function might need hooking or patching.

1 Like

For someone who is not a kernel hacker, compiling, forking, and installing a patched module seems more complex compared to using kernel live patching.

I agree, but I would prefer to not throw away a potential solution because I haven’t currently got experience with it, I can always learn it.

By contrast, when using the kernel live patching API, updates might only break if the specific lines being patched or hooked undergo significant changes.

Do you think we are going to be able to fail closed with the kernel live patching API? What happens if the patch can’t be applied?

I think choosing between live patching and compiled modules should be selected based on whichever will allow us to do this the safest. Which do you think that is? If it’s equal then I will do live patching as you’ve suggested.

I cannot imagine the kernel does not have that kind of abstraction? If it has, if you can find that code and hook/patch that, then no driver specific modifications will be required. Then only 1 kernel function might need hooking or patching.

It’s not quite that simple. There’s the MAC address that is set on the netdevice and other generic kernel structs, which is what’s returned when a query for the MAC address is made. However, there’s also the MAC address that gets written to the hardware, which is done in a driver-specific way. I will look again to try and find a way to do it at the level of abstraction you mention, but I don’t know if it’s possible.

1 Like

Asking on the kernel mailing list might be helpful.

Yes.

The failure can be handled.

Kernel modules have wide powers. They can even cause a kernel panic. As a reaction of being unable to apply the kernel patch, either a sysctl or /proc file could be written. Based on that, a systemd unit might refuse to bring up networking or the kernel itself disables networking.

I guess live patching is the safest. It has the least demands on user space (udev, systemd, NetworkManger). No demands on user space actually. I expect it to require the fewest maintenance changes since only relevant kernel code is patched. Any kernel upgrades will just work. If the patch fails to apply in case the kernel modified the patched lines, networking can be disabled.


It really depends on the specific kernel code in question whether a hook or patch is more suitable. In any case, short of patching Linux upstream, a kernel module seems the best overall.

1 Like

Thanks. I think there’s a clear path to getting this done. I’ll update you once I have more.

3 Likes