0

Inspired by this minimal ELF executable I created the following executable which works (and returns 42):

00000000: 7f45 4c46 0101 0100 0000 0000 0000 0000  .ELF............
00000010: 0200 0300 0100 0000 5400 0008 3400 0000  ........T...4...
00000020: 0000 0000 0000 0000 3400 2000 0100 0000  ........4. .....
00000030: 0000 0000 0100 0000 0000 0000 0000 0008  ................
00000040: 0000 0008 6000 0000 6000 0000 0500 0000  ....`...`.......
00000050: 0000 0000 b801 0000 00bb 2a00 0000 cd80  ..........*.....

There is an ELF header here from 00 to 33, a single program header from 34 to 53 and some code from 54 to 5F.

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x8000054
  Start of program headers:          52 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x08000000 0x08000000 0x00060 0x00060 R E 0

If I now go ahead and change the p_vaddr and p_paddr from 0x8000000 to 0x1000 (or anything below 0x10000) and adjust the entry point accordingly, I get an unexpected SEGFAULT. Here is the file after the adjustments:

00000000: 7f45 4c46 0101 0100 0000 0000 0000 0000  .ELF............
00000010: 0200 0300 0100 0000 5410 0000 3400 0000  ........T...4...
00000020: 0000 0000 0000 0000 3400 2000 0100 0000  ........4. .....
00000030: 0000 0000 0100 0000 0000 0000 0010 0000  ................
00000040: 0010 0000 6000 0000 6000 0000 0500 0000  ....`...`.......
00000050: 0000 0000 b801 0000 00bb 2a00 0000 cd80  ..........*.....
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x1054
  Start of program headers:          52 (bytes into file)
  Start of section headers:          0 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         1
  Size of section headers:           0 (bytes)
  Number of section headers:         0
  Section header string table index: 0

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x000000 0x00001000 0x00001000 0x00060 0x00060 R E 0

This is surprising to me since GCC produces working executables with the following program header:

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00000034 0x00000034 0x00180 0x00180 R   0x4
  INTERP         0x0001b4 0x000001b4 0x000001b4 0x00013 0x00013 R   0x1
      [Requesting program interpreter: /lib/ld-linux.so.2]
  LOAD           0x000000 0x00000000 0x00000000 0x00418 0x00418 R   0x1000
  LOAD           0x001000 0x00001000 0x00001000 0x00524 0x00524 R E 0x1000
  LOAD           0x002000 0x00002000 0x00002000 0x00228 0x00228 R   0x1000
  LOAD           0x002ed4 0x00003ed4 0x00003ed4 0x00134 0x00138 RW  0x1000
  DYNAMIC        0x002edc 0x00003edc 0x00003edc 0x000f8 0x000f8 RW  0x4
  NOTE           0x0001c8 0x000001c8 0x000001c8 0x00060 0x00060 R   0x4
  GNU_PROPERTY   0x0001ec 0x000001ec 0x000001ec 0x0001c 0x0001c R   0x4
  GNU_EH_FRAME   0x002008 0x00002008 0x00002008 0x00074 0x00074 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x10
  GNU_RELRO      0x002ed4 0x00003ed4 0x00003ed4 0x0012c 0x0012c R   0x1

Can someone explain to me why changing the p_vaddr makes my program SEGFAULT?

eyelash
  • 3,197
  • 25
  • 33

1 Answers1

1

This is surprising to me since GCC produces working executables with the following program header

Look at the output from readelf -h a.out for the executable produced by GCC.
You'll discover that its type is ET_DYN, not ET_EXEC (your binary is ET_EXEC) -- your GCC is (apparently) configured to produce PIE executables by default.

Unlike ET_DYN, ET_EXEC binary must be loaded at the linked-at address (zero in your case), and the kernel does not allow for anything to be loaded there.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Can you explain what the limitation is exactly? Why does loading at `0x10000` work but at `0x1000` doesn't? Does this depend on the kernel or the hardware? How do I pick the right number here? – eyelash Aug 28 '22 at 10:28
  • @eyelash Loading at address `0x1000` should work (I think). It's hard to answer without a http://stackoverflow.com/help/mcve, but you can start in `fs/binfmt_elf.c` to understand what restrictions the kernel places on the various addresses. – Employed Russian Aug 28 '22 at 17:48
  • 1
    I have changed the example to be compatible with `xxd`. You should now be able to turn them back into executables with `xxd -r`. – eyelash Aug 28 '22 at 18:31