diff --git a/bolt/include/bolt/Core/BinarySection.h b/bolt/include/bolt/Core/BinarySection.h --- a/bolt/include/bolt/Core/BinarySection.h +++ b/bolt/include/bolt/Core/BinarySection.h @@ -56,6 +56,7 @@ unsigned Alignment; // alignment in bytes (must be > 0) unsigned ELFType; // ELF section type unsigned ELFFlags; // ELF section flags + bool IsRelro{false}; // GNU RELRO section (read-only after relocation) // Relocations associated with this section. Relocation offsets are // wrt. to the original section address and size. @@ -287,6 +288,8 @@ } bool isReordered() const { return IsReordered; } bool isAnonymous() const { return IsAnonymous; } + bool isRelro() const { return IsRelro; } + void setRelro() { IsRelro = true; } unsigned getELFType() const { return ELFType; } unsigned getELFFlags() const { return ELFFlags; } diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h --- a/bolt/include/bolt/Rewrite/RewriteInstance.h +++ b/bolt/include/bolt/Rewrite/RewriteInstance.h @@ -236,6 +236,12 @@ /// Return value for the symbol \p Name in the output. uint64_t getNewValueForSymbol(const StringRef Name); + /// Check for PT_GNU_RELRO segment presence, mark covered sections as + /// (dynamically) read-only (written once), as specified in LSB Chapter 12: + /// "segment which may be made read-only after relocations have been + /// processed". + Error markGnuRelroSections(); + /// Detect addresses and offsets available in the binary for allocating /// new sections. Error discoverStorage(); diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp --- a/bolt/lib/Rewrite/RewriteInstance.cpp +++ b/bolt/lib/Rewrite/RewriteInstance.cpp @@ -410,6 +410,66 @@ return !BF.isIgnored(); } +template +static bool checkOffsets(const typename ELFT::Phdr &Phdr, + const typename ELFT::Shdr &Sec) { + // SHT_NOBITS sections don't need to have an offset inside the segment. + if (Sec.sh_type == ELF::SHT_NOBITS) + return true; + + if (Sec.sh_offset < Phdr.p_offset) + return false; + + // Only non-empty sections can be at the end of a segment. + if (Sec.sh_size == 0) + return (Sec.sh_offset + 1 <= Phdr.p_offset + Phdr.p_filesz); + return Sec.sh_offset + Sec.sh_size <= Phdr.p_offset + Phdr.p_filesz; +} + +// Check that an allocatable section belongs to a virtual address +// space of a segment. +template +static bool checkVMA(const typename ELFT::Phdr &Phdr, + const typename ELFT::Shdr &Sec) { + if (!(Sec.sh_flags & ELF::SHF_ALLOC)) + return true; + + if (Sec.sh_addr < Phdr.p_vaddr) + return false; + + // Only non-empty sections can be at the end of a segment. + if (Sec.sh_size == 0) + return Sec.sh_addr + 1 <= Phdr.p_vaddr + Phdr.p_memsz; + return Sec.sh_addr + Sec.sh_size <= Phdr.p_vaddr + Phdr.p_memsz; +} + +Error RewriteInstance::markGnuRelroSections() { + using ELFT = ELF64LE; + using ELFShdrTy = typename ELFObjectFile::Elf_Shdr; + auto ELF64LEFile = cast(InputFile); + const ELFFile &Obj = ELF64LEFile->getELFFile(); + + llvm::for_each(cantFail(Obj.program_headers()), [&](const ELFT::Phdr &Phdr) { + if (Phdr.p_type != ELF::PT_GNU_RELRO) + return; + for (SectionRef SecRef : InputFile->sections()) { + const ELFShdrTy *Sec = cantFail(Obj.getSection(SecRef.getIndex())); + bool IsTbss = (Sec->sh_type == ELF::SHT_NOBITS) && + (Sec->sh_flags & ELF::SHF_TLS); + if (IsTbss || + !checkOffsets(Phdr, *Sec) || !checkVMA(Phdr, *Sec)) + continue; + if (BinarySection *BinarySection = BC->getSectionForSectionRef(SecRef)) { + BinarySection->setRelro(); + if (opts::Verbosity >= 1) + outs() << "BOLT-INFO: marking " << BinarySection->getName() + << " as GNU_RELRO\n"; + } + } + }); + return Error::success(); +} + Error RewriteInstance::discoverStorage() { NamedRegionTimer T("discoverStorage", "discover storage", TimerGroupName, TimerGroupDesc, opts::TimeRewrite); @@ -1702,6 +1762,10 @@ opts::LinuxKernelMode = true; } + // Set IsRelro section attribute based on PT_GNU_RELRO segment. + if (Error E = markGnuRelroSections()) + return E; + if (HasDebugInfo && !opts::UpdateDebugSections && !opts::AggregateOnly) { errs() << "BOLT-WARNING: debug info will be stripped from the binary. " "Use -update-debug-sections to keep it.\n"; diff --git a/bolt/test/X86/pt_gnu_relro.s b/bolt/test/X86/pt_gnu_relro.s new file mode 100644 --- /dev/null +++ b/bolt/test/X86/pt_gnu_relro.s @@ -0,0 +1,43 @@ +# REQUIRES: system-linux + +# Check that BOLT recognizes PT_GNU_RELRO segment and marks respective sections +# read-only. + +# RUN: llvm-mc -filetype=obj -triple x86_64-unknown-linux %s -o %t.o -relax-relocations +# RUN: ld.lld %t.o -o %t.exe -q --no-relax +# RUN: llvm-readelf -We %t.exe | FileCheck --check-prefix=READELF %s +# Unfortunately there's no direct way to extract a segment to section mapping +# for a given section from readelf. Use the fool-proof way of matching +# readelf output line-by-line. +# READELF: Program Headers: +# READELF-NEXT: Type Offset {{.*}} +# READELF-NEXT: PHDR +# READELF-NEXT: LOAD +# READELF-NEXT: LOAD +# READELF-NEXT: LOAD +# READELF-NEXT: GNU_RELRO +# (GNU_RELRO is segment 4) + +# READELF: Section to Segment mapping: +# READELF: 04 .got + +# RUN: llvm-bolt %t.exe --relocs -o /dev/null -v=1 \ +# RUN: |& FileCheck --check-prefix=BOLT %s +# BOLT: BOLT-INFO: marking .got as GNU_RELRO + + .globl _start + .type _start, %function +_start: + .cfi_startproc + jmp *foo@GOTPCREL(%rip) + ret + .cfi_endproc + .size _start, .-_start + + .globl foo + .type foo, %function +foo: + .cfi_startproc + ret + .cfi_endproc + .size foo, .-foo