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.
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.