.rodata section loaded in executable page
Asked Answered
D

1

10

So out of curiosity, I tried to run this code today (compiled with gcc -m32 1.c):

int main(void)
{
    // EB is the opcode for jmp rel/8
    // FE is hex for -2
    // So this is essentially an infinite loop

    ((void(*)(void))"\xEB\xFE")();
}

... and it worked! No segfaults, the program (correctly?) goes into an infinite loop. Looking at the disassembly (objdump -d a.out), you can see the call to... whatever is at address 0x8048480:

080483d6 <main>:
 ....
 80483e7:   b8 80 84 04 08          mov    $0x8048480,%eax
 80483ec:   ff d0                   call   *%eax
 ....

objdump -s -j .rodata a.out gives:

Contents of section .rodata:
 8048478 03000000 01000200 ebfe00             ...........
                           ~~~~  

So it is indeed executing the string, which is stored in the .rodata section. So I ran readelf --sections a.out and got:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000040 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804820c 00020c 000045 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          08048252 000252 000008 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804825c 00025c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             0804827c 00027c 000008 08   A  5   0  4
  [10] .rel.plt          REL             08048284 000284 000008 08  AI  5  23  4
  [11] .init             PROGBITS        0804828c 00028c 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080482b0 0002b0 000020 04  AX  0   0 16
  [13] .plt.got          PROGBITS        080482d0 0002d0 000008 00  AX  0   0  8
  [14] .text             PROGBITS        080482e0 0002e0 000182 00  AX  0   0 16
  [15] .fini             PROGBITS        08048464 000464 000014 00  AX  0   0  4
  [16] .rodata           PROGBITS        08048478 000478 00000b 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        08048484 000484 000034 00   A  0   0  4
  [18] .eh_frame         PROGBITS        080484b8 0004b8 0000e0 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      08049f0c 000f0c 000004 04  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f10 000f10 000004 04  WA  0   0  4
  [21] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [22] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [23] .got.plt          PROGBITS        0804a000 001000 000010 04  WA  0   0  4
  [24] .data             PROGBITS        0804a010 001010 000008 00  WA  0   0  4
  [25] .bss              NOBITS          0804a018 001018 000004 00  WA  0   0  1
  [26] .comment          PROGBITS        00000000 001018 00001a 01  MS  0   0  1
  [27] .symtab           SYMTAB          00000000 001034 0003f0 10     28  45  4
  [28] .strtab           STRTAB          00000000 001424 0001bd 00      0   0  1
  [29] .shstrtab         STRTAB          00000000 0015e1 000105 00      0   0  1

So in the ELF binary, the section is marked non-executable. But in memory, the page is executable (cat /proc/xxx/maps):

08048000-08049000 r-xp 00000000 08:01 663551 /home/andrew/Desktop/a.out
08049000-0804a000 r--p 00000000 08:01 663551 /home/andrew/Desktop/a.out
0804a000-0804b000 rw-p 00001000 08:01 663551 /home/andrew/Desktop/a.out

My original guess was that the sections too closely-spaced (there are both AX and A sections in the 08048000-08049000 range), so Linux is forced to give the page the union of the ELF permission bits (AX | A == AX). However, even after increasing the size of the .rodata section (by adding many long strings), all of the pages containing the .rodata section are still executable. Why is this?

(For the record, I'm running on Linux kernel 4.11.7, GCC 7.1.1, and compiling as 64-bit still exhibits this behavior)

Dooryard answered 6/7, 2017 at 2:13 Comment(1)
This may be a duplicate, but upvoting anyway for asking a clear question and showing your work so we know what you already figured out. Old questions could probably be marked as duplicates of this, since has a nice canonical answer (made possible by the well-written question).Bullis
O
9

My original guess was that the segments too closely-spaced

You should not call sections segments (ELF has both, and they mean different things).

Sections only matter at static link time, and can be completely removed (are not needed at runtime). Only segments matter at runtime, and a typical ELF binary will have two segments with R-X and RW- permissions.

The .rodata section is usually merged with .text section and put into the executable segment. You can change that with the --rosegment flag if you use gold linker (patch which introduced this).

You can see section to segment mapping in the readelf -Wl a.out output.

Update:

Can there ever be a situation where .rodata needs to be executable, or is it for optimization, or something else?

There are no portable situations where .rodata needs to be executable. It is possible to construct a non-portable program that requires it, as you've done in your question.

Merging of .rodata and .text is an optimization: it requires two mmap calls instead of three (a program linked with --rosegment will have three separate PT_LOAD segments with R-X, R-- and R-W protections) and also fragments the virtual space less. In addition, on Linux there is a system-wide limit on total mappings, so you'll reduce the total number of programs you can run at once by 50% if you link everything with --rosegment.

Update 2:

Recent Linux distributions stopped merging .text and .rodata, and now have three or four separate LOAD segments. See this answer.

Outlast answered 6/7, 2017 at 2:27 Comment(3)
Interesting! Thank you, I was not aware that section and segment referred to different things. Is there a reason why .rodata is merged into a r-x segment? Can there ever be a situation where .rodata needs to be executable, or is it for optimization, or something else?Dooryard
And I though my student is cheating when they said that their function is .rodata just works. TIL.Pinard
Note: This answer is specific to Linux user space (as tags suggest). For bare metal systems without a loader nor XN bits it might make sense to merge them. Merging may do little if there is no compiler support for PC relative pointers to constant data. This is technically possible with ISAs but would take compiler machinery that is not used frequently. It might be useful for shared libraries such as 'static shared libraries'.Sabbatical

© 2022 - 2024 — McMap. All rights reserved.