By Jonathan Corbet
November 17, 2010
Regardless of whether one believes that the security of the Linux kernel is
as good as it should be, it is hard to disagree with the idea that it could
be made more secure. For some years, it has seemed like much of the
security-related work on the kernel has been directed toward the creation
of new access control mechanisms. But access control is only so helpful if
the kernel itself is vulnerable, allowing any access control system to be
bypassed. Recently we have begun to see more work aimed at making small
improvements to the
security of the kernel itself; this article will survey some of that work.
One key to hardening a system against attackers is to make it harder for
them to obtain information which could be used to compromise the kernel.
So it is not surprising to see an increase in patches which lock down
access to information. It turns out, though, that there is not universal
agreement on the value of restricting any kind of information about the
running system.
Marcus Meissner started things off with a
simple patch removing world-read access from /proc/kallsyms.
It is difficult to subvert the kernel without knowledge of how the kernel's
memory is laid out, so, Marcus thought, there is no point in providing that
information to anybody who asks. The problem with this change, as Ingo
Molnar pointed out, is that there are many
sources of that information. For example, the System.map file shipped by
most distributors also has the locations of all symbols built into the
kernel.
Now, one can certainly read-protect System.map as well, but that may not be
particularly helpful. Most systems out there are running
distributor-supplied kernels, and the packages for those kernels are widely
available. So an attacker does not need to read /proc/kallsyms or
System.map if the target system is running a stock kernel; they need only
dig up a package file containing the needed information. For this reason,
Ingo suggested that a complete solution would require restricting access to
the running kernel version as well. Removing all of the globally-readable
kernel version information from a system would be hard, but, if it could be
done, attackers would no longer have easy access to the locations of
functions and data structures within the kernel.
Suffice to say that this idea was not received with universal acclaim.
Critics claim that there are plenty of ways to determine which kernel
version is running; hiding version information would just make life harder
for legitimate applications (which may need that information to know which
features are available) without appreciably slowing attackers. Ingo talked
some about instrumenting the kernel to detect an attacker's attempts to
determine the running kernel version, thus giving an early alarm, but this
idea did not seem to gain a great deal of traction. So, chances are,
kernel versions will not be hidden in any near-future release (the
/proc/kallsyms patch has been merged for 2.6.37, though).
Dan Rosenberg has a similar concern: when the kernel exposes pointer values
to user space, it gives information to potential attackers. These values
can be found in a number of places, including the system log and numerous
places in /proc. Keeping pointer values out of the system log
seems like a hopeless task, but it is possible to better restrict access to
that log. To that end, Dan has posted a patch adding a new sysctl
knob controlling access to the syslog() system call. Later
versions of the patch include a configuration option for the default
setting of this knob; with that, distributors can make the system log off
limits for unprivileged users starting at boot.
Kernel addresses also show up in other places, though; for example,
/proc/net/tcp contains the address of the sock structure
associated with each open TCP connection. Dan worries about exposing the
address of these structures, especially since many of them contain function
pointers; if an attacker is somehow able to change the contents of kernel
memory, this kind of address might facilitate the task of taking over the
system. To raise the bar a bit, Dan posted a series of patches which
replaces the pointer value with an integer value (often zero) if the
process reading the associated /proc file is not suitably
privileged.
Unlike the syslog patch, which has made it into the mainline, the
/proc modification ran into some stiff opposition. It was
described as "security theater," and developers worried that it would break
applications which are legitimately using the pointer values. There were
suggestions that, perhaps, pointer values could be hashed, or that a more
general solution could be had by modifying the behavior of "%p" in
format strings. We might see the "%p" patch at some point, but
Dan has given up on the /proc
patches for now, saying "It's clear that there's too much resistance
to this effort for it to ever succeed, so I'm ceasing attempts to get this
patch series through."
Making it difficult to find structures containing function pointers may
make life harder for an attacker, but it still seems better to block the
modification of those structures whenever possible, regardless of who knows
their location. To that end, Kees Cook has announced his intent to try to lock down more
of the kernel:
The proposal is simple: as much of the kernel should be read-only
as possible, most especially function pointers and other execution
control points, which are the easiest target to exploit when an
arbitrary kernel memory write becomes available to an attacker.
Getting various structures marked const is an obvious starting
point; "constification" patches have been produced by many developers over
the years, but many structures still can be modified at run time. Beyond
that, though, Kees would like to have working read-only and no-execute
memory in loadable modules, "set once" pointers for things like the
security module operations vector, and more; many of the changes he would
like to see merged can currently be found in the grsecurity tree. It could
be a long process, but Kees says that it would be a security win for
everybody and that he would appreciate cooperation from subsystem
maintainers.
Not all kernel vulnerabilities are in the core code; many, instead, are
found in loadable modules. An attacker wishing to exploit a vulnerability
in a module must first ensure that the module is loaded. Module loading is a
privileged operation, but there are a number of ways in which an
unprivileged user can cause the kernel to load a module anyway; the kernel
normally goes out of its way to autoload modules on demand so that things
"just work." It seems clear that a kernel which never allows users to
trigger the loading of modules is less likely to be affected by any
vulnerability which is found in a loadable module.
Dan has posted another patch (again, based
on work done in the grsecurity tree) which makes the demand loading of
modules harder. It replaces the existing modules_disable sysctl
knob with a more flexible version; if it is set to one, only root can
trigger the loading of modules. Setting it to two disables module loading
entirely until the next boot. The changing of the existing ABI was not
well received, so a future version of the patch will keep the existing
switch and its semantics. Beyond that, doubts have been expressed
regarding whether administrators will enable this option, since demand
loading is a convenient feature.
Hardening the kernel to make the exploiting of vulnerabilities more
difficult seems like a good thing, but it would also be nice if we could
find those vulnerabilities before anybody even tries to exploit them. One
technique which can help in this regard is "fuzzing," the process of
passing random values into system calls and looking for unexpected
behavior. Some attackers certainly have good fuzzing tools, but the
development community seems to be rather less well equipped. So it is good
to see some recent
work by Dave Jones aimed at the creation of a more intelligent fuzzer.
It turns out that, by making system call parameters a bit less fuzzy, the
tool is more likely to get past the trivial checks and turn up real
problems; the improved fuzzer has already turned up one real bug.
The value of all this work may not be clear to everybody, and it probably
will not all make it into the mainline kernel. But it does seem that we
are seeing the beginning of a more focused effort to improve the security
of the kernel and to make it harder to exploit the inevitable bugs. A more
secure kernel may make it harder to gain true ownership of our gadgets in
the future, but it still is generally a good thing.
(
Log in to post comments)