Comportamento padrão do Linux na seção `.data`
História
Caso 1
Eu acidentalmente escrevi meu código Assembly na .dataseção. Eu compilei e executei. O programa funcionou normalmente no Linux 5.4.0-53-generic, embora eu não tenha especificado um sinalizador como execstack.
Caso 2:
Depois disso, executei o programa no Linux 5.9.0-050900rc5-generic. O programa conseguiu SIGSEGV. Inspecionei a permissão da memória virtual lendo /proc/$pid/maps. Descobriu-se que a seção não é executável.
Acho que há uma configuração no Linux que gerencia essa permissão. Mas não sei onde encontrar.
Código
[Linux 5.4.0-53-genérico]
Executar (normal)
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$
Verifique a permissão (00400000-00402000 rwxp), para que seja executável.
## 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➤
[Linux 5.9.0-050900rc5-genérico]
Executar (Segfault)
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#
Verifique a permissão (00400000-00402000 rw-p), portanto, NÃO é executável.
## 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➤
objdump -p
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-
Questões
- Onde está a configuração no Linux que gerencia a permissão de seções ELF padrão?
- Minhas observações sobre as permissões estão corretas?
Resumo
- A permissão padrão para a
.dataseção no Linux5.4.0-53-genericé executável. - A permissão padrão para a
.dataseção no Linux NÃO5.9.0-050900rc5-genericé executável.
Respostas
Isso é apenas um palpite : acho que o culpado é a READ_IMPLIES_EXECpersonalidade que estava sendo definida automaticamente na ausência de um PT_GNU_STACKsegmento.
No código-fonte do kernel 5.4, podemos encontrar este trecho 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;
Essa é a única coisa que pode transformar uma seção RW em uma RWX. Qualquer outro uso de PROC_EXECnão pareceu ser alterado ou relevante para esta questão, para mim.
O executable_stacké definido aqui :
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;
Mas se o PT_GNU_STACKsegmento não estiver presente, essa variável retém seu valor padrão :
int executable_stack = EXSTACK_DEFAULT;
Agora, esse fluxo de trabalho é idêntico no 5.4 e na fonte do kernel mais recente, o que mudou foi a definição de elf_read_implies_exec:
Linux 5.4 :
/*
* 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)
Linux mais recente :
/*
* 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/[email protected]
*
*/
#define elf_read_implies_exec(ex, executable_stack) \
(mmap_is_ia32() && executable_stack == EXSTACK_DEFAULT)
Observe como na versão 5.4 o elf_read_implies_execretornou um valor verdadeiro se a pilha não foi explicitamente marcada como não executável (por meio do PT_GNU_STACKsegmento).
Na fonte mais recente, a verificação agora é mais defensiva: o elf_read_implies_execé verdadeiro apenas em executáveis de 32 bits, no caso em que nenhum PT_GNU_STACKsegmento foi encontrado no binário ELF.
Montei seu programa, vinculei-o e não encontrei nenhum PT_GNU_STACKsegmento, então esse pode ser o motivo.
Se este for realmente o problema e se eu segui o código corretamente, se você definir a pilha como não executável no binário, sua seção de dados não deve mais ser mapeada como executável (nem mesmo no Linux 5.4).
Seu binário está faltando PT_GNU_STACK. Como tal, esta mudança parece ter sido causada por commit9fccc5c0c99f238aa1b0460fccbdb30a887e7036 :
From 9fccc5c0c99f238aa1b0460fccbdb30a887e7036 Mon Sep 17 00:00:00 2001
From: Kees Cook <[email protected]>
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 <[email protected]>
Signed-off-by: Kees Cook <[email protected]>
Signed-off-by: Borislav Petkov <[email protected]>
Reviewed-by: Jason Gunthorpe <[email protected]>
Link: https://lkml.kernel.org/r/[email protected]
---
arch/x86/include/asm/elf.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/include/asm/elf.h b/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.el7
Isso estava presente pela primeira vez na série 5.8. Consulte também Permissão exec inesperada do mmap quando os arquivos de montagem forem incluídos no projeto .