Linux Default Behavior of Executable .Data Section Changed Between 5.4 and 5.9

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

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/20190418055759.GA3155@mellanox.com
*
*/
#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).

Assembly section .code and .text behave differently

On Linux with the standard toolchain (GNU Binutils ld), .text is a "special" section name that gets special treatment (exec permission by default), but .code isn't. (Other special sections include .data (writeable) and .bss (writable nobits), and all with a default alignment > 1.)

section .text is the NASM ELF/Linux equivalent of Windows MASM .code directive, but that does not mean that Linux tools recognize a .code directive or section name1.

section .code is no different from section xyz123; it just uses the defaults which are noexec nowrite. See the other entry at the bottom of the table in the NASM docs.

Use readelf -a hello to see the section (linking) and segment (program-loader) attributes, with a distinct lack of an X anywhere.

Footnote 1: In fact, I think Windows executables still use the actual section name .text. At least GNU objdump -d still says the code is in the .text section.
So the MASM .code directive is a shortcut for switching to the .text section.


Fun fact: this does happen to run correctly "by accident" if you build it as 32-bit code (which you should because it's using only 32-bit int 0x80 system calls), like in this case that used section .code when incorrectly porting from 16-bit MASM code to Linux NASM.

Or if you'd run your 64-bit code on an older kernel.

The reason is that without explicitly specifying a PT_GNU_STACK note, the kernel uses backwards-compat assumptions for 32-bit executables and uses READ_IMPLIES_EXEC which affects every single page: Linux default behavior of executable .data section changed between 5.4 and 5.9?. Older kernels do this even for 64-bit executables, newer kernels only make the stack itself executable in this case.

Adding section .note.GNU-stack noalloc noexec nowrite progbits to your source makes it segfault as it should, even when build into a 32-bit executable. (nasm -felf32 / ld -melf_i386 -o foo foo.o). See this answer.

See also Unexpected exec permission from mmap when assembly files included in the project about the old situation.

Executable becomes invalid as data section exceeds 1 page

Obviously the RVA (virtual address) of the BSS segment is wrong (0x5000, must be 0x4000)!

define code section

You can make your section directive state explicitly that the section's contents are executable:

section .stage1_main exec

and you might also want to insist on 16-byte alignment (which nasm will do automatically for the .text section):

section .stage1_main exec align=16



Related Topics



Leave a reply



Submit