Read .note.gnu.property sections and emit a merged .note.gnu.property section.

This patch also adds `--require-cet` option for the sake of testing.
The actual feature for IBT-aware PLT is not included in this patch.

This is a part of https://reviews.llvm.org/D59780. Submitting this
first should make it easy to work with a related change
(https://reviews.llvm.org/D62609).

Differential Revision: https://reviews.llvm.org/D62853

llvm-svn: 362579
This commit is contained in:
Rui Ueyama 2019-06-05 03:04:46 +00:00
parent aade782a98
commit 2057f8366a
19 changed files with 371 additions and 1 deletions

View File

@ -82,6 +82,7 @@ struct VersionDefinition {
// Most fields are initialized by the driver. // Most fields are initialized by the driver.
struct Configuration { struct Configuration {
uint8_t OSABI = 0; uint8_t OSABI = 0;
uint32_t AndFeatures = 0;
llvm::CachePruningPolicy ThinLTOCachePolicy; llvm::CachePruningPolicy ThinLTOCachePolicy;
llvm::StringMap<uint64_t> SectionStartMap; llvm::StringMap<uint64_t> SectionStartMap;
llvm::StringRef Chroot; llvm::StringRef Chroot;
@ -147,6 +148,7 @@ struct Configuration {
bool ExportDynamic; bool ExportDynamic;
bool FixCortexA53Errata843419; bool FixCortexA53Errata843419;
bool FormatBinary = false; bool FormatBinary = false;
bool RequireCET;
bool GcSections; bool GcSections;
bool GdbIndex; bool GdbIndex;
bool GnuHash = false; bool GnuHash = false;

View File

@ -334,6 +334,9 @@ static void checkOptions() {
if (Config->SingleRoRx && !Script->HasSectionsCommand) if (Config->SingleRoRx && !Script->HasSectionsCommand)
error("-execute-only and -no-rosegment cannot be used together"); error("-execute-only and -no-rosegment cannot be used together");
} }
if (Config->ZRetpolineplt && Config->RequireCET)
error("--require-cet may not be used with -z retpolineplt");
} }
static const char *getReproduceOption(opt::InputArgList &Args) { static const char *getReproduceOption(opt::InputArgList &Args) {
@ -813,6 +816,7 @@ static void readConfigs(opt::InputArgList &Args) {
Config->FilterList = args::getStrings(Args, OPT_filter); Config->FilterList = args::getStrings(Args, OPT_filter);
Config->Fini = Args.getLastArgValue(OPT_fini, "_fini"); Config->Fini = Args.getLastArgValue(OPT_fini, "_fini");
Config->FixCortexA53Errata843419 = Args.hasArg(OPT_fix_cortex_a53_843419); Config->FixCortexA53Errata843419 = Args.hasArg(OPT_fix_cortex_a53_843419);
Config->RequireCET = Args.hasArg(OPT_require_cet);
Config->GcSections = Args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, false); Config->GcSections = Args.hasFlag(OPT_gc_sections, OPT_no_gc_sections, false);
Config->GnuUnique = Args.hasFlag(OPT_gnu_unique, OPT_no_gnu_unique, true); Config->GnuUnique = Args.hasFlag(OPT_gnu_unique, OPT_no_gnu_unique, true);
Config->GdbIndex = Args.hasFlag(OPT_gdb_index, OPT_no_gdb_index, false); Config->GdbIndex = Args.hasFlag(OPT_gdb_index, OPT_no_gdb_index, false);
@ -1584,6 +1588,30 @@ static void wrapSymbols(ArrayRef<WrappedSymbol> Wrapped) {
Symtab->wrap(W.Sym, W.Real, W.Wrap); Symtab->wrap(W.Sym, W.Real, W.Wrap);
} }
// To enable CET (x86's hardware-assited control flow enforcement), each
// source file must be compiled with -fcf-protection. Object files compiled
// with the flag contain feature flags indicating that they are compatible
// with CET. We enable the feature only when all object files are compatible
// with CET.
//
// This function returns the merged feature flags. If 0, we cannot enable CET.
//
// Note that the CET-aware PLT is not implemented yet. We do error
// check only.
template <class ELFT> static uint32_t getAndFeatures() {
if (Config->EMachine != EM_386 && Config->EMachine != EM_X86_64)
return 0;
uint32_t Ret = -1;
for (InputFile *F : ObjectFiles) {
uint32_t Features = cast<ObjFile<ELFT>>(F)->AndFeatures;
if (!Features && Config->RequireCET)
error(toString(F) + ": --require-cet: file is not compatible with CET");
Ret &= Features;
}
return Ret;
}
static const char *LibcallRoutineNames[] = { static const char *LibcallRoutineNames[] = {
#define HANDLE_LIBCALL(code, name) name, #define HANDLE_LIBCALL(code, name) name,
#include "llvm/IR/RuntimeLibcalls.def" #include "llvm/IR/RuntimeLibcalls.def"
@ -1762,6 +1790,10 @@ template <class ELFT> void LinkerDriver::link(opt::InputArgList &Args) {
(S->Name.startswith(".debug") || S->Name.startswith(".zdebug")); (S->Name.startswith(".debug") || S->Name.startswith(".zdebug"));
}); });
// Read .note.gnu.property sections from input object files which
// contain a hint to tweak linker's and loader's behaviors.
Config->AndFeatures = getAndFeatures<ELFT>();
Config->EFlags = Target->calcEFlags(); Config->EFlags = Target->calcEFlags();
// MaxPageSize (sometimes called abi page size) is the maximum page size that // MaxPageSize (sometimes called abi page size) is the maximum page size that
// the output can be run on. For example if the OS can use 4k or 64k page // the output can be run on. For example if the OS can use 4k or 64k page

View File

@ -25,6 +25,7 @@
#include "llvm/Object/ELFObjectFile.h" #include "llvm/Object/ELFObjectFile.h"
#include "llvm/Support/ARMAttributeParser.h" #include "llvm/Support/ARMAttributeParser.h"
#include "llvm/Support/ARMBuildAttributes.h" #include "llvm/Support/ARMBuildAttributes.h"
#include "llvm/Support/Endian.h"
#include "llvm/Support/Path.h" #include "llvm/Support/Path.h"
#include "llvm/Support/TarWriter.h" #include "llvm/Support/TarWriter.h"
#include "llvm/Support/raw_ostream.h" #include "llvm/Support/raw_ostream.h"
@ -34,6 +35,7 @@ using namespace llvm::ELF;
using namespace llvm::object; using namespace llvm::object;
using namespace llvm::sys; using namespace llvm::sys;
using namespace llvm::sys::fs; using namespace llvm::sys::fs;
using namespace llvm::support::endian;
using namespace lld; using namespace lld;
using namespace lld::elf; using namespace lld::elf;
@ -753,6 +755,68 @@ static void updateSupportedARMFeatures(const ARMAttributeParser &Attributes) {
} }
} }
// If a source file is compiled with x86 hardware-assisted call flow control
// enabled, the generated object file contains feature flags indicating that
// fact. This function reads the feature flags and returns it.
//
// Essentially we want to read a single 32-bit value in this function, but this
// function is rather complicated because the value is buried deep inside a
// .note.gnu.property section.
//
// The section consists of one or more NOTE records. Each NOTE record consists
// of zero or more type-length-value fields. We want to find a field of a
// certain type. It seems a bit too much to just store a 32-bit value, perhaps
// the ABI is unnecessarily complicated.
template <class ELFT>
static uint32_t readAndFeatures(ObjFile<ELFT> *Obj, ArrayRef<uint8_t> Data) {
using Elf_Nhdr = typename ELFT::Nhdr;
using Elf_Note = typename ELFT::Note;
while (!Data.empty()) {
// Read one NOTE record.
if (Data.size() < sizeof(Elf_Nhdr))
fatal(toString(Obj) + ": .note.gnu.property: section too short");
auto *Nhdr = reinterpret_cast<const Elf_Nhdr *>(Data.data());
if (Data.size() < Nhdr->getSize())
fatal(toString(Obj) + ": .note.gnu.property: section too short");
Elf_Note Note(*Nhdr);
if (Nhdr->n_type != NT_GNU_PROPERTY_TYPE_0 || Note.getName() != "GNU") {
Data = Data.slice(Nhdr->getSize());
continue;
}
// Read a body of a NOTE record, which consists of type-length-value fields.
ArrayRef<uint8_t> Desc = Note.getDesc();
while (!Desc.empty()) {
if (Desc.size() < 8)
fatal(toString(Obj) + ": .note.gnu.property: section too short");
uint32_t Type = read32le(Desc.data());
uint32_t Size = read32le(Desc.data() + 4);
if (Type == GNU_PROPERTY_X86_FEATURE_1_AND) {
// We found the field.
return read32le(Desc.data() + 8);
}
// On 64-bit, a payload may be followed by a 4-byte padding to make its
// size a multiple of 8.
if (ELFT::Is64Bits)
Size = alignTo(Size, 8);
Desc = Desc.slice(Size + 8); // +8 for Type and Size
}
// Go to next NOTE record if a note section didn't contain
// X86_FEATURES_1_AND description.
Data = Data.slice(Nhdr->getSize());
}
return 0;
}
template <class ELFT> template <class ELFT>
InputSectionBase *ObjFile<ELFT>::getRelocTarget(const Elf_Shdr &Sec) { InputSectionBase *ObjFile<ELFT>::getRelocTarget(const Elf_Shdr &Sec) {
uint32_t Idx = Sec.sh_info; uint32_t Idx = Sec.sh_info;
@ -901,6 +965,19 @@ InputSectionBase *ObjFile<ELFT>::createInputSection(const Elf_Shdr &Sec) {
if (Name == ".note.GNU-stack") if (Name == ".note.GNU-stack")
return &InputSection::Discarded; return &InputSection::Discarded;
// If an object file is compatible with Intel Control-Flow Enforcement
// Technology (CET), it has a .note.gnu.property section containing the
// GNU_PROPERTY_X86_FEATURE_1_IBT flag. Read a bitmap containing the flag.
//
// Since we merge bitmaps from multiple object files to create a new
// .note.gnu.property containing a single AND'ed bitmap, we discard an input
// file's .note.gnu.property section.
if (Name == ".note.gnu.property") {
ArrayRef<uint8_t> Contents = check(this->getObj().getSectionContents(&Sec));
this->AndFeatures = readAndFeatures(this, Contents);
return &InputSection::Discarded;
}
// Split stacks is a feature to support a discontiguous stack, // Split stacks is a feature to support a discontiguous stack,
// commonly used in the programming language Go. For the details, // commonly used in the programming language Go. For the details,
// see https://gcc.gnu.org/wiki/SplitStacks. An object file compiled // see https://gcc.gnu.org/wiki/SplitStacks. An object file compiled

View File

@ -228,6 +228,8 @@ public:
// R_MIPS_GPREL16 / R_MIPS_GPREL32 relocations. // R_MIPS_GPREL16 / R_MIPS_GPREL32 relocations.
uint32_t MipsGp0 = 0; uint32_t MipsGp0 = 0;
uint32_t AndFeatures = 0;
// Name of source file obtained from STT_FILE symbol value, // Name of source file obtained from STT_FILE symbol value,
// or empty string if there is no such symbol in object file // or empty string if there is no such symbol in object file
// symbol table. // symbol table.

View File

@ -171,6 +171,10 @@ defm fini: Eq<"fini", "Specify a finalizer function">, MetaVarName<"<symbol>">;
def fix_cortex_a53_843419: F<"fix-cortex-a53-843419">, def fix_cortex_a53_843419: F<"fix-cortex-a53-843419">,
HelpText<"Apply fixes for AArch64 Cortex-A53 erratum 843419">; HelpText<"Apply fixes for AArch64 Cortex-A53 erratum 843419">;
// This option is intentionally hidden from the user as the implementation
// is not complete.
def require_cet: F<"require-cet">;
defm format: Eq<"format", "Change the input format of the inputs following this option">, defm format: Eq<"format", "Change the input format of the inputs following this option">,
MetaVarName<"[default,elf,binary]">; MetaVarName<"[default,elf,binary]">;

View File

@ -288,6 +288,35 @@ static size_t getHashSize() {
} }
} }
// This class represents a linker-synthesized .note.gnu.property section.
//
// In x86, object files may contain feature flags indicating the features that
// they are using. The flags are stored in a .note.gnu.property section.
//
// lld reads the sections from input files and merges them by computing AND of
// the flags. The result is written as a new .note.gnu.property section.
//
// If the flag is zero (which indicates that the intersection of the feature
// sets is empty, or some input files didn't have .note.gnu.property sections),
// we don't create this section.
GnuPropertySection::GnuPropertySection()
: SyntheticSection(llvm::ELF::SHF_ALLOC, llvm::ELF::SHT_NOTE, 4,
".note.gnu.property") {}
void GnuPropertySection::writeTo(uint8_t *Buf) {
write32(Buf, 4); // Name size
write32(Buf + 4, Config->Is64 ? 16 : 12); // Content size
write32(Buf + 8, NT_GNU_PROPERTY_TYPE_0); // Type
memcpy(Buf + 12, "GNU", 4); // Name string
write32(Buf + 16, GNU_PROPERTY_X86_FEATURE_1_AND); // Feature type
write32(Buf + 20, 4); // Feature size
write32(Buf + 24, Config->AndFeatures); // Feature flags
if (Config->Is64)
write32(Buf + 28, 0); // Padding
}
size_t GnuPropertySection::getSize() const { return Config->Is64 ? 32 : 28; }
BuildIdSection::BuildIdSection() BuildIdSection::BuildIdSection()
: SyntheticSection(SHF_ALLOC, SHT_NOTE, 4, ".note.gnu.build-id"), : SyntheticSection(SHF_ALLOC, SHT_NOTE, 4, ".note.gnu.build-id"),
HashSize(getHashSize()) {} HashSize(getHashSize()) {}

View File

@ -147,6 +147,13 @@ public:
size_t getSize() const override { return 0; } size_t getSize() const override { return 0; }
}; };
class GnuPropertySection : public SyntheticSection {
public:
GnuPropertySection();
void writeTo(uint8_t *Buf) override;
size_t getSize() const override;
};
// .note.gnu.build-id section. // .note.gnu.build-id section.
class BuildIdSection : public SyntheticSection { class BuildIdSection : public SyntheticSection {
// First 16 bytes are a header. // First 16 bytes are a header.

View File

@ -433,6 +433,9 @@ template <class ELFT> static void createSyntheticSections() {
In.Iplt = make<PltSection>(true); In.Iplt = make<PltSection>(true);
Add(In.Iplt); Add(In.Iplt);
if (Config->AndFeatures)
Add(make<GnuPropertySection>());
// .note.GNU-stack is always added when we are creating a re-linkable // .note.GNU-stack is always added when we are creating a re-linkable
// object file. Other linkers are using the presence of this marker // object file. Other linkers are using the presence of this marker
// section to control the executable-ness of the stack area, but that // section to control the executable-ness of the stack area, but that

View File

@ -0,0 +1,16 @@
.section ".note.gnu.property", "a"
.align 4
.long 4
.long 12
.long 5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 3
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,20 @@
.section ".note.gnu.property", "a"
.align 4
.long 4
.long 24
.long 5
.asciz "GNU"
.long 0xc0000000
.long 4
.long 0
.long 0xc0000002
.long 4
.long 3
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,5 @@
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,16 @@
.section ".note.gnu.property", "a"
.align 4
.long 4
.long 12
.long 5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 0xfffffffd
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,17 @@
.section ".note.gnu.property", "a"
.align 4
.long 4
.long 16
.long 5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 3
.long 0
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,22 @@
.section ".note.gnu.property", "a"
.align 4
.long 4
.long 32
.long 5
.asciz "GNU"
.long 0xc0000000
.long 4
.long 0
.long 0
.long 0xc0000002
.long 4
.long 3
.long 0
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,5 @@
.text
.globl func2
.type func2,@function
func2:
ret

View File

@ -0,0 +1,17 @@
.section ".note.gnu.property", "a"
.align 4
.long 4
.long 16
.long 5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 0xfffffffd
.long 0
.text
.globl func2
.type func2,@function
func2:
ret

48
lld/test/ELF/i386-cet.s Normal file
View File

@ -0,0 +1,48 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=i386-unknown-linux %s -o %t.o
# RUN: llvm-mc -filetype=obj -triple=i386-unknown-linux %p/Inputs/i386-cet1.s -o %t1.o
# RUN: llvm-mc -filetype=obj -triple=i386-unknown-linux %p/Inputs/i386-cet2.s -o %t2.o
# RUN: llvm-mc -filetype=obj -triple=i386-unknown-linux %p/Inputs/i386-cet3.s -o %t3.o
# RUN: llvm-mc -filetype=obj -triple=i386-unknown-linux %p/Inputs/i386-cet4.s -o %t4.o
# RUN: ld.lld -e func1 %t.o %t1.o -o %t
# RUN: llvm-readelf -n %t | FileCheck -check-prefix=CET -match-full-lines %s
# RUN: ld.lld -e func1 %t.o %t2.o -o %t
# RUN: llvm-readelf -n %t | FileCheck -check-prefix=CET -match-full-lines %s
# CET: Properties: x86 feature: IBT, SHSTK
# RUN: ld.lld -e func1 %t.o %t3.o -o %t
# RUN: llvm-readelf -S %t | FileCheck -check-prefix=NOCET %s
# NOCET: Section Headers
# NOCET-NOT: .note.gnu.property
# RUN: not ld.lld -e func1 %t.o %t3.o -o %t --require-cet 2>&1 \
# RUN: | FileCheck -check-prefix=ERROR %s
# ERROR: i386-cet.s.tmp3.o: --require-cet: file is not compatible with CET
# RUN: ld.lld -e func1 %t.o %t4.o -o %t
# RUN: llvm-readelf -n %t | FileCheck -check-prefix=NOSHSTK -match-full-lines %s
# Check .note.gnu.protery without property SHSTK.
# NOSHSTK: Properties: x86 feature: IBT
.section ".note.gnu.property", "a"
.long 4
.long 0x10
.long 0x5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 3
.long 0
.text
.globl func1
.type func1,@function
func1:
call func2
ret

48
lld/test/ELF/x86-64-cet.s Normal file
View File

@ -0,0 +1,48 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %s -o %t.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/x86-64-cet1.s -o %t1.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/x86-64-cet2.s -o %t2.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/x86-64-cet3.s -o %t3.o
# RUN: llvm-mc -filetype=obj -triple=x86_64-unknown-linux %p/Inputs/x86-64-cet4.s -o %t4.o
# RUN: ld.lld -e func1 %t.o %t1.o -o %t
# RUN: llvm-readelf -n %t | FileCheck -check-prefix=CET -match-full-lines %s
# RUN: ld.lld -e func1 %t.o %t2.o -o %t
# RUN: llvm-readelf -n %t | FileCheck -check-prefix=CET -match-full-lines %s
# CET: Properties: x86 feature: IBT, SHSTK
# RUN: ld.lld -e func1 %t.o %t3.o -o %t
# RUN: llvm-readelf -S %t | FileCheck -check-prefix=NOCET %s
# NOCET: Section Headers
# NOCET-NOT: .note.gnu.property
# RUN: not ld.lld -e func1 %t.o %t3.o -o %t --require-cet 2>&1 \
# RUN: | FileCheck -check-prefix=ERROR %s
# ERROR: x86-64-cet.s.tmp3.o: --require-cet: file is not compatible with CET
# RUN: ld.lld -e func1 %t.o %t4.o -o %t
# RUN: llvm-readelf -n %t | FileCheck -check-prefix=NOSHSTK -match-full-lines %s
# Check .note.gnu.protery without property SHSTK.
# NOSHSTK: Properties: x86 feature: IBT
.section ".note.gnu.property", "a"
.long 4
.long 0x10
.long 0x5
.asciz "GNU"
.long 0xc0000002
.long 4
.long 3
.long 0
.text
.globl func1
.type func1,@function
func1:
call func2
ret

View File

@ -592,9 +592,9 @@ class Elf_Note_Impl {
template <class NoteIteratorELFT> friend class Elf_Note_Iterator_Impl; template <class NoteIteratorELFT> friend class Elf_Note_Iterator_Impl;
public:
Elf_Note_Impl(const Elf_Nhdr_Impl<ELFT> &Nhdr) : Nhdr(Nhdr) {} Elf_Note_Impl(const Elf_Nhdr_Impl<ELFT> &Nhdr) : Nhdr(Nhdr) {}
public:
/// Get the note's name, excluding the terminating null byte. /// Get the note's name, excluding the terminating null byte.
StringRef getName() const { StringRef getName() const {
if (!Nhdr.n_namesz) if (!Nhdr.n_namesz)