-
Notifications
You must be signed in to change notification settings - Fork 14.8k
[lld][AArch64][Build Attributes] Add support for converting AArch64 Build Attributes to GNU Properties #131990
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 9 commits
f98c952
d4299e9
783dbf9
5dfca32
8b6377a
9d81bc0
9c8f950
94bebe0
e73a6bd
60a5eea
9052abf
52869a1
534e55e
02a1fa7
ec89018
7292c36
d026726
6709e9a
04e2a16
eb2abbc
eb84c8c
be821a5
f9a0fcb
ab6dc73
8a21905
fd365cb
024847d
f9d7aa8
7903817
f225428
0e643e5
f187b20
81a86d8
592100f
fe23b71
dceec93
de3e43e
72e725a
902f9ef
2df0fcb
ecd2c51
d439930
22c6478
3321d46
17dbb95
ed7b5e6
f0eb74d
6a541b8
4620091
c8637a0
1470bdf
98dc05f
57d7ff9
ddb1b3e
debcd30
deaf7de
88359f4
64a45ed
8873a15
9e70cbe
bbb33fe
711ea6f
b46b466
24f4b80
8e27785
da33de6
5cb1940
572e5f4
31214b8
7d2d795
b2803a3
cb49f85
dd89ea1
93f21f2
e40609a
3f5b14c
5013203
9738781
45f7246
d802fcc
ae1a032
b5c882e
54fe275
afca83d
20760a9
8b1e002
37a3aec
da6ddb2
b3e62ec
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,16 +20,21 @@ | |
#include "lld/Common/DWARF.h" | ||
#include "llvm/ADT/CachedHashString.h" | ||
#include "llvm/ADT/STLExtras.h" | ||
#include "llvm/ADT/StringRef.h" | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#include "llvm/LTO/LTO.h" | ||
#include "llvm/Object/IRObjectFile.h" | ||
#include "llvm/Support/AArch64AttributeParser.h" | ||
#include "llvm/Support/ARMAttributeParser.h" | ||
#include "llvm/Support/ARMBuildAttributes.h" | ||
#include "llvm/Support/ELFAttributes.h" | ||
#include "llvm/Support/Endian.h" | ||
#include "llvm/Support/Errc.h" | ||
#include "llvm/Support/FileSystem.h" | ||
#include "llvm/Support/Path.h" | ||
#include "llvm/Support/RISCVAttributeParser.h" | ||
#include "llvm/Support/TimeProfiler.h" | ||
#include "llvm/Support/raw_ostream.h" | ||
#include <cassert> | ||
#include <optional> | ||
|
||
using namespace llvm; | ||
|
@@ -207,6 +212,166 @@ static void updateSupportedARMFeatures(Ctx &ctx, | |
ctx.arg.armHasThumb2ISA |= thumb && *thumb >= ARMBuildAttrs::AllowThumb32; | ||
} | ||
|
||
// Sanitize pauth values | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
static void sanitizePauthSubSection( | ||
Ctx &ctx, std::optional<llvm::BuildAttributeSubSection> &pauthSubSection, | ||
InputSection isec) { | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/* | ||
Incomplete data: ignore | ||
*/ | ||
if (!pauthSubSection) | ||
return; | ||
// Currently there are 2 known tags defined for the pauth subsection, | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// however, user is allowed to add other, unknown tag. If such tags exists, | ||
// remove them. (no need to check for duplicates, they should not be possible) | ||
pauthSubSection->Content.erase( | ||
std::remove_if(pauthSubSection->Content.begin(), | ||
pauthSubSection->Content.end(), | ||
[](const BuildAttributeItem &item) { | ||
return item.Tag != 1 && item.Tag != 2; | ||
}), | ||
pauthSubSection->Content.end()); | ||
|
||
if (pauthSubSection->Content.size() < 2) { | ||
if (0 == pauthSubSection->Content.size()) | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Warn(ctx) << &isec | ||
<< ": AArch64 Build Attributes: empty 'aeabi_pauthabi' " | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"subsection detected; ignoring subsection"; | ||
if (1 == pauthSubSection->Content.size()) { | ||
if (1 == pauthSubSection->Content[0].Tag) | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Warn(ctx) | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<< &isec | ||
<< ": AArch64 Build Attributes: 'aeabi_pauthabi' subsection " | ||
"contains only an ID (scheme missing); ignoring subsection"; | ||
if (2 == pauthSubSection->Content[0].Tag) | ||
Warn(ctx) << &isec | ||
<< ": AArch64 Build Attributes: 'aeabi_pauthabi' subsection " | ||
"contains only a scheme (ID missing); ignoring subsection"; | ||
} | ||
pauthSubSection = std::nullopt; | ||
return; | ||
} | ||
// printvec(*pauthSubSection); | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert(2 == pauthSubSection->Content.size() && "vector size should be 2"); | ||
std::sort(pauthSubSection->Content.begin(), pauthSubSection->Content.end(), | ||
[](const auto &a, const auto &b) { return a.Tag < b.Tag; }); | ||
assert(1 == pauthSubSection->Content[0].Tag && "first tag should be 1"); | ||
assert(2 == pauthSubSection->Content[1].Tag && "first tag should be 2"); | ||
} | ||
|
||
// Sanitize features bits | ||
static void sanitizeFAndBSubSection( | ||
std::optional<llvm::BuildAttributeSubSection> &fAndBSubSection) { | ||
/* | ||
Same as gnu properties: treat a missing 'aeabi_feature_and_bits' feature as | ||
being set to 0 | ||
*/ | ||
if (!fAndBSubSection) { | ||
fAndBSubSection.emplace("aeabi_feature_and_bits", 1, 0, | ||
SmallVector<BuildAttributeItem, 64>()); | ||
} else { | ||
// Currently there are 3 known tags defined for the features and bits | ||
// subsection, however, user is allowed to add other, unknown tag. If such | ||
// tags exists, remove them. (duplicates are not possible) | ||
fAndBSubSection->Content.erase( | ||
std::remove_if(fAndBSubSection->Content.begin(), | ||
fAndBSubSection->Content.end(), | ||
[](const BuildAttributeItem &item) { | ||
return item.Tag != 0 && item.Tag != 1 && item.Tag != 2; | ||
}), | ||
fAndBSubSection->Content.end()); | ||
} | ||
|
||
constexpr unsigned tagBTI = 0, tagPAC = 1, tagGCS = 2; | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Find missing tags | ||
std::set<unsigned> requiredTags = {tagBTI, tagPAC, tagGCS}; | ||
for (const auto &item : fAndBSubSection->Content) | ||
requiredTags.erase(item.Tag); | ||
|
||
// Add missing tags | ||
for (const auto &tag : requiredTags) | ||
fAndBSubSection->Content.push_back( | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
BuildAttributeItem(BuildAttributeItem::NumericAttribute, tag, 0, "")); | ||
|
||
assert(3 == fAndBSubSection->Content.size() && "vector size should be 3"); | ||
std::sort(fAndBSubSection->Content.begin(), fAndBSubSection->Content.end(), | ||
[](const auto &a, const auto &b) { return a.Tag < b.Tag; }); | ||
assert(0 == fAndBSubSection->Content[0].Tag && "first tag should be 0"); | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
assert(1 == fAndBSubSection->Content[1].Tag && "first tag should be 1"); | ||
assert(2 == fAndBSubSection->Content[2].Tag && "first tag should be 2"); | ||
} | ||
|
||
static std::array<std::optional<llvm::BuildAttributeSubSection>, 2> | ||
extractBuildAttributesSubsection( | ||
Ctx &ctx, | ||
const SmallVector<llvm::BuildAttributeSubSection, 8> | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
&buildAttributesSubsections, | ||
InputSection isec) { | ||
|
||
std::optional<llvm::BuildAttributeSubSection> newPauthSubSection; | ||
std::optional<llvm::BuildAttributeSubSection> newFAndBSubSection; | ||
|
||
for (const auto &newSubSection : buildAttributesSubsections) { | ||
if (newPauthSubSection && newFAndBSubSection) | ||
break; | ||
if ("aeabi_pauthabi" == newSubSection.Name) { | ||
newPauthSubSection.emplace(newSubSection); | ||
continue; | ||
} | ||
if ("aeabi_feature_and_bits" == newSubSection.Name) { | ||
newFAndBSubSection.emplace(newSubSection); | ||
} | ||
} | ||
sanitizePauthSubSection(ctx, newPauthSubSection, isec); | ||
sanitizeFAndBSubSection(newFAndBSubSection); | ||
|
||
return {std::move(newPauthSubSection), std::move(newFAndBSubSection)}; | ||
} | ||
|
||
// Merge AArch64 Build Attributes subsection | ||
static void mergeAArch64BuildAttributes( | ||
Ctx &ctx, | ||
const std::array<std::optional<llvm::BuildAttributeSubSection>, 2> | ||
&buildAttributesSubsections, | ||
InputSection isec) { | ||
|
||
auto [newPauthSubSection, newFAndBSubSection] = buildAttributesSubsections; | ||
|
||
if (ctx.mergedPauthSubSection == std::nullopt) { | ||
ctx.mergedPauthSubSection = newPauthSubSection; | ||
} | ||
|
||
if (ctx.mergedFAndBSubSection == std::nullopt) | ||
ctx.mergedFAndBSubSection = newFAndBSubSection; | ||
|
||
if (newPauthSubSection) { | ||
// Since sanitizePauthSubSection sorts, we know that both vectors align. | ||
// Merge pauth (values has to match) | ||
if ((ctx.mergedPauthSubSection->Content[0].IntValue != | ||
newPauthSubSection->Content[0].IntValue) || | ||
ctx.mergedPauthSubSection->Content[1].IntValue != | ||
newPauthSubSection->Content[1].IntValue) { | ||
ctx.mergedPauthSubSection->Content[0].IntValue = | ||
std::numeric_limits<unsigned>::max(); | ||
ctx.mergedPauthSubSection->Content[1].IntValue = | ||
std::numeric_limits<unsigned>::max(); | ||
Warn(ctx) | ||
<< &isec | ||
<< ": AArch64 Build Attributes: mismatch in 'aeabi_pauthabi' values " | ||
"detected; marking 'aeabi_pauthabi' as invalid for this project"; | ||
} | ||
} | ||
|
||
// Since sanitizeFAndBSubSection sorts, we know that both vectors align. | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Merge Features and Bits | ||
ctx.mergedFAndBSubSection->Content[0].IntValue &= | ||
newFAndBSubSection->Content[0].IntValue; | ||
ctx.mergedFAndBSubSection->Content[1].IntValue &= | ||
newFAndBSubSection->Content[1].IntValue; | ||
ctx.mergedFAndBSubSection->Content[2].IntValue &= | ||
newFAndBSubSection->Content[2].IntValue; | ||
} | ||
|
||
InputFile::InputFile(Ctx &ctx, Kind k, MemoryBufferRef m) | ||
: ctx(ctx), mb(m), groupId(ctx.driver.nextGroupId), fileKind(k) { | ||
// All files within the same --{start,end}-group get the same group ID. | ||
|
@@ -552,6 +717,7 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) { | |
// done in parallel. | ||
ArrayRef<Elf_Shdr> objSections = getELFShdrs<ELFT>(); | ||
StringRef shstrtab = CHECK2(obj.getSectionStringTable(objSections), this); | ||
bool hasGnuProperties = false; | ||
uint64_t size = objSections.size(); | ||
sections.resize(size); | ||
for (size_t i = 0; i != size; ++i) { | ||
|
@@ -574,9 +740,12 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) { | |
.try_emplace(CachedHashStringRef(signature), this) | ||
.second; | ||
if (keepGroup) { | ||
if (!ctx.arg.resolveGroups) | ||
sections[i] = createInputSection( | ||
i, sec, check(obj.getSectionName(sec, shstrtab))); | ||
if (!ctx.arg.resolveGroups) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unnecessary change. |
||
StringRef name = check(obj.getSectionName(sec, shstrtab)); | ||
if (name == ".note.gnu.property") | ||
hasGnuProperties = true; | ||
sections[i] = createInputSection(i, sec, name); | ||
} | ||
} else { | ||
// Otherwise, discard group members. | ||
for (uint32_t secIndex : entries.slice(1)) { | ||
|
@@ -638,25 +807,73 @@ template <class ELFT> void ObjFile<ELFT>::parse(bool ignoreComdats) { | |
} | ||
} | ||
break; | ||
case EM_AARCH64: | ||
// FIXME: BuildAttributes have been implemented in llvm, but not yet in | ||
// lld. Remove the section so that it does not accumulate in the output | ||
// file. When support is implemented we expect not to output a build | ||
// attributes section in files of type ET_EXEC or ET_SHARED, but ld -r | ||
// ouptut will need a single merged attributes section. | ||
if (sec.sh_type == SHT_AARCH64_ATTRIBUTES) | ||
case EM_AARCH64: { | ||
// The specification states that if a file contains both GNU properties | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The second sentence here doesn't logically follow from the first. The spec allows for this behaviour, but it would be better to check that the two encodings match (if both are present in one object file), and emit a warning if they do not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changing to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To be clear, while I'm happy for this error checking to not be included in this PR, it's still very important because silently ignoring a build attribute can result in a security feature being disabled. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to depend also on your comment below:
If it is indeed possible to have any of the subsections in one file but not in the other, the files will have to be read. Which is basically depends on the meaning of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now GNU properties content is being checked |
||
// and AArch64 build attributes, they must be identical. Therefore, if a | ||
// file contains GNU properties, the AArch64 build attributes are ignored. | ||
// If a file does not contain GNU properties, we leverage the existing GNU | ||
// properties mechanism by populating the corresponding data structures, | ||
// which will later be handled by Driver.cpp::readSecurityNotes. This | ||
// ensures that AArch64 build attributes are represented in the linked | ||
// object file as GNU properties, which are already supported by the Linux | ||
// kernel and the dynamic dispatcher. | ||
if (sec.sh_type == SHT_AARCH64_ATTRIBUTES) { | ||
StringRef name = check(obj.getSectionName(sec, shstrtab)); | ||
AArch64AttributeParser attributes; | ||
ArrayRef<uint8_t> contents = check(obj.getSectionContents(sec)); | ||
if (Error e = attributes.parse(contents, ELFT::Endianness)) { | ||
InputSection isec(*this, sec, name); | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Warn(ctx) << &isec << ": " << std::move(e); | ||
} else { | ||
// for functions that has to warn/err/report | ||
InputSection isec(*this, sec, name); | ||
const SmallVector<llvm::BuildAttributeSubSection, 8> | ||
buildAttributesSubSections = | ||
attributes.getBuildAttributesSection(); | ||
auto subsections = extractBuildAttributesSubsection( | ||
ctx, buildAttributesSubSections, isec); | ||
mergeAArch64BuildAttributes(ctx, subsections, isec); | ||
if (!hasGnuProperties) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if the input object has a GNU properties section, but it does not have both of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Now content of GP is being read. |
||
ObjFile<ELFT> &f = *this; | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
auto [pauthSubSection, fAndBSubSection] = subsections; | ||
if (pauthSubSection) { | ||
assert( | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(pauthSubSection->Content.size() == 2) && | ||
"pauthSubSection must contain exactly two build attributes"); | ||
// sanitizePauthSubSection already sorts | ||
f.aarch64PauthAbiCoreInfoStorage = | ||
std::make_unique<std::array<uint8_t, 16>>(); | ||
uint64_t values[2] = { | ||
static_cast<uint64_t>(pauthSubSection->Content[0].IntValue), | ||
static_cast<uint64_t>(pauthSubSection->Content[1].IntValue)}; | ||
std::memcpy(f.aarch64PauthAbiCoreInfoStorage->data(), &values[0], | ||
sizeof(values)); | ||
f.aarch64PauthAbiCoreInfo = *f.aarch64PauthAbiCoreInfoStorage; | ||
} | ||
if (fAndBSubSection) { | ||
assert((fAndBSubSection->Content.size() == 3) && | ||
"fAndBSubSection must contain exactly three build " | ||
"attributes"); | ||
// sanitizeFAndBSubSection already sorts | ||
f.andFeatures = 0; | ||
f.andFeatures |= (fAndBSubSection->Content[0].IntValue) << 0; | ||
f.andFeatures |= (fAndBSubSection->Content[1].IntValue) << 1; | ||
f.andFeatures |= (fAndBSubSection->Content[2].IntValue) << 2; | ||
} | ||
} | ||
} | ||
sections[i] = &InputSection::discarded; | ||
// Producing a static binary with MTE globals is not currently supported, | ||
// remove all SHT_AARCH64_MEMTAG_GLOBALS_STATIC sections as they're unused | ||
// medatada, and we don't want them to end up in the output file for | ||
// static executables. | ||
} | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Producing a static binary with MTE globals is not currently | ||
// supported, remove all SHT_AARCH64_MEMTAG_GLOBALS_STATIC sections as | ||
// they're unused metadata, and we don't want them to end up in the | ||
// output file for static executables. | ||
if (sec.sh_type == SHT_AARCH64_MEMTAG_GLOBALS_STATIC && | ||
!canHaveMemtagGlobals(ctx)) | ||
sections[i] = &InputSection::discarded; | ||
break; | ||
} break; | ||
} | ||
} | ||
|
||
Stylie777 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Read a symbol table. | ||
initializeSymbols(obj); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ | |
#include "llvm/Object/ELF.h" | ||
#include "llvm/Support/MemoryBufferRef.h" | ||
#include "llvm/Support/Threading.h" | ||
#include <memory> | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
namespace llvm { | ||
struct DILineInfo; | ||
|
@@ -199,7 +200,7 @@ class ELFFileBase : public InputFile { | |
} | ||
MutableArrayRef<Symbol *> getMutableGlobalSymbols() { | ||
return llvm::MutableArrayRef(symbols.get() + firstGlobal, | ||
numSymbols - firstGlobal); | ||
numSymbols - firstGlobal); | ||
sivan-shani marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. drop irrelevant formatting. Use sth like
|
||
} | ||
|
||
template <typename ELFT> typename ELFT::ShdrRange getELFShdrs() const { | ||
|
@@ -239,9 +240,11 @@ class ELFFileBase : public InputFile { | |
public: | ||
// Name of source file obtained from STT_FILE, if present. | ||
StringRef sourceFile; | ||
std::unique_ptr<std::string> sourceFileStorage; | ||
uint32_t andFeatures = 0; | ||
bool hasCommonSyms = false; | ||
ArrayRef<uint8_t> aarch64PauthAbiCoreInfo; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this ever point anywhere other than There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No
Yes In other places There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. An alternative that involves a bit of refactoring of the existing aarch64PauthABICoreInfo, which is optimal for a note section, but not ideal for build attributes, is to deserialize the aarch64PauthABICoreInfo and store the members as two uint64_t instead of an arrayRef. We'll need to update the comparison code in Driver.cpp and in the .note.gnu.property writing code, but it may be neater overall. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the proposed approach could lead to a cleaner design overall, but given the scope and context of the current changes, it feels a bit too involved to refactor at this stage. It might be better to revisit this as a follow-up if needed. |
||
std::unique_ptr<std::array<uint8_t, 16>> aarch64PauthAbiCoreInfoStorage; | ||
}; | ||
|
||
// .o file. | ||
|
@@ -268,7 +271,6 @@ template <class ELFT> class ObjFile : public ELFFileBase { | |
|
||
uint32_t getSectionIndex(const Elf_Sym &sym) const; | ||
|
||
|
||
// Pointer to this input file's .llvm_addrsig section, if it has one. | ||
const Elf_Shdr *addrsigSec = nullptr; | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.