[PATCH v1 0/4] [RFC] Implement Trampoline File Descriptor

madvenka
2020-07-28T13:11:03Z

From: "Madhavan T. Venkataraman" <madvenka@linux.microsoft.com>

Introduction
------------

Trampolines are used in many different user applications. Trampoline
code is often generated at runtime. Trampoline code can also just be a
pre-defined sequence of machine instructions in a data buffer.

Trampoline code is placed either in a data page or in a stack page. In
order to execute a trampoline, the page it resides in needs to be mapped
with execute permissions. Writable pages with execute permissions provide
an attack surface for hackers. Attackers can use this to inject malicious
code, modify existing code or do other harm.

To mitigate this, LSMs such as SELinux may not allow pages to have both
write and execute permissions. This prevents trampolines from executing
and blocks applications that use trampolines. To allow genuine applications
to run, exceptions have to be made for them (by setting execmem, etc).
In this case, the attack surface is just the pages of such applications.

An application that is not allowed to have writable executable pages
may try to load trampoline code into a file and map the file with execute
permissions. In this case, the attack surface is just the buffer that
contains trampoline code. However, a successful exploit may provide the
hacker with means to load his own code in a file, map it and execute it.

LSMs (such as the IPE proposal [1]) may allow only properly signed object
files to be mapped with execute permissions. This will prevent trampoline
files from being mapped. Again, exceptions have to be made for genuine
applications.

We need a way to execute trampolines without making security exceptions
where possible and to reduce the attack surface even further.

Examples of trampolines
-----------------------

libffi (A Portable Foreign Function Interface Library):

libffi allows a user to define functions with an arbitrary list of
arguments and return value through a feature called "Closures".
Closures use trampolines to jump to ABI handlers that handle calling
conventions and call a target function. libffi is used by a lot
of different applications. To name a few:

- Python
- Java
- Javascript
- Ruby FFI
- Lisp
- Objective C

GCC nested functions:

GCC has traditionally used trampolines for implementing nested
functions. The trampoline is placed on the user stack. So, the stack
needs to be executable.

Currently available solution
----------------------------

One solution that has been proposed to allow trampolines to be executed
without making security exceptions is Trampoline Emulation. See:

https://pax.grsecurity.net/docs/emutramp.txt

In this solution, the kernel recognizes certain sequences of instructions
as "well-known" trampolines. When such a trampoline is executed, a page
fault happens because the trampoline page does not have execute permission.
The kernel recognizes the trampoline and emulates it. Basically, the
kernel does the work of the trampoline on behalf of the application.

Here, the attack surface is the buffer that contains the trampoline.
The attack surface is narrower than before. A hacker may still be able to
modify what gets loaded in the registers or modify the target PC to point
to arbitrary locations.

Currently, the emulated trampolines are the ones used in libffi and GCC
nested functions. To my knowledge, only X86 is supported at this time.

As noted in emutramp.txt, this is not a generic solution. For every new
trampoline that needs to be supported, new instruction sequences need to
be recognized by the kernel and emulated. And this has to be done for
every architecture that needs to be supported.

emutramp.txt notes the following:

"... the real solution is not in emulation but by designing a kernel API
for runtime code generation and modifying userland to make use of it."

Trampoline File Descriptor (trampfd)
--------------------------

I am proposing a kernel API using anonymous file descriptors that
can be used to create and execute trampolines with the help of the
kernel. In this solution also, the kernel does the work of the trampoline.
The API is described in patch 1/4 of this patchset. I provide a
summary here:

Trampolines commonly execute the following sequence:

- Load some values in some registers and/or
- Push some values on the stack
- Jump to a target PC

libffi and GCC nested function trampolines fit into this model.

Using the kernel API, applications and libraries can:

- Create a trampoline object
- Associate a register context with the trampoline (including
a target PC)
- Associate a stack context with the trampoline
- Map the trampoline into a process address space
- Execute the trampoline by executing at the trampoline address

The kernel creates the trampoline mapping without any permissions. When
the trampoline is executed by user code, a page fault happens and the
kernel gets control. The kernel recognizes that this is a trampoline
invocation. It sets up the user registers based on the specified
register context, and/or pushes values on the user stack based on the
specified stack context, and sets the user PC to the requested target
PC. When the kernel returns, execution continues at the target PC.
So, the kernel does the work of the trampoline on behalf of the
application.

In this case, the attack surface is the context buffer. A hacker may
attack an application with a vulnerability and may be able to modify the
context buffer. So, when the register or stack context is set for
a trampoline, the values may have been tampered with. From an attack
surface perspective, this is similar to Trampoline Emulation. But
with trampfd, user code can retrieve a trampoline's context from the
kernel and add defensive checks to see if the context has been
tampered with.

As for the target PC, trampfd implements a measure called the
"Allowed PCs" context (see Advantages) to prevent a hacker from making
the target PC point to arbitrary locations. So, the attack surface is
narrower than Trampoline Emulation.

Advantages of the Trampoline File Descriptor approach
-----------------------------------------------------

- trampfd is customizable. The user can specify any combination of
allowed register name-value pairs in the register context and the kernel
will set it up accordingly. This allows different user trampolines to be
converted to use trampfd.

- trampfd allows a stack context to be set up so that trampolines that
need to push values on the user stack can do that.

- The initial work is targeted for X86 and ARM. But the implementation
leverages small portions of existing signal delivery code. Specifically,
it uses pt_regs for setting up user registers and copy_to_user()
to push values on the stack. So, this can be very easily ported to other
architectures.

- trampfd provides a basic framework. In the future, new trampoline types
can be implemented, new contexts can be defined, and additional rules
can be implemented for security purposes.

- For instance, trampfd defines an "Allowed PCs" context in this initial
work. As an example, libffi can create a read-only array of all ABI
handlers for an architecture at build time. This array can be used to
set the list of allowed PCs for a trampoline. This will mean that a hacker
cannot hack the PC part of the register context and make it point to
arbitrary locations.

- An SELinux setting called "exectramp" can be implemented along the
lines of "execmem", "execstack" and "execheap" to selectively allow the
use of trampolines on a per application basis.

- User code can add defensive checks in the code before invoking a
trampoline to make sure that a hacker has not modified the context data.
It can do this by getting the trampoline context from the kernel and
double checking it.

- In the future, if the kernel can be enhanced to use a safe code
generation component, that code can be placed in the trampoline mapping
pages. Then, the trampoline invocation does not have to incur a trip
into the kernel.

- Also, if the kernel can be enhanced to use a safe code generation
component, other forms of dynamic code such as JIT code can be
addressed by the trampfd framework.

- Trampolines can be shared across processes which can give rise to
interesting uses in the future.

- Trampfd can be used for other purposes to extend the kernel's
functionality.

libffi
------

I have implemented my solution for libffi and provided the changes for
X86 and ARM, 32-bit and 64-bit. Here is the reference patch:

http://linux.microsoft.com/~madvenka/libffi/libffi.txt

If the trampfd patchset gets accepted, I will send the libffi changes
to the maintainers for a review. BTW, I have also successfully executed
the libffi self tests.

Work that is pending
--------------------

- I am working on implementing an SELinux setting called "exectramp"
similar to "execmem" to allow the use of trampfd on a per application
basis.

- I have a comprehensive test program to test the kernel API. I am
working on adding it to selftests.

References
----------

[1] https://microsoft.github.io/ipe/
---
Madhavan T. Venkataraman (4):
fs/trampfd: Implement the trampoline file descriptor API
x86/trampfd: Support for the trampoline file descriptor
arm64/trampfd: Support for the trampoline file descriptor
arm/trampfd: Support for the trampoline file descriptor

arch/arm/include/uapi/asm/ptrace.h | 20 ++
arch/arm/kernel/Makefile | 1 +
arch/arm/kernel/trampfd.c | 214 +++++++++++++++++
arch/arm/mm/fault.c | 12 +-
arch/arm/tools/syscall.tbl | 1 +
arch/arm64/include/asm/ptrace.h | 9 +
arch/arm64/include/asm/unistd.h | 2 +-
arch/arm64/include/asm/unistd32.h | 2 +
arch/arm64/include/uapi/asm/ptrace.h | 57 +++++
arch/arm64/kernel/Makefile | 2 +
arch/arm64/kernel/trampfd.c | 278 ++++++++++++++++++++++
arch/arm64/mm/fault.c | 15 +-
arch/x86/entry/syscalls/syscall_32.tbl | 1 +
arch/x86/entry/syscalls/syscall_64.tbl | 1 +
arch/x86/include/uapi/asm/ptrace.h | 38 +++
arch/x86/kernel/Makefile | 2 +
arch/x86/kernel/trampfd.c | 313 +++++++++++++++++++++++++
arch/x86/mm/fault.c | 11 +
fs/Makefile | 1 +
fs/trampfd/Makefile | 6 +
fs/trampfd/trampfd_data.c | 43 ++++
fs/trampfd/trampfd_fops.c | 131 +++++++++++
fs/trampfd/trampfd_map.c | 78 ++++++
fs/trampfd/trampfd_pcs.c | 95 ++++++++
fs/trampfd/trampfd_regs.c | 137 +++++++++++
fs/trampfd/trampfd_stack.c | 131 +++++++++++
fs/trampfd/trampfd_stubs.c | 41 ++++
fs/trampfd/trampfd_syscall.c | 92 ++++++++
include/linux/syscalls.h | 3 +
include/linux/trampfd.h | 82 +++++++
include/uapi/asm-generic/unistd.h | 4 +-
include/uapi/linux/trampfd.h | 171 ++++++++++++++
init/Kconfig | 8 +
kernel/sys_ni.c | 3 +
34 files changed, 1998 insertions(+), 7 deletions(-)
create mode 100644 arch/arm/kernel/trampfd.c
create mode 100644 arch/arm64/kernel/trampfd.c
create mode 100644 arch/x86/kernel/trampfd.c
create mode 100644 fs/trampfd/Makefile
create mode 100644 fs/trampfd/trampfd_data.c
create mode 100644 fs/trampfd/trampfd_fops.c
create mode 100644 fs/trampfd/trampfd_map.c
create mode 100644 fs/trampfd/trampfd_pcs.c
create mode 100644 fs/trampfd/trampfd_regs.c
create mode 100644 fs/trampfd/trampfd_stack.c
create mode 100644 fs/trampfd/trampfd_stubs.c
create mode 100644 fs/trampfd/trampfd_syscall.c
create mode 100644 include/linux/trampfd.h
create mode 100644 include/uapi/linux/trampfd.h

--
2.17.1