[lld-macho] add code signature for native arm64 macOS
authorGreg McGary <gkm@fb.com>
Thu, 7 Jan 2021 02:11:44 +0000 (18:11 -0800)
committerGreg McGary <gkm@fb.com>
Thu, 25 Feb 2021 01:05:23 +0000 (17:05 -0800)
Differential Revision: https://reviews.llvm.org/D96164

lld/MachO/OutputSegment.h
lld/MachO/SyntheticSections.cpp
lld/MachO/SyntheticSections.h
lld/MachO/Writer.cpp
llvm/include/llvm/BinaryFormat/MachO.h

index 63b62d5..7572123 100644 (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;
index 977a7af..2a71308 100644 (file)
 #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);
+}
index 843966c..aff5a08 100644 (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;
index f4cd5cc..71f3a64 100644 (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)));
index 1ec6536..df2ba94 100644 (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