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

Casey Schaufler
2020-07-28T16:05:27Z

On 7/28/2020 6:10 AM, madvenka@linux.microsoft.com wrote:
> 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.

What prevents a malicious process from using the "well-known" trampoline
to its own purposes? I expect it is obvious, but I'm not seeing it. Old
eyes, I suppose.

> 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.

You could make a separate LSM to do these checks instead of limiting
it to SELinux. Your use case, your call, of course.

>
> - 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
>