[go: up one dir, main page]

|
|
Log in / Subscribe / Register

Preventing kernel-stack leaks

By Jonathan Corbet
March 7, 2018
The kernel stack is a small, frequently reused region of memory in each thread's address space. That reuse allows for efficient memory use and good performance as a result of cache locality, but it also presents a problem: data left on the stack can also end up being reused in ways that were not intended. The PaX patch set contains a mechanism designed to clear that data from the stack and prevent leaks, but an attempt to merge that code into the kernel has run into a snag.

By design, the C language does not define the contents of automatic variables — those that are created on the stack when the function defining them is called. If the programmer does not initialize automatic variables, they will thus contain garbage values; in particular, they will contain whatever happened to be left on the stack in the location where the variables are allocated. Failure to initialize these variables can, as a result, lead to a number of undesirable behaviors. Writing an uninitialized variable to user space will leak the data on the stack, which may be sensitive in one way or another. If the uninitialized value is used within the function, surprising results may ensue; if an attacker can find a way to control what will be left on the stack, they may be able to exploit this behavior to compromise the kernel. Both types of vulnerability have arisen in the kernel in the past and will certainly continue to pop up in the future.

Note that, while most uses of uninitialized data can be squarely blamed on the programmer, that is not always the case. For example, structures stored on the stack may contain padding between fields, and the compiler may well decide that it need not initialize the padding, since the program will not use that memory. But that memory can still be exposed to user space, should the kernel write such a structure in response to a system call.

The best solution to this problem would be to find and fix every location where on-stack variables are not properly initialized. Tools like KASan can help with this task, but chasing down this kind of problem is a never-ending game. It would, thus, be nice to have a way of automatically preventing this type of vulnerability.

For some time, Alexander Popov has been working on a port of the PaX STACKLEAK feature to the mainline kernel; the ninth version of the patch set was posted on March 3. This series adds a GCC plugin that tracks the maximum depth of the kernel stack; this information can be used to help prevent stack overruns. The main purpose of this tracking, though, is to allow the kernel to clear the kernel stack on return from every system call; the stack-clearing code can use the maximum depth to avoid clearing more stack space than was actually used. According to the cover letter, turning on this feature incurs a performance cost of about 1%; in return for this overhead, kernel code always runs in an environment where the contents of the stack are known to have been properly set.

Incidentally, the "clearing" of the stack is not setting it to zero. Instead, a special poison value is used; that should help to identify crashes that are caused by the use of uninitialized on-stack variables.

Kees Cook remarked that this series "should be ready to land pretty soon", but that was before Linus Torvalds became aware of it. Torvalds was not pleased, and made it clear that the STACKLEAK code was unlikely to make it into the mainline in its current form. He complained that:

It doesn't actually seem to help *find* bugs at all. As such, it's another "paper over and forget" thing that just adds fairly high overhead when it's enabled.

He suggested that security developers should focus more on finding and fixing problems, thus improving the kernel, rather than papering over issues in this way.

Needless to say, the developers involved see the situation a little differently. Cook responded:

I think it does improve the kernel, especially if we can gain more complete coverage through native compiler options (instead of just a plugin). Right now, for example, the kernel is littered with memset()s because the compiler can't be trusted to correctly zero-init padding, etc. This is an endless source of bugs, and this patch series provides a comprehensive and fast way to keep the stack cleared.

That response led Torvalds to start thinking about what he described as a "*smart*" way of dealing with the problem. Simply clearing the stack did not strike him as *smart*, but having the compiler initialize all automatic variables to zero would be. This initialization would provide similar protection from uninitialized data, but it could also be omitted whenever the compiler could determine that the variable was properly initialized in some other way. The result should be protection with significantly lower overhead.

That overhead could be reduced further in performance-sensitive code by adding a special marker for variables that the compiler should not initialize, even if it seems that initialization is necessary. Places where this marker is needed would stand out in performance profiles, and the marker itself would be a red flag that uninitialized data may be present.

Cook was in favor of adding this functionality to the compiler, but he also said that it is insufficient. It takes a long time for a new compiler to be widely adopted; people will build new kernels with old compilers for a surprisingly long time. So an approach based solely on the compiler will not provide anything close to universal coverage for years. Adding the stack clearing into the kernel can protect sites regardless of whether a new compiler is used to build it. He also pointed out that there are a couple of cases where the zeroing of automatic variables does not provide complete coverage. If a vulnerability allows an attacker to read data below the current stack boundary, it can be exploited to read the possibly interesting data that will be sitting there. Clearing the stack also wipes out data that might otherwise be read by an unrelated vulnerability, considerably narrowing the window in which that vulnerability could be exploited.

The discussion has no definitive conclusions as of this writing. The STACKLEAK code has encountered a significant obstacle on its way into the mainline, but it shouldn't necessarily be written off quite yet. There do appear to be some valid reasons for having this feature in the kernel, in the short term at least, and the stack clearing can be disabled for users who do not want to pay the cost. So, with some persistence (and security developers have learned to be persistent), there may yet be a place for the STACKLEAK patches in the mainline.

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


to post comments

About stack depth overflow

Posted Mar 7, 2018 20:42 UTC (Wed) by a13xp0p0v (guest, #118926) [Link]

Thanks for a nice article, Jonathan.

Let me correct the description of the STACKLEAK gcc plugin. The plugin performs two kinds of the kernel code instrumentation:

1. It inserts track_stack() calls for tracking the lowest border of the kernel stack. That is needed for erasing only the used part of the kernel stack at the end of syscalls. But that is _not_ used for detecting the stack depth overflow.

2. The plugin inserts the check_alloca() call before each alloca in the kernel. That blocks the Stack Clash attack against the kernel stack. So the combination of STACKLEAK, VMAP_STACK (providing the guard pages) and THREAD_INFO_IN_TASK protects the kernel against known stack depth overflow attacks.

I've described that in the last patch of the series, which updates Documentation/security/self-protection.rst.

Preventing kernel-stack leaks

Posted Mar 8, 2018 12:56 UTC (Thu) by edos (guest, #116377) [Link] (2 responses)

In the article it is said,
"Note that, while most uses of uninitialized data can be squarely blamed on the programmer, that is not always the case. For example, structures stored on the stack may contain padding between fields, and the compiler may well decide that it need not initialize the padding, since the program will not use that memory. But that memory can still be exposed to user space, should the kernel write such a structure in response to a system call."

But actually, it is not quite clear for me, could someone suggest a way exploiting a non-initialized paddings in the structures on the stack?

Preventing kernel-stack leaks

Posted Mar 8, 2018 13:28 UTC (Thu) by domenpk (guest, #12382) [Link] (1 responses)

It's an information leak. Stack often contains pointers, and leaking (parts of) them could be used to defeat KASLR for example.

Preventing kernel-stack leaks

Posted Mar 11, 2018 18:06 UTC (Sun) by edos (guest, #116377) [Link]

Well, seems like, regardless of KASLR usage, I can find code area I need from these pre-initialized paddings in the memory

gcc plugin?

Posted Mar 8, 2018 16:38 UTC (Thu) by mstefani (guest, #31644) [Link]

A gcc plugin cannot be used to initialize all automatic variables to zero?
I guess the answer is "no" else it would have been proposed instead of having to wait for new compiler version.
Just my curiosity on the "why?" that isn't possible.

Preventing kernel-stack leaks

Posted Mar 8, 2018 16:49 UTC (Thu) by pspinler (subscriber, #2922) [Link] (1 responses)

I think I'm missing something here, as a non kernel developer. Might someone clarify for me, please:

Cook is not in favor of an alternative compiler based solution due in large part to long delays in compiler adaptation, right? But yet, his proposed solution depends on a compiler plugin. Would not this compiler plugin, presumably distributed with the compiler, also not be available for years to many people?

-- Pat

Plugins

Posted Mar 8, 2018 17:02 UTC (Thu) by corbet (editor, #1) [Link]

Plugins can be distributed with the kernel itself - they live in scripts/gcc-plugins/.

Preventing kernel-stack leaks

Posted Mar 15, 2018 8:23 UTC (Thu) by ledow (guest, #11753) [Link] (1 responses)

So...

It's like having a chef in a busy kitchen.

Left to his own devices, the kitchen builds up gunk and he just keeps cooking on the surfaces. This is obviously bad and dirt leaks out in the food.

The stack-cleaning solution is like saying "Let's clean up the entire kitchen for each meal, and then dye every surface purple. Then we know that purple surfaces are clean and any food that comes out not-purple could have been prepared in a bad way." (I assume that people don't care about purple food so long as it's safe...)

The compiler solution is like saying "Let's just make sure that the chef cleans whatever he uses before he starts using it in cooking".

It does seem that this should be fixed in the compiler, not the kernel. However, that may take a little while and in the meantime is it really worth hitting the kernel to do the compiler's job until people catch up?

I'm not sure it is. Those who want the feature (e.g. distributions and people who care) will compile with a known-good compiler. Those who don't, won't care and probably won't want the 1% hit either.

And though the stack-cleaning solution is a great debugging tool to find those places Linus is talking about, it's not going to do much once those places are found and probably has little use in the real kernel. If it was zero-impact, sure, throw it in. But 1% is a measurable hit, and that's 24 hours a day, 7 days a week, even if the software a computer is running never changes or suffers from the stack data being revealed like so.

There have always been fuzzers that do things like deliberately corrupt stack data to see what happens when code is run on them. But I'm not sure they belong in the kernel as anything other than a DEBUG_ option. Fix the real problem - which is that variables aren't initialised if the programmer forgets to do so. I've always said it should have been part of the specification of the language, we could have changed it decades ago and nobody ever did. The impact is minimal as, pretty much, 99.999% of the time you have to initialise anyway and you can easily have a compiler indication of when not to bother, just like anything else.

But clearing an entire stack every time you do something, and THEN code initialising over the top (as we have to assume the stack isn't clean), that's just doing the same job twice. Let's do it once, but properly, which suggests extending the compiler's code, not the kernel's.

Preventing kernel-stack leaks

Posted Sep 20, 2018 15:25 UTC (Thu) by roblucid (guest, #48964) [Link]

I think the compiler people tend to look at the specs and try to do as little compute work in the object code as they can, to do better in benchmarks; but then real programs end up having to take cover all defence measures or pay a cost in reliability, when they paper over the cracks.

In Linux benchmarks to, distros have been penalised in comparative reviews, due to benchmarkers policy of accepting defaults, so a distro installation defaulting to settings intended to minimise risk of filesystem data loss are penalised.

It's the way of the tl;dr world we are in!


Copyright © 2018, 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