Skip to content

Commit

Permalink
[AArch64][PAC][lld][ELF] Support GNU_PROPERTY_AARCH64_FEATURE_PAUTH
Browse files Browse the repository at this point in the history
- In new PAuth ABI version, support for extra data (except platform and
  version) is dropped. Change container for holding the tag from
  `SmallVector<uint8_t>` to `std::optional<std::array<uint8_t, 16>>`.

- 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.
  • Loading branch information
kovdan01 committed Feb 16, 2024
1 parent a3ddb4e commit e4ff3bf
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 47 deletions.
3 changes: 2 additions & 1 deletion lld/ELF/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/GlobPattern.h"
#include "llvm/Support/PrettyStackTrace.h"
#include <array>
#include <atomic>
#include <memory>
#include <optional>
Expand Down Expand Up @@ -492,7 +493,7 @@ struct Ctx {

llvm::raw_fd_ostream openAuxiliaryFile(llvm::StringRef, std::error_code &);

SmallVector<uint8_t, 0> aarch64PauthAbiTag;
std::optional<std::array<uint8_t, 16>> aarch64PauthAbiTag;
};

LLVM_LIBRARY_VISIBILITY extern Ctx ctx;
Expand Down
20 changes: 10 additions & 10 deletions lld/ELF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2662,17 +2662,17 @@ 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;

ctx.aarch64PauthAbiTag = (*NonEmptyIt)->aarch64PauthAbiTag;
StringRef F1 = (*NonEmptyIt)->getName();
for (ELFFileBase *F : ArrayRef(ctx.objectFiles)) {
StringRef F2 = F->getName();
const SmallVector<uint8_t, 0> &D1 = ctx.aarch64PauthAbiTag;
const SmallVector<uint8_t, 0> &D2 = F->aarch64PauthAbiTag;
if (D1.empty() != D2.empty()) {
std::optional<std::array<uint8_t, 16>> D1 = ctx.aarch64PauthAbiTag;
std::optional<std::array<uint8_t, 16>> D2 = F->aarch64PauthAbiTag;
if (D1.has_value() != D2.has_value()) {
auto Helper = [](StringRef Report, const Twine &Msg) {
if (Report == "warning")
warn(Msg);
Expand All @@ -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())));
}
}

Expand Down
53 changes: 41 additions & 12 deletions lld/ELF/InputFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
#include "llvm/Support/TarWriter.h"
#include "llvm/Support/raw_ostream.h"

#include <tuple>

using namespace llvm;
using namespace llvm::ELF;
using namespace llvm::object;
Expand Down Expand Up @@ -878,11 +880,14 @@ void ObjFile<ELFT>::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 <class ELFT> static uint32_t readAndFeatures(const InputSection &sec) {
template <class ELFT>
static std::pair<uint32_t, std::optional<std::array<uint8_t, 16>>>
readGnuProperty(const InputSection &sec) {
using Elf_Nhdr = typename ELFT::Nhdr;
using Elf_Note = typename ELFT::Note;

uint32_t featuresSet = 0;
std::optional<std::array<uint8_t, 16>> aarch64PauthAbiTag;
ArrayRef<uint8_t> data = sec.content();
auto reportFatal = [&](const uint8_t *place, const char *msg) {
fatal(toString(sec.file) + ":(" + sec.name + "+0x" +
Expand Down Expand Up @@ -924,6 +929,19 @@ template <class ELFT> static uint32_t readAndFeatures(const InputSection &sec) {
if (size < 4)
reportFatal(place, "FEATURE_1_AND entry is too short");
featuresSet |= read32<ELFT::TargetEndianness>(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<uint8_t, 16>{};
memcpy(aarch64PauthAbiTag->data(), desc.data(),
aarch64PauthAbiTag->size());
}

// Padding is present in the note descriptor, if necessary.
Expand All @@ -934,7 +952,7 @@ template <class ELFT> 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
Expand Down Expand Up @@ -966,13 +984,15 @@ static void readAArch64PauthAbiTag(const InputSection &sec, ObjFile<ELFT> &f) {
" (ARM expected)");

ArrayRef<uint8_t> 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<uint8_t, 0>(iterator_range(desc));
f.aarch64PauthAbiTag = std::array<uint8_t, 16>{};
memcpy(f.aarch64PauthAbiTag->data(), desc.data(),
f.aarch64PauthAbiTag->size());
}

template <class ELFT>
Expand Down Expand Up @@ -1029,15 +1049,24 @@ InputSectionBase *ObjFile<ELFT>::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<ELFT>(InputSection(*this, sec, name));
std::tie(this->andFeatures, this->aarch64PauthAbiTag) =
readGnuProperty<ELFT>(InputSection(*this, sec, name));
return &InputSection::discarded;
}

if (config->emachine == EM_AARCH64 &&
name == ".note.AARCH64-PAUTH-ABI-tag") {
readAArch64PauthAbiTag<ELFT>(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<ELFT>(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,
Expand Down
2 changes: 1 addition & 1 deletion lld/ELF/InputFiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ class ELFFileBase : public InputFile {
public:
uint32_t andFeatures = 0;
bool hasCommonSyms = false;
SmallVector<uint8_t, 0> aarch64PauthAbiTag;
std::optional<std::array<uint8_t, 16>> aarch64PauthAbiTag;
};

// .o file.
Expand Down
52 changes: 35 additions & 17 deletions lld/ELF/SyntheticSections.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t, 0> &data = ctx.aarch64PauthAbiTag;
std::array<uint8_t, 16> 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
Expand All @@ -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()) {}
Expand Down
2 changes: 1 addition & 1 deletion lld/ELF/SyntheticSections.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

Expand Down
15 changes: 10 additions & 5 deletions lld/ELF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -521,13 +521,18 @@ template <class ELFT> void elf::createSyntheticSections() {
in.iplt = std::make_unique<IpltSection>();
add(*in.iplt);

if (config->andFeatures)
if (config->andFeatures || ctx.aarch64PauthAbiTag.has_value())
add(*make<GnuPropertySection>());

if (!ctx.aarch64PauthAbiTag.empty()) {
in.aarch64PauthAbiTag = std::make_unique<AArch64PauthAbiTag>();
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<AArch64PauthAbiTag>();
// 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
Expand Down
96 changes: 96 additions & 0 deletions lld/test/ELF/aarch64-feature-pauth-gnu-prop.s
Original file line number Diff line number Diff line change
@@ -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"
Loading

0 comments on commit e4ff3bf

Please sign in to comment.