From e4ff3bfc7da92ec223c8a108d40da33e9f710133 Mon Sep 17 00:00:00 2001 From: Daniil Kovalev Date: Thu, 8 Feb 2024 17:51:00 +0300 Subject: [PATCH] [AArch64][PAC][lld][ELF] Support `GNU_PROPERTY_AARCH64_FEATURE_PAUTH` - In new PAuth ABI version, support for extra data (except platform and version) is dropped. Change container for holding the tag from `SmallVector` to `std::optional>`. - In new PAuth ABI version, the GNU property section becomes the main way of ELF marking. Support for alternative way is temporarily disabled in this commit unless the corresponding mainline PR and ARM PAuth ABI PR are merged. --- lld/ELF/Config.h | 3 +- lld/ELF/Driver.cpp | 20 ++-- lld/ELF/InputFiles.cpp | 53 +++++++--- lld/ELF/InputFiles.h | 2 +- lld/ELF/SyntheticSections.cpp | 52 ++++++---- lld/ELF/SyntheticSections.h | 2 +- lld/ELF/Writer.cpp | 15 ++- lld/test/ELF/aarch64-feature-pauth-gnu-prop.s | 96 +++++++++++++++++++ lld/test/ELF/aarch64-feature-pauth.s | 6 ++ 9 files changed, 202 insertions(+), 47 deletions(-) create mode 100644 lld/test/ELF/aarch64-feature-pauth-gnu-prop.s diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h index 842c21fa2ac5..598416016da7 100644 --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -27,6 +27,7 @@ #include "llvm/Support/FileSystem.h" #include "llvm/Support/GlobPattern.h" #include "llvm/Support/PrettyStackTrace.h" +#include #include #include #include @@ -492,7 +493,7 @@ struct Ctx { llvm::raw_fd_ostream openAuxiliaryFile(llvm::StringRef, std::error_code &); - SmallVector aarch64PauthAbiTag; + std::optional> aarch64PauthAbiTag; }; LLVM_LIBRARY_VISIBILITY extern Ctx ctx; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp index 8cf6438f5ae4..4f370e2b7eb8 100644 --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -2662,7 +2662,7 @@ static void getAarch64PauthInfo() { auto NonEmptyIt = std::find_if( ctx.objectFiles.begin(), ctx.objectFiles.end(), - [](const ELFFileBase *f) { return !f->aarch64PauthAbiTag.empty(); }); + [](const ELFFileBase *f) { return f->aarch64PauthAbiTag.has_value(); }); if (NonEmptyIt == ctx.objectFiles.end()) return; @@ -2670,9 +2670,9 @@ static void getAarch64PauthInfo() { StringRef F1 = (*NonEmptyIt)->getName(); for (ELFFileBase *F : ArrayRef(ctx.objectFiles)) { StringRef F2 = F->getName(); - const SmallVector &D1 = ctx.aarch64PauthAbiTag; - const SmallVector &D2 = F->aarch64PauthAbiTag; - if (D1.empty() != D2.empty()) { + std::optional> D1 = ctx.aarch64PauthAbiTag; + std::optional> D2 = F->aarch64PauthAbiTag; + if (D1.has_value() != D2.has_value()) { auto Helper = [](StringRef Report, const Twine &Msg) { if (Report == "warning") warn(Msg); @@ -2681,19 +2681,19 @@ static void getAarch64PauthInfo() { }; Helper(config->zPauthReport, - (D1.empty() ? F1.str() : F2.str()) + + (D1.has_value() ? F2.str() : F1.str()) + " has no AArch64 PAuth compatibility info while " + - (D1.empty() ? F2.str() : F1.str()) + + (D1.has_value() ? F1.str() : F2.str()) + " has one; either all or no input files must have it"); } - if (!D1.empty() && !D2.empty() && - !std::equal(D1.begin(), D1.end(), D2.begin(), D2.end())) + if (D1.has_value() && D2.has_value() && + !std::equal(D1->begin(), D1->end(), D2->begin(), D2->end())) errorOrWarn( "incompatible values of AArch64 PAuth compatibility info found" "\n" + - F1 + ": 0x" + toHex(ArrayRef(D1.data(), D1.size())) + "\n" + F2 + - ": 0x" + toHex(ArrayRef(D2.data(), D2.size()))); + F1 + ": 0x" + toHex(ArrayRef(D1->data(), D1->size())) + "\n" + F2 + + ": 0x" + toHex(ArrayRef(D2->data(), D2->size()))); } } diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp index 873438033a65..821a14003df1 100644 --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -31,6 +31,8 @@ #include "llvm/Support/TarWriter.h" #include "llvm/Support/raw_ostream.h" +#include + using namespace llvm; using namespace llvm::ELF; using namespace llvm::object; @@ -878,11 +880,14 @@ void ObjFile::initializeSections(bool ignoreComdats, // 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 static uint32_t readAndFeatures(const InputSection &sec) { +template +static std::pair>> +readGnuProperty(const InputSection &sec) { using Elf_Nhdr = typename ELFT::Nhdr; using Elf_Note = typename ELFT::Note; uint32_t featuresSet = 0; + std::optional> aarch64PauthAbiTag; ArrayRef data = sec.content(); auto reportFatal = [&](const uint8_t *place, const char *msg) { fatal(toString(sec.file) + ":(" + sec.name + "+0x" + @@ -924,6 +929,19 @@ template static uint32_t readAndFeatures(const InputSection &sec) { if (size < 4) reportFatal(place, "FEATURE_1_AND entry is too short"); featuresSet |= read32(desc.data()); + } else if (config->emachine == EM_AARCH64 && + type == GNU_PROPERTY_AARCH64_FEATURE_PAUTH) { + if (aarch64PauthAbiTag != std::nullopt) + reportFatal(data.data(), + "multiple GNU_PROPERTY_AARCH64_FEATURE_PAUTH properties " + "are not allowed"); + if (size != 16) + reportFatal( + data.data(), + "size of GNU_PROPERTY_AARCH64_FEATURE_PAUTH property must be 16"); + aarch64PauthAbiTag = std::array{}; + memcpy(aarch64PauthAbiTag->data(), desc.data(), + aarch64PauthAbiTag->size()); } // Padding is present in the note descriptor, if necessary. @@ -934,7 +952,7 @@ template static uint32_t readAndFeatures(const InputSection &sec) { data = data.slice(nhdr->getSize(sec.addralign)); } - return featuresSet; + return {featuresSet, aarch64PauthAbiTag}; } // Extract compatibility info for aarch64 pointer authentication from the @@ -966,13 +984,15 @@ static void readAArch64PauthAbiTag(const InputSection &sec, ObjFile &f) { " (ARM expected)"); ArrayRef desc = note.getDesc(sec.addralign); - if (desc.size() < 16) { - reportError("too short AArch64 PAuth compatibility info " - "(at least 16 bytes expected)"); + if (desc.size() != 16) { + reportError("invalid AArch64 PAuth compatibility info length " + "(exactly 16 bytes expected)"); return; } - f.aarch64PauthAbiTag = SmallVector(iterator_range(desc)); + f.aarch64PauthAbiTag = std::array{}; + memcpy(f.aarch64PauthAbiTag->data(), desc.data(), + f.aarch64PauthAbiTag->size()); } template @@ -1029,15 +1049,24 @@ InputSectionBase *ObjFile::createInputSection(uint32_t idx, // .note.gnu.property containing a single AND'ed bitmap, we discard an input // file's .note.gnu.property section. if (name == ".note.gnu.property") { - this->andFeatures = readAndFeatures(InputSection(*this, sec, name)); + std::tie(this->andFeatures, this->aarch64PauthAbiTag) = + readGnuProperty(InputSection(*this, sec, name)); return &InputSection::discarded; } - if (config->emachine == EM_AARCH64 && - name == ".note.AARCH64-PAUTH-ABI-tag") { - readAArch64PauthAbiTag(InputSection(*this, sec, name), *this); - return &InputSection::discarded; - } + // TODO: alternative PAuth ELF marking way + // To be implemented after the following PRs are merged: + // - https://github.com/ARM-software/abi-aa/pull/240 + // - https://github.com/llvm/llvm-project/pull/72714 + // + // if (config->emachine == EM_AARCH64 && + // name == ".note.AARCH64-PAUTH-ABI-tag") { + // if (this->aarch64PauthAbiTag != std::nullopt) + // error("cannot mix two PAuth ABI tag ELF marking ways in one object + // file"); + // readAArch64PauthAbiTag(InputSection(*this, sec, name), *this); + // return &InputSection::discarded; + // } // Split stacks is a feature to support a discontiguous stack, // commonly used in the programming language Go. For the details, diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h index 7b6eb0720068..63a7a5da03fd 100644 --- a/lld/ELF/InputFiles.h +++ b/lld/ELF/InputFiles.h @@ -218,7 +218,7 @@ class ELFFileBase : public InputFile { public: uint32_t andFeatures = 0; bool hasCommonSyms = false; - SmallVector aarch64PauthAbiTag; + std::optional> aarch64PauthAbiTag; }; // .o file. diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp index 51a017d8fb6c..24737733a814 100644 --- a/lld/ELF/SyntheticSections.cpp +++ b/lld/ELF/SyntheticSections.cpp @@ -314,33 +314,56 @@ GnuPropertySection::GnuPropertySection() config->wordsize, ".note.gnu.property") {} void GnuPropertySection::writeTo(uint8_t *buf) { + write32(buf, 4); // Name size + write32(buf + 4, getSize() - 16); // Content size + write32(buf + 8, NT_GNU_PROPERTY_TYPE_0); // Type + memcpy(buf + 12, "GNU", 4); // Name string + uint32_t featureAndType = config->emachine == EM_AARCH64 ? GNU_PROPERTY_AARCH64_FEATURE_1_AND : GNU_PROPERTY_X86_FEATURE_1_AND; - 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, featureAndType); // Feature type - write32(buf + 20, 4); // Feature size - write32(buf + 24, config->andFeatures); // Feature flags - if (config->is64) - write32(buf + 28, 0); // Padding + unsigned offset = 16; + + if (config->andFeatures != 0) { + write32(buf + offset + 0, featureAndType); // Feature type + write32(buf + offset + 4, 4); // Feature size + write32(buf + offset + 8, config->andFeatures); // Feature flags + if (config->is64) + write32(buf + offset + 12, 0); // Padding + offset += 16; + } + + if (ctx.aarch64PauthAbiTag != std::nullopt) { + write32(buf + offset + 0, GNU_PROPERTY_AARCH64_FEATURE_PAUTH); + write32(buf + offset + 4, 8 * 2); + memcpy(buf + offset + 8, ctx.aarch64PauthAbiTag->data(), + ctx.aarch64PauthAbiTag->size()); + } } -size_t GnuPropertySection::getSize() const { return config->is64 ? 32 : 28; } +size_t GnuPropertySection::getSize() const { + uint32_t contentSize = 0; + if (config->andFeatures != 0) + contentSize += config->is64 ? 16 : 12; + if (ctx.aarch64PauthAbiTag != std::nullopt) { + assert(config->emachine == EM_AARCH64); + contentSize += 4 + 4 + 8 * 2; + } + assert(contentSize != 0); + return contentSize + 16; +} AArch64PauthAbiTag::AArch64PauthAbiTag() : SyntheticSection(llvm::ELF::SHF_ALLOC, llvm::ELF::SHT_NOTE, config->wordsize, ".note.AARCH64-PAUTH-ABI-tag") {} bool AArch64PauthAbiTag::isNeeded() const { - return !ctx.aarch64PauthAbiTag.empty(); + return ctx.aarch64PauthAbiTag != std::nullopt; } void AArch64PauthAbiTag::writeTo(uint8_t *buf) { - const SmallVector &data = ctx.aarch64PauthAbiTag; + std::array data = ctx.aarch64PauthAbiTag.value(); write32(buf, 4); // Name size write32(buf + 4, data.size()); // Content size write32(buf + 8, NT_ARM_TYPE_PAUTH_ABI_TAG); // Type @@ -349,11 +372,6 @@ void AArch64PauthAbiTag::writeTo(uint8_t *buf) { memset(buf + 16 + data.size(), 0, getSize() - 16 - data.size()); // Padding } -size_t AArch64PauthAbiTag::getSize() const { - return alignToPowerOf2(16 + ctx.aarch64PauthAbiTag.size(), - config->is64 ? 8 : 4); -} - BuildIdSection::BuildIdSection() : SyntheticSection(SHF_ALLOC, SHT_NOTE, 4, ".note.gnu.build-id"), hashSize(getHashSize()) {} diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h index d183a547c682..5b81d2005228 100644 --- a/lld/ELF/SyntheticSections.h +++ b/lld/ELF/SyntheticSections.h @@ -150,7 +150,7 @@ class AArch64PauthAbiTag final : public SyntheticSection { public: AArch64PauthAbiTag(); void writeTo(uint8_t *buf) override; - size_t getSize() const override; + size_t getSize() const override { return 32; }; bool isNeeded() const override; }; diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp index ed68da5d442e..a1ff1d2a7969 100644 --- a/lld/ELF/Writer.cpp +++ b/lld/ELF/Writer.cpp @@ -521,13 +521,18 @@ template void elf::createSyntheticSections() { in.iplt = std::make_unique(); add(*in.iplt); - if (config->andFeatures) + if (config->andFeatures || ctx.aarch64PauthAbiTag.has_value()) add(*make()); - if (!ctx.aarch64PauthAbiTag.empty()) { - in.aarch64PauthAbiTag = std::make_unique(); - add(*in.aarch64PauthAbiTag); - } + // TODO: alternative PAuth ELF marking way + // To be implemented after the following PRs are merged: + // - https://github.com/ARM-software/abi-aa/pull/240 + // - https://github.com/llvm/llvm-project/pull/72714 + // + // if (!ctx.aarch64PauthAbiTag.empty()) { + // in.aarch64PauthAbiTag = std::make_unique(); + // add(*in.aarch64PauthAbiTag); + // } // .note.GNU-stack is always added when we are creating a re-linkable // object file. Other linkers are using the presence of this marker diff --git a/lld/test/ELF/aarch64-feature-pauth-gnu-prop.s b/lld/test/ELF/aarch64-feature-pauth-gnu-prop.s new file mode 100644 index 000000000000..976f9c1955c1 --- /dev/null +++ b/lld/test/ELF/aarch64-feature-pauth-gnu-prop.s @@ -0,0 +1,96 @@ +# REQUIRES: aarch64 + +# RUN: rm -rf %t && split-file %s %t && cd %t + +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu abi-tag1.s -o tag11.o +# RUN: cp tag11.o tag12.o +# RUN: ld.lld -shared tag11.o tag12.o -o tagok.so +# RUN: llvm-readelf -n tagok.so | FileCheck --check-prefix OK %s + +# OK: Properties: AArch64 PAuth ABI tag: platform 0x2a, version 0x1 + +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu abi-tag2.s -o tag2.o +# RUN: not ld.lld tag11.o tag12.o tag2.o -o /dev/null 2>&1 | FileCheck --check-prefix ERR1 %s + +# ERR1: error: incompatible values of AArch64 PAuth compatibility info found +# ERR1: {{.*}}: 0x2A000000000000000{{1|2}}00000000000000 +# ERR1: {{.*}}: 0x2A000000000000000{{1|2}}00000000000000 + +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu abi-tag-short.s -o short.o +# RUN: not ld.lld short.o -o /dev/null 2>&1 | FileCheck --check-prefix ERR2 %s + +# ERR2: error: short.o:(.note.gnu.property+0x0): size of GNU_PROPERTY_AARCH64_FEATURE_PAUTH property must be 16 + +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu abi-tag-multiple.s -o multiple.o +# RUN: not ld.lld multiple.o -o /dev/null 2>&1 | FileCheck --check-prefix ERR3 %s +# ERR3: error: multiple.o:(.note.gnu.property+0x0): multiple GNU_PROPERTY_AARCH64_FEATURE_PAUTH properties are not allowed + +# RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu no-info.s -o noinfo1.o +# RUN: cp noinfo1.o noinfo2.o +# RUN: not ld.lld -z pauth-report=error tag11.o noinfo1.o noinfo2.o -o /dev/null 2>&1 | FileCheck --check-prefix ERR4 %s +# RUN: ld.lld -z pauth-report=warning tag11.o noinfo1.o noinfo2.o -o /dev/null 2>&1 | FileCheck --check-prefix WARN %s +# RUN: ld.lld -z pauth-report=none tag11.o noinfo1.o noinfo2.o -o /dev/null 2>&1 | FileCheck --check-prefix NONE %s + +# ERR4: error: {{.*}}noinfo1.o has no AArch64 PAuth compatibility info while {{.*}}tag11.o has one; either all or no input files must have it +# ERR4-NEXT: error: {{.*}}noinfo2.o has no AArch64 PAuth compatibility info while {{.*}}tag11.o has one; either all or no input files must have it +# WARN: warning: {{.*}}noinfo1.o has no AArch64 PAuth compatibility info while {{.*}}tag11.o has one; either all or no input files must have it +# WARN-NEXT: warning: {{.*}}noinfo2.o has no AArch64 PAuth compatibility info while {{.*}}tag11.o has one; either all or no input files must have it +# NONE-NOT: {{.*}} has no AArch64 PAuth compatibility info while {{.*}} has one; either all or no input files must have it + +#--- abi-tag-short.s + +# Version is 4 bytes instead of 8 bytes, must emit an error + +.section ".note.gnu.property", "a" +.long 4 +.long 20 +.long 5 +.asciz "GNU" +.long 0xc0000001 +.long 12 +.quad 2 +.long 31 + +#--- abi-tag-multiple.s + +.section ".note.gnu.property", "a" +.long 4 +.long 48 +.long 5 +.asciz "GNU" +.long 0xc0000001 +.long 16 +.quad 42 // platform +.quad 1 // version +.long 0xc0000001 +.long 16 +.quad 42 // platform +.quad 1 // version + +#--- abi-tag1.s + +.section ".note.gnu.property", "a" +.long 4 +.long 24 +.long 5 +.asciz "GNU" +.long 0xc0000001 +.long 16 +.quad 42 // platform +.quad 1 // version + +#--- abi-tag2.s + +.section ".note.gnu.property", "a" +.long 4 +.long 24 +.long 5 +.asciz "GNU" +.long 0xc0000001 +.long 16 +.quad 42 // platform +.quad 2 // version + +#--- no-info.s + +.section ".test", "a" diff --git a/lld/test/ELF/aarch64-feature-pauth.s b/lld/test/ELF/aarch64-feature-pauth.s index 0520b2f28631..c7fe27b330ac 100644 --- a/lld/test/ELF/aarch64-feature-pauth.s +++ b/lld/test/ELF/aarch64-feature-pauth.s @@ -1,5 +1,11 @@ # REQUIRES: aarch64 +# XFAIL: * +# TODO: alternative PAuth ELF marking way +# To be implemented after the following PRs are merged: +# - https://github.com/ARM-software/abi-aa/pull/240 +# - https://github.com/llvm/llvm-project/pull/72714 + # RUN: rm -rf %t && split-file %s %t && cd %t # RUN: llvm-mc -filetype=obj -triple=aarch64-linux-gnu abi-tag1.s -o tag11.o