[vm] Add LC_ENCRYPTION_INFO(_64) load command to iOS snapshots.
When uploading to the App Store, the program must contain an LC_ENCRYPTION_INFO segment in the Mach-O header so the App Store can appropriately modify it for its purposes without changing the header size and/or offsets/addresses in the rest of the shared object. By default, the load command is only added to iOS snapshots, but it can be added to any Mach-O snapshot using the --macho-encryptable command line option. TEST=vm/dart/use_macho_options_test Cq-Include-Trybots: luci.dart.try:vm-aot-mac-debug-x64-try,vm-aot-mac-debug-arm64-try,vm-aot-linux-debug-x64-try Change-Id: I4e79fd9cfb9ba9d49707ec209eca3fee1cefc28c Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/475040 Reviewed-by: Alexander Markov <alexmarkov@google.com> Commit-Queue: Tess Strickland <sstrickl@google.com>
This commit is contained in:
committed by
Commit Queue
parent
87005f595c
commit
8cbf864289
@@ -130,6 +130,8 @@ class LoadCommand {
|
||||
static const LC_UUID = 0x1b;
|
||||
static const LC_RPATH = 0x1c | LC_REQ_DYLD;
|
||||
static const LC_CODE_SIGNATURE = 0x1d; // Only used in vm/dart tests.
|
||||
static const LC_ENCRYPTION_INFO = 0x21;
|
||||
static const LC_ENCRYPTION_INFO_64 = 0x2c;
|
||||
static const LC_BUILD_VERSION = 0x32;
|
||||
|
||||
static LoadCommand fromReader(Reader reader) {
|
||||
@@ -156,6 +158,9 @@ class LoadCommand {
|
||||
command = DylibCommand.fromReader(reader, cmd, cmdsize);
|
||||
case LC_RPATH:
|
||||
command = RunPathCommand.fromReader(reader, cmd, cmdsize);
|
||||
case LC_ENCRYPTION_INFO:
|
||||
case LC_ENCRYPTION_INFO_64:
|
||||
command = EncryptionInfoCommand.fromReader(reader, cmd, cmdsize);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -602,6 +607,38 @@ class BuildVersionCommand extends LoadCommand {
|
||||
}
|
||||
}
|
||||
|
||||
class EncryptionInfoCommand extends LoadCommand {
|
||||
final int fileOffset;
|
||||
final int fileSize;
|
||||
final int cryptId;
|
||||
|
||||
EncryptionInfoCommand._(
|
||||
super.cmd, super.cmdsize, this.fileOffset, this.fileSize, this.cryptId)
|
||||
: super._();
|
||||
|
||||
static EncryptionInfoCommand fromReader(Reader reader, int cmd, int cmdsize) {
|
||||
final fileOffset = _readMachOUint32(reader);
|
||||
final fileSize = _readMachOUint32(reader);
|
||||
final cryptId = _readMachOUint32(reader);
|
||||
if (cmd == LoadCommand.LC_ENCRYPTION_INFO_64) {
|
||||
_readMachOUint32(reader); // padding
|
||||
}
|
||||
return EncryptionInfoCommand._(cmd, cmdsize, fileOffset, fileSize, cryptId);
|
||||
}
|
||||
|
||||
@override
|
||||
void writeToStringBuffer(StringBuffer buffer) {
|
||||
buffer
|
||||
..writeln('Encryption info: ')
|
||||
..write(' File offset: ')
|
||||
..writeln(paddedHex(fileOffset, 4))
|
||||
..write(' File size: ')
|
||||
..writeln(fileSize)
|
||||
..write(' Crypt id: ')
|
||||
..writeln(cryptId);
|
||||
}
|
||||
}
|
||||
|
||||
class MachOHeader {
|
||||
final int magic;
|
||||
final int cputype;
|
||||
|
||||
@@ -140,6 +140,14 @@ static constexpr uint32_t LC_RPATH = (0x1c | LC_REQ_DYLD);
|
||||
// The code signature which protects the preceding portion of the object file.
|
||||
// Must be the last contents in the object file. (linkedit_data_command)
|
||||
static constexpr uint32_t LC_CODE_SIGNATURE = 0x1d;
|
||||
// Information about encrypted segments for 32-bit targets. Must also be present
|
||||
// for programs with no encrypted segments that are uploaded to the App Store
|
||||
// so it can be updated appropriately.
|
||||
static constexpr uint32_t LC_ENCRYPTION_INFO = 0x21;
|
||||
// Information about encrypted segments for 64-bit targets. Must also be present
|
||||
// for programs with no encrypted segments that are uploaded to the App Store
|
||||
// so it can be updated appropriately.
|
||||
static constexpr uint32_t LC_ENCRYPTION_INFO_64 = 0x2c;
|
||||
// An arbitrary piece of data not specified by the Mach-O format. (note_command)
|
||||
static constexpr uint32_t LC_NOTE = 0x31;
|
||||
// The target platform and minimum and target OS versions for this object file.
|
||||
@@ -743,6 +751,23 @@ static constexpr uint32_t RELOC_TYPE_ARM64_UNSIGNED = 0 << 28;
|
||||
// _before_ the target (UNSIGNED) entry that it is subtracted from.
|
||||
static constexpr uint32_t RELOC_TYPE_ARM64_SUBTRACTOR = 1 << 28;
|
||||
|
||||
struct encryption_info_command {
|
||||
uint32_t cmd; // LC_ENCRYPTION_INFO
|
||||
uint32_t cmdsize;
|
||||
uint32_t cryptoff; // file offset of the encrypted segment(s)
|
||||
uint32_t cryptsize; // file size of the encrypted segment(s)
|
||||
uint32_t cryptid; // encryption cypher used (0 == not encrypted)
|
||||
};
|
||||
|
||||
struct encryption_info_command_64 {
|
||||
uint32_t cmd; // LC_ENCRYPTION_INFO_64
|
||||
uint32_t cmdsize;
|
||||
uint32_t cryptoff; // file offset of the encrypted segment(s)
|
||||
uint32_t cryptsize; // file size of the encrypted segment(s)
|
||||
uint32_t cryptid; // encryption cypher used (0 == not encrypted)
|
||||
uint32_t pad; // padding to a multiple of 64 bits
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
} // namespace mach_o
|
||||
|
||||
@@ -129,6 +129,7 @@ enum TestType {
|
||||
MinOSVersion,
|
||||
NoLinkerSignature,
|
||||
ReplaceInstallName,
|
||||
Encryptable,
|
||||
}
|
||||
|
||||
@pragma('vm:platform-const')
|
||||
@@ -156,6 +157,7 @@ Future<void> checkCases(List<TestCase> testCases) async {
|
||||
checkRunPaths(c);
|
||||
checkBuildVersion(c);
|
||||
checkCodeSignature(c);
|
||||
checkEncryptionInfo(c);
|
||||
}
|
||||
// Unsigned snapshots are not runnable.
|
||||
final runnableCases = testCases
|
||||
@@ -329,6 +331,21 @@ void checkCodeSignature(TestCase testCase) {
|
||||
}
|
||||
}
|
||||
|
||||
void checkEncryptionInfo(TestCase testCase) {
|
||||
final encryptionInfoCommands = testCase.snapshot.commands
|
||||
.whereType<macho.EncryptionInfoCommand>();
|
||||
if (testCase.type == TestType.Encryptable) {
|
||||
Expect.equals(1, encryptionInfoCommands.length);
|
||||
final cmd = encryptionInfoCommands.singleOrNull;
|
||||
if (cmd != null) {
|
||||
// gen_snapshot doesn't encrypt any segments.
|
||||
Expect.equals(0, cmd.cryptId);
|
||||
}
|
||||
} else {
|
||||
Expect.isEmpty(encryptionInfoCommands);
|
||||
}
|
||||
}
|
||||
|
||||
void checkBuildVersion(TestCase testCase) {
|
||||
final buildVersion = testCase.snapshot
|
||||
.commandsWhereType<macho.BuildVersionCommand>()
|
||||
|
||||
+129
-16
@@ -40,6 +40,18 @@ DEFINE_FLAG(bool,
|
||||
"Whether to use a smaller alignment size for segments and the "
|
||||
"text/const sections in Mach-O outputs.")
|
||||
|
||||
#if defined(DART_TARGET_OS_MACOS_IOS)
|
||||
static constexpr bool kDefaultEncryptable = true;
|
||||
#else
|
||||
static constexpr bool kDefaultEncryptable = false;
|
||||
#endif
|
||||
|
||||
DEFINE_FLAG(bool,
|
||||
macho_encryptable,
|
||||
kDefaultEncryptable,
|
||||
"Whether to include an LC_ENCRYPTION_INFO load command "
|
||||
"in the snapshot");
|
||||
|
||||
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
||||
DEFINE_FLAG(charp,
|
||||
macho_min_os_version,
|
||||
@@ -102,7 +114,8 @@ static constexpr intptr_t kLinearInitValue = -1;
|
||||
V(MachODynamicSymbolTable) \
|
||||
V(MachOUuid) \
|
||||
V(MachOIdDylib) \
|
||||
V(MachOCodeSignature)
|
||||
V(MachOCodeSignature) \
|
||||
V(MachOEncryptionInfo)
|
||||
|
||||
#define DECLARE_CONTENTS_TYPE_CLASS(Type) class Type;
|
||||
FOR_EACH_CHECKABLE_MACHO_CONTENTS_TYPE(DECLARE_CONTENTS_TYPE_CLASS)
|
||||
@@ -126,6 +139,7 @@ class MachOWriteStream : public SharedObjectWriter::WriteStream {
|
||||
explicit MachOWriteStream(const MachOWriter& macho)
|
||||
: SharedObjectWriter::WriteStream(macho.type()), macho_(macho) {}
|
||||
|
||||
const MachOHeader& Header() const;
|
||||
const MachOSegment& TextSegment() const;
|
||||
|
||||
// Write methods that write values of a certain size out to disk.
|
||||
@@ -536,6 +550,16 @@ class MachOContents : public ZoneAllocated {
|
||||
|
||||
FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD_METHODS);
|
||||
|
||||
protected:
|
||||
static bool HasEncryptionInfo(SharedObjectWriter::Type type) {
|
||||
// Return the same result for debug info as for snapshots so that
|
||||
// padding calculations for it mimic those for snapshots and so
|
||||
// keeps offsets/addresses the same.
|
||||
return FLAG_macho_encryptable &&
|
||||
(type == SharedObjectWriter::Type::Snapshot ||
|
||||
type == SharedObjectWriter::Type::DebugInfo);
|
||||
}
|
||||
|
||||
private:
|
||||
FOR_EACH_CONTENTS_LINEAR_FIELD(DEFINE_LINEAR_FIELD);
|
||||
|
||||
@@ -829,6 +853,8 @@ class MachOSegment : public MachOCommand {
|
||||
using SegmentCommandType = mach_o::segment_command_64;
|
||||
#endif
|
||||
|
||||
using SnapshotType = SharedObjectWriter::Type;
|
||||
|
||||
public:
|
||||
#if defined(TARGET_ARCH_IS_32_BIT)
|
||||
static constexpr uint32_t kCommandCode = mach_o::LC_SEGMENT;
|
||||
@@ -837,6 +863,7 @@ class MachOSegment : public MachOCommand {
|
||||
#endif
|
||||
|
||||
MachOSegment(Zone* zone,
|
||||
SnapshotType type,
|
||||
const char* name,
|
||||
intptr_t initial_vm_protection = mach_o::VM_PROT_READ,
|
||||
intptr_t max_vm_protection = mach_o::VM_PROT_READ)
|
||||
@@ -844,6 +871,7 @@ class MachOSegment : public MachOCommand {
|
||||
// know what it contains, so set it to 0 in ComputeOffsets()
|
||||
// if there are no contents.
|
||||
: MachOCommand(kCommandCode),
|
||||
type_(type),
|
||||
name_(name),
|
||||
initial_vm_protection_(initial_vm_protection),
|
||||
max_vm_protection_(max_vm_protection),
|
||||
@@ -917,7 +945,7 @@ class MachOSegment : public MachOCommand {
|
||||
if (PadFileSizeToAlignment()) {
|
||||
file_size = Utils::RoundUp(file_size, Alignment());
|
||||
}
|
||||
return file_size;
|
||||
return AddRequiredPadding(file_size);
|
||||
}
|
||||
|
||||
intptr_t UnpaddedMemorySize() const {
|
||||
@@ -931,7 +959,8 @@ class MachOSegment : public MachOCommand {
|
||||
}
|
||||
|
||||
intptr_t MemorySize() const override {
|
||||
return Utils::RoundUp(UnpaddedMemorySize(), Alignment());
|
||||
intptr_t memory_size = Utils::RoundUp(UnpaddedMemorySize(), Alignment());
|
||||
return AddRequiredPadding(memory_size);
|
||||
}
|
||||
|
||||
// The initial segment of the Mach-O file always includes the header
|
||||
@@ -1027,6 +1056,14 @@ class MachOSegment : public MachOCommand {
|
||||
}
|
||||
|
||||
private:
|
||||
intptr_t AddRequiredPadding(intptr_t unpadded_size) const {
|
||||
// Pad the text segment to page size if it is encryptable.
|
||||
return HasEncryptionInfo(type_) && HasName(mach_o::SEG_TEXT)
|
||||
? Utils::RoundUp(unpadded_size, MachOWriter::kPageSize)
|
||||
: unpadded_size;
|
||||
}
|
||||
|
||||
const SnapshotType type_;
|
||||
const char* const name_;
|
||||
bool has_contents_ = false;
|
||||
intptr_t next_contents_index_ = 0;
|
||||
@@ -1598,6 +1635,48 @@ class MachODynamicSymbolTable : public MachOCommand {
|
||||
DISALLOW_COPY_AND_ASSIGN(MachODynamicSymbolTable);
|
||||
};
|
||||
|
||||
class MachOEncryptionInfo : public MachOCommand {
|
||||
#if defined(TARGET_ARCH_IS_32_BIT)
|
||||
using CommandType = mach_o::encryption_info_command;
|
||||
#else
|
||||
using CommandType = mach_o::encryption_info_command_64;
|
||||
#endif
|
||||
|
||||
public:
|
||||
#if defined(TARGET_ARCH_IS_32_BIT)
|
||||
static constexpr uint32_t kCommandCode = mach_o::LC_ENCRYPTION_INFO;
|
||||
#else
|
||||
static constexpr uint32_t kCommandCode = mach_o::LC_ENCRYPTION_INFO_64;
|
||||
#endif
|
||||
|
||||
MachOEncryptionInfo()
|
||||
: MachOCommand(kCommandCode,
|
||||
/*needs_offset=*/false,
|
||||
/*in_segment=*/false) {}
|
||||
|
||||
uint32_t cmdsize() const override { return sizeof(CommandType); }
|
||||
|
||||
void WriteLoadCommand(MachOWriteStream* stream) const override {
|
||||
MachOCommand::WriteLoadCommand(stream);
|
||||
auto [cryptoff, cryptsize] = GetCryptOffsetAndSize(stream);
|
||||
stream->WriteOffsetCount(cryptoff, cryptsize);
|
||||
stream->Write32(0); // No encryption cypher used.
|
||||
#if !defined(TARGET_ARCH_IS_32_BIT)
|
||||
stream->Write32(0); // padding.
|
||||
#endif
|
||||
}
|
||||
|
||||
void Accept(Visitor* visitor) override {
|
||||
visitor->VisitMachOEncryptionInfo(this);
|
||||
}
|
||||
|
||||
private:
|
||||
std::pair<uint32_t, uint32_t> GetCryptOffsetAndSize(
|
||||
MachOWriteStream* stream) const;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(MachOEncryptionInfo);
|
||||
};
|
||||
|
||||
class MachOLinkEditData : public MachOCommand {
|
||||
public:
|
||||
uint32_t cmdsize() const override {
|
||||
@@ -1806,10 +1885,6 @@ class MachOHeader : public MachOContents {
|
||||
bool IsAllocated() const override { return type_ != SnapshotType::Object; }
|
||||
intptr_t Alignment() const override { return compiler::target::kWordSize; }
|
||||
|
||||
// The header uses the default MemorySize() implementation, because
|
||||
// VisitChildren() doesn't visit the load commands and so the header is
|
||||
// not considered to contain nested content.
|
||||
//
|
||||
// This should be used if the size of the header without the load commands
|
||||
// is desired.
|
||||
intptr_t SizeWithoutLoadCommands() const {
|
||||
@@ -1823,6 +1898,11 @@ class MachOHeader : public MachOContents {
|
||||
return SelfFileSize();
|
||||
}
|
||||
|
||||
intptr_t MemorySize() const override {
|
||||
if (!IsAllocated()) return 0;
|
||||
return AddRequiredPadding(SelfMemorySize());
|
||||
}
|
||||
|
||||
intptr_t SelfFileSize() const override {
|
||||
intptr_t size = SizeWithoutLoadCommands();
|
||||
for (auto* const command : commands_) {
|
||||
@@ -1831,7 +1911,9 @@ class MachOHeader : public MachOContents {
|
||||
return size;
|
||||
}
|
||||
|
||||
intptr_t FileSize() const override { return SelfFileSize(); }
|
||||
intptr_t FileSize() const override {
|
||||
return AddRequiredPadding(SelfFileSize());
|
||||
}
|
||||
|
||||
uint32_t filetype() const {
|
||||
switch (type_) {
|
||||
@@ -1985,8 +2067,8 @@ class MachOHeader : public MachOContents {
|
||||
type_ == SnapshotType::Object
|
||||
? mach_o::VM_PROT_ALL
|
||||
: mach_o::VM_PROT_READ | mach_o::VM_PROT_EXECUTE;
|
||||
text_segment_ =
|
||||
new (zone()) MachOSegment(zone(), name, vm_protection, vm_protection);
|
||||
text_segment_ = new (zone())
|
||||
MachOSegment(zone(), type_, name, vm_protection, vm_protection);
|
||||
commands_.Add(text_segment_);
|
||||
}
|
||||
return text_segment_;
|
||||
@@ -2025,6 +2107,15 @@ class MachOHeader : public MachOContents {
|
||||
}
|
||||
|
||||
private:
|
||||
intptr_t AddRequiredPadding(intptr_t unpadded_size) const {
|
||||
// If the text segment contents can be encrypted, pad the header
|
||||
// to page size so that its pages are entirely excluded
|
||||
// from the file contents covered by LC_ENCRYPTION_INFO.
|
||||
return HasEncryptionInfo(type_)
|
||||
? Utils::RoundUp(unpadded_size, MachOWriter::kPageSize)
|
||||
: unpadded_size;
|
||||
}
|
||||
|
||||
void GenerateUuid();
|
||||
void CreateBSS();
|
||||
void GenerateUnwindingInformation();
|
||||
@@ -2118,6 +2209,22 @@ bool MachOWriteStream::HasValueForLabel(intptr_t label, intptr_t* value) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> MachOEncryptionInfo::GetCryptOffsetAndSize(
|
||||
MachOWriteStream* stream) const {
|
||||
const auto& header = stream->Header();
|
||||
const auto& text_segment = header.text_segment();
|
||||
const uint32_t offset = header.FileSize();
|
||||
const uint32_t size = text_segment.FileSize() - offset;
|
||||
// Both the offset and size should be a multiple of the page size.
|
||||
ASSERT((offset % MachOWriter::kPageSize) == 0);
|
||||
ASSERT((size % MachOWriter::kPageSize) == 0);
|
||||
return {offset, size};
|
||||
}
|
||||
|
||||
const MachOHeader& MachOWriteStream::Header() const {
|
||||
return macho_.header();
|
||||
}
|
||||
|
||||
const MachOSegment& MachOWriteStream::TextSegment() const {
|
||||
return macho_.header().text_segment();
|
||||
}
|
||||
@@ -2424,8 +2531,8 @@ void MachOHeader::CreateBSS() {
|
||||
// shouldn't already exist.
|
||||
ASSERT(FindSegment(mach_o::SEG_DATA) == nullptr);
|
||||
auto const vm_protection = mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE;
|
||||
data_segment = new (zone())
|
||||
MachOSegment(zone(), mach_o::SEG_DATA, vm_protection, vm_protection);
|
||||
data_segment = new (zone()) MachOSegment(zone(), type_, mach_o::SEG_DATA,
|
||||
vm_protection, vm_protection);
|
||||
commands_.Add(data_segment);
|
||||
}
|
||||
|
||||
@@ -2737,10 +2844,14 @@ void MachOHeader::GenerateMiscellaneousCommands() {
|
||||
// Not idempotent;
|
||||
ASSERT(!HasCommand(MachOIdDylib::kCommandCode));
|
||||
commands_.Add(new (zone_) MachOIdDylib(identifier_));
|
||||
if (FLAG_macho_encryptable) {
|
||||
ASSERT(!HasCommand(MachOEncryptionInfo::kCommandCode));
|
||||
commands_.Add(new (zone_) MachOEncryptionInfo());
|
||||
}
|
||||
#if defined(DART_TARGET_OS_MACOS) || defined(DART_TARGET_OS_MACOS_IOS)
|
||||
ASSERT(!HasCommand(MachOLoadDylib::kCommandCode));
|
||||
ASSERT(!HasCommand(MachORunPath::kCommandCode));
|
||||
commands_.Add(MachOLoadDylib::CreateLoadSystemDylib(zone_));
|
||||
ASSERT(!HasCommand(MachORunPath::kCommandCode));
|
||||
if (FLAG_macho_rpath != nullptr) {
|
||||
const char* current = FLAG_macho_rpath;
|
||||
for (const char* next = current;; next += 1) {
|
||||
@@ -2831,8 +2942,9 @@ void MachOHeader::FinalizeDwarfSections() {
|
||||
auto const init_vm_protection =
|
||||
mach_o::VM_PROT_READ | mach_o::VM_PROT_WRITE;
|
||||
auto const max_vm_protection = init_vm_protection | mach_o::VM_PROT_EXECUTE;
|
||||
dwarf_segment = new (zone()) MachOSegment(
|
||||
zone(), mach_o::SEG_DWARF, init_vm_protection, max_vm_protection);
|
||||
dwarf_segment =
|
||||
new (zone()) MachOSegment(zone(), type_, mach_o::SEG_DWARF,
|
||||
init_vm_protection, max_vm_protection);
|
||||
commands_.Add(dwarf_segment);
|
||||
}
|
||||
|
||||
@@ -2939,7 +3051,8 @@ void MachOHeader::FinalizeCommands() {
|
||||
ASSERT(!linkedit_commands.is_empty());
|
||||
MachOSegment* linkedit_segment = nullptr;
|
||||
if (type_ != SnapshotType::Object) {
|
||||
linkedit_segment = new (zone_) MachOSegment(zone_, mach_o::SEG_LINKEDIT);
|
||||
linkedit_segment =
|
||||
new (zone_) MachOSegment(zone_, type_, mach_o::SEG_LINKEDIT);
|
||||
num_commands += 1;
|
||||
for (auto* const c : linkedit_commands) {
|
||||
linkedit_segment->AddContents(c);
|
||||
|
||||
Reference in New Issue
Block a user