On Thu, May 06, 2021 at 08:26:41AM -0700, James Bottomley wrote:
On Wed, 2021-05-05 at 12:08 -0700, Andrew Morton wrote:
> On Wed, 3 Mar 2021 18:22:00 +0200 Mike Rapoport <rppt(a)kernel.org>
> wrote:
>
> > This is an implementation of "secret" mappings backed by a file
> > descriptor.
tl;dr: I like this series, I think there are number of clarifications
needed, though. See below.
> >
> > The file descriptor backing secret memory mappings is created using
> > a dedicated memfd_secret system call The desired protection mode
> > for the memory is configured using flags parameter of the system
> > call. The mmap() of the file descriptor created with memfd_secret()
> > will create a "secret" memory mapping. The pages in that mapping
> > will be marked as not present in the direct map and will be present
> > only in the page table of the owning mm.
> >
> > Although normally Linux userspace mappings are protected from other
> > users, such secret mappings are useful for environments where a
> > hostile tenant is trying to trick the kernel into giving them
> > access to other tenants mappings.
>
> I continue to struggle with this and I don't recall seeing much
> enthusiasm from others. Perhaps we're all missing the value point
> and some additional selling is needed.
>
> Am I correct in understanding that the overall direction here is to
> protect keys (and perhaps other things) from kernel bugs? That if
> the kernel was bug-free then there would be no need for this
> feature? If so, that's a bit sad. But realistic I guess.
Secret memory really serves several purposes. The "increase the level
of difficulty of secret exfiltration" you describe. And, as you say,
if the kernel were bug free this wouldn't be necessary.
But also:
1. Memory safety for user space code. Once the secret memory is
allocated, the user can't accidentally pass it into the kernel to be
transmitted somewhere.
In my first read through, I didn't see how cross-userspace operations
were blocked, but it looks like it's the various gup paths where
{vma,page}_is_secretmem() is called. (Thank you for the self-test! That
helped me follow along.) I think this access pattern should be more
clearly spelled out in the cover later (i.e. "This will block things
like process_vm_readv()").
I like the results (inaccessible outside the process), though I suspect
this will absolutely melt gdb or other ptracers that try to see into
the memory. Don't get me wrong, I'm a big fan of such concepts[0], but
I see nothing in the cover letter about it (e.g. the effects on "ptrace"
or "gdb" are not mentioned.)
There is also a risk here of this becoming a forensics nightmare:
userspace malware will just download their entire executable region
into a memfd_secret region. Can we, perhaps, disallow mmap/mprotect
with PROT_EXEC when vma_is_secretmem()? The OpenSSL example, for
example, certainly doesn't need PROT_EXEC.
What's happening with O_CLOEXEC in this code? I don't see that mentioned
in the cover letter either. Why is it disallowed? That seems a strange
limitation for something trying to avoid leaking secrets into other
processes.
And just so I'm sure I understand: if a vma_is_secretmem() check is
missed in future mm code evolutions, it seems there is nothing to block
the kernel from accessing the contents directly through copy_from_user()
via the userspace virtual address, yes?
2. It also serves as a basis for context protection of virtual
machines, but other groups are working on this aspect, and it is
broadly similar to the secret exfiltration from the kernel problem.
>
> Is this intended to protect keys/etc after the attacker has gained
> the ability to run arbitrary kernel-mode code? If so, that seems
> optimistic, doesn't it?
Not exactly: there are many types of kernel attack, but mostly the
attacker either manages to effect a privilege escalation to root or
gets the ability to run a ROP gadget. The object of this code is to be
completely secure against root trying to extract the secret (some what
similar to the lockdown idea), thus defeating privilege escalation and
to provide "sufficient" protection against ROP gadgets.
The ROP gadget thing needs more explanation: the usual defeatist
approach is to say that once the attacker gains the stack, they can do
anything because they can find enough ROP gadgets to be turing
complete. However, in the real world, given the kernel stack size
limit and address space layout randomization making finding gadgets
really hard, usually the attacker gets one or at most two gadgets to
string together. Not having any in-kernel primitive for accessing
secret memory means the one gadget ROP attack can't work. Since the
only way to access secret memory is to reconstruct the missing mapping
entry, the attacker has to recover the physical page and insert a PTE
pointing to it in the kernel and then retrieve the contents. That
takes at least three gadgets which is a level of difficulty beyond most
standard attacks.
As for protecting against exploited kernel flaws I also see benefits
here. While the kernel is already blocked from directly reading contents
from userspace virtual addresses (i.e. SMAP), this feature does help by
blocking the kernel from directly reading contents via the direct map
alias. (i.e. this feature is a specialized version of XPFO[1], which
tried to do this for ALL user memory.) So in that regard, yes, this has
value in the sense that to perform exfiltration, an attacker would need
a significant level of control over kernel execution or over page table
contents.
Sufficient control over PTE allocation and positioning is possible
without kernel execution control[3], and "only" having an arbitrary
write primitive can lead to direct PTE control. Because of this, it
would be nice to have page tables strongly protected[2] in the kernel.
They remain a viable "data only" attack given a sufficiently
"capable"
write flaw.
I would argue that page table entries are a more important asset to
protect than userspace secrets, but given the difficulties with XPFO
and the not-yet-available PKS I can understand starting here. It does,
absolutely, narrow the ways exploits must be written to exfiltrate secret
contents. (We are starting to now constrict[4] many attack methods
into attacking the page table itself, which is good in the sense that
protecting page tables will be a big win, and bad in the sense that
focusing attack research on page tables means we're going to see some
very powerful attacks.)
> I think that a very complete description of the threats which
this
> feature addresses would be helpful.
It's designed to protect against three different threats:
1. Detection of user secret memory mismanagement
I would say "cross-process secret userspace memory exposures" (via a
number of common interfaces by blocking it at the GUP level).
2. significant protection against privilege escalation
I don't see how this series protects against privilege escalation. (It
protects against exfiltration.) Maybe you mean include this in the first
bullet point (i.e. "cross-process secret userspace memory exposures,
even in the face of privileged processes")?
3. enhanced protection (in conjunction with all the other
in-kernel
attack prevention systems) against ROP attacks.
Same here, I don't see it preventing ROP, but I see it making "simple"
ROP insufficient to perform exfiltration.
-Kees
[0]
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/s...
[1]
https://lore.kernel.org/linux-mm/cover.1554248001.git.khalid.aziz@oracle....
[2]
https://lore.kernel.org/lkml/20210505003032.489164-1-rick.p.edgecombe@int...
[3]
https://googleprojectzero.blogspot.com/2015/03/exploiting-dram-rowhammer-...
[4]
https://git.kernel.org/linus/cf68fffb66d60d96209446bfc4a15291dc5a5d41
--
Kees Cook