Thread (18 messages) 18 messages, 5 authors, 2020-05-01

Re: [PATCH v3 0/5] Add support for RESOLVE_MAYEXEC

From: Jann Horn <jannh@google.com>
Date: 2020-04-28 19:22:22
Also in: linux-fsdevel, linux-security-module, lkml

On Tue, Apr 28, 2020 at 7:51 PM Mickaël Salaün [off-list ref] wrote:
The goal of this patch series is to enable to control script execution
with interpreters help.  A new RESOLVE_MAYEXEC flag, usable through
openat2(2), is added to enable userspace script interpreter to delegate
to the kernel (and thus the system security policy) the permission to
interpret/execute scripts or other files containing what can be seen as
commands.

This third patch series mainly differ from the previous one by relying
on the new openat2(2) system call to get rid of the undefined behavior
of the open(2) flags.  Thus, the previous O_MAYEXEC flag is now replaced
with the new RESOLVE_MAYEXEC flag and benefits from the openat2(2)
strict check of this kind of flags.

A simple system-wide security policy can be enforced by the system
administrator through a sysctl configuration consistent with the mount
points or the file access rights.  The documentation patch explains the
prerequisites.

Furthermore, the security policy can also be delegated to an LSM, either
a MAC system or an integrity system.  For instance, the new kernel
MAY_OPENEXEC flag closes a major IMA measurement/appraisal interpreter
integrity gap by bringing the ability to check the use of scripts [1].
Other uses are expected, such as for openat2(2) [2], SGX integration
[3], bpffs [4] or IPE [5].

Userspace needs to adapt to take advantage of this new feature.  For
example, the PEP 578 [6] (Runtime Audit Hooks) enables Python 3.8 to be
extended with policy enforcement points related to code interpretation,
which can be used to align with the PowerShell audit features.
Additional Python security improvements (e.g. a limited interpreter
withou -c, stdin piping of code) are on their way.

The initial idea come from CLIP OS 4 and the original implementation has
been used for more than 11 years:
https://github.com/clipos-archive/clipos4_doc

An introduction to O_MAYEXEC (original name of RESOLVE_MAYEXEC) was
given at the Linux Security Summit Europe 2018 - Linux Kernel Security
Contributions by ANSSI:
https://www.youtube.com/watch?v=chNjCRtPKQY&t=17m15s
The "write xor execute" principle was explained at Kernel Recipes 2018 -
CLIP OS: a defense-in-depth OS:
https://www.youtube.com/watch?v=PjRE0uBtkHU&t=11m14s

This patch series can be applied on top of v5.7-rc3.  This can be tested
with CONFIG_SYSCTL.  I would really appreciate constructive comments on
this patch series.
Just as a comment: You'd probably also have to use RESOLVE_MAYEXEC in
the dynamic linker. A while back, I wrote a proof-of-concept ELF
library that can execute arbitrary code without triggering IMA because
it has no executable segments - instead it uses init_array to directly
trigger code execution at a JOP gadget in libc that then uses
mprotect() to make the code executable. I tested this on Debian
Stretch back in 2018.

=============================
user@debian:~/ima_stuff$ cat make_segments_rw.c
#include <stdlib.h>
#include <fcntl.h>
#include <err.h>
#include <elf.h>
#include <sys/mman.h>
#include <sys/stat.h>

int main(int argc, char **argv) {
        int fd = open(argv[1], O_RDWR);
        if (fd == -1) err(1, "open");
        struct stat st;
        if (fstat(fd, &st)) err(1, "stat");
        unsigned char *mapping = mmap(NULL, st.st_size,
PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if (mapping == MAP_FAILED) err(1, "mmap");
        Elf64_Ehdr *ehdr = (void*)mapping;
        Elf64_Phdr *phdrs = (void*)(mapping + ehdr->e_phoff);

        for (int i=0; i<ehdr->e_phnum; i++) {
                phdrs[i].p_flags &= ~PF_X;
                phdrs[i].p_flags |= PF_W;
        }

        return 0;
}
user@debian:~/ima_stuff$ cat test.s
        .text
        .section        .text.startup,"aw",@progbits
        .globl  foobar
        .align 4096
foobar:
        /* alignment for xmm stuff in libc */
        sub     $8, %rsp
        call    getpid
        mov     %rax, %rsi
        leaq    message(%rip), %rdi
        call    printf
        movq    stdout_indir(%rip), %rdi
        movq    (%rdi), %rdi
        call fflush
        xor     %edi, %edi
        call    _exit

        .section        .init_array,"aw"
        .align 8
        .quad   rmdir+0x774

        .section        .fini_array,"aw"
        .quad   0xdeadbeef
        .quad   0xdeadbeef
        .quad   0xdeadbeef
        .quad   ucontext_data /* goes into rdi */
        .quad   0xdeadbeef
        .quad   0xdeadbeef
        .quad   0xdeadbeef
        .quad   0xdeadbeef
        .quad   setcontext+0x35 /* call target */

        .data
ucontext_data:
        /* 0x00 */
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        /* 0x40 */
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        .quad 0xdeadbeefdeadbeef, foobar
        .quad             0x1000, 0xdeadbeefdeadbeef
        /* 0x80 */
        .quad 0xdeadbeefdeadbeef, 0x7
        .quad 0xdeadbeefdeadbeef, 0xdeadbeefdeadbeef
        .quad stack_end, mprotect

        /* my stack */
        .fill 0x10000, 1, 0x42
stack_end:
        .quad foobar
message:
        .string "hello world from PID %d\n"
stdout_indir:
        .quad stdout
user@debian:~/ima_stuff$ gcc -o make_segments_rw make_segments_rw.c
user@debian:~/ima_stuff$ as -o test.o test.s
test.s: Assembler messages:
test.s:2: Warning: setting incorrect section attributes for .text.startup
user@debian:~/ima_stuff$ ld -shared -znorelro -o test.so test.o
user@debian:~/ima_stuff$ ./make_segments_rw test.so
user@debian:~/ima_stuff$ LD_PRELOAD=./test.so /bin/echo
hello world from PID 1053
user@debian:~/ima_stuff$ sudo tail
/sys/kernel/security/ima/runtime_measurements_count
1182
user@debian:~/ima_stuff$ sudo tail /sys/kernel/security/ima/runtime_measurements
tail: cannot open '/sys/kernel/security/ima/runtime_measurements' for
reading: No such file or directory
user@debian:~/ima_stuff$ sudo tail
/sys/kernel/security/ima/ascii_runtime_measurements
10 2435d24127ce5bcfbe776589ac86bc85530da07d ima-ng
sha256:ae35ddf5dbbef6ea31e8b87326001d12a6b4ec479bb8195b874d733d69ed1a4d
/usr/bin/x86_64-linux-gnu-gcc-6
10 f3ed20073a77fbc020d2807ce12ffc4cdbced976 ima-ng
sha256:65af5a9ea7ce00be9b921d4b13f5224c2369451eb918d4fa7721442283545957
/usr/bin/x86_64-linux-gnu-g++-6
10 25f0128e89a730a6f1cdd42d8de71d3db2625c9e ima-ng
sha256:d5d7e609b95939d0ae9f75a953d5cda4f1d8b9e4c1db98aeee7f792028bf026e
/usr/bin/x86_64-linux-gnu-as
10 51cf269a0008ab8173c7a696bee11be86a0bbd45 ima-ng
sha256:2d10a4e221ef8454b635f1ec646e6f4ff7f3db8e2e59b489c642758b2624a659
/usr/lib/x86_64-linux-gnu/libopcodes-2.28-system.so
10 b5c1db60c50722e1af84b83b34c0adb04b98d040 ima-ng
sha256:d3eef29b5b5bfc12999c5dbcc91029302477b70c7599aeb6b564140a336ab00b
/usr/lib/x86_64-linux-gnu/libbfd-2.28-system.so
10 6364d50cdac1733b7fd5dcfd9df124d1e4362a12 ima-ng
sha256:30c26e4b3cbd0677b2a23d09a72989002af138be57d301ed529c91b88427098f
/usr/lib/gcc/x86_64-linux-gnu/6/collect2
10 2a8c7ddacee57967e8a00ee1a522b552e29f559f ima-ng
sha256:a7b6287a8095701713e9ee7a886cae1f1ceefd0ce9c45dcc38719af563200964
/usr/bin/x86_64-linux-gnu-ld.bfd
10 e55a9c15349e2271cbdfe2f4fe36cd5b4070d3d0 ima-ng
sha256:b31674ad141a40eb2603f20400cc0dea4ee32ecf87771df3d039f16aae60ee26
/usr/lib/gcc/x86_64-linux-gnu/6/liblto_plugin.so.0.0.0
10 617aab630be74cd5bb7d830a727fd29cda361743 ima-ng
sha256:40fbf6acd3182d7a1ad158cd4de48da704bfe84f468d7b58dd557db93fe8a34c
/usr/bin/vim.basic
10 2c1fe398ecc0a8db6651621715d60a7e1b1958dc ima-ng
sha256:8523b422a01af773eff76b981c763cf0c739ea3030e592bb4d4f7854e295c418
/home/user/ima_stuff/make_segments_rw
user@debian:~/ima_stuff$
=============================

When looking at the syscalls the process is making, you can see that
it indeed never calls mmap() with PROT_EXEC on the library (I use
mprotect() to make my code executable, but IMA doesn't use the
mprotect security hook):

=============================
user@debian:~/ima_stuff$ strace -E LD_PRELOAD=./test.so /bin/echo
execve("/bin/echo", ["/bin/echo"], [/* 44 vars */]) = 0
brk(NULL)                               = 0x5642c52bc000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,
0) = 0x7fb83e817000
open("./test.so", O_RDONLY|O_CLOEXEC)   = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\0\20\0\0\0\0\0\0"...,
832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=72232, ...}) = 0
getcwd("/home/user/ima_stuff", 128)     = 21
mmap(NULL, 2167449, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_DENYWRITE,
3, 0) = 0x7fb83e3e5000
mprotect(0x7fb83e3e7000, 2093056, PROT_NONE) = 0
mmap(0x7fb83e5e6000, 69632, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1000) = 0x7fb83e5e6000
mprotect(0x7ffea1b1f000, 4096,
PROT_READ|PROT_WRITE|PROT_EXEC|PROT_GROWSDOWN) = 0
close(3)                                = 0
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=103509, ...}) = 0
mmap(NULL, 103509, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fb83e7fd000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\3\2\0\0\0\0\0"...,
832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=1689360, ...}) = 0
mmap(NULL, 3795360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3,
0) = 0x7fb83e046000
mprotect(0x7fb83e1db000, 2097152, PROT_NONE) = 0
mmap(0x7fb83e3db000, 24576, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x195000) = 0x7fb83e3db000
mmap(0x7fb83e3e1000, 14752, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fb83e3e1000
close(3)                                = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1,
0) = 0x7fb83e7fb000
arch_prctl(ARCH_SET_FS, 0x7fb83e7fb700) = 0
mprotect(0x7fb83e3db000, 16384, PROT_READ) = 0
mprotect(0x5642c3eed000, 4096, PROT_READ) = 0
mprotect(0x7fb83e81a000, 4096, PROT_READ) = 0
munmap(0x7fb83e7fd000, 103509)          = 0
mprotect(0x7fb83e3e6000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
getpid()                                = 1084
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 4), ...}) = 0
brk(NULL)                               = 0x5642c52bc000
brk(0x5642c52dd000)                     = 0x5642c52dd000
write(1, "hello world from PID 1084\n", 26hello world from PID 1084
) = 26
exit_group(0)                           = ?
+++ exited with 0 +++
=============================
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help