|
|
Log in / Subscribe / Register

Another try for address-space isolation

Did you know...?

LWN.net is a subscriber-supported publication; we rely on subscribers to keep the entire operation going. Please help out by buying a subscription and keeping LWN on the net.

By Jonathan Corbet
May 21, 2024

LSFMM+BPF
Brendan Jackman started his memory-management-track session at the 2024 Linux Storage, Filesystem, Memory-Management and BPF Summit by saying that, for some years now, the kernel community has been stuck in a reactive posture with regard to hardware vulnerabilities. Each problem shows up with its own scary name, and kernel developers find a way to mitigate it, usually losing performance in the process. Jackman said that it is time to take back the initiative against these vulnerabilities by reconsidering the more general use of address-space isolation.

In a typical exploit, he said, an attacker will start by carefully mistraining a CPU's branch-prediction hardware. Then, a call into the kernel will cause speculative execution to take a wrong path; the erroneous speculation will be mostly cleaned up when it becomes clear that it was wrong, but not without leaving a secret behind somewhere. The attacker then recovers that secret and leaks it by way of some sort of covert channel.

Keeping data unmapped

[Brendan Jackman] The key to getting out of the reactive mode is the realization that speculative execution cannot leak data that is not mapped at the time. Thus, keeping sensitive data unmapped when it is not needed can mitigate a number of known exploits — and those we don't yet know about as well. Address-space isolation has been pursued by a number of developers over the years; Jackman was there to talk about a specific patch set that was first implemented for Hyper-V in 2019, and which has been partially deployed at Google this year. The company plans to reach full deployment in the future, and intends to maintain the work going forward.

This work was covered here in 2022; see that article for details on this work (which has not been publicly posted since then). Jackman began a brief overview of this work by pointing out that Linux uses address-space isolation now to keep much of the kernel inaccessible (even via speculation) from user space; there are separate page tables for user and kernel mode. Keeping the kernel's address space isolated from user space protects it from Meltdown vulnerabilities.

The proposed patch addresses Spectre vulnerabilities by providing address-space isolation within the kernel. It splits the kernel page table into two: a "restricted" page table that only maps readily available (nonsensitive) data, and an "unrestricted" table that maps all of the kernel, including sensitive data. The restricted table is active until there is an actual need to access sensitive data; any attempt to do so will cause a page fault, at which point the kernel will flush caches, perhaps halt sibling processors, then continue with the unrestricted table. That switch is expensive, so the best performance will be had if most paths through the kernel only access nonsensitive data.

This is, he said, a naive solution, in that everything is either sensitive or not, with no shades of gray in between. Making it less naive involves adding a third level, called "local nonsensitive" (this approach was already reflected in the 2022 patch set). Data in this class can be leaked back to the calling process without ill effect; it is, essentially, information that this process already has access to. But locally nonsensitive data should be protected from any other process in the system. In this mode, each process will have its own set of restricted kernel page tables; it adds complication, so Jackman would like to proceed without this aspect in the beginning, if possible.

He put up a performance chart showing that existing mitigations for Spectre vulnerabilities have a significant performance cost. With address-space isolation in place and the other mitigations turned off, almost all of that performance was regained and the system was still protected against speculative vulnerabilities.

There are, he said, some questions that need to be answered about this work; the first of those is about how sensitivity of data is annotated. There is a new set of GFP flags that are used at allocation time for that purpose, Jackman said. In the future, it might also be possible to use the subsystem context more directly; perhaps everything touched by the crypto layer should be seen as sensitive. Eventually the desire will be to figure out sensitivity at run time.

Even with allocation flags, there are two alternatives that need to be considered, given the need to minimize the amount of restricted data in the interest of better performance. One would be to consider all allocations to be sensitive unless they are specifically marked otherwise; that is, he said, "the only competent security answer". The other is to consider data nonsensitive unless specifically marked as sensitive — "the only competent performance answer". In the end, he said, there are three objectives to aim for: full mitigation, good performance, and reviewable patches. The community, somehow, has to pick which two of those it wants.

An audience member grumbled that all of this work is just a band-aid, that the proper solution is to just keep sensitive data on a separate processor. David Hildenbrand complained that the community is stuck writing code with the assumption that the hardware is compromised. That is the situation we are in, but he worried that address-space isolation would make it easier for hardware companies to just not care about speculative-execution vulnerabilities. Address-space isolation is designed around the idea that speculative-execution bugs will always be severe, and that may end up perpetuating that situation. Jackman responded that he did not believe that it is possible to create a CPU that is entirely free of this kind of problem, so speculative vulnerabilities will be with us for a long time regardless.

He returned to his question of whether the initial version of this work should start by emphasizing security or performance. His instinct is to prioritize security, then work on performance until it reaches a point where people actually want to run it. Until that happens, though, bad performance is likely to inhibit testing of the patches. As the session closed, Dan Williams pointed out that Spectre mitigations like retpolines started by emphasizing security, leaving performance for later. That has worked out well, he said; the community tends to be more motivated to innovate around performance than security. So, chances are, that is the tradeoff we are likely to see when this patch series returns to the mailing lists.

Implementation details

The discussion was not finished at that point, though; Jackman was able to schedule another slot the next day to get into a few of the details that he was trying to resolve. The core challenge, he said, is that the kernel has to take pains to flush the translation lookaside buffer (TLB) as part of the transition between the unrestricted and restricted modes to prevent use of the TLB as a covert channel. This flushing is expensive, so it should not be done more often than is strictly necessary.

The most conservative approach, he said, would be to perform a flush every time a page is freed; that would clearly slow things down considerably. So the current approach is to free pages in batches in a kernel thread, then perform the flush once at the end. A proper solution would look different, but would require the kernel to remember the sensitivity of every free page — whether it had been mapped into the restricted address space, in other words. Then, if an allocation request comes in, and the page used to satisfy it was nonsensitive, there is no need to bother with a TLB flush before returning a page.

Jackman was unsure of how to remember the previous sensitivity of free pages, though. One possibility might be to add a new migration type to track it. Another could be to add a new memory zone; this idea was met with a resounding "no" from the room.

Michal Hocko asked how developers would request sensitive memory; the answer is to use the new __GFP_SENSITIVE allocation flag. Since all of user-space memory is considered sensitive (the kernel has no way to know which user pages actually contain sensitive data), that flag is folded into GFP_USER and need not be added separately. There is a new page flag used to mark sensitive pages. Jackman said that he hadn't realized prior to the conference that adding new GFP flags is discouraged; Hocko answered that those flags are in short supply, and that kernel code tends to use them incorrectly in any case.

Jackman asked for alternative suggestions; Hocko mentioned the scoped interface that is used to modify allocations performed from within the filesystem and I/O paths. Perhaps something similar could be done for sensitive data; that could be better than annotating specific allocations, he said. There are a lot of allocation sites in the kernel, annotating them all is not really feasible and the end result is sure to be incorrect.

As this session came to a close (for real, this time), Jackman noted that some allocations must be marked as nonsensitive, regardless of the data to be stored there. Specifically, the kernel cannot take page faults around the system-call entry path, so memory accessed then must be nonsensitive.

Index entries for this article
KernelMemory management/Address-space isolation
KernelSecurity/Meltdown and Spectre
ConferenceStorage, Filesystem, Memory-Management and BPF Summit/2024


to post comments

Another try for address-space isolation

Posted May 22, 2024 4:17 UTC (Wed) by jengelh (subscriber, #33263) [Link] (3 responses)

>unsure of how to remember the previous sensitivity of free pages, though

Is it possible to just record that information in the (now-unallocated) memory the page referenced?

Another try for address-space isolation

Posted May 22, 2024 15:43 UTC (Wed) by iabervon (subscriber, #722) [Link]

I think they're trying to find out if the page might contain leftover sensitive data at a time when there's no way to read the page, before mapping it in a way that could leak its contents if they don't take expensive steps. If I'm understanding that correctly, the unallocated memory is also unreadable until after the information is needed.

Another try for address-space isolation

Posted May 23, 2024 9:10 UTC (Thu) by bjackman (subscriber, #109548) [Link]

We have actually explored this trick for similar information (it's a little awkward because of debug_page_alloc, but it still might be useful one day), but for page sensitivity we don't just want to remember sensitivity but also _index_ free pages by it.

Another try for address-space isolation

Posted May 24, 2024 10:27 UTC (Fri) by walex (guest, #69836) [Link]

An operating system from the 60-70s (MUSS, University of Manchester, Derrick Morris and many others) had a very good design:

  • Virtual memory segments with a global id, created unmapped
  • As a separate concept, VM "windows" as pure address space, with no memory mapped to them whern created.

  • The ability map a VM segment to a VM window if needed for access, or to just copy copy data to/from unmapped segments.
  • The ability to "move" unmapped segments by global-id from one process to another (without any copy being necessary)
  • Files as VM segments on disk instead of memory, created by a file manager process and "moved" from it to clients on request.

The extra step is the ability to "move" threads from one address space to another, of which "system calls" are a small case (an idea that has been re-invented in part in Android's "binder").

Another try for address-space isolation

Posted May 22, 2024 8:41 UTC (Wed) by SLi (subscriber, #53131) [Link] (4 responses)

I'm curious about the idea to flush the TLB in batches from a background thread. Perhaps I'm missing some context. Is this fundamentally about making speculative attacks harder, not impossible? Is this also what the current expensive mitigations do?

If I understand correctly, these attacks happen when a malicious thread runs on the same core in a hyperthreading context. Is the plan that the sensitive sections are so short that this is hard to exploit? Doesn't leaving flushing to the end of the sensitive execution widen the window of opportunity?

Or is the idea actually to prevent hyperthreading on the same core when sensitive data is mapped, hopefully for a short time?

Another try for address-space isolation

Posted May 22, 2024 11:43 UTC (Wed) by farnz (subscriber, #17727) [Link] (2 responses)

Firstly, these attacks do not depend on running a malicious thread on a separate hyperthread that shares the same core with the "nice" thread; instead, they depend on the fact that if you have a dynamically shared resource (such as a memory controller) access timings to that resource depend not just on your code, but also on all other code that shares that resource. You can thus use access timing to the shared resource as a covert communications channel between privileged code and unprivileged code.

Normally, this isn't a big problem; either the covert communications channel is too slow to be useful (e.g. DRAM page opening and closing), or you can't convince the privileged code to access data that you can't infer by other means. Speculation attacks work by convincing the privileged code to access data you can't infer by other means during speculative execution, and then extracting details of that data from the covert channel.

Mitigations come in two forms: either you prevent any speculative execution at all across a privilege boundary (expensive, and what most of the current mitigations do), or you slow down the covert channel to the point where it's not useful (cheap, but hard). After all, the machine has a finite lifetime, and useful secrets aren't small; if the covert channel gives you one bit per second, extracting your TLS session keys (at most 256 bit symmetric keys) is relatively easy (5 minutes, and I have the key); in contrast, if I can only extract 1 bit per month, extracting your 128 bit SSD encryption key is likely to take longer than the SSD's operational lifespan (it'll take 10 years, so if you replace or re-encrypt in that time, the effort I've gone to so far is wasted).

As far as hyperthreading goes, it makes the attacks easier because there's faster shared resources, and thus the "baseline" speed of the covert channel is higher in the absence of mitigations; for example, your access to execution units directly affects the performance of the other hyperthread. When it comes to TLBs in HT, there are two implementation strategies, and CPUs use some mix of these for different TLBs:

  1. Each CPU thread has its own TLB entries, and no other CPU thread can access those entries while hyperthreading is enabled (either because the thread has its own TLB which is turned off when hyperthreading is disabled, or because the core's TLB is statically partitioned when hyperthreading is enabled).
  2. TLB entries are tagged with the thread that owns them, and the TLB is dynamically partitioned between the two threads based on use.

The first of those strategies is inherently proof against TLB-based communication channels between HTs; the TLB is not dynamically shared, so accesses to it from one HT don't influence the other. The second can be used as a communication channel, but flushing the TLB on the other HT will slow it down considerably, even if the TLB flushes happen in batches, since the flushes free up entries, and you can only measure the effect of the dynamic sharing when the TLB is full and evictions are happening.

Another try for address-space isolation

Posted May 22, 2024 16:37 UTC (Wed) by SLi (subscriber, #53131) [Link]

Thanks, that clarifies a lot! I was assuming the point of the TLB unmapping was only to prevent speculative access to the sensitive data, which could be depended on in a speculative execution that may evict data from a cache (which I think would in no way be affected by how full the TLB is)—and then the covert channel would be memory read timing that indicates presence or absence of a cache miss.

And that idea probably comes from a simplistic understanding of Spectre. I clearly need to study the latest attacks...

Another try for address-space isolation

Posted May 23, 2024 10:07 UTC (Thu) by bjackman (subscriber, #109548) [Link]

I wanna be pedantic here in case this thread leads to any misunderstandings about what ASI does.

> or you slow down the covert channel to the point where it's not useful

The best mitigations don't just slow down covert channels but rather eliminate them completely. For example to mitigate L1TF you have to totally wipe L1D when it's at risk of containing secrets and you're about to head back into untrusted code. The kernel already has logic to do that, ASI just gives you a more advanced way to determine whether it's "at risk of containing secrets". (Plus, it prevents attackers from arranging for it to contain secrets via speculation steering).

You are totally right about how flushing the TLB makes exploits much harder - this has been really painful for the engineers trying to develop stable exploits for testing mitigations! However, that's not the reason for the flushing being discussed in this article. That is not about slowing down covert channels, it's just normal flushing for the traditionl reasons, i.e. it's about removing pages from an address space.

Another try for address-space isolation

Posted May 23, 2024 9:48 UTC (Thu) by bjackman (subscriber, #109548) [Link]

Cross-hyperthread attacks are just one of the vectors for these exploits. You can also exploit them on a single thread across user->kernel or guest->host boundaries.

The flushing we're talking about here is to allow a page to transition from nonsensitive (i.e. it doesn't contain any secrets and we don't care if the attacker leaks it) to sensitive (i.e. it's among the memory being protected by ASI, it's unmapped from the kernel until needed). The reason we need to do this in a background thread is just an implementation detail of the mm subsystem, plus performance reasons. Until this flush is complete, a page isn't considered to have become sensitive, so it can't be reallocated for a use that potentially involves writing sensitive data to it - this is important because until it's flushed we can't guarantee that data can't be leaked from it.

So, this flushing isn't about reducing attack windows or "just making it harder" or anything, it's part of a mitigation that totally mitigates the relevant attacks. (Although, only for the data that is determined to be sensitive - that's the whole point of ASI).

Anyway,

> Or is the idea actually to prevent hyperthreading on the same core when sensitive data is mapped, hopefully for a short time?

Yes, this is absolutely something we wanna do. It's been prototyped but the initial implementation doesn't do this, it would be too much complexity to add at once.

Another try for address-space isolation

Posted May 29, 2024 16:54 UTC (Wed) by bjackman (subscriber, #109548) [Link]


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