[lld-macho] add code signature for native arm64 macOS

Differential Revision: https://reviews.llvm.org/D96164
This commit is contained in:
Greg McGary 2021-01-06 18:11:44 -08:00
parent e9445765a5
commit 151990dd94
5 changed files with 355 additions and 1 deletions

View File

@ -45,6 +45,7 @@ public:
size_t numNonHiddenSections() const;
uint64_t fileOff = 0;
uint64_t fileSize = 0;
StringRef name;
uint32_t maxProt = 0;
uint32_t initProt = 0;

View File

@ -24,6 +24,11 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/LEB128.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SHA256.h"
#if defined(__APPLE__)
#include <sys/mman.h>
#endif
using namespace llvm;
using namespace llvm::support;
@ -887,3 +892,93 @@ void StringTableSection::writeTo(uint8_t *buf) const {
off += str.size() + 1; // account for null terminator
}
}
CodeSignatureSection::CodeSignatureSection()
: LinkEditSection(segment_names::linkEdit, section_names::codeSignature) {
align = 16; // required by libstuff
fileName = config->outputFile;
size_t slashIndex = fileName.rfind("/");
if (slashIndex != std::string::npos)
fileName = fileName.drop_front(slashIndex + 1);
allHeadersSize = alignTo<16>(fixedHeadersSize + fileName.size() + 1);
fileNamePad = allHeadersSize - fixedHeadersSize - fileName.size();
}
uint32_t CodeSignatureSection::getBlockCount() const {
return (fileOff + blockSize - 1) / blockSize;
}
uint64_t CodeSignatureSection::getRawSize() const {
return allHeadersSize + getBlockCount() * hashSize;
}
void CodeSignatureSection::writeHashes(uint8_t *buf) const {
uint8_t *code = buf;
uint8_t *codeEnd = buf + fileOff;
uint8_t *hashes = codeEnd + allHeadersSize;
while (code < codeEnd) {
StringRef block(reinterpret_cast<char *>(code),
std::min(codeEnd - code, static_cast<ssize_t>(blockSize)));
SHA256 hasher;
hasher.update(block);
StringRef hash = hasher.final();
assert(hash.size() == hashSize);
memcpy(hashes, hash.data(), hashSize);
code += blockSize;
hashes += hashSize;
}
#if defined(__APPLE__)
// This is macOS-specific work-around and makes no sense for any
// other host OS. See https://openradar.appspot.com/FB8914231
//
// The macOS kernel maintains a signature-verification cache to
// quickly validate applications at time of execve(2). The trouble
// is that for the kernel creates the cache entry at the time of the
// mmap(2) call, before we have a chance to write either the code to
// sign or the signature header+hashes. The fix is to invalidate
// all cached data associated with the output file, thus discarding
// the bogus prematurely-cached signature.
msync(buf, fileOff + getSize(), MS_INVALIDATE);
#endif
}
void CodeSignatureSection::writeTo(uint8_t *buf) const {
using namespace llvm::MachO;
uint32_t signatureSize = static_cast<uint32_t>(getSize());
auto *superBlob = reinterpret_cast<CS_SuperBlob *>(buf);
write32be(&superBlob->magic, CSMAGIC_EMBEDDED_SIGNATURE);
write32be(&superBlob->length, signatureSize);
write32be(&superBlob->count, 1);
auto *blobIndex = reinterpret_cast<CS_BlobIndex *>(&superBlob[1]);
write32be(&blobIndex->type, CSSLOT_CODEDIRECTORY);
write32be(&blobIndex->offset, blobHeadersSize);
auto *codeDirectory =
reinterpret_cast<CS_CodeDirectory *>(buf + blobHeadersSize);
write32be(&codeDirectory->magic, CSMAGIC_CODEDIRECTORY);
write32be(&codeDirectory->length, signatureSize - blobHeadersSize);
write32be(&codeDirectory->version, CS_SUPPORTSEXECSEG);
write32be(&codeDirectory->flags, CS_ADHOC | CS_LINKER_SIGNED);
write32be(&codeDirectory->hashOffset,
sizeof(CS_CodeDirectory) + fileName.size() + fileNamePad);
write32be(&codeDirectory->identOffset, sizeof(CS_CodeDirectory));
codeDirectory->nSpecialSlots = 0;
write32be(&codeDirectory->nCodeSlots, getBlockCount());
write32be(&codeDirectory->codeLimit, fileOff);
codeDirectory->hashSize = static_cast<uint8_t>(hashSize);
codeDirectory->hashType = kSecCodeSignatureHashSHA256;
codeDirectory->platform = 0;
codeDirectory->pageSize = blockSizeShift;
codeDirectory->spare2 = 0;
codeDirectory->scatterOffset = 0;
codeDirectory->teamOffset = 0;
codeDirectory->spare3 = 0;
codeDirectory->codeLimit64 = 0;
OutputSegment *textSeg = getOrCreateOutputSegment(segment_names::text);
write64be(&codeDirectory->execSegBase, textSeg->fileOff);
write64be(&codeDirectory->execSegLimit, textSeg->fileSize);
write64be(&codeDirectory->execSegFlags,
config->outputType == MH_EXECUTE ? CS_EXECSEG_MAIN_BINARY : 0);
auto *id = reinterpret_cast<char *>(&codeDirectory[1]);
memcpy(id, fileName.begin(), fileName.size());
memset(id + fileName.size(), 0, fileNamePad);
}

View File

@ -18,6 +18,7 @@
#include "llvm/ADT/PointerUnion.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
namespace llvm {
@ -40,6 +41,7 @@ constexpr const char export_[] = "__export";
constexpr const char symbolTable[] = "__symbol_table";
constexpr const char indirectSymbolTable[] = "__ind_sym_tab";
constexpr const char stringTable[] = "__string_table";
constexpr const char codeSignature[] = "__code_signature";
constexpr const char got[] = "__got";
constexpr const char threadPtrs[] = "__thread_ptrs";
constexpr const char unwindInfo[] = "__unwind_info";
@ -94,7 +96,7 @@ public:
// NOTE: This assumes that the extra bytes required for alignment can be
// zero-valued bytes.
uint64_t getSize() const override final {
return llvm::alignTo(getRawSize(), WordSize);
return llvm::alignTo(getRawSize(), align);
}
};
@ -482,6 +484,32 @@ public:
void writeTo(uint8_t *buf) const override;
};
// The code signature comes at the very end of the linked output file.
class CodeSignatureSection : public LinkEditSection {
public:
static constexpr uint8_t blockSizeShift = 12;
static constexpr size_t blockSize = (1 << blockSizeShift); // 4 KiB
static constexpr size_t hashSize = 256 / 8;
static constexpr size_t blobHeadersSize = llvm::alignTo<8>(
sizeof(llvm::MachO::CS_SuperBlob) + sizeof(llvm::MachO::CS_BlobIndex));
static constexpr uint32_t fixedHeadersSize =
blobHeadersSize + sizeof(llvm::MachO::CS_CodeDirectory);
uint32_t fileNamePad = 0;
uint32_t allHeadersSize = 0;
StringRef fileName;
CodeSignatureSection();
uint64_t getRawSize() const override;
bool isNeeded() const override { return true; }
void writeTo(uint8_t *buf) const override;
uint32_t getBlockCount() const;
void writeHashes(uint8_t *buf) const;
};
static_assert((CodeSignatureSection::blobHeadersSize % 8) == 0, "");
static_assert((CodeSignatureSection::fixedHeadersSize % 8) == 0, "");
struct InStruct {
MachHeaderSection *header = nullptr;
RebaseSection *rebase = nullptr;

View File

@ -52,6 +52,7 @@ public:
void openFile();
void writeSections();
void writeUuid();
void writeCodeSignature();
void run();
@ -62,6 +63,7 @@ public:
StringTableSection *stringTableSection = nullptr;
SymtabSection *symtabSection = nullptr;
IndirectSymtabSection *indirectSymtabSection = nullptr;
CodeSignatureSection *codeSignatureSection = nullptr;
UnwindInfoSection *unwindInfoSection = nullptr;
LCUuid *uuidCommand = nullptr;
};
@ -400,6 +402,23 @@ public:
mutable uint8_t *uuidBuf;
};
class LCCodeSignature : public LoadCommand {
public:
LCCodeSignature(CodeSignatureSection *section) : section(section) {}
uint32_t getSize() const override { return sizeof(linkedit_data_command); }
void writeTo(uint8_t *buf) const override {
auto *c = reinterpret_cast<linkedit_data_command *>(buf);
c->cmd = LC_CODE_SIGNATURE;
c->cmdsize = getSize();
c->dataoff = static_cast<uint32_t>(section->fileOff);
c->datasize = section->getSize();
}
CodeSignatureSection *section;
};
} // namespace
static void prepareSymbolRelocation(lld::macho::Symbol *sym,
@ -521,6 +540,9 @@ void Writer::createLoadCommands() {
}
}
if (codeSignatureSection)
in.header->addLoadCommand(make<LCCodeSignature>(codeSignatureSection));
const uint32_t MACOS_MAXPATHLEN = 1024;
config->headerPad = std::max(
config->headerPad, (config->headerPadMaxInstallNames
@ -624,6 +646,7 @@ static int sectionOrder(OutputSection *osec) {
.Case(section_names::symbolTable, -3)
.Case(section_names::indirectSymbolTable, -2)
.Case(section_names::stringTable, -1)
.Case(section_names::codeSignature, std::numeric_limits<int>::max())
.Default(0);
}
// ZeroFill sections must always be the at the end of their segments,
@ -678,6 +701,9 @@ void Writer::createOutputSections() {
unwindInfoSection = make<UnwindInfoSection>(); // TODO(gkm): only when no -r
symtabSection = make<SymtabSection>(*stringTableSection);
indirectSymtabSection = make<IndirectSymtabSection>();
if (config->outputType == MH_EXECUTE &&
(config->arch == AK_arm64 || config->arch == AK_arm64e))
codeSignatureSection = make<CodeSignatureSection>();
switch (config->outputType) {
case MH_EXECUTE:
@ -743,6 +769,7 @@ void Writer::assignAddresses(OutputSegment *seg) {
addr += osec->getSize();
fileOff += osec->getFileSize();
}
seg->fileSize = fileOff - seg->fileOff;
}
void Writer::openFile() {
@ -770,6 +797,11 @@ void Writer::writeUuid() {
uuidCommand->writeUuid(digest);
}
void Writer::writeCodeSignature() {
if (codeSignatureSection)
codeSignatureSection->writeHashes(buffer->getBufferStart());
}
void Writer::run() {
// dyld requires __LINKEDIT segment to always exist (even if empty).
OutputSegment *linkEditSegment =
@ -817,6 +849,7 @@ void Writer::run() {
writeSections();
writeUuid();
writeCodeSignature();
if (auto e = buffer->commit())
error("failed to write to the output file: " + toString(std::move(e)));

View File

@ -2007,6 +2007,203 @@ union alignas(4) macho_load_command {
};
LLVM_PACKED_END
/* code signing attributes of a process */
enum CodeSignAttrs {
CS_VALID = 0x00000001, /* dynamically valid */
CS_ADHOC = 0x00000002, /* ad hoc signed */
CS_GET_TASK_ALLOW = 0x00000004, /* has get-task-allow entitlement */
CS_INSTALLER = 0x00000008, /* has installer entitlement */
CS_FORCED_LV =
0x00000010, /* Library Validation required by Hardened System Policy */
CS_INVALID_ALLOWED = 0x00000020, /* (macOS Only) Page invalidation allowed by
task port policy */
CS_HARD = 0x00000100, /* don't load invalid pages */
CS_KILL = 0x00000200, /* kill process if it becomes invalid */
CS_CHECK_EXPIRATION = 0x00000400, /* force expiration checking */
CS_RESTRICT = 0x00000800, /* tell dyld to treat restricted */
CS_ENFORCEMENT = 0x00001000, /* require enforcement */
CS_REQUIRE_LV = 0x00002000, /* require library validation */
CS_ENTITLEMENTS_VALIDATED =
0x00004000, /* code signature permits restricted entitlements */
CS_NVRAM_UNRESTRICTED =
0x00008000, /* has com.apple.rootless.restricted-nvram-variables.heritable
entitlement */
CS_RUNTIME = 0x00010000, /* Apply hardened runtime policies */
CS_LINKER_SIGNED = 0x00020000, /* Automatically signed by the linker */
CS_ALLOWED_MACHO =
(CS_ADHOC | CS_HARD | CS_KILL | CS_CHECK_EXPIRATION | CS_RESTRICT |
CS_ENFORCEMENT | CS_REQUIRE_LV | CS_RUNTIME | CS_LINKER_SIGNED),
CS_EXEC_SET_HARD = 0x00100000, /* set CS_HARD on any exec'ed process */
CS_EXEC_SET_KILL = 0x00200000, /* set CS_KILL on any exec'ed process */
CS_EXEC_SET_ENFORCEMENT =
0x00400000, /* set CS_ENFORCEMENT on any exec'ed process */
CS_EXEC_INHERIT_SIP =
0x00800000, /* set CS_INSTALLER on any exec'ed process */
CS_KILLED = 0x01000000, /* was killed by kernel for invalidity */
CS_DYLD_PLATFORM =
0x02000000, /* dyld used to load this is a platform binary */
CS_PLATFORM_BINARY = 0x04000000, /* this is a platform binary */
CS_PLATFORM_PATH =
0x08000000, /* platform binary by the fact of path (osx only) */
CS_DEBUGGED = 0x10000000, /* process is currently or has previously been
debugged and allowed to run with invalid pages */
CS_SIGNED = 0x20000000, /* process has a signature (may have gone invalid) */
CS_DEV_CODE =
0x40000000, /* code is dev signed, cannot be loaded into prod signed code
(will go away with rdar://problem/28322552) */
CS_DATAVAULT_CONTROLLER =
0x80000000, /* has Data Vault controller entitlement */
CS_ENTITLEMENT_FLAGS = (CS_GET_TASK_ALLOW | CS_INSTALLER |
CS_DATAVAULT_CONTROLLER | CS_NVRAM_UNRESTRICTED),
};
/* executable segment flags */
enum CodeSignExecSegFlags {
CS_EXECSEG_MAIN_BINARY = 0x1, /* executable segment denotes main binary */
CS_EXECSEG_ALLOW_UNSIGNED = 0x10, /* allow unsigned pages (for debugging) */
CS_EXECSEG_DEBUGGER = 0x20, /* main binary is debugger */
CS_EXECSEG_JIT = 0x40, /* JIT enabled */
CS_EXECSEG_SKIP_LV = 0x80, /* OBSOLETE: skip library validation */
CS_EXECSEG_CAN_LOAD_CDHASH = 0x100, /* can bless cdhash for execution */
CS_EXECSEG_CAN_EXEC_CDHASH = 0x200, /* can execute blessed cdhash */
};
/* Magic numbers used by Code Signing */
enum CodeSignMagic {
CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */
CSMAGIC_REQUIREMENTS =
0xfade0c01, /* Requirements vector (internal requirements) */
CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */
CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */
CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */
CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */
CSMAGIC_DETACHED_SIGNATURE =
0xfade0cc1, /* multi-arch collection of embedded signatures */
CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */
CS_SUPPORTSSCATTER = 0x20100,
CS_SUPPORTSTEAMID = 0x20200,
CS_SUPPORTSCODELIMIT64 = 0x20300,
CS_SUPPORTSEXECSEG = 0x20400,
CS_SUPPORTSRUNTIME = 0x20500,
CS_SUPPORTSLINKAGE = 0x20600,
CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */
CSSLOT_INFOSLOT = 1,
CSSLOT_REQUIREMENTS = 2,
CSSLOT_RESOURCEDIR = 3,
CSSLOT_APPLICATION = 4,
CSSLOT_ENTITLEMENTS = 5,
CSSLOT_ALTERNATE_CODEDIRECTORIES =
0x1000, /* first alternate CodeDirectory, if any */
CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */
CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT =
CSSLOT_ALTERNATE_CODEDIRECTORIES +
CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */
CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */
CSSLOT_IDENTIFICATIONSLOT = 0x10001,
CSSLOT_TICKETSLOT = 0x10002,
CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */
CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */
CS_HASHTYPE_SHA1 = 1,
CS_HASHTYPE_SHA256 = 2,
CS_HASHTYPE_SHA256_TRUNCATED = 3,
CS_HASHTYPE_SHA384 = 4,
CS_SHA1_LEN = 20,
CS_SHA256_LEN = 32,
CS_SHA256_TRUNCATED_LEN = 20,
CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */
CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */
/*
* Currently only to support Legacy VPN plugins, and Mac App Store
* but intended to replace all the various platform code, dev code etc. bits.
*/
CS_SIGNER_TYPE_UNKNOWN = 0,
CS_SIGNER_TYPE_LEGACYVPN = 5,
CS_SIGNER_TYPE_MAC_APP_STORE = 6,
CS_SUPPL_SIGNER_TYPE_UNKNOWN = 0,
CS_SUPPL_SIGNER_TYPE_TRUSTCACHE = 7,
CS_SUPPL_SIGNER_TYPE_LOCAL = 8,
};
struct CS_CodeDirectory {
uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */
uint32_t length; /* total length of CodeDirectory blob */
uint32_t version; /* compatibility version */
uint32_t flags; /* setup and mode flags */
uint32_t hashOffset; /* offset of hash slot element at index zero */
uint32_t identOffset; /* offset of identifier string */
uint32_t nSpecialSlots; /* number of special hash slots */
uint32_t nCodeSlots; /* number of ordinary (code) hash slots */
uint32_t codeLimit; /* limit to main image signature range */
uint8_t hashSize; /* size of each hash in bytes */
uint8_t hashType; /* type of hash (cdHashType* constants) */
uint8_t platform; /* platform identifier; zero if not platform binary */
uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */
uint32_t spare2; /* unused (must be zero) */
/* Version 0x20100 */
uint32_t scatterOffset; /* offset of optional scatter vector */
/* Version 0x20200 */
uint32_t teamOffset; /* offset of optional team identifier */
/* Version 0x20300 */
uint32_t spare3; /* unused (must be zero) */
uint64_t codeLimit64; /* limit to main image signature range, 64 bits */
/* Version 0x20400 */
uint64_t execSegBase; /* offset of executable segment */
uint64_t execSegLimit; /* limit of executable segment */
uint64_t execSegFlags; /* executable segment flags */
};
static_assert(sizeof(CS_CodeDirectory) == 88, "");
struct CS_BlobIndex {
uint32_t type; /* type of entry */
uint32_t offset; /* offset of entry */
};
struct CS_SuperBlob {
uint32_t magic; /* magic number */
uint32_t length; /* total length of SuperBlob */
uint32_t count; /* number of index entries following */
/* followed by Blobs in no particular order as indicated by index offsets */
};
enum SecCSDigestAlgorithm {
kSecCodeSignatureNoHash = 0, /* null value */
kSecCodeSignatureHashSHA1 = 1, /* SHA-1 */
kSecCodeSignatureHashSHA256 = 2, /* SHA-256 */
kSecCodeSignatureHashSHA256Truncated =
3, /* SHA-256 truncated to first 20 bytes */
kSecCodeSignatureHashSHA384 = 4, /* SHA-384 */
kSecCodeSignatureHashSHA512 = 5, /* SHA-512 */
};
} // end namespace MachO
} // end namespace llvm