Re: [PATCH] elf: Support multiple PT_GNU_RELRO segments

Fangrui Song <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <[email protected]>
With linker scripts' PHDRS, we can create multiple PT_GNU_RELRO segments, working with both GNU ld and ld.lld.

I have recently added support for multiple PT_GNU_RELRO segments without PHDRS
https://github.com/llvm/llvm-project/pull/203675

// a.c

#define _GNU_SOURCE
#include <link.h>
#include <stdio.h>

const char *const greeting = "Hello, world!";

int counter = 42;

static char perm_at(unsigned long addr) {
  FILE *f = fopen("/proc/self/maps", "r");
  char line[128], w = '?';
  unsigned long beg, end;
  char perms[8];
  while (f && fgets(line, sizeof line, f))
    if (sscanf(line, "%lx-%lx %7s", &beg, &end, perms) == 3 && beg <= addr &&
        addr < end) {
      w = perms[1];
      break;
    }
  if (f)
    fclose(f);
  return w;
}

static int report(struct dl_phdr_info *info, size_t size, void *data) {
  if (info->dlpi_name[0] != '\0')
    return 0;
  for (unsigned i = 0; i < info->dlpi_phnum; i++) {
    const ElfW(Phdr) *ph = &info->dlpi_phdr[i];
    if (ph->p_type != PT_GNU_RELRO)
      continue;
    unsigned long beg = info->dlpi_addr + ph->p_vaddr;
    printf("PT_GNU_RELRO [0x%lx, 0x%lx): %s\n", beg, beg + ph->p_memsz,
           perm_at(beg) == 'w' ? "still writable (ld.so did not protect it)"
                               : "read-only");
  }
  return 0;
}

int main(void) {
  counter++;
  printf("%s (counter=%d, greeting is in .data.rel.ro)\n", greeting, counter);
  dl_iterate_phdr(report, NULL);
  return 0;
}

## relro.lds

SECTIONS {
  . = SIZEOF_HEADERS;
  .interp : { *(.interp) }
  .rodata : { *(.rodata .rodata.*) }
  .eh_frame_hdr : { *(.eh_frame_hdr) }
  .eh_frame : { *(.eh_frame) }

  . = ALIGN(CONSTANT(MAXPAGESIZE));
  .init : { *(.init) }
  .text : { *(.text .text.*) }
  .fini : { *(.fini) }
  .plt : { *(.plt) }

  /* relro run 1, padded to a full page so that a loader honoring it can
     mprotect whole pages */
  . = ALIGN(CONSTANT(MAXPAGESIZE));
  .init_array : { *(.init_array) }
  .fini_array : { *(.fini_array) }
  .dynamic : { *(.dynamic) }
  .got : { *(.got) }
  .got.plt : { *(.got.plt) . = ALIGN(CONSTANT(MAXPAGESIZE)); }

  /* non-relro: splits PT_GNU_RELRO in two */
  .data : { *(.data) }

  /* relro run 2, page-aligned and page-padded (see above) */
  . = ALIGN(CONSTANT(MAXPAGESIZE));
  .data.rel.ro : { *(.data.rel.ro .data.rel.ro.*) . = ALIGN(CONSTANT(MAXPAGESIZE)); }

  .bss : { *(.bss) *(COMMON) }
}

## build.sh

clang -O1 -g -fPIE -c hello.c -o hello.o
clang -pie --ld-path=/tmp/Rel/bin/ld.lld -Wl,-z,now -Wl,-T,relro.lds hello.o -o hello
readelf -lW hello | grep -E 'Type|LOAD|GNU_RELRO'
./hello

## phdrs.lds

PHDRS {
  hdrs PT_PHDR PHDRS FLAGS (4);
  interp PT_INTERP FLAGS (4);
  ro PT_LOAD FILEHDR PHDRS FLAGS (4);
  text PT_LOAD FLAGS (5);
  rw PT_LOAD FLAGS (6);
  dyn PT_DYNAMIC FLAGS (6);
  relro1 PT_GNU_RELRO FLAGS (4);
  relro2 PT_GNU_RELRO FLAGS (4);
  eh PT_GNU_EH_FRAME FLAGS (4);
  stack PT_GNU_STACK FLAGS (6);
}

SECTIONS {
  . = SIZEOF_HEADERS;
  .interp : { *(.interp) } :ro :interp
  .note : { *(.note*) } :ro
  .dynsym : { *(.dynsym) } :ro
  .gnu.version : { *(.gnu.version) } :ro
  .gnu.version_r : { *(.gnu.version_r) } :ro
  .gnu.hash : { *(.gnu.hash) } :ro
  .dynstr : { *(.dynstr) } :ro
  .rela.dyn : { *(.rela.dyn) } :ro
  .rela.plt : { *(.rela.plt) } :ro
  .rodata : { *(.rodata .rodata.*) } :ro
  .eh_frame_hdr : { *(.eh_frame_hdr) } :ro :eh
  .eh_frame : { *(.eh_frame) } :ro

  . = ALIGN(CONSTANT(MAXPAGESIZE));
  .init : { *(.init) } :text
  .text : { *(.text .text.*) } :text
  .fini : { *(.fini) } :text
  .plt : { *(.plt) } :text

  /* relro run 1, page-padded so that a loader honoring it can mprotect
     whole pages */
  . = ALIGN(CONSTANT(MAXPAGESIZE));
  .init_array : { *(.init_array) } :rw :relro1
  .fini_array : { *(.fini_array) } :rw :relro1
  .dynamic : { *(.dynamic) } :rw :dyn :relro1
  .got : { *(.got) } :rw :relro1
  .got.plt : { *(.got.plt) . = ALIGN(CONSTANT(MAXPAGESIZE)); } :rw :relro1

  /* non-relro: splits PT_GNU_RELRO in two */
  .data : { *(.data) } :rw
  .data.rel.local : { *(.data.rel.local) } :rw

  /* relro run 2, page-aligned and page-padded (see above) */
  . = ALIGN(CONSTANT(MAXPAGESIZE));
  .data.rel.ro : { *(.data.rel.ro .data.rel.ro.*) . = ALIGN(CONSTANT(MAXPAGESIZE)); } :rw :relro2

  .bss : { *(.bss) *(COMMON) } :rw
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.