|
|
Subscribe / Log in / New account

Two PaX features move toward the mainline

By Jake Edge
December 23, 2015

As the Kernel self-protection project (KSPP) ramps up in the month and a half since its formation, several features from the PaX project are starting their journey toward the mainline. The reception on the kernel-hardening mailing list that is being used to coordinate KSPP work has been positive, but the real test for these features will come when they are proposed for the mainline. Two specific patch sets have been posted recently, for PAX_REFCOUNT and PAX_MEMORY_SANITIZE, that we will look at here.

PAX_REFCOUNT

The idea behind the PAX_REFCOUNT patch set, posted by David Windsor, is to detect and handle overflows in reference-count variables. The kernel uses reference counts to track objects that have been allocated, incrementing or decrementing the count as references to them come and go; the kernel frees those objects when the count reaches zero. But if there is a path in the kernel where the count doesn't get decremented when an object reference gets dropped, an attacker could use that path to overflow and wrap the reference counter, effectively setting it to zero when there are actually still valid references to it. The object will be freed, but will still be used by those with references, leading to a use-after-free vulnerability.

This is not the first attempt to add this kind of overflow protection to the kernel. But when Windsor posted about a related idea for kref, which is a kernel abstraction for reference counts, back in 2012, the idea ran aground on how it handled the overflows. Like the original PaX patches, Windsor's patch would call BUG_ON() for reference counts that reached INT_MAX, instead of incrementing them. That would crash the kernel if the count ever reaches INT_MAX, which Greg Kroah-Hartman objected to:

So you are guaranteeing to crash a machine here if this fails? And you were trying to say this is a "security" based fix?

And people wonder why I no longer have any hair...

But as Windsor and others pointed out, there is no sensible recovery that can be done if a reference count is about to wrap. An alternative might be to simply not change the counter (and put a warning into the kernel log) once it reaches INT_MAX, but that would lead to a memory leak. Overall, at least at the time, Kroah-Hartman was clearly skeptical of the whole idea—or even that a kref wrap could be exploited. However, Kees Cook did describe the way an exploit might work:

Based on what I've seen, the "normal" exploit follows this pattern:

user1: alloc(), inc
user2: inc
user2: fail to dec
*repeat user2's actions until wrap*
user3: inc
user3: dec, free()
user1: operate on freed memory zomg

In the recent posting of PAX_REFCOUNT, Windsor has essentially broken up the PaX project's patches and applied them to the 4.2.6 stable kernel, though he is working on rebasing on linux-next. He noted a post on the grsecurity forums where the feature is well documented. The implementation changes the kernel's operations on atomic_t types so that overflows cannot occur; increments beyond INT_MAX are disallowed. In addition, processes that would have caused an overflow are sent a SIGKILL so that they can do no further damage. Windsor suggested that the signal might be too severe to start with:

When an overflow is detected, SIGKILL is sent to the offending process. This may be too drastic for an initial upstream submission. WARN_ON may be more appropriate until distros have some time to absorb it and report any unaddressed overflows.

The patches also create an atomic_unchecked_t type that acts just like today's atomic_t; it does no checking for overflow. In fact, the bulk of the patches are to various subsystems that use atomic variables but don't use them as reference counts; they are switched to use the new unchecked type. If the patches get merged, new users of atomic variables will need to determine if they are being used as reference counts or not to choose the proper atomic type.

So far, the comments on the patches have been light, but one suspects the code churn needed to switch all of those atomic types will bring some complaints when the patches get posted more widely. One could imagine creating a new type for those variables that need the checking, but that would require constant vigilance to ensure that any reference counts added to the kernel actually used the new type. That problem still exists with the posted patches, however, since new atomic_unchecked_t variables will need to be scrutinized to see that they aren't being used as reference counts.

PAX_MEMORY_SANITIZE

One way to mitigate the effect of use-after-free vulnerability or to stop various information leaks is to "sanitize" memory that is being freed by writing zeroes or some other constant value to it. That is the idea behind the PAX_MEMORY_SANITIZE feature. Laura Abbott posted a partial port of the feature to kernel-hardening on December 21.

In particular, Abbott's patches add the sanitization to the slab allocators (slab, slob, and slub), but not for the buddy allocator as the full PAX_MEMORY_SANITIZE feature does. That means "that allocations which go directly to the buddy allocator (i.e. large allocations) aren't sanitized". The actual sanitization is done using a fixed value (0xff for all architectures except x86-64, which uses 0xfe) that is written over the entire object before it is freed. Abbott plans to look into adding sanitization to the buddy allocator sometime in the new year. Another change that Abbott made to the PaX version of the feature was to add an option to handle the sanitization in the slow path of the allocator.

Christoph Lameter complained that the feature was similar to the slab-poisoning feature, so it should use that mechanism instead. Abbott agreed that the features were similar, but said that poisoning is a debug feature and this work is targeting kernel hardening so "it seemed more appropriate to keep debug features and non-debug features separate hence the separate option and configuration".

The cost of sanitization is performance, of course. Abbott said she measured impacts of 3-20% depending on the benchmark. But the impact of compiling the feature into the kernel, but turning it off at runtime (using the sanitize_slab=off boot option), is negligible.

Lameter also suggested using the GFP_ZERO flag to make allocations be zeroed before being returned. If there were a mode that set that flag for all allocations it would provide "implied sanitization". But doing it that way would move the performance impact from the free path to the allocation side, which is typically more performance sensitive, as Dave Hansen pointed out. It also means that unallocated memory would still store the potentially sensitive contents of the previous object until it is allocated again.

Instead of writing the fixed sanitization value across the object, writing zeroes would potentially allow the allocation path to skip the zeroing step, Hansen suggested. That might reduce some of the performance impact, though doing the zeroing at allocation time does leave the object's memory cache-hot, as Lameter noted. But zeroing has another downside that Abbott mentioned:

poisoning with non-zero memory makes it easier to determine that the error came from accessing the sanitized memory vs. some other case. I don't think the feature would be as strong if the memory was only zeroed vs. some other data value.

Overall, both patches were fairly well-received, but the hardening list is likely made up of those who are predisposed to look favorably on these kinds of changes. Based on discussions at last year's Kernel Summit, mainline developers should in theory be more receptive to patches that seek to mitigate whole classes of security bugs. If these PaX features can get merged eventually, there are some even more intrusive ones that could also attempt to run the gauntlet of the linux-kernel mailing list. Just where the line is—or even if there is one—is still unclear, but patches like these may help define it.


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


to post comments

Choose your poison

Posted Dec 24, 2015 15:02 UTC (Thu) by abatters (✭ supporter ✭, #6932) [Link] (2 responses)

> (0xff for all architectures except x86-64, which uses 0xfe)

What is the reason for setting SLAB_MEMORY_SANITIZE_VALUE differently for x86-64?

Choose your poison

Posted Dec 28, 2015 18:14 UTC (Mon) by iq-0 (subscriber, #36655) [Link] (1 responses)

I think it's partially meant as a value that can't sensibly be used as a pointer and is also unusable for most other purposes when used accidentally. By using 0xfe on x86-64 you also run afoul of the sign extension for pointers, which gives even beter protection for pointers.

Choose your poison

Posted Dec 29, 2015 9:13 UTC (Tue) by renox (guest, #23785) [Link]

Thanks for this clarification, I had the same reaction as barryascott: this patch seemed to conflate security and debugability, now with your explanation it seems that this is not necessarily the case.

Two PaX features move toward the mainline

Posted Dec 27, 2015 23:11 UTC (Sun) by barryascott (subscriber, #80640) [Link]

Writing 0x00 on deallocation seems to be the perfomance option, assuming the allocate can avoid the zeroing.
Writting any other value seems like a debug option and the author of the patch claimed that these uses
should be kept independent. A default of 0xff/0xfe sounds like a debug value to me.


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