Message ID | 20200428175129.634352-1-mic@digikod.net (mailing list archive) |
---|---|
Headers | show |
Series | Add support for RESOLVE_MAYEXEC | expand |
On Tue, Apr 28, 2020 at 7:51 PM Mickaël Salaün <mic@digikod.net> 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 +++ =============================
* Jann Horn: > Just as a comment: You'd probably also have to use RESOLVE_MAYEXEC in > the dynamic linker. Absolutely. In typical configurations, the kernel does not enforce that executable mappings must be backed by files which are executable. It's most obvious with using an explicit loader invocation to run executables on noexec mounts. RESOLVE_MAYEXEC is much more useful than trying to reimplement the kernel permission checks (or what some believe they should be) in userspace.
On Tue, Apr 28, 2020 at 11:21 PM Florian Weimer <fw@deneb.enyo.de> wrote: > * Jann Horn: > > > Just as a comment: You'd probably also have to use RESOLVE_MAYEXEC in > > the dynamic linker. > > Absolutely. In typical configurations, the kernel does not enforce > that executable mappings must be backed by files which are executable. > It's most obvious with using an explicit loader invocation to run > executables on noexec mounts. RESOLVE_MAYEXEC is much more useful > than trying to reimplement the kernel permission checks (or what some > believe they should be) in userspace. Oh, good point. That actually seems like something Mickaël could add to his series? If someone turns on that knob for "When an interpreter wants to execute something, enforce that we have execute access to it", they probably also don't want it to be possible to just map files as executable? So perhaps when that flag is on, the kernel should either refuse to map anything as executable if it wasn't opened with RESOLVE_MAYEXEC or (less strict) if RESOLVE_MAYEXEC wasn't used, print a warning, then check whether the file is executable and bail out if not? A configuration where interpreters verify that scripts are executable, but other things can just mmap executable pages, seems kinda inconsistent...
On 29/04/2020 00:01, Jann Horn wrote: > On Tue, Apr 28, 2020 at 11:21 PM Florian Weimer <fw@deneb.enyo.de> wrote: >> * Jann Horn: >> >>> Just as a comment: You'd probably also have to use RESOLVE_MAYEXEC in >>> the dynamic linker. >> >> Absolutely. In typical configurations, the kernel does not enforce >> that executable mappings must be backed by files which are executable. >> It's most obvious with using an explicit loader invocation to run >> executables on noexec mounts. RESOLVE_MAYEXEC is much more useful >> than trying to reimplement the kernel permission checks (or what some >> believe they should be) in userspace. Indeed it makes sense to use RESOLVE_MAYEXEC for the dynamic linker too. Only the noexec mount option is taken into account for mmap(2) with PROT_EXEC, and if you can trick the dynamic linker with JOP as Jann explained, it may enable to execute new code. However, a kernel which forbids remapping memory with PROT_EXEC still enables to implement a W^X policy. Any JOP/ROP still enables unexpected code execution though. > > Oh, good point. > > That actually seems like something Mickaël could add to his series? If > someone turns on that knob for "When an interpreter wants to execute > something, enforce that we have execute access to it", they probably > also don't want it to be possible to just map files as executable? So > perhaps when that flag is on, the kernel should either refuse to map > anything as executable if it wasn't opened with RESOLVE_MAYEXEC or > (less strict) if RESOLVE_MAYEXEC wasn't used, print a warning, then > check whether the file is executable and bail out if not? > > A configuration where interpreters verify that scripts are executable, > but other things can just mmap executable pages, seems kinda > inconsistent... As it is written in the documentation patch, this RESOLVE_MAYEXEC feature is an important missing piece, but to implement a consistent security policy we need to enable other restrictions starting with a noexec mount point policy. The purpose of this patch series is not to bring a full-feature LSM with process states handling, but it brings what is needed for LSMs such as SELinux, IMA or IPE to extend their capabilities to reach what you would expect.
On 2020-04-28, Mickaël Salaün <mic@digikod.net> 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. My only strong upfront objection is with this being a RESOLVE_ flag. RESOLVE_ flags have a specific meaning (they generally apply to all components, and affect the rules of path resolution). RESOLVE_MAYEXEC does neither of these things and so seems out of place among the other RESOLVE_ flags. I would argue this should be an O_ flag, but not supported for the old-style open(2). This is what the O_SPECIFIC_FD patchset does[1] and I think it's a reasonable way of solving such problems.
On Thu, Apr 30, 2020 at 11:54:29AM +1000, Aleksa Sarai wrote: > On 2020-04-28, Mickaël Salaün <mic@digikod.net> 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. > > My only strong upfront objection is with this being a RESOLVE_ flag. > > RESOLVE_ flags have a specific meaning (they generally apply to all > components, and affect the rules of path resolution). RESOLVE_MAYEXEC > does neither of these things and so seems out of place among the other > RESOLVE_ flags. > > I would argue this should be an O_ flag, but not supported for the I agree. Christian
On 30/04/2020 10:07, Christian Brauner wrote: > On Thu, Apr 30, 2020 at 11:54:29AM +1000, Aleksa Sarai wrote: >> On 2020-04-28, Mickaël Salaün <mic@digikod.net> 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. >> >> My only strong upfront objection is with this being a RESOLVE_ flag. >> >> RESOLVE_ flags have a specific meaning (they generally apply to all >> components, and affect the rules of path resolution). RESOLVE_MAYEXEC >> does neither of these things and so seems out of place among the other >> RESOLVE_ flags. >> >> I would argue this should be an O_ flag, but not supported for the > > I agree. OK, I'll switch back to O_MAYEXEC.
On Thu, Apr 30, 2020 at 4:55 AM Aleksa Sarai <cyphar@cyphar.com> wrote: > > On 2020-04-28, Mickaël Salaün <mic@digikod.net> 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. > > My only strong upfront objection is with this being a RESOLVE_ flag. > > RESOLVE_ flags have a specific meaning (they generally apply to all > components, and affect the rules of path resolution). RESOLVE_MAYEXEC > does neither of these things and so seems out of place among the other > RESOLVE_ flags. > > I would argue this should be an O_ flag, but not supported for the > old-style open(2). This is what the O_SPECIFIC_FD patchset does[1] and I > think it's a reasonable way of solving such problems. > > -- > Aleksa Sarai > Senior Software Engineer (Containers) > SUSE Linux GmbH > <https://www.cyphar.com/> I see that we discuss here the problem which was originated due to the lack of flags checks at open() As a newbie in kernel I want to propose maybe naive approach - add missing flag cheeks to open(). In order to not break badly coded application at once (applications hat pass full u32 number I propose to introduce 2 new sysctls: fs.verify_open_flags = BITMASK of flags open() uses in this kernel, i.r O_CREAT||O_RDOBLY|O_RDWR,, fs.verify_open_flags_action = NONE|WARN 1) fs.verify_open_flags and flags_action knobs will allow distributions and package maintainers to detect and fix bad code. On the other hand, it will allow to finally verify flags. passed to open 2) It will show kernel ABI to libc 3) The semantics of O_MAYEXEC is clear and is long waited for. I think that O_MAYEXEC pretty well describes the intention of application to treat the file as the source of actions, whether it is ELF or script or another format. (not necessary Posix permissions case) This intention is passed to the kernel and then to LSM, IMA. 4) since we need to rewrite interpretersin order to use this flag, we can also query fs.verify_open_flags to see whether it is available. I hope I added my 2 cents.
On Tue, 28 Apr 2020, Mickaël Salaün wrote: > 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]. Confirming that this is a highly desirable feature for the proposed IPE LSM.
On 29/04/2020 00.01, Jann Horn wrote: > On Tue, Apr 28, 2020 at 11:21 PM Florian Weimer <fw@deneb.enyo.de> wrote: >> * Jann Horn: >> >>> Just as a comment: You'd probably also have to use RESOLVE_MAYEXEC in >>> the dynamic linker. >> >> Absolutely. In typical configurations, the kernel does not enforce >> that executable mappings must be backed by files which are executable. >> It's most obvious with using an explicit loader invocation to run >> executables on noexec mounts. RESOLVE_MAYEXEC is much more useful >> than trying to reimplement the kernel permission checks (or what some >> believe they should be) in userspace. > > Oh, good point. > > That actually seems like something Mickaël could add to his series? If > someone turns on that knob for "When an interpreter wants to execute > something, enforce that we have execute access to it", they probably > also don't want it to be possible to just map files as executable? So > perhaps when that flag is on, the kernel should either refuse to map > anything as executable if it wasn't opened with RESOLVE_MAYEXEC or > (less strict) if RESOLVE_MAYEXEC wasn't used, print a warning, then > check whether the file is executable and bail out if not? > > A configuration where interpreters verify that scripts are executable, > but other things can just mmap executable pages, seems kinda > inconsistent... +1 I worked with Steve Downer on Python PEP 578 [1] that added audit hooks and PyFile_OpenCode() to CPython. A PyFile_OpenCode() implementation with RESOLVE_MAYEXEC will hep to secure loading of Python code. But Python also includes a wrapper of libffi. ctypes or cffi can load native code from either shared libraries with dlopen() or execute native code from mmap() regions. For example SnakeEater [2] is a clever attack that abused memfd_create syscall and proc filesystem to execute code. A consistent security policy must also ensure that mmap() PROT_EXEC enforces the same restrictions as RESOLVE_MAYEXEC. The restriction doesn't have be part of this patch, though. Christian [1] https://www.python.org/dev/peps/pep-0578/ [2] https://github.com/nullbites/SnakeEater/blob/master/SnakeEater2.py
On 01/05/2020 13:47, Christian Heimes wrote: > On 29/04/2020 00.01, Jann Horn wrote: >> On Tue, Apr 28, 2020 at 11:21 PM Florian Weimer <fw@deneb.enyo.de> wrote: >>> * Jann Horn: >>> >>>> Just as a comment: You'd probably also have to use RESOLVE_MAYEXEC in >>>> the dynamic linker. >>> >>> Absolutely. In typical configurations, the kernel does not enforce >>> that executable mappings must be backed by files which are executable. >>> It's most obvious with using an explicit loader invocation to run >>> executables on noexec mounts. RESOLVE_MAYEXEC is much more useful >>> than trying to reimplement the kernel permission checks (or what some >>> believe they should be) in userspace. >> >> Oh, good point. >> >> That actually seems like something Mickaël could add to his series? If >> someone turns on that knob for "When an interpreter wants to execute >> something, enforce that we have execute access to it", they probably >> also don't want it to be possible to just map files as executable? So >> perhaps when that flag is on, the kernel should either refuse to map >> anything as executable if it wasn't opened with RESOLVE_MAYEXEC or >> (less strict) if RESOLVE_MAYEXEC wasn't used, print a warning, then >> check whether the file is executable and bail out if not? >> >> A configuration where interpreters verify that scripts are executable, >> but other things can just mmap executable pages, seems kinda >> inconsistent... > > +1 > > I worked with Steve Downer on Python PEP 578 [1] that added audit hooks > and PyFile_OpenCode() to CPython. A PyFile_OpenCode() implementation > with RESOLVE_MAYEXEC will hep to secure loading of Python code. But > Python also includes a wrapper of libffi. ctypes or cffi can load native > code from either shared libraries with dlopen() or execute native code > from mmap() regions. For example SnakeEater [2] is a clever attack that > abused memfd_create syscall and proc filesystem to execute code. > > A consistent security policy must also ensure that mmap() PROT_EXEC > enforces the same restrictions as RESOLVE_MAYEXEC. The restriction > doesn't have be part of this patch, though. > > Christian > > [1] https://www.python.org/dev/peps/pep-0578/ > [2] https://github.com/nullbites/SnakeEater/blob/master/SnakeEater2.py To be consistent, a "noexec" policy must indeed also restricts features such as mprotect(2) and mmap(2) which may enable to set arbitrary memory as executable. This can be restricted with SELinux (i.e. execmem, execmod,execheap and execstack permissions), PaX MPROTECT [1] or SARA [2]. [1] https://pax.grsecurity.net/docs/mprotect.txt [2] https://lore.kernel.org/lkml/1562410493-8661-1-git-send-email-s.mesoraca16@gmail.com/