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". + void 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 @@ -31,6 +31,7 @@ #include "bolt/RuntimeLibs/InstrumentationRuntimeLibrary.h" #include "bolt/Utils/CommandLineOpts.h" #include "bolt/Utils/Utils.h" +#include "llvm/ADT/AddressRanges.h" #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/DWARF/DWARFDebugFrame.h" @@ -415,6 +416,85 @@ return !BF.isIgnored(); } +// Return if a section stored in the image falls into a segment address space. +// If not, Set \p Overlap to true if there's a partial overlap. +template +static bool checkOffsets(const typename ELFT::Phdr &Phdr, + const typename ELFT::Shdr &Sec, bool &Overlap) { + // SHT_NOBITS sections don't need to have an offset inside the segment. + if (Sec.sh_type == ELF::SHT_NOBITS) + return true; + + // Only non-empty sections can be at the end of a segment. + uint64_t SectionSize = Sec.sh_size ? Sec.sh_size : 1; + AddressRange SectionAddressRange(Sec.sh_offset, Sec.sh_offset + SectionSize); + AddressRange SegmentAddressRange(Phdr.p_offset, + Phdr.p_offset + Phdr.p_filesz); + if (SegmentAddressRange.contains(SectionAddressRange)) + return true; + + Overlap = SegmentAddressRange.intersects(SectionAddressRange); + return false; +} + +// 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, bool &Overlap) { + // Only non-empty sections can be at the end of a segment. + uint64_t SectionSize = Sec.sh_size ? Sec.sh_size : 1; + AddressRange SectionAddressRange(Sec.sh_addr, Sec.sh_addr + SectionSize); + AddressRange SegmentAddressRange(Phdr.p_vaddr, Phdr.p_vaddr + Phdr.p_memsz); + + if (SegmentAddressRange.contains(SectionAddressRange)) + return true; + Overlap = SegmentAddressRange.intersects(SectionAddressRange); + return false; +} + +void RewriteInstance::markGnuRelroSections() { + using ELFT = ELF64LE; + using ELFShdrTy = typename ELFObjectFile::Elf_Shdr; + auto ELF64LEFile = cast(InputFile); + const ELFFile &Obj = ELF64LEFile->getELFFile(); + + auto handleSection = [&](const ELFT::Phdr &Phdr, SectionRef SecRef) { + BinarySection *BinarySection = BC->getSectionForSectionRef(SecRef); + // If the section is non-allocatable, ignore it for GNU_RELRO purposes: + // it can't be made read-only after runtime relocations processing. + if (!BinarySection || !BinarySection->isAllocatable()) + return; + const ELFShdrTy *Sec = cantFail(Obj.getSection(SecRef.getIndex())); + bool ImageOverlap{false}, VMAOverlap{false}; + bool ImageContains = checkOffsets(Phdr, *Sec, ImageOverlap); + bool VMAContains = checkVMA(Phdr, *Sec, VMAOverlap); + if (ImageOverlap) { + if (opts::Verbosity >= 1) + errs() << "BOLT-WARNING: GNU_RELRO segment has partial file offset " + << "overlap with section " << BinarySection->getName() << '\n'; + return; + } + if (VMAOverlap) { + if (opts::Verbosity >= 1) + errs() << "BOLT-WARNING: GNU_RELRO segment has partial VMA overlap " + << "with section " << BinarySection->getName() << '\n'; + return; + } + if (!ImageContains || !VMAContains) + return; + BinarySection->setRelro(); + if (opts::Verbosity >= 1) + outs() << "BOLT-INFO: marking " << BinarySection->getName() + << " as GNU_RELRO\n"; + }; + + for (const ELFT::Phdr &Phdr : cantFail(Obj.program_headers())) + if (Phdr.p_type == ELF::PT_GNU_RELRO) + for (SectionRef SecRef : InputFile->sections()) + handleSection(Phdr, SecRef); +} + Error RewriteInstance::discoverStorage() { NamedRegionTimer T("discoverStorage", "discover storage", TimerGroupName, TimerGroupDesc, opts::TimeRewrite); @@ -1755,6 +1835,9 @@ opts::LinuxKernelMode = true; } + // Set IsRelro section attribute based on PT_GNU_RELRO segment. + markGnuRelroSections(); + 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 +# accordingly. + +# 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