From 0376fd09bc9f29ceeb83760e32532923e4fe7f98 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Sat, 19 Mar 2022 16:54:11 +0100 Subject: [PATCH] macho: extend CodeSignature to accept entitlements With this change, we can now bake in entitlements into the binary. Additionally, I see this as the first step towards full code signature support which includes baking in Apple issued certificates for redistribution, etc. --- lib/std/build.zig | 7 + src/Compilation.zig | 4 + src/link.zig | 3 + src/link/MachO.zig | 84 +++--- src/link/MachO/CodeSignature.zig | 425 +++++++++++++++++++++++-------- src/main.zig | 7 + 6 files changed, 386 insertions(+), 144 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index 29a5d80705..f1287c7be5 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1570,6 +1570,9 @@ pub const LibExeObjStep = struct { /// (Darwin) Install name for the dylib install_name: ?[]const u8 = null, + /// (Darwin) Path to entitlements file + entitlements: ?[]const u8 = null, + /// Position Independent Code force_pic: ?bool = null, @@ -2515,6 +2518,10 @@ pub const LibExeObjStep = struct { } } + if (self.entitlements) |entitlements| { + try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements }); + } + if (self.bundle_compiler_rt) |x| { if (x) { try zig_args.append("-fcompiler-rt"); diff --git a/src/Compilation.zig b/src/Compilation.zig index f6fe452951..64848659a7 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -815,6 +815,8 @@ pub const InitOptions = struct { native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null, /// (Darwin) Install name of the dylib install_name: ?[]const u8 = null, + /// (Darwin) Path to entitlements file + entitlements: ?[]const u8 = null, }; fn addPackageTableToCacheHash( @@ -1624,6 +1626,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { .enable_link_snapshots = options.enable_link_snapshots, .native_darwin_sdk = options.native_darwin_sdk, .install_name = options.install_name, + .entitlements = options.entitlements, }); errdefer bin_file.destroy(); comp.* = .{ @@ -2351,6 +2354,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes // Mach-O specific stuff man.hash.addListOfBytes(comp.bin_file.options.framework_dirs); man.hash.addListOfBytes(comp.bin_file.options.frameworks); + try man.addOptionalFile(comp.bin_file.options.entitlements); // COFF specific stuff man.hash.addOptional(comp.bin_file.options.subsystem); diff --git a/src/link.zig b/src/link.zig index 3a18f204e8..d431e9d5f1 100644 --- a/src/link.zig +++ b/src/link.zig @@ -183,6 +183,9 @@ pub const Options = struct { /// (Darwin) Install name for the dylib install_name: ?[]const u8 = null, + /// (Darwin) Path to entitlements file + entitlements: ?[]const u8 = null, + pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode { return if (options.use_lld) .Obj else options.output_mode; } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index 1cab5fe067..8bb0101301 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -58,11 +58,6 @@ d_sym: ?DebugSymbols = null, /// For x86_64 that's 4KB, whereas for aarch64, that's 16KB. page_size: u16, -/// TODO Should we figure out embedding code signatures for other Apple platforms as part of the linker? -/// Or should this be a separate tool? -/// https://github.com/ziglang/zig/issues/9567 -requires_adhoc_codesig: bool, - /// If true, the linker will preallocate several sections and segments before starting the linking /// process. This is for example true for stage2 debug builds, however, this is false for stage1 /// and potentially stage2 release builds in the future. @@ -76,6 +71,9 @@ header_pad: u16 = 0x1000, /// The absolute address of the entry point. entry_addr: ?u64 = null, +/// Code signature (if any) +code_signature: ?CodeSignature = null, + objects: std.ArrayListUnmanaged(Object) = .{}, archives: std.ArrayListUnmanaged(Archive) = .{}, @@ -402,7 +400,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO { .file = null, }, .page_size = page_size, - .requires_adhoc_codesig = requires_adhoc_codesig, + .code_signature = if (requires_adhoc_codesig) CodeSignature.init(page_size) else null, .needs_prealloc = needs_prealloc, }; @@ -534,6 +532,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { } link.hashAddSystemLibs(&man.hash, self.base.options.system_libs); man.hash.addOptionalBytes(self.base.options.sysroot); + try man.addOptionalFile(self.base.options.entitlements); // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock. _ = try man.hit(); @@ -859,6 +858,19 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { self.load_commands_dirty = true; } + // code signature and entitlements + if (self.base.options.entitlements) |path| { + if (self.code_signature) |*csig| { + try csig.addEntitlements(self.base.allocator, path); + csig.code_directory.ident = self.base.options.emit.?.sub_path; + } else { + var csig = CodeSignature.init(self.page_size); + try csig.addEntitlements(self.base.allocator, path); + csig.code_directory.ident = self.base.options.emit.?.sub_path; + self.code_signature = csig; + } + } + if (self.base.options.verbose_link) { var argv = std.ArrayList([]const u8).init(arena); @@ -1033,13 +1045,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try d_sym.flushModule(self.base.allocator, self.base.options); } - if (self.requires_adhoc_codesig) { + if (self.code_signature) |*csig| { + csig.clear(self.base.allocator); + csig.code_directory.ident = self.base.options.emit.?.sub_path; // Preallocate space for the code signature. // We need to do this at this stage so that we have the load commands with proper values // written out to the file. // The most important here is to have the correct vm and filesize of the __LINKEDIT segment // where the code signature goes into. - try self.writeCodeSignaturePadding(); + try self.writeCodeSignaturePadding(csig); } try self.writeLoadCommands(); @@ -1055,8 +1069,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { assert(!self.load_commands_dirty); - if (self.requires_adhoc_codesig) { - try self.writeCodeSignature(); // code signing always comes last + if (self.code_signature) |*csig| { + try self.writeCodeSignature(csig); // code signing always comes last } if (build_options.enable_link_snapshots) { @@ -3315,7 +3329,7 @@ fn addLoadDylibLC(self: *MachO, id: u16) !void { } fn addCodeSignatureLC(self: *MachO) !void { - if (self.code_signature_cmd_index != null or !self.requires_adhoc_codesig) return; + if (self.code_signature_cmd_index != null or self.code_signature == null) return; self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len); try self.load_commands.append(self.base.allocator, .{ .linkedit_data = .{ @@ -3429,6 +3443,10 @@ pub fn deinit(self: *MachO) void { } self.atom_by_index_table.deinit(self.base.allocator); + + if (self.code_signature) |*csig| { + csig.deinit(self.base.allocator); + } } pub fn closeFiles(self: MachO) void { @@ -6143,7 +6161,7 @@ fn writeLinkeditSegment(self: *MachO) !void { seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size); } -fn writeCodeSignaturePadding(self: *MachO) !void { +fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void { const tracy = trace(@src()); defer tracy.end(); @@ -6153,11 +6171,7 @@ fn writeCodeSignaturePadding(self: *MachO) !void { // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271 const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16); const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize); - const needed_size = CodeSignature.calcCodeSignaturePaddingSize( - self.base.options.emit.?.sub_path, - fileoff, - self.page_size, - ); + const needed_size = code_sig.estimateSize(fileoff); code_sig_cmd.dataoff = @intCast(u32, fileoff); code_sig_cmd.datasize = needed_size; @@ -6173,34 +6187,30 @@ fn writeCodeSignaturePadding(self: *MachO) !void { self.load_commands_dirty = true; } -fn writeCodeSignature(self: *MachO) !void { +fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void { const tracy = trace(@src()); defer tracy.end(); const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment; const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data; - var code_sig: CodeSignature = .{}; - defer code_sig.deinit(self.base.allocator); + var buffer = std.ArrayList(u8).init(self.base.allocator); + defer buffer.deinit(); + try buffer.ensureTotalCapacityPrecise(code_sig.size()); + try code_sig.writeAdhocSignature(self.base.allocator, .{ + .file = self.base.file.?, + .text_segment = text_segment.inner, + .code_sig_cmd = code_sig_cmd, + .output_mode = self.base.options.output_mode, + }, buffer.writer()); + assert(buffer.items.len == code_sig.size()); - try code_sig.calcAdhocSignature( - self.base.allocator, - self.base.file.?, - self.base.options.emit.?.sub_path, - text_segment.inner, - code_sig_cmd, - self.base.options.output_mode, - self.page_size, - ); + log.debug("writing code signature from 0x{x} to 0x{x}", .{ + code_sig_cmd.dataoff, + code_sig_cmd.dataoff + buffer.items.len, + }); - var buffer = try self.base.allocator.alloc(u8, code_sig.size()); - defer self.base.allocator.free(buffer); - var stream = std.io.fixedBufferStream(buffer); - try code_sig.write(stream.writer()); - - log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len }); - - try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); + try self.base.file.?.pwriteAll(buffer.items, code_sig_cmd.dataoff); } /// Writes all load commands and section headers. diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 41c7c07741..6f2d516bf5 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -12,12 +12,102 @@ const Sha256 = std.crypto.hash.sha2.Sha256; const hash_size: u8 = 32; +const Blob = union(enum) { + code_directory: *CodeDirectory, + requirements: *Requirements, + entitlements: *Entitlements, + signature: *Signature, + + fn slotType(self: Blob) u32 { + return switch (self) { + .code_directory => |x| x.slotType(), + .requirements => |x| x.slotType(), + .entitlements => |x| x.slotType(), + .signature => |x| x.slotType(), + }; + } + + fn size(self: Blob) u32 { + return switch (self) { + .code_directory => |x| x.size(), + .requirements => |x| x.size(), + .entitlements => |x| x.size(), + .signature => |x| x.size(), + }; + } + + fn write(self: Blob, writer: anytype) !void { + return switch (self) { + .code_directory => |x| x.write(writer), + .requirements => |x| x.write(writer), + .entitlements => |x| x.write(writer), + .signature => |x| x.write(writer), + }; + } +}; + const CodeDirectory = struct { inner: macho.CodeDirectory, - data: std.ArrayListUnmanaged(u8) = .{}, + ident: []const u8, + special_slots: [n_special_slots][hash_size]u8, + code_slots: std.ArrayListUnmanaged([hash_size]u8) = .{}, + + const n_special_slots: usize = 7; + + fn init(page_size: u16) CodeDirectory { + var cdir: CodeDirectory = .{ + .inner = .{ + .magic = macho.CSMAGIC_CODEDIRECTORY, + .length = @sizeOf(macho.CodeDirectory), + .version = macho.CS_SUPPORTSEXECSEG, + .flags = macho.CS_ADHOC, + .hashOffset = 0, + .identOffset = @sizeOf(macho.CodeDirectory), + .nSpecialSlots = 0, + .nCodeSlots = 0, + .codeLimit = 0, + .hashSize = hash_size, + .hashType = macho.CS_HASHTYPE_SHA256, + .platform = 0, + .pageSize = @truncate(u8, std.math.log2(page_size)), + .spare2 = 0, + .scatterOffset = 0, + .teamOffset = 0, + .spare3 = 0, + .codeLimit64 = 0, + .execSegBase = 0, + .execSegLimit = 0, + .execSegFlags = 0, + }, + .ident = undefined, + .special_slots = undefined, + }; + comptime var i = 0; + inline while (i < n_special_slots) : (i += 1) { + cdir.special_slots[i] = [_]u8{0} ** hash_size; + } + return cdir; + } + + fn deinit(self: *CodeDirectory, allocator: Allocator) void { + self.code_slots.deinit(allocator); + } + + fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { + assert(index > 0); + self.inner.nSpecialSlots = std.math.max(self.inner.nSpecialSlots, index); + mem.copy(u8, &self.special_slots[index - 1], &hash); + } + + fn slotType(self: CodeDirectory) u32 { + _ = self; + return macho.CSSLOT_CODEDIRECTORY; + } fn size(self: CodeDirectory) u32 { - return self.inner.length; + const code_slots = self.inner.nCodeSlots * hash_size; + const special_slots = self.inner.nSpecialSlots * hash_size; + return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1) + special_slots + code_slots; } fn write(self: CodeDirectory, writer: anytype) !void { @@ -42,142 +132,263 @@ const CodeDirectory = struct { try writer.writeIntBig(u64, self.inner.execSegBase); try writer.writeIntBig(u64, self.inner.execSegLimit); try writer.writeIntBig(u64, self.inner.execSegFlags); - try writer.writeAll(self.data.items); + + try writer.writeAll(self.ident); + try writer.writeByte(0); + + var i: isize = @intCast(isize, self.inner.nSpecialSlots); + while (i > 0) : (i -= 1) { + try writer.writeAll(&self.special_slots[@intCast(usize, i - 1)]); + } + + for (self.code_slots.items) |slot| { + try writer.writeAll(&slot); + } } }; -/// Code signature blob header. -inner: macho.SuperBlob = .{ - .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, - .length = @sizeOf(macho.SuperBlob), - .count = 0, -}, +const Requirements = struct { + fn deinit(self: *Requirements, allocator: Allocator) void { + _ = self; + _ = allocator; + } -/// CodeDirectory header which holds the hash of the binary. -cdir: ?CodeDirectory = null, + fn slotType(self: Requirements) u32 { + _ = self; + return macho.CSSLOT_REQUIREMENTS; + } -pub fn calcAdhocSignature( - self: *CodeSignature, - allocator: Allocator, + fn size(self: Requirements) u32 { + _ = self; + return 3 * @sizeOf(u32); + } + + fn write(self: Requirements, writer: anytype) !void { + try writer.writeIntBig(u32, macho.CSMAGIC_REQUIREMENTS); + try writer.writeIntBig(u32, self.size()); + try writer.writeIntBig(u32, 0); + } +}; + +const Entitlements = struct { + inner: []const u8, + + fn deinit(self: *Entitlements, allocator: Allocator) void { + allocator.free(self.inner); + } + + fn slotType(self: Entitlements) u32 { + _ = self; + return macho.CSSLOT_ENTITLEMENTS; + } + + fn size(self: Entitlements) u32 { + return @intCast(u32, self.inner.len) + 2 * @sizeOf(u32); + } + + fn write(self: Entitlements, writer: anytype) !void { + try writer.writeIntBig(u32, macho.CSMAGIC_EMBEDDED_ENTITLEMENTS); + try writer.writeIntBig(u32, self.size()); + try writer.writeAll(self.inner); + } +}; + +const Signature = struct { + fn deinit(self: *Signature, allocator: Allocator) void { + _ = self; + _ = allocator; + } + + fn slotType(self: Signature) u32 { + _ = self; + return macho.CSSLOT_SIGNATURESLOT; + } + + fn size(self: Signature) u32 { + _ = self; + return 2 * @sizeOf(u32); + } + + fn write(self: Signature, writer: anytype) !void { + try writer.writeIntBig(u32, macho.CSMAGIC_BLOBWRAPPER); + try writer.writeIntBig(u32, self.size()); + } +}; + +page_size: u16, +code_directory: CodeDirectory, +requirements: ?Requirements = null, +entitlements: ?Entitlements = null, +signature: ?Signature = null, + +pub fn init(page_size: u16) CodeSignature { + return .{ + .page_size = page_size, + .code_directory = CodeDirectory.init(page_size), + }; +} + +pub fn deinit(self: *CodeSignature, allocator: Allocator) void { + self.code_directory.deinit(allocator); + if (self.requirements) |*req| { + req.deinit(allocator); + } + if (self.entitlements) |*ents| { + ents.deinit(allocator); + } + if (self.signature) |*sig| { + sig.deinit(allocator); + } +} + +pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void { + const file = try fs.cwd().openFile(path, .{}); + defer file.close(); + const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32)); + self.entitlements = .{ .inner = inner }; +} + +pub const WriteOpts = struct { file: fs.File, - id: []const u8, text_segment: macho.segment_command_64, code_sig_cmd: macho.linkedit_data_command, output_mode: std.builtin.OutputMode, - page_size: u16, +}; + +pub fn writeAdhocSignature( + self: *CodeSignature, + allocator: Allocator, + opts: WriteOpts, + writer: anytype, ) !void { - const execSegBase: u64 = text_segment.fileoff; - const execSegLimit: u64 = text_segment.filesize; - const execSegFlags: u64 = if (output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0; - const file_size = code_sig_cmd.dataoff; - var cdir = CodeDirectory{ - .inner = .{ - .magic = macho.CSMAGIC_CODEDIRECTORY, - .length = @sizeOf(macho.CodeDirectory), - .version = macho.CS_SUPPORTSEXECSEG, - .flags = macho.CS_ADHOC, - .hashOffset = 0, - .identOffset = 0, - .nSpecialSlots = 0, - .nCodeSlots = 0, - .codeLimit = file_size, - .hashSize = hash_size, - .hashType = macho.CS_HASHTYPE_SHA256, - .platform = 0, - .pageSize = @truncate(u8, std.math.log2(page_size)), - .spare2 = 0, - .scatterOffset = 0, - .teamOffset = 0, - .spare3 = 0, - .codeLimit64 = 0, - .execSegBase = execSegBase, - .execSegLimit = execSegLimit, - .execSegFlags = execSegFlags, - }, + var header: macho.SuperBlob = .{ + .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, + .length = @sizeOf(macho.SuperBlob), + .count = 0, }; - const total_pages = mem.alignForward(file_size, page_size) / page_size; + var blobs = std.ArrayList(Blob).init(allocator); + defer blobs.deinit(); - var hash: [hash_size]u8 = undefined; - var buffer = try allocator.alloc(u8, page_size); + self.code_directory.inner.execSegBase = opts.text_segment.fileoff; + self.code_directory.inner.execSegLimit = opts.text_segment.filesize; + self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0; + const file_size = opts.code_sig_cmd.dataoff; + self.code_directory.inner.codeLimit = file_size; + + const total_pages = mem.alignForward(file_size, self.page_size) / self.page_size; + + var buffer = try allocator.alloc(u8, self.page_size); defer allocator.free(buffer); - try cdir.data.ensureTotalCapacityPrecise(allocator, total_pages * hash_size + id.len + 1); + try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages); - // 1. Save the identifier and update offsets - cdir.inner.identOffset = cdir.inner.length; - cdir.data.appendSliceAssumeCapacity(id); - cdir.data.appendAssumeCapacity(0); - - // 2. Calculate hash for each page (in file) and write it to the buffer - // TODO figure out how we can cache several hashes since we won't update - // every page during incremental linking - cdir.inner.hashOffset = cdir.inner.identOffset + @intCast(u32, id.len) + 1; + // Calculate hash for each page (in file) and write it to the buffer + var hash: [hash_size]u8 = undefined; var i: usize = 0; while (i < total_pages) : (i += 1) { - const fstart = i * page_size; - const fsize = if (fstart + page_size > file_size) file_size - fstart else page_size; - const len = try file.preadAll(buffer, fstart); + const fstart = i * self.page_size; + const fsize = if (fstart + self.page_size > file_size) file_size - fstart else self.page_size; + const len = try opts.file.preadAll(buffer, fstart); assert(fsize <= len); Sha256.hash(buffer[0..fsize], &hash, .{}); - cdir.data.appendSliceAssumeCapacity(&hash); - cdir.inner.nCodeSlots += 1; + self.code_directory.code_slots.appendAssumeCapacity(hash); + self.code_directory.inner.nCodeSlots += 1; } - // 3. Update CodeDirectory length - cdir.inner.length += @intCast(u32, cdir.data.items.len); + try blobs.append(.{ .code_directory = &self.code_directory }); + header.length += @sizeOf(macho.BlobIndex); + header.count += 1; - self.inner.length += @sizeOf(macho.BlobIndex) + cdir.size(); - self.inner.count = 1; - self.cdir = cdir; + if (self.requirements) |*req| { + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + try req.write(buf.writer()); + Sha256.hash(buf.items, &hash, .{}); + self.code_directory.addSpecialHash(req.slotType(), hash); + + try blobs.append(.{ .requirements = req }); + header.count += 1; + header.length += @sizeOf(macho.BlobIndex) + req.size(); + } + + if (self.entitlements) |*ents| { + var buf = std.ArrayList(u8).init(allocator); + defer buf.deinit(); + try ents.write(buf.writer()); + Sha256.hash(buf.items, &hash, .{}); + self.code_directory.addSpecialHash(ents.slotType(), hash); + + try blobs.append(.{ .entitlements = ents }); + header.count += 1; + header.length += @sizeOf(macho.BlobIndex) + ents.size(); + } + + if (self.signature) |*sig| { + try blobs.append(.{ .signature = sig }); + header.count += 1; + header.length += @sizeOf(macho.BlobIndex) + sig.size(); + } + + self.code_directory.inner.hashOffset = + @sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1) + self.code_directory.inner.nSpecialSlots * hash_size; + self.code_directory.inner.length = self.code_directory.size(); + header.length += self.code_directory.size(); + + try writer.writeIntBig(u32, header.magic); + try writer.writeIntBig(u32, header.length); + try writer.writeIntBig(u32, header.count); + + var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @intCast(u32, blobs.items.len); + for (blobs.items) |blob| { + try writer.writeIntBig(u32, blob.slotType()); + try writer.writeIntBig(u32, offset); + offset += blob.size(); + } + + for (blobs.items) |blob| { + try blob.write(writer); + } } pub fn size(self: CodeSignature) u32 { - return self.inner.length; -} - -pub fn write(self: CodeSignature, writer: anytype) !void { - try self.writeHeader(writer); - const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex); - try writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, writer); - try self.cdir.?.write(writer); -} - -pub fn deinit(self: *CodeSignature, allocator: Allocator) void { - if (self.cdir) |*cdir| { - cdir.data.deinit(allocator); + var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); + if (self.requirements) |req| { + ssize += @sizeOf(macho.BlobIndex) + req.size(); } + if (self.entitlements) |ent| { + ssize += @sizeOf(macho.BlobIndex) + ent.size(); + } + if (self.signature) |sig| { + ssize += @sizeOf(macho.BlobIndex) + sig.size(); + } + return ssize; } -fn writeHeader(self: CodeSignature, writer: anytype) !void { - try writer.writeIntBig(u32, self.inner.magic); - try writer.writeIntBig(u32, self.inner.length); - try writer.writeIntBig(u32, self.inner.count); +pub fn estimateSize(self: CodeSignature, file_size: u64) u32 { + var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); + // Approx code slots + const total_pages = mem.alignForwardGeneric(u64, file_size, self.page_size) / self.page_size; + ssize += total_pages * hash_size; + var n_special_slots: u32 = 0; + if (self.requirements) |req| { + ssize += @sizeOf(macho.BlobIndex) + req.size(); + n_special_slots = std.math.max(n_special_slots, req.slotType()); + } + if (self.entitlements) |ent| { + ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size; + n_special_slots = std.math.max(n_special_slots, ent.slotType()); + } + if (self.signature) |sig| { + ssize += @sizeOf(macho.BlobIndex) + sig.size(); + } + ssize += n_special_slots * hash_size; + return @intCast(u32, mem.alignForwardGeneric(u64, ssize, @sizeOf(u64))); } -fn writeBlobIndex(tt: u32, offset: u32, writer: anytype) !void { - try writer.writeIntBig(u32, tt); - try writer.writeIntBig(u32, offset); -} - -test "CodeSignature header" { - var code_sig: CodeSignature = .{}; - defer code_sig.deinit(testing.allocator); - - var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined; - var stream = std.io.fixedBufferStream(&buffer); - try code_sig.writeHeader(stream.writer()); - - const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 }; - try testing.expect(mem.eql(u8, expected, &buffer)); -} - -pub fn calcCodeSignaturePaddingSize(id: []const u8, file_size: u64, page_size: u16) u32 { - const ident_size = id.len + 1; - const total_pages = mem.alignForwardGeneric(u64, file_size, page_size) / page_size; - const hashed_size = total_pages * hash_size; - const codesig_header = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + @sizeOf(macho.CodeDirectory); - return @intCast(u32, mem.alignForwardGeneric(u64, codesig_header + ident_size + hashed_size, @sizeOf(u64))); +pub fn clear(self: *CodeSignature, allocator: Allocator) void { + self.code_directory.deinit(allocator); + self.code_directory = CodeDirectory.init(self.page_size); } diff --git a/src/main.zig b/src/main.zig index a071ca9a60..115f3748b6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -433,6 +433,7 @@ const usage_build_generic = \\ -framework [name] (Darwin) link against framework \\ -F[dir] (Darwin) add search path for frameworks \\ -install_name=[value] (Darwin) add dylib's install name + \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature \\ --import-memory (WebAssembly) import memory from the environment \\ --import-table (WebAssembly) import function table from the host environment \\ --export-table (WebAssembly) export function table to the host environment @@ -680,6 +681,7 @@ fn buildOutputType( var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null; var install_name: ?[]const u8 = null; var hash_style: link.HashStyle = .both; + var entitlements: ?[]const u8 = null; // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names. // This array is populated by zig cc frontend and then has to be converted to zig-style @@ -1036,6 +1038,10 @@ fn buildOutputType( } else { enable_link_snapshots = true; } + } else if (mem.eql(u8, arg, "--entitlements")) { + entitlements = args_iter.next() orelse { + fatal("expected parameter after {s}", .{arg}); + }; } else if (mem.eql(u8, arg, "-fcompiler-rt")) { want_compiler_rt = true; } else if (mem.eql(u8, arg, "-fno-compiler-rt")) { @@ -2729,6 +2735,7 @@ fn buildOutputType( .enable_link_snapshots = enable_link_snapshots, .native_darwin_sdk = native_darwin_sdk, .install_name = install_name, + .entitlements = entitlements, }) catch |err| switch (err) { error.LibCUnavailable => { const target = target_info.target;