102

I am banging my head into the wall with this.

In my project, when I'm allocating memory with mmap the mapping (/proc/self/maps) shows that it is an readable and executable region despite I requested only readable memory.

After looking into strace (which was looking good) and other debugging, I was able to identify the only thing that seems to avoid this strange problem: removing assembly files from the project and leaving only pure C. (what?!)

So here is my strange example, I am working on Ubunbtu 19.04 and default gcc.

If you compile the target executable with the ASM file (which is empty) then mmap returns a readable and executable region, if you build without then it behave correctly. See the output of /proc/self/maps which I have embedded in my example.

example.c

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main()
{
    void* p;
    p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);

    {
        FILE *f;
        char line[512], s_search[17];
        snprintf(s_search,16,"%lx",(long)p);
        f = fopen("/proc/self/maps","r");
        while (fgets(line,512,f))
        {
            if (strstr(line,s_search)) fputs(line,stderr);
        }

        fclose(f);
    }

    return 0;
}

example.s: Is an empty file!

Outputs

With the ASM included version

VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example
7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0 

Without the ASM included version

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Ben Hirschberg
  • 1,410
  • 1
  • 12
  • 17

2 Answers2

98

Linux has an execution domain called READ_IMPLIES_EXEC, which causes all pages allocated with PROT_READ to also be given PROT_EXEC. Older Linux kernels used to use this for executables that used the equivalent of gcc -z execstack. This program will show you whether that's enabled for itself:

#include <stdio.h>
#include <sys/personality.h>

int main(void) {
    printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
    return 0;
}

If you compile that along with an empty .s file, you'll see that it's enabled, but without one, it'll be disabled. The initial value of this comes from the ELF meta-information in your binary. Do readelf -Wl example. You'll see this line when you compiled without the empty .s file:

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

But this one when you compiled with it:

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

Note RWE instead of just RW. The reason for this is that the linker assumes that your assembly files require read-implies-exec unless it's explicitly told that they don't, and if any part of your program requires read-implies-exec, then it's enabled for your whole program. The assembly files that GCC compiles tell it that it doesn't need this, with this line (you'll see this if you compile with -S):

    .section        .note.GNU-stack,"",@progbits

The default section permissions don't include exec. See the ELF part of the .section documentation for the meaning of the "flags" and @attributes.

(And don't forget to switch to another section like .text or .data after that .section directive, if your .s was relying on .text because the default section at the top of the file.)

Put that line in example.s (and every other .s file in your project). The presence of that .note.GNU-stack section will serve to tell the linker that this object file doesn't depend on an executable stack, so the linker will use RW instead of RWE on the GNU_STACK metadata, and your program will then work as expected.

Similarly for NASM, a section directive with the right flags specifies non-executable stacks.


Modern Linux kernels between 5.4 and 5.8 changed the behaviour of the ELF program-loader. For x86-64, nothing turns on READ_IMPLIES_EXEC anymore. At most (with an RWE GNU_STACK added by ld), you'll get the stack itself being executable, not every readable page. (This answer covers the last change, in 5.8, but there must have been other changes before that, since that question shows successful execution of code in .data on x86-64 Linux 5.4)

exec-all (READ_IMPLIES_EXEC) only happens for legacy 32-bit executables where the linker didn't add a GNU_STACK header entry at all. But as shown here, modern ld always adds that with one setting or the other, even when an input .o file is missing a note.

You should still use this .note section to signal non-executable stacks in normal programs. But if you were hoping to test self-modifying code in .data or following some old tutorial for testing shellcode, that's not an option on modern kernels.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 16
    Holy crap, that's a weird default. I guess the toolchain existed before noexec, and making noexec the default could have broken things. Now I'm curious how other assemblers like NASM / YASM create `.o` files! But anyway, I guess this is the mechanism that `gcc -zexecstack` uses, and why it makes not just the stack but everything executable. – Peter Cordes Oct 06 '19 at 20:03
  • 24
    @Peter - That's why projects like Botan, Crypto++ and OpenSSL, which use the assembler, add `-Wa,--noexecstack`. I think it is a very nasty sharp edge. Silent loss of nx-stacks should be a security vulnerability. The Binutil folks should fix it. – jww Oct 07 '19 at 04:58
  • 15
    @jww It is indeed a security problem, strange that no one reported it before – Ben Hirschberg Oct 07 '19 at 05:05
  • 5
    +1, but this answer would be much better if the meaning/logic of the line `.note.GNU-stack,"",@progbits` was explained - right now it's opaque, equivalent to "this magic string of characters causes this effect", but the string clearly looks like it has some sort of semantics. – mtraceur Oct 08 '19 at 20:44
  • This seems to have been fixed by a recent Linux kernel change: the default with no .note.GNU-stack section is exec-none, instead of exec-all [Linux default behavior against \`.data\` section](https://stackoverflow.com/q/64833715), and even `gcc -zexecstack` (RWX) is only truly exec-stack, not exec-all. – Peter Cordes Nov 17 '20 at 15:56
  • @PeterCordes No .note.GNU-stack section in the assembly file doesn't mean there's no GNU_STACK in the resulting binary. GCC still adds one that requests an executable stack in that case, so the only effect of the kernel change on this program is to go from exec-all to exec-stack, not to exec-none like it is without the assembly file. – Joseph Sible-Reinstate Monica Nov 17 '20 at 16:13
  • @PeterCordes Is the part of your latest edit to this that says "used to" actually correct? In particular, doesn't `-z execstack` just change `PT_GNU_STACK` from `RW` to `RWX`, and doesn't the change you linked to only affect the case when `PT_GNU_STACK` was completely missing? – Joseph Sible-Reinstate Monica Oct 08 '21 at 03:52
  • @JosephSible-ReinstateMonica: The change you found and quoted in [Linux default behavior of executable .data section changed between 5.4 and 5.9?](https://stackoverflow.com/q/64833715) must not have been the only change. `ld` for *years* has included an RWX `PT_GNU_STACK` when there's no `.note`, for that questions 5.4 testcase. And to double check, I built a shellcode-test with global `char code[] = {...};`. Building on my current system with `gcc -z execstack`, and checked that it has a GNU_STACK RWE note. It faults of course. But on an old Linux 4.5.0 system, same binary runs. – Peter Cordes Oct 08 '21 at 04:11
  • In general, yeah I was sure I remembered that `gcc -z execstack` used to make .data (and everything else) executable, and it works by setting GNU_STACK to RWE, not omitting it. So the test result wasn't a surprise. – Peter Cordes Oct 08 '21 at 04:16
36

As an alternative to modifying your assembly files with GNU-specific section directive variants, you can add -Wa,--noexecstack to your command line for building assembly files. For example, see how I do it in musl's configure:

https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a

I believe at least some versions of clang with integrated-assembler may require it to be passed as --noexecstack (without the -Wa), so your configure script should probably check both and see which is accepted.

You can also use -Wl,-z,noexecstack at link time (in LDFLAGS) to get the same result. The disadvantage of this is that it doesn't help if your project produces static (.a) library files for use by other software, since you then don't control the link-time options when it's used by other programs.

R.. GitHub STOP HELPING ICE
  • 208,859
  • 35
  • 376
  • 711
  • 4
    Hmm... I didn't know that you were Rich Felker before reading this post. Why isn't your display name dalias? – S.S. Anne Oct 08 '19 at 20:35