Accidentalmente escribí mi código de ensamblaje en la sección .data . Lo compilé y lo ejecuté. El programa se ejecutó normalmente en Linux 5.4.0-53-generic aunque no especifiqué un indicador como execstack .
Después de eso, ejecuté el programa bajo Linux 5.9.0-050900rc5-generic . El programa obtuvo SIGSEGV . Inspeccioné el permiso de memoria virtual leyendo /proc/$pid/maps . Resultó que la sección no es ejecutable.
Creo que hay una configuración en Linux que gestiona ese permiso. Pero no se donde encontrar.
ammarfaizi2@integral:/tmp$ uname -r 5.4.0-53-generic ammarfaizi2@integral:/tmp$ cat test.asm [section .data] global _start _start: mov eax, 60 xor edi, edi syscall ammarfaizi2@integral:/tmp$ nasm --version NASM version 2.14.02 ammarfaizi2@integral:/tmp$ nasm -felf64 test.asm -o test.o ammarfaizi2@integral:/tmp$ ld test.o -o test ammarfaizi2@integral:/tmp$ ./test ammarfaizi2@integral:/tmp$ echo $? 0 ammarfaizi2@integral:/tmp$ md5sum test 7ffff5fd44e6ff0a278e881732fba525 test ammarfaizi2@integral:/tmp$ ## Debug gef➤ shell cat /proc/`pgrep test`/maps 00400000-00402000 rwxp 00000000 08:03 7471589 /tmp/test 7ffff7ffb000-7ffff7ffe000 r--p 00000000 00:00 0 [vvar] 7ffff7ffe000-7ffff7fff000 r-xp 00000000 00:00 0 [vdso] 7ffffffde000-7ffffffff000 rwxp 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] gef➤ root@esteh:/tmp# uname -r 5.9.0-050900rc5-generic root@esteh:/tmp# cat test.asm [section .data] global _start _start: mov eax, 60 xor edi, edi syscall root@esteh:/tmp# nasm --version NASM version 2.14.02 root@esteh:/tmp# nasm -felf64 test.asm -o test.o root@esteh:/tmp# ld test.o -o test root@esteh:/tmp# ./test Segmentation fault (core dumped) root@esteh:/tmp# echo $? 139 root@esteh:/tmp# md5sum test 7ffff5fd44e6ff0a278e881732fba525 test root@esteh:/tmp# ## Debug gef➤ shell cat /proc/`pgrep test`/maps 00400000-00402000 rw-p 00000000 fc:01 2412 /tmp/test 7ffff7ff9000-7ffff7ffd000 r--p 00000000 00:00 0 [vvar] 7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0 [vdso] 7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack] ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall] gef➤ root@esteh:/tmp# objdump -p test test: file format elf64-x86-64 Program Header: LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**12 filesz 0x0000000000001009 memsz 0x0000000000001009 flags rw-.data en Linux 5.4.0-53-generic es ejecutable..data en Linux 5.9.0-050900rc5-generic NO es ejecutable.A su binario le falta PT_GNU_STACK . Como tal, este cambio parece haber sido causado por la confirmación 9fccc5c0c99f238aa1b0460fccbdb30a887e7036 :
From 9fccc5c0c99f238aa1b0460fccbdb30a887e7036 Mon Sep 17 00:00:00 2001 From: Kees Cook <keescook@chromium.org> Date: Thu, 26 Mar 2020 23:48:17 -0700 Subject: x86/elf: Disable automatic READ_IMPLIES_EXEC on 64-bit With modern x86 64-bit environments, there should never be a need for automatic READ_IMPLIES_EXEC, as the architecture is intended to always be execute-bit aware (as in, the default memory protection should be NX unless a region explicitly requests to be executable). There were very old x86_64 systems that lacked the NX bit, but for those, the NX bit is, obviously, unenforceable, so these changes should have no impact on them. Suggested-by: Hector Marco-Gisbert <hecmargi@upv.es> Signed-off-by: Kees Cook <keescook@chromium.org> Signed-off-by: Borislav Petkov <bp@suse.de> Reviewed-by: Jason Gunthorpe <jgg@mellanox.com> Link: https://lkml.kernel.org/r/20200327064820.12602-4-keescook@chromium.org --- arch/x86/include/asm/elf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arch/x86/include/asm/elf.hb/arch/x86/include/asm/elf.h index 397a1c74433ec..452beed7892bb 100644 --- a/arch/x86/include/asm/elf.h +++ b/arch/x86/include/asm/elf.h @@ -287,7 +287,7 @@ extern u32 elf_hwcap2; * CPU: | lacks NX* | has NX, ia32 | has NX, x86_64 | * ELF: | | | | * ---------------------|------------|------------------|----------------| - * missing PT_GNU_STACK | exec-all | exec-all | exec-all | + * missing PT_GNU_STACK | exec-all | exec-all | exec-none | * PT_GNU_STACK == RWX | exec-stack | exec-stack | exec-stack | * PT_GNU_STACK == RW | exec-none | exec-none | exec-none | * @@ -303,7 +303,7 @@ extern u32 elf_hwcap2; * */ #define elf_read_implies_exec(ex, executable_stack) \ - (executable_stack == EXSTACK_DEFAULT) + (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT) struct task_struct; -- cgit 1.2.3-1.el7Esto estuvo presente por primera vez en la serie 5.8. Consulte también Permiso de ejecución inesperado de mmap cuando se incluyen archivos ensamblados en el proyecto .
Esto es solo una suposición : creo que el culpable es la personalidad READ_IMPLIES_EXEC que se configuraba automáticamente en ausencia de un segmento PT_GNU_STACK .
En la fuente del kernel 5.4 podemos encontrar este fragmento de código :
SET_PERSONALITY2(loc->elf_ex, &arch_state); if (elf_read_implies_exec(loc->elf_ex, executable_stack)) current->personality |= READ_IMPLIES_EXEC; Eso es lo único que puede transformar una sección RW en una RWX. Cualquier otro uso de PROC_EXEC no parecía haber cambiado o no era relevante para esta pregunta, para mí.
El executable_stack se establece aquí :
for (i = 0; i < loc->elf_ex.e_phnum; i++, elf_ppnt++) switch (elf_ppnt->p_type) { case PT_GNU_STACK: if (elf_ppnt->p_flags & PF_X) executable_stack = EXSTACK_ENABLE_X; else executable_stack = EXSTACK_DISABLE_X; break; Pero si el segmento PT_GNU_STACK no está presente, esa variable conserva su valor predeterminado :
int executable_stack = EXSTACK_DEFAULT; Ahora este flujo de trabajo es idéntico tanto en 5.4 como en la última fuente del kernel, lo que cambió es la definición de elf_read_implies_exec :
/* * An executable for which elf_read_implies_exec() returns TRUE will * have the READ_IMPLIES_EXEC personality flag set automatically. */ #define elf_read_implies_exec(ex, executable_stack) \ (executable_stack != EXSTACK_DISABLE_X) /* * An executable for which elf_read_implies_exec() returns TRUE will * have the READ_IMPLIES_EXEC personality flag set automatically. * * The decision process for determining the results are: * * CPU: | lacks NX* | has NX, ia32 | has NX, x86_64 | * ELF: | | | | * ---------------------|------------|------------------|----------------| * missing PT_GNU_STACK | exec-all | exec-all | exec-none | * PT_GNU_STACK == RWX | exec-stack | exec-stack | exec-stack | * PT_GNU_STACK == RW | exec-none | exec-none | exec-none | * * exec-all : all PROT_READ user mappings are executable, except when * backed by files on a noexec-filesystem. * exec-none : only PROT_EXEC user mappings are executable. * exec-stack: only the stack and PROT_EXEC user mappings are executable. * * *this column has no architectural effect: NX markings are ignored by * hardware, but may have behavioral effects when "wants X" collides with * "cannot be X" constraints in memory permission flags, as in * https://lkml.kernel.org/r/20190418055759.GA3155@mellanox.com * */ #define elf_read_implies_exec(ex, executable_stack) \ (mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT) Tenga en cuenta cómo en la versión 5.4, elf_read_implies_exec devolvía un valor verdadero si la pila no se marcaba explícitamente como no ejecutable (a través del segmento PT_GNU_STACK ).
En la fuente más reciente, la verificación ahora es más defensiva: elf_read_implies_exec es verdadero solo en el ejecutable de 32 bits, en el caso de que no se haya encontrado ningún segmento PT_GNU_STACK en el binario ELF.
Ensamblé su programa, lo vinculé y no encontré ningún segmento PT_GNU_STACK , por lo que esta puede ser la razón.
Si este es realmente el problema y si seguí el código correctamente, si configura la pila como no ejecutable en el binario, su sección de datos ya no debería ser mapeada como ejecutable (ni siquiera en Linux 5.4).