|
|
Log in / Subscribe / Register

Restricting automatic kernel-module loading

By Jonathan Corbet
December 4, 2017
The kernel's module mechanism allows the building of a kernel with a wide range of hardware and software support without requiring that all of that code actually be loaded into any given running system. The availability of all of those modules in a typical distributor kernel means that a lot of features are available — but also, potentially, a lot of exploitable bugs. There have been numerous cases where the kernel's automatic module loader has been used to bring buggy code into a running system. An attempt to reduce the kernel's exposure to buggy modules shows how difficult some kinds of hardening work can be.

Module autoloading

There are two ways in which a module can be loaded into the kernel without explicit action on the administrator's part. On most contemporary systems, it happens when hardware is discovered, either by a bus driver (on buses that support discovery) or from an external description like a device tree. Discovery causes an event to be sent to user space, where a daemon like udev applies whatever policies have been configured and loads the appropriate modules. This mechanism is driven by the available hardware and is relatively hard for an attacker to influence.

Within the kernel, though, lurks an older mechanism, in the form of the request_module() function. When a kernel function determines that a needed module is missing, it can call request_module() to send a request to user space to load the module in question. For example, if an application opens a char device with a given major and minor number and no driver exists for those numbers, the char device code will attempt to locate a driver by calling:

    request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev));

If a driver module has declared an alias with matching numbers, it will be automatically loaded into the kernel to handle the open request.

There are hundreds of request_module() calls in the kernel. Some are quite specific; one will load the ide-tape module should the user be unfortunate enough to have such a device. Others are more general; there are many calls in the networking subsystem, for example, to locate modules implementing specific network protocols or packet-filtering mechanisms. While the device-specific calls have been mostly supplanted by the udev mechanism, modules for features like network protocols still rely on request_module() for user-transparent automatic loading.

Autoloading makes for convenient system administration, but it can also make for convenient system exploitation. The DCCP protocol vulnerability disclosed in February, for example, is not exploitable if the DCCP module is not loaded in the kernel — which is normally the case, since DCCP has few users. But the autoloading mechanism allows any user to force that module to be loaded simply by creating a DCCP socket. Autoloading thus widens the kernel's attack surface to include anything in a module that unprivileged users can cause to be loaded — and there are a lot of modules in a typical distributor kernel.

Tightening the system

Djalal Harouni has been working on a patch set aimed at reducing the exposure from autoloading; the most recent version was posted on November 27. Harouni's work takes inspiration from the hardening found in the grsecurity patch set, but takes no code from there. In this incarnation (it has changed somewhat over time), it adds a new sysctl knob (/proc/sys/kernel/modules_autoload_mode) that can be used to restrict the kernel's autoloading mechanism. If this knob is set to zero (the default), autoloading works as it does in current kernels. Setting it to one restricts autoloading to processes with specific capabilities: processes with CAP_SYS_MODULE can cause any module to be loaded, while those with CAP_NET_ADMIN can autoload any module whose alias starts with netdev-. Setting this knob to two disables autoloading entirely. Once this value has been raised above zero, it cannot be lowered during the lifetime of the system.

The patch set also implements a per-process flag that could be set with the prctl() system call. This flag (which takes the same values as the global flag) could restrict autoloading for a specific process and all of its descendants without changing module-loading behavior in the system overall.

It is safe to say that this patch set will not be merged in its current form for a simple reason: Linus Torvalds strongly disliked it. Disabling autoloading is likely to break a lot of systems, meaning that distributors will be unwilling to enable this option and it will not see much use. "A security option that people can't use without breaking their system is pointless", he said. The discussion got heated at times, but Torvalds is not opposed to the idea of reducing the kernel's exposure to autoloaded vulnerabilities. It was just a matter of finding the right solution.

The per-process flag looks like it could be a part of that solution. It could be used, for example, to restrict autoloading for code running within a container while leaving the system as a whole unchanged. It is not uncommon to create a process within a container with the CAP_NET_ADMIN capability to configure that container's networking while wanting most of the code running in the container to be unable to force module loading.

But, Torvalds said, a single flag will never be able to properly control all of the situations where autoloading comes into play. Some modules should perhaps always be loadable, while others may need a specific capability. So he suggested retaining the request_module_cap() function added by Harouni's patch set (which performs the load only if a specific capability is present) and using it more widely. But he did have a couple of changes to request.

The first is that request_module_cap() shouldn't actually block module loading if the needed capability is absent — at least not initially. Instead, it should log a message. That will allow a study of where module autoloading is actually needed that would, with luck, point out the places where autoloading could be restricted without breaking existing systems. He also suggested that the capability check is too simplistic. For example, the "char-major-" autoload described above only happens if a process is able to open a device node with the given major and minor numbers. In such cases, a permission test (the ability to open that special file) has already been passed and the module should load unconditionally. So there may need to be other variants of request_module() to describe settings where capabilities do not apply.

Finally, Torvalds had another idea related to the idea that the worst bugs tend to lurk in modules that are poorly maintained at best. The DCCP module mentioned above, for example, is known to be little used and nearly unmaintained. If the modules that are well maintained were marked with a special flag, it might be possible to restrict unprivileged autoloading to those modules only. That would prevent the autoloading of some of the cruftier modules while not breaking autoloading in general. This idea does raise one question that nobody asked, though: when a module ceases being maintained, who will maintain it well enough to remove the "well maintained" flag?

In any case, that flag will probably not be added right away, if this proposed plan from Kees Cook holds. He suggested starting with the request_module_cap() approach with warnings enabled. The per-process flag would be added for those who can use it, but the global knob to restrict autoloading would not. Eventually it might be possible to get rid of unprivileged module loading, but that will be a goal for the future. The short-term benefit would be better information about how autoloading is actually used and the per-process option for administrators who want to tighten things down now.

This conversation highlights one of the fundamental tensions that can be found around kernel hardening work. Few people are opposed to a more secure kernel, but things get much more difficult as soon as the hardening work can break existing systems — and that is often the case. Security-oriented developers often get frustrated with the kernel community's resistance to hardening changes with user-visible impacts, while kernel developers have little sympathy for changes that will lead to bug reports and unhappy users. Some of those frustrations surfaced in this discussion, but most of the developers involved were mostly interested in converging on a solution that works for everybody involved.

Index entries for this article
KernelModules
KernelSecurity/Kernel hardening
SecurityLinux kernel/Hardening


to post comments

Restricting automatic kernel-module loading

Posted Dec 4, 2017 17:43 UTC (Mon) by gutschke (subscriber, #27910) [Link] (10 responses)

Allowing auto-loading of well-maintained modules could work.

It isn't possible to do a perfect job at auto-detecting well-maintained modules. But it is in fact possible to implement a dead-man switch that could become a reasonable approximation. Instead of making it a boolean flag, make it a kernel version number. If this number ever lags behind the actual kernel version number by more than two versions, we know that the module sources haven't been updated in several months. That's when the module would revert to non-maintained status.

There are all sorts of ways that this system could be gamed (e.g. picking the value with the help of a macro instead of a constant). But that's a social and not a technical problem. Those hacks shouldn't be permitted.

Restricting automatic kernel-module loading

Posted Dec 4, 2017 18:48 UTC (Mon) by edeloget (subscriber, #88392) [Link] (5 responses)

(I have a strong headache, so my English might be a little broken tonight. I hereby present you my apologies).

I don't get it: I'm pretty sure that modules compiled for a specific kernel have the same kernel version as the kernel itself, so I don't understand how this would work. You cannot base this on the "last commit distance" as well, since a commit that would be unrelated to the module core code (for example a change in the driver API) would reduce this distance to a small value.

Thus, a flag is (IMHO) a good solution. Add those to well known and/or often used modules. In order to remove add the relevant flag, a maintainer will have to show that the module code is well maintained.

But before doing that, one must have some metrics, including which module is auto-loaded and how often it happens.

Now, from my point of view, it would be a good idea to get rid of module autoloading :)

Restricting automatic kernel-module loading

Posted Dec 4, 2017 21:43 UTC (Mon) by simcop2387 (subscriber, #101710) [Link] (4 responses)

I think he's proposing that we add another bit of information. The last kernel version that this module had changes in.

Restricting automatic kernel-module loading

Posted Dec 4, 2017 21:53 UTC (Mon) by gutschke (subscriber, #27910) [Link] (3 responses)

That's exactly right. It would be a manually maintained field. It doesn't *technically* require that this is the last time the source has changed. But it would be the last time a maintainer re-affirmed that the module is still actively maintained. Frequently, that would go hand-in-hand with making source code changes. But it doesn't have to.

Similarly, it is possible for somebody to change the source code (e.g. to make adjustments for kernel-wide API changes) without asserting the responsibility of a full-time committed maintainer. In that case, they would not bump up the version number.

The important part is that this number would never be changed automatically. It is always a conscious decision by a human developer.

Restricting automatic kernel-module loading

Posted Dec 4, 2017 22:05 UTC (Mon) by nix (subscriber, #2304) [Link] (1 responses)

The existing MODULE_VERSION macro shows what the fate of any such field is likely to be: rotting unnoticed even though the module *is* in fact actively maintained.

Restricting automatic kernel-module loading

Posted Dec 4, 2017 22:09 UTC (Mon) by gutschke (subscriber, #27910) [Link]

But that's OK, isn't it. The only people making use of this field are those maintainers who explicitly want to request enhanced permissions for their code.

If updating the field isn't something they can fit into their workflow, then maybe their code doesn't deserve these extra permissions. After all, it sounds as if the long-term goal would be for auto-loading to become the exception rather than the rule.

There probably would be a lengthy transition path before any of these policies would be widely and uniformly enforced anyway.

Restricting automatic kernel-module loading

Posted Dec 5, 2017 0:46 UTC (Tue) by edeloget (subscriber, #88392) [Link]

I get it.

Restricting automatic kernel-module loading

Posted Dec 7, 2017 10:03 UTC (Thu) by tsr2 (subscriber, #4293) [Link] (3 responses)

My "simple" solution to how the flag is maintained.

At the start of each release cycle, set all "well maintained" flags to false. They then have to be updated by the module maintainer in that cycle for the module to be considered well maintained.

Restricting automatic kernel-module loading

Posted Dec 7, 2017 13:28 UTC (Thu) by nix (subscriber, #2304) [Link] (2 responses)

This requires every maintainer to get at least one pull request accepted by Linus each cycle. Given that they are normally prepared atop the *previous* kernel release (so something that lands in 4.15.x was often prepared atop 4.13.x or even 4.12.x) and given that not everything needs an update in every release (the kernel has a *great many* modules), one cannot control when or even if Linus chooses to reject your pulls, this seems likely to lead to a huge number of things falsely being considered unmaintained.

Perhaps a decaying function of sorts? A per-module 'last updated version' (a KERNELVERSION, obviously), updated when the maintainer sees fit, and a constantly-advancing threshold that the kernel uses to consider a module 'unmaintained' if a module's last-updated is older than that? The threshold would be a few versions, so that maybe something that hadn't seen maintenance in four releases (~ 1 year) was considered unmaintained...

Restricting automatic kernel-module loading

Posted Dec 15, 2017 1:43 UTC (Fri) by wmealing (guest, #31633) [Link] (1 responses)

I can see a problem in this, the AUF CVE-2017-6074 would reset this counter as "maintained".

Restricting automatic kernel-module loading

Posted Dec 15, 2017 5:28 UTC (Fri) by gutschke (subscriber, #27910) [Link]

I fail to see the problem. This number would only ever be updated manually. So, either the maintainer explicitly takes responsibility for maintaining the code for another year and updates the number; or he states that emergency edits to the source should not be misconstrued as extending support, and be leaves the old number unchanged.

In either case, it's a deliberate and conscious decision

Restricting automatic kernel-module loading

Posted Dec 4, 2017 21:42 UTC (Mon) by neilbrown (subscriber, #359) [Link] (8 responses)

> If this knob is set to zero (the default), autoloading works as it does in current kernels. Setting it to one restricts autoloading to processes with specific capabilities: processes with CAP_SYS_MODULE can cause any module to be loaded, while those with CAP_NET_ADMIN can autoload any module whose alias starts with netdev-. Setting this knob to two disables autoloading entirely. Once this value has been raised above zero, it cannot be lowered during the lifetime of the system.

One day we developers will grow up and understand that "always", "by-capability", and "never" are more human-readable than "0", "1", and "2".
Today, it seems, is not that day.

Restricting automatic kernel-module loading

Posted Dec 4, 2017 22:03 UTC (Mon) by nix (subscriber, #2304) [Link] (5 responses)

It's also more extensible. Sure, 0 1 and 2 are nicely ordered, with more capability towards the 0 end -- but what if you want to add more layers in the middle? They'd have to get stuck up around 3, 4, 5 etc (to avoid breaking systems using the existing values), breaking the ordering and rendering it strictly worse in all respects than using readable strings. (Sure, those strings are only readable to English-speakers, but *anything* is better than simple numbers. Well, almost anything. Three random UUIDs would be worse. Oh god someone's going to do that now I just know it. echo efc2654e-5890-47dd-95e7-87de662a2acb into this file to enable and 566a274d-bb12-4735-8400-1a2637b0a922 to disable! augh)

Restricting automatic kernel-module loading

Posted Dec 5, 2017 3:21 UTC (Tue) by sorokin (guest, #88478) [Link] (1 responses)

Remembering my BASIC days, may I suggest using 10, 20 and 30 instead?

Restricting automatic kernel-module loading

Posted Dec 7, 2017 19:22 UTC (Thu) by jospoortvliet (guest, #33164) [Link]

Exactly! Simple and effective... doesn't solve all problems but that was what GOTO was for!

Restricting automatic kernel-module loading

Posted Dec 5, 2017 11:21 UTC (Tue) by ballombe (subscriber, #9523) [Link] (2 responses)

On the other hand, It is far easier to remember "1" than how to spell "by-capability".

Restricting automatic kernel-module loading

Posted Dec 5, 2017 12:00 UTC (Tue) by hrw (subscriber, #44826) [Link]

root@krzys:/sys/devices/platform/ar934x_wmac/leds/ath9k-phy0# cat trigger
none nand-disk switch0 timer default-on netdev usbport phy0rx phy0tx phy0assoc phy0radio [phy0tpt] phy1rx phy1tx phy1assoc phy1radio phy1tpt

$ cat the-switch-controlling-autoload-of-modules
always [by-capability] never

Doable?

Restricting automatic kernel-module loading

Posted Dec 6, 2017 13:59 UTC (Wed) by nix (subscriber, #2304) [Link]

It's far easier to remember how to spell '1', yes, but its meaning is entirely implicit and context-dependent. In practice you're going to have to look it up every time you use it, since this is not a flag you're likely to flip often. Meanwhile, textual strings are self-describing.

(Imagine this with the block layer. It's easy to flip to the deadline scheduler, just echo 1 > /sys/block/$foo/queue/scheduler! The default, cfq, is 3, of course, because it happened to be the third scheduler added. The second scheduler was removed long ago, so 2 is not valid any more, until of course someone adds another scheduler, when 2 is apt to get randomly picked up by that instead...)

Restricting automatic kernel-module loading

Posted Dec 5, 2017 6:15 UTC (Tue) by tixxdz (subscriber, #60564) [Link]

Actually I was going to do that, but given that all other related sysctl are numbers and the patch introduces lot of changes, I minimized any other new changes and probably you can guess why! the doc tries hard to reflect to their real semantics.

Restricting automatic kernel-module loading

Posted Dec 5, 2017 21:45 UTC (Tue) by vomlehn (guest, #45588) [Link]

Ach, you're getting soft lad. Why, when I was a little one and we wanted granola, we'd eat the whole oat plant and leave it to the digestive system to sort it all out. Just think of the performance gains we got from not processing the grains out beforehand. :-)

Restricting automatic kernel-module loading

Posted Dec 5, 2017 6:20 UTC (Tue) by dambacher (subscriber, #1710) [Link] (1 responses)

Maybe one can write a patch to either route the request_module call to the udev helper system to apply some rules or to add a ebpf filter before it?

Restricting automatic kernel-module loading

Posted Dec 5, 2017 9:04 UTC (Tue) by lkundrak (subscriber, #43452) [Link]

Having userspace do the policing indeed sounds like a good idea.

The mechanism to do so is already in place: request_module() already asks the userspace helper. modprobe is already able to deny loading of modules and even launch commands instead, and if modprobe is not up to the job then a different helper could be specified with the kernel.modprobe sysctl?

Why not remove the code?

Posted Dec 5, 2017 10:57 UTC (Tue) by NAR (subscriber, #1313) [Link] (2 responses)

I'm not sure I get the attack vector here. If the problem is that there is unmaintained code in the kernel that might be loaded by the user - why not remove the unmaintained code from the kernel instead? If there are legitimate users of the user-triggered kernel module autoloading, I don't see any solution that would not break their use cases. Sufficiently paranoid system administrators could have locked down their systems already by compiling everything into the kernel and disable module loading, couldn't they?

Why not remove the code?

Posted Dec 8, 2017 15:14 UTC (Fri) by MarcB (subscriber, #101804) [Link] (1 responses)

The issue really isn't unmaintained code.

If you think about it, the current situation in Linux is really strange: Unprivileged users are not allowed to reconfigure an existing network protocol, for example to add an IP address. But they are allowed to enable a completely new protocol and expose its code to the network

Depending on the protocol, it might be completely auto-configuring, or it might just be a transport layer protocol on top an already configured protocol.

Even if everything is fine with this protocol's code, this seems very wrong to me. Only an administrator should be able to this. And it should not be necessary to use any blacklisting to prevent this, because combined with default distribution kernel configurations, that contain about everything, this is just absurd.

I like the approach suggested in https://lwn.net/Articles/740662/. With proper documentation, reasonable defaults and proper tools, this might work fine.

Permission to cause kernel modules to be loaded

Posted Dec 10, 2017 21:50 UTC (Sun) by giraffedata (guest, #1954) [Link]

If you think it's really strange that an unprivileged user has the power to get code added to the running kernel, you're looking at module loading the wrong way. That network protocol was already enabled; the code for it is part of the kernel installed on the computer. We just delay loading it into memory until it is needed, for reasons of efficiency.

We have to restrict a user's power to set an IP address because it affects other users, but letting the user use a network protocol, for his own messages, that no one else has used yet since boot isn't in the same category.

The concept of restricting automatic module loads to reduce the attack surface just takes advantage of that delayed loading we invented for efficiency to implement a version of the more general concept of requiring special permission to do anything, so that Trojan horses cannot exploit bugs in code the user has no need to run.

Restricting automatic kernel-module loading

Posted Dec 7, 2017 19:55 UTC (Thu) by flussence (guest, #85566) [Link]

Windows was a bit forward-thinking here by coldplugging everything detected at OS install time and leaving the rest either on the install media, or in later versions when hard disk space became cheaper copying the packages but not installing them until needed. There's a clear delineation between tangible/common/“plug and play” drivers like USB drivers that always autoload (for better or worse) and weird stuff like packet sniffer virtual network interfaces that always require some degree of human intervention to enable.

Kitchen-sink distros need to adopt some of that model. The kernel itself could improve things, but the onus is really on the distros to provide sane defaults - that's their entire purpose.


Copyright © 2017, Eklektix, Inc.
This article may be redistributed under the terms of the Creative Commons CC BY-SA 4.0 license
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds