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 @@ -277,6 +277,7 @@ bool isRela() const { return ELFType == ELF::SHT_RELA; } bool isRelr() const { return ELFType == ELF::SHT_RELR; } bool isWritable() const { return (ELFFlags & ELF::SHF_WRITE); } + void toggleWritable() { ELFFlags ^= ELF::SHF_WRITE; } bool isAllocatable() const { if (isELF()) { return (ELFFlags & ELF::SHF_ALLOC) && !isTBSS(); 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 @@ -235,6 +235,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 checkGnuRelroSegment(); + /// 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 @@ -414,6 +414,93 @@ return !BF.isIgnored(); } +template +static bool checkTLSSections(const typename ELFT::Phdr &Phdr, + const typename ELFT::Shdr &Sec) { + if (Sec.sh_flags & ELF::SHF_TLS) { + // .tbss must only be shown in the PT_TLS segment. + if (Sec.sh_type == ELF::SHT_NOBITS) + return Phdr.p_type == ELF::PT_TLS; + + // SHF_TLS sections are only shown in PT_TLS, PT_LOAD or PT_GNU_RELRO + // segments. + return (Phdr.p_type == ELF::PT_TLS) || (Phdr.p_type == ELF::PT_LOAD) || + (Phdr.p_type == ELF::PT_GNU_RELRO); + } + + // PT_TLS must only have SHF_TLS sections. + return Phdr.p_type != ELF::PT_TLS; +} + +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; + + bool IsTbss = + (Sec.sh_type == ELF::SHT_NOBITS) && ((Sec.sh_flags & ELF::SHF_TLS) != 0); + // .tbss is special, it only has memory in PT_TLS and has NOBITS properties. + bool IsTbssInNonTLS = IsTbss && Phdr.p_type != ELF::PT_TLS; + // Only non-empty sections can be at the end of a segment. + if (Sec.sh_size == 0 || IsTbssInNonTLS) + 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::checkGnuRelroSegment() { + using ELFT = ELF64LE; + using ELFShdrTy = typename ELFObjectFile::Elf_Shdr; + auto ELF64LEFile = cast(InputFile); + const ELFFile &Obj = ELF64LEFile->getELFFile(); + + auto UpdateSectionsForSegment = [&](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())); + if (!checkTLSSections(Phdr, *Sec) || + !checkOffsets(Phdr, *Sec) || !checkVMA(Phdr, *Sec)) + continue; + if (BinarySection *BinarySection = BC->getSectionForSectionRef(SecRef)) { + if (!BinarySection->isWritable()) + continue; + BinarySection->toggleWritable(); + if (opts::Verbosity >= 1) + outs() << "BOLT-INFO: marking " << BinarySection->getName() + << " as read-only per PT_GNU_RELRO segment\n"; + } + } + }; + if (Expected PHsOrErr = Obj.program_headers()) { + llvm::for_each(PHsOrErr.get(), UpdateSectionsForSegment); + } else { + return PHsOrErr.takeError(); + } + return Error::success(); +} + Error RewriteInstance::discoverStorage() { NamedRegionTimer T("discoverStorage", "discover storage", TimerGroupName, TimerGroupDesc, opts::TimeRewrite); @@ -1776,7 +1863,11 @@ parseSDTNotes(); // Read .dynamic/PT_DYNAMIC. - return readELFDynamic(); + if (Error E = readELFDynamic()) + return E; + + // Adjust read only status based on PT_GNU_RELRO segment. + return checkGnuRelroSegment(); } void RewriteInstance::adjustCommandLineOptions() { diff --git a/bolt/test/X86/gotpcrelx.s b/bolt/test/X86/gotpcrelx.s --- a/bolt/test/X86/gotpcrelx.s +++ b/bolt/test/X86/gotpcrelx.s @@ -64,7 +64,7 @@ # DISASM-NEXT: cmpq $0x[[#ADDR]], %rax jmp *foo@GOTPCREL(%rip) -# NO-RELAX-BOLT-NEXT: jmpq *DATA{{.*}}(%rip) +# NO-RELAX-BOLT-NEXT: jmpl *DATA{{.*}}(%rip) # BOLT-NEXT: jmp foo # PIE-BOLT-NEXT: jmp foo # DISASM-NEXT: jmp 0x[[#ADDR]] 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 read-only per PT_GNU_RELRO segment + + .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