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
How to Merge Two Files Using Awk
How to Get the Total Cpu Usage of an Application from /Proc/Pid/Stat
Sorting Multiple Keys With Unix Sort
How to Change 'Rpath' in an Already Compiled Binary
How to Kill All Processes With a Given Partial Name
Why Data and Stack Segments Are Executable
How to Only Get File Name With Linux 'Find'
How to Run a Shell Script on a Unix Console or MAC Terminal
Round a Divided Number in Bash
How to Invoke a System Call Via Syscall or Sysenter in Inline Assembly
How Many Socket Connections Possible
How to Get Docker Linux Container Information from Within the Container Itself
How to Run a Perl Script as a System Daemon in Linux
How to Replace a String in Multiple Files in Linux Command Line
Difference Between "#!/Usr/Bin/Env Bash" and "#!/Usr/Bin/Bash"
Make $Java_Home Easily Changable in Ubuntu
How to Find All Serial Devices (Ttys, Ttyusb, ..) on Linux Without Opening Them