Granularidade de proteção de memória forçada (x86-64)

1

Eu estava experimentando como o Linux aloca e protege a memória.

Para algumas das minhas experiências, criei um pequeno programa em c:

#include <stdio.h>

int gv=10;

int main(){

  char *v=(char*)0x601000;//0x601030
  printf("gv=%p\n", &gv);
  scanf("%s", v);
  printf("You gave=%s\n", v);

}

Após a compilação (relo parcial), o readelf -t a.out retorna:

There are 30 section headers, starting at offset 0x1a18:

Section Headers:
 [Nr] Name
      Type              Address          Offset            Link
      Size              EntSize          Info              Align
      Flags
 [ 0]
      NULL                   NULL             0000000000000000  0000000000000000  0
      0000000000000000 0000000000000000  0                 0
      [0000000000000000]:
 [ 1] .interp
      PROGBITS               PROGBITS         0000000000400238  0000000000000238  0
      000000000000001c 0000000000000000  0                 1
      [0000000000000002]: ALLOC
 [ 2] .note.ABI-tag
      NOTE                   NOTE             0000000000400254  0000000000000254  0
      0000000000000020 0000000000000000  0                 4
      [0000000000000002]: ALLOC
 [ 3] .note.gnu.build-id
      NOTE                   NOTE             0000000000400274  0000000000000274  0
      0000000000000024 0000000000000000  0                 4
      [0000000000000002]: ALLOC
 [ 4] .gnu.hash
      GNU_HASH               GNU_HASH         0000000000400298  0000000000000298  5
      000000000000001c 0000000000000000  0                 8
      [0000000000000002]: ALLOC
 [ 5] .dynsym
      DYNSYM                 DYNSYM           00000000004002b8  00000000000002b8  6
      0000000000000078 0000000000000018  1                 8
      [0000000000000002]: ALLOC
 [ 6] .dynstr
      STRTAB                 STRTAB           0000000000400330  0000000000000330  0
      0000000000000058 0000000000000000  0                 1
      [0000000000000002]: ALLOC
 [ 7] .gnu.version
      VERSYM                 VERSYM           0000000000400388  0000000000000388  5
      000000000000000a 0000000000000002  0                 2
      [0000000000000002]: ALLOC
 [ 8] .gnu.version_r
      VERNEED                VERNEED          0000000000400398  0000000000000398  6
      0000000000000030 0000000000000000  1                 8
      [0000000000000002]: ALLOC
 [ 9] .rela.dyn
      RELA                   RELA             00000000004003c8  00000000000003c8  5
      0000000000000018 0000000000000018  0                 8
      [0000000000000002]: ALLOC
 [10] .rela.plt
      RELA                   RELA             00000000004003e0  00000000000003e0  5
      0000000000000060 0000000000000018  12                8
      [0000000000000042]: ALLOC, INFO LINK
 [11] .init
      PROGBITS               PROGBITS         0000000000400440  0000000000000440  0
      000000000000001a 0000000000000000  0                 4
      [0000000000000006]: ALLOC, EXEC
 [12] .plt
      PROGBITS               PROGBITS         0000000000400460  0000000000000460  0
      0000000000000050 0000000000000010  0                 16
      [0000000000000006]: ALLOC, EXEC
 [13] .text
      PROGBITS               PROGBITS         00000000004004b0  00000000000004b0  0
      00000000000001b2 0000000000000000  0                 16
      [0000000000000006]: ALLOC, EXEC
 [14] .fini
      PROGBITS               PROGBITS         0000000000400664  0000000000000664  0
      0000000000000009 0000000000000000  0                 4
      [0000000000000006]: ALLOC, EXEC
 [15] .rodata
      PROGBITS               PROGBITS         0000000000400670  0000000000000670  0
      0000000000000029 0000000000000000  0                 8
      [0000000000000002]: ALLOC
 [16] .eh_frame_hdr
      PROGBITS               PROGBITS         000000000040069c  000000000000069c  0
      0000000000000034 0000000000000000  0                 4
      [0000000000000002]: ALLOC
 [17] .eh_frame
      PROGBITS               PROGBITS         00000000004006d0  00000000000006d0  0
      00000000000000f4 0000000000000000  0                 8
      [0000000000000002]: ALLOC
 [18] .init_array
      INIT_ARRAY             INIT_ARRAY       0000000000600e10  0000000000000e10  0
      0000000000000008 0000000000000000  0                 8
      [0000000000000003]: WRITE, ALLOC
 [19] .fini_array
      FINI_ARRAY             FINI_ARRAY       0000000000600e18  0000000000000e18  0
      0000000000000008 0000000000000000  0                 8
      [0000000000000003]: WRITE, ALLOC
 [20] .jcr
      PROGBITS               PROGBITS         0000000000600e20  0000000000000e20  0
      0000000000000008 0000000000000000  0                 8
      [0000000000000003]: WRITE, ALLOC
 [21] .dynamic
      DYNAMIC                DYNAMIC          0000000000600e28  0000000000000e28  6
      00000000000001d0 0000000000000010  0                 8
      [0000000000000003]: WRITE, ALLOC
 [22] .got
      PROGBITS               PROGBITS         0000000000600ff8  0000000000000ff8  0
      0000000000000008 0000000000000008  0                 8
      [0000000000000003]: WRITE, ALLOC
 [23] .got.plt
      PROGBITS               PROGBITS         0000000000601000  0000000000001000  0
      0000000000000038 0000000000000008  0                 8
      [0000000000000003]: WRITE, ALLOC
 [24] .data
      PROGBITS               PROGBITS         0000000000601038  0000000000001038  0
      0000000000000008 0000000000000000  0                 4
      [0000000000000003]: WRITE, ALLOC
 [25] .bss
      NOBITS                 NOBITS           0000000000601040  0000000000001040  0
      0000000000000008 0000000000000000  0                 1
      [0000000000000003]: WRITE, ALLOC
 [26] .comment
      PROGBITS               PROGBITS         0000000000000000  0000000000001040  0
      000000000000002d 0000000000000001  0                 1
      [0000000000000030]: MERGE, STRINGS
 [27] .shstrtab
      STRTAB                 STRTAB           0000000000000000  000000000000106d  0
      0000000000000108 0000000000000000  0                 1
      [0000000000000000]:
 [28] .symtab
      SYMTAB                 SYMTAB           0000000000000000  0000000000001178  29
      0000000000000648 0000000000000018  45                8
      [0000000000000000]:
 [29] .strtab
      STRTAB                 STRTAB           0000000000000000  00000000000017c0  0
      0000000000000255 0000000000000000  0                 1
      [0000000000000000]:

e o readelf -l a.out retorna:

Elf file type is EXEC (Executable file)
Entry point 0x4004b0
There are 9 program headers, starting at offset 64

Program Headers:
 Type           Offset             VirtAddr           PhysAddr
                FileSiz            MemSiz              Flags  Align
 PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                0x00000000000001f8 0x00000000000001f8  R E    8
 INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                0x000000000000001c 0x000000000000001c  R      1
     [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
 LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                0x00000000000007c4 0x00000000000007c4  R E    200000
 LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                0x0000000000000230 0x0000000000000238  RW     200000
 DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                0x00000000000001d0 0x00000000000001d0  RW     8
 NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                0x0000000000000044 0x0000000000000044  R      4
 GNU_EH_FRAME   0x000000000000069c 0x000000000040069c 0x000000000040069c
                0x0000000000000034 0x0000000000000034  R      4
 GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                0x0000000000000000 0x0000000000000000  RW     10
 GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                0x00000000000001f0 0x00000000000001f0  R      1

Section to Segment mapping:
 Segment Sections...
  00
  01     .interp
  02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
  03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
  04     .dynamic
  05     .note.ABI-tag .note.gnu.build-id
  06     .eh_frame_hdr
  07
  08     .init_array .fini_array .jcr .dynamic .got

Em uma instância do programa quando executado pelo gdb, / proc / self / maps retornou:

00400000-00401000 r-xp 00000000 00:30f 2262070116                        .../a.out
00600000-00601000 r--p 00000000 00:30f 2262070116                        .../a.out
00601000-00602000 rw-p 00001000 00:30f 2262070116                        .../a.out
7ffff7a18000-7ffff7bd0000 r-xp 00000000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7bd0000-7ffff7dd0000 ---p 001b8000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7dd0000-7ffff7dd4000 r--p 001b8000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7dd4000-7ffff7dd6000 rw-p 001bc000 fd:00 137613                     /usr/lib64/libc-2.17.so
7ffff7dd6000-7ffff7ddb000 rw-p 00000000 00:00 0
7ffff7ddb000-7ffff7dfc000 r-xp 00000000 fd:00 137605                     /usr/lib64/ld-2.17.so
7ffff7fbd000-7ffff7fc0000 rw-p 00000000 00:00 0
7ffff7ff7000-7ffff7ffa000 rw-p 00000000 00:00 0
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0                          [vdso]
7ffff7ffc000-7ffff7ffd000 r--p 00021000 fd:00 137605                     /usr/lib64/ld-2.17.so
7ffff7ffd000-7ffff7ffe000 rw-p 00022000 fd:00 137605                     /usr/lib64/ld-2.17.so
7ffff7ffe000-7ffff7fff000 rw-p 00000000 00:00 0
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0                          [stack]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Tendo isso em mente, o que eu esperaria é que a região de memória entre 0x601000-0x602000 fosse gravável. Mas quando executo o programa com 24 caracteres ou mais, com v = 0x601000 ele falhou.

Quando alterei o ponteiro v para 0x6010 20 , o programa travou depois que eu digitei os caracteres 0x1000- 0x20 .

Como esse comportamento é explicado. A proteção de memória não é aplicada na granularidade da página? (Aqui parece que a região de memória entre 0x601018-0x601020 é de alguma forma somente leitura)

Eu acho que o problema está dentro da seção .got.plt (carregada em 0x601000 com tamanho 0x38), mas o que é isso exatamente?

Editar:

A saída de ld --verbose | fgrep -A 3 -B 3 -i relro é:

 .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) }
 .dynamic        : { *(.dynamic) }
 .got            : { *(.got) *(.igot) }
 . = DATA_SEGMENT_RELRO_END (SIZEOF (.got.plt) >= 24 ? 24 : 0, .);
 .got.plt        : { *(.got.plt)  *(.igot.plt) }
 .data           :

Isso provavelmente nos diz que o RELRO tem algo a ver com isso, mas ainda assim, a memória não deveria estar protegida na granularidade de tamanho de página?

    
por gip 25.10.2017 / 17:15

2 respostas

1

Eu encontrei o problema. Ao preencher essa região com lixo eu havia sobrescrito a entrada do printf, que com sua invocação final causava a segmentação.

Então, se modificarmos o código para:

#include <stdio.h>

int gv=10;

int main(){

  char *v=(char*)0x601000;
  printf("gv=%p\n", &gv);
  scanf("%s", v);
  //printf("You gave=%s\n", v);

}

não deve haver problema com o preenchimento de v com caracteres 0x1000 (cuidado com \ 0)

A referência RELRO do primeiro post não é relacionada.

    
por 25.10.2017 / 18:51
2

.got.plt é essencial para fazer chamadas na biblioteca (incluindo, mas não se limitando a, chamadas para libc ). Uma vez que você tenha terminado de corromper aquela seção, fazer qualquer chamada libc está fora dos limites.

Se você executar o binário através de gdb , você não deve obter um segfault em scanf - você deve obter um segfault em printf@plt , já que a chamada dinâmica não pode ser feita com% corrompida seção.got.plt.

Você ainda deve poder fazer chamadas e syscalls não dinâmicos:

#include <stdio.h>
  __asm(
    "finish:"
        "mov $60, %rax\n"
        "mov $42, %rdi\n"
        "syscall\n"
     );

int gv=10;

int main(){

  char *v=(char*)0x601000;//0x601030
  printf("gv=%p\n", &gv);
  scanf("%s", v);
  _Noreturn void finish(void);
  finish();
  //should exit with 42

  printf("You gave=%s\n", v);

}
    
por 25.10.2017 / 18:54

Tags