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
Linux has an execution domain called
READ_IMPLIES_EXEC, which causes all pages allocated withPROT_READto also be givenPROT_EXEC. Older Linux kernels used to use this for executables that used the equivalent ofgcc -z execstack. This program will show you whether that's enabled for itself:If you compile that along with an empty
.sfile, 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. Doreadelf -Wl example. You'll see this line when you compiled without the empty.sfile:But this one when you compiled with it:
Note
RWEinstead of justRW. 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):The default section permissions don't include e
xec. See the ELF part of the.sectiondocumentation for the meaning of the "flags" and @attributes.(And don't forget to switch to another section like
.textor.dataafter that.sectiondirective, if your.swas relying on.textbecause the default section at the top of the file.)Put that line in
example.s(and every other.sfile in your project). The presence of that.note.GNU-stacksection will serve to tell the linker that this object file doesn't depend on an executable stack, so the linker will useRWinstead ofRWEon theGNU_STACKmetadata, and your program will then work as expected.Similarly for NASM, a
sectiondirective 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_EXECanymore. At most (with an RWEGNU_STACKadded byld), 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.dataon x86-64 Linux 5.4)exec-all(READ_IMPLIES_EXEC) only happens for legacy 32-bit executables where the linker didn't add aGNU_STACKheader entry at all. But as shown here, modernldalways adds that with one setting or the other, even when an input.ofile is missing a note.You should still use this
.notesection to signal non-executable stacks in normal programs. But if you were hoping to test self-modifying code in.dataor following some old tutorial for testing shellcode, that's not an option on modern kernels.