For example, say you have this:
const char* const errors[] = { "error 1", "error 2", /* ... */ };
Now, you'd expect all to be read-only, but with Position Independent Code (PIC) and Position Independent Executables (PIE), which are prerequisite for Address Space Layout Randomization (ASLR), it's not, because that errors array contains pointers to those literal strings, which means those pointers need to be "relocated": the executable or library can't contain the actual pointer to those, since their addess may vary between executions. So the executable/library only contains offsets, and the dynamic linker adjusts those offsets to make them real pointers. Thus that errors array is actually not read-only.
There is an additional ELF segment for those read-only-but-really-write-because-relocated data, GNU_RELRO, which tells the dynamic linker to remove the write bit on those parts of the read-write section where relocations happened.
Amusingly enough, I ran into some issues because of this; e.g. valgrind's symbolication was failing on LLD-linked binaries unless I used --no-rosegment. I didn't dig into it too much, but it's probably making some bad assumptions about the text section's load address. (LLD places the read-executable segment after the read-only segment, and I think valgrind was assuming that the text section would be part of the first segment.)
Platform support is irrelevant, systems not supporting the access flags simply gives you more access. E.g. running a modern Linux on pre-Athlon 64 CPUs will just cause all readable pages to be executable as well.
It would be interesting to know if you can ask the linux linker to put rodata in its own r-- segment -- I scanned the docs but didn't see an option for it.
Depends on what you mean, the original page table entries have a R (Read/Write) flag, if not set the page is read-only. What you couldn't do was mark a page non-executable. But nevertheless the second LOAD segment is marked rw- and not rwx, so it would seem that it wasn't deemed a problem in the past having segments with unsupported permissions.
At the time when we got the NX bit it did happen that some programs broke because they expected executable data, but the security benefits were more important.
> It would be interesting to know if you can ask the linux linker to put rodata in its own r-- segment -- I scanned the docs but didn't see an option for it.
You have to write your own linker script, see e.g. [0].
[0] http://www.cl.cam.ac.uk/~srk31/blog/devel/custom-elf-phdrs.h...
It's just not the default, for historical reasons.
What is used at runtime is the program headers named LOAD, which specifies the segments. Here is an example:
LOAD off 0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**21
filesz 0x000000000000069c memsz 0x000000000000069c flags r-x
LOAD off 0x0000000000000e10 vaddr 0x0000000000600e10 paddr 0x0000000000600e10 align 2**21
filesz 0x0000000000000220 memsz 0x0000000000000650 flags rw-
... Idx Name Size VMA LMA File off Algn
25 .bss 00000420 0000000000601040 0000000000601040 00001030 2**5
ALLOC
So note that .bss is placed at the end of the second segment (.bss at 0x601040 + 0x420 = 0x601460,
and the second segments ends in memory at 0x600e10 + 0x650 which is also 0x601460).
But note also that the file size is only 0x220, which places the end of the data mapped from the
file at 0x600e10 + 0x220 = 0x601030 which is slightly before .bss starts. So what happens is
that the information about .bss is actually described by setting the memory size of the LOAD
segment bigger than the file size, the dynamic linker will then fill the rest with zeroes.[0] http://www.muppetlabs.com/~breadbox/software/elfkickers.html
My point was essentially that for ELF this is not some kind of kludgeish special case for .bdd, but general feature that any segment can be zero extended to arbitrary size larger than what is contained in the executable image (although on sane platforms it is not useful for anything but .bss)