Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux default behavior of executable .data section changed between 5.4 and 5.9?

Story

Case 1

I accidentally wrote my Assembly code in the .data section. I compiled it and executed it. The program ran normally under Linux 5.4.0-53-generic even though I didn't specify a flag like execstack.

Case 2:

After that, I executed the program under Linux 5.9.0-050900rc5-generic. The program got SIGSEGV. I inspected the virtual memory permission by reading /proc/$pid/maps. It turned out that the section is not executable.

I think there is a configuration on Linux that manages that permission. But I don't know where to find.

Code

[Linux 5.4.0-53-generic]

Run (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$ 

Check Permission (00400000-00402000 rwxp), so it is executable.

## 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-generic]

Run (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# 

Check Permission (00400000-00402000 rw-p), so it is NOT executable.

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

Questions

  1. Where is the configuration on Linux that manages default ELF sections permission?
  2. Are my observations on permissions correct?

Summary

  • Default permission for .data section on Linux 5.4.0-53-generic is executable.
  • Default permission for .data section on Linux 5.9.0-050900rc5-generic is NOT executable.
like image 555
Ammar Faizi Avatar asked Nov 14 '20 12:11

Ammar Faizi


1 Answers

This is only a guess: I think the culprit is the READ_IMPLIES_EXEC personality that was being set automatically in the absence of a PT_GNU_STACK segment.

In the 5.4 kernel source we can find this piece of code:

SET_PERSONALITY2(loc->elf_ex, &arch_state);
if (elf_read_implies_exec(loc->elf_ex, executable_stack))
    current->personality |= READ_IMPLIES_EXEC;

That's the only thing that can transform an RW section into an RWX one. Any other use of PROC_EXEC didn't seem to be changed or relevant to this question, to me.

The executable_stack is set here:

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;

But if the PT_GNU_STACK segment is not present, that variable retains its default value:

int executable_stack = EXSTACK_DEFAULT;

Now this workflow is identical in both 5.4 and the latest kernel source, what changed is the definition of 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)

Latest Linux:

/*
 * 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)

Note how in the 5.4 version the elf_read_implies_exec returned a true value if the stack was not explicitly marked as not executable (via the PT_GNU_STACK segment).

In the latest source, the check is now more defensive: the elf_read_implies_exec is true only on 32-bit executable, in the case where no PT_GNU_STACK segment was found in the ELF binary.

I assembled your program, linked it, and found no PT_GNU_STACK segment, so this may be the reason.
If this is indeed the issue and if I followed the code correctly, if you set the stack as not executable in the binary, its data section should not be mapped executable anymore (not even on Linux 5.4).

like image 125
Margaret Bloom Avatar answered Sep 29 '22 12:09

Margaret Bloom