authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-19 16:54:11+01:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-03-22 07:06:39+01:00
log0376fd09bc9f29ceeb83760e32532923e4fe7f98
tree2458f38c54fa77f642dc41658f3cf89c7af506ea
parent91fd0f42c88f4bea424b5a5c58435a2a98b57a58

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.

6 files changed, 384 insertions(+), 142 deletions(-)

lib/std/build.zig+7
...@@ -1570,6 +1570,9 @@ pub const LibExeObjStep = struct {...@@ -1570,6 +1570,9 @@ pub const LibExeObjStep = struct {
1570 /// (Darwin) Install name for the dylib1570 /// (Darwin) Install name for the dylib
1571 install_name: ?[]const u8 = null,1571 install_name: ?[]const u8 = null,
15721572
1573 /// (Darwin) Path to entitlements file
1574 entitlements: ?[]const u8 = null,
1575
1573 /// Position Independent Code1576 /// Position Independent Code
1574 force_pic: ?bool = null,1577 force_pic: ?bool = null,
15751578
...@@ -2515,6 +2518,10 @@ pub const LibExeObjStep = struct {...@@ -2515,6 +2518,10 @@ pub const LibExeObjStep = struct {
2515 }2518 }
2516 }2519 }
25172520
2521 if (self.entitlements) |entitlements| {
2522 try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });
2523 }
2524
2518 if (self.bundle_compiler_rt) |x| {2525 if (self.bundle_compiler_rt) |x| {
2519 if (x) {2526 if (x) {
2520 try zig_args.append("-fcompiler-rt");2527 try zig_args.append("-fcompiler-rt");
src/Compilation.zig+4
...@@ -815,6 +815,8 @@ pub const InitOptions = struct {...@@ -815,6 +815,8 @@ pub const InitOptions = struct {
815 native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,815 native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
816 /// (Darwin) Install name of the dylib816 /// (Darwin) Install name of the dylib
817 install_name: ?[]const u8 = null,817 install_name: ?[]const u8 = null,
818 /// (Darwin) Path to entitlements file
819 entitlements: ?[]const u8 = null,
818};820};
819821
820fn addPackageTableToCacheHash(822fn addPackageTableToCacheHash(
...@@ -1624,6 +1626,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {...@@ -1624,6 +1626,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
1624 .enable_link_snapshots = options.enable_link_snapshots,1626 .enable_link_snapshots = options.enable_link_snapshots,
1625 .native_darwin_sdk = options.native_darwin_sdk,1627 .native_darwin_sdk = options.native_darwin_sdk,
1626 .install_name = options.install_name,1628 .install_name = options.install_name,
1629 .entitlements = options.entitlements,
1627 });1630 });
1628 errdefer bin_file.destroy();1631 errdefer bin_file.destroy();
1629 comp.* = .{1632 comp.* = .{
...@@ -2351,6 +2354,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes...@@ -2351,6 +2354,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
2351 // Mach-O specific stuff2354 // Mach-O specific stuff
2352 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);2355 man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
2353 man.hash.addListOfBytes(comp.bin_file.options.frameworks);2356 man.hash.addListOfBytes(comp.bin_file.options.frameworks);
2357 try man.addOptionalFile(comp.bin_file.options.entitlements);
23542358
2355 // COFF specific stuff2359 // COFF specific stuff
2356 man.hash.addOptional(comp.bin_file.options.subsystem);2360 man.hash.addOptional(comp.bin_file.options.subsystem);
src/link.zig+3
...@@ -183,6 +183,9 @@ pub const Options = struct {...@@ -183,6 +183,9 @@ pub const Options = struct {
183 /// (Darwin) Install name for the dylib183 /// (Darwin) Install name for the dylib
184 install_name: ?[]const u8 = null,184 install_name: ?[]const u8 = null,
185185
186 /// (Darwin) Path to entitlements file
187 entitlements: ?[]const u8 = null,
188
186 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {189 pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
187 return if (options.use_lld) .Obj else options.output_mode;190 return if (options.use_lld) .Obj else options.output_mode;
188 }191 }
src/link/MachO.zig+48-38
...@@ -58,11 +58,6 @@ d_sym: ?DebugSymbols = null,...@@ -58,11 +58,6 @@ d_sym: ?DebugSymbols = null,
58/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB.58/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB.
59page_size: u16,59page_size: u16,
6060
61/// TODO Should we figure out embedding code signatures for other Apple platforms as part of the linker?
62/// Or should this be a separate tool?
63/// https://github.com/ziglang/zig/issues/9567
64requires_adhoc_codesig: bool,
65
66/// If true, the linker will preallocate several sections and segments before starting the linking61/// If true, the linker will preallocate several sections and segments before starting the linking
67/// process. This is for example true for stage2 debug builds, however, this is false for stage162/// process. This is for example true for stage2 debug builds, however, this is false for stage1
68/// and potentially stage2 release builds in the future.63/// and potentially stage2 release builds in the future.
...@@ -76,6 +71,9 @@ header_pad: u16 = 0x1000,...@@ -76,6 +71,9 @@ header_pad: u16 = 0x1000,
76/// The absolute address of the entry point.71/// The absolute address of the entry point.
77entry_addr: ?u64 = null,72entry_addr: ?u64 = null,
7873
74/// Code signature (if any)
75code_signature: ?CodeSignature = null,
76
79objects: std.ArrayListUnmanaged(Object) = .{},77objects: std.ArrayListUnmanaged(Object) = .{},
80archives: std.ArrayListUnmanaged(Archive) = .{},78archives: std.ArrayListUnmanaged(Archive) = .{},
8179
...@@ -402,7 +400,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {...@@ -402,7 +400,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
402 .file = null,400 .file = null,
403 },401 },
404 .page_size = page_size,402 .page_size = page_size,
405 .requires_adhoc_codesig = requires_adhoc_codesig,403 .code_signature = if (requires_adhoc_codesig) CodeSignature.init(page_size) else null,
406 .needs_prealloc = needs_prealloc,404 .needs_prealloc = needs_prealloc,
407 };405 };
408406
...@@ -534,6 +532,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {...@@ -534,6 +532,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
534 }532 }
535 link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);533 link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);
536 man.hash.addOptionalBytes(self.base.options.sysroot);534 man.hash.addOptionalBytes(self.base.options.sysroot);
535 try man.addOptionalFile(self.base.options.entitlements);
537536
538 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.537 // We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
539 _ = try man.hit();538 _ = try man.hit();
...@@ -859,6 +858,19 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {...@@ -859,6 +858,19 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
859 self.load_commands_dirty = true;858 self.load_commands_dirty = true;
860 }859 }
861860
861 // code signature and entitlements
862 if (self.base.options.entitlements) |path| {
863 if (self.code_signature) |*csig| {
864 try csig.addEntitlements(self.base.allocator, path);
865 csig.code_directory.ident = self.base.options.emit.?.sub_path;
866 } else {
867 var csig = CodeSignature.init(self.page_size);
868 try csig.addEntitlements(self.base.allocator, path);
869 csig.code_directory.ident = self.base.options.emit.?.sub_path;
870 self.code_signature = csig;
871 }
872 }
873
862 if (self.base.options.verbose_link) {874 if (self.base.options.verbose_link) {
863 var argv = std.ArrayList([]const u8).init(arena);875 var argv = std.ArrayList([]const u8).init(arena);
864876
...@@ -1033,13 +1045,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {...@@ -1033,13 +1045,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
1033 try d_sym.flushModule(self.base.allocator, self.base.options);1045 try d_sym.flushModule(self.base.allocator, self.base.options);
1034 }1046 }
10351047
1036 if (self.requires_adhoc_codesig) {1048 if (self.code_signature) |*csig| {
1049 csig.clear(self.base.allocator);
1050 csig.code_directory.ident = self.base.options.emit.?.sub_path;
1037 // Preallocate space for the code signature.1051 // Preallocate space for the code signature.
1038 // We need to do this at this stage so that we have the load commands with proper values1052 // We need to do this at this stage so that we have the load commands with proper values
1039 // written out to the file.1053 // written out to the file.
1040 // The most important here is to have the correct vm and filesize of the __LINKEDIT segment1054 // The most important here is to have the correct vm and filesize of the __LINKEDIT segment
1041 // where the code signature goes into.1055 // where the code signature goes into.
1042 try self.writeCodeSignaturePadding();1056 try self.writeCodeSignaturePadding(csig);
1043 }1057 }
10441058
1045 try self.writeLoadCommands();1059 try self.writeLoadCommands();
...@@ -1055,8 +1069,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {...@@ -1055,8 +1069,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
10551069
1056 assert(!self.load_commands_dirty);1070 assert(!self.load_commands_dirty);
10571071
1058 if (self.requires_adhoc_codesig) {1072 if (self.code_signature) |*csig| {
1059 try self.writeCodeSignature(); // code signing always comes last1073 try self.writeCodeSignature(csig); // code signing always comes last
1060 }1074 }
10611075
1062 if (build_options.enable_link_snapshots) {1076 if (build_options.enable_link_snapshots) {
...@@ -3315,7 +3329,7 @@ fn addLoadDylibLC(self: *MachO, id: u16) !void {...@@ -3315,7 +3329,7 @@ fn addLoadDylibLC(self: *MachO, id: u16) !void {
3315}3329}
33163330
3317fn addCodeSignatureLC(self: *MachO) !void {3331fn addCodeSignatureLC(self: *MachO) !void {
3318 if (self.code_signature_cmd_index != null or !self.requires_adhoc_codesig) return;3332 if (self.code_signature_cmd_index != null or self.code_signature == null) return;
3319 self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);3333 self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
3320 try self.load_commands.append(self.base.allocator, .{3334 try self.load_commands.append(self.base.allocator, .{
3321 .linkedit_data = .{3335 .linkedit_data = .{
...@@ -3429,6 +3443,10 @@ pub fn deinit(self: *MachO) void {...@@ -3429,6 +3443,10 @@ pub fn deinit(self: *MachO) void {
3429 }3443 }
34303444
3431 self.atom_by_index_table.deinit(self.base.allocator);3445 self.atom_by_index_table.deinit(self.base.allocator);
3446
3447 if (self.code_signature) |*csig| {
3448 csig.deinit(self.base.allocator);
3449 }
3432}3450}
34333451
3434pub fn closeFiles(self: MachO) void {3452pub fn closeFiles(self: MachO) void {
...@@ -6143,7 +6161,7 @@ fn writeLinkeditSegment(self: *MachO) !void {...@@ -6143,7 +6161,7 @@ fn writeLinkeditSegment(self: *MachO) !void {
6143 seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size);6161 seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size);
6144}6162}
61456163
6146fn writeCodeSignaturePadding(self: *MachO) !void {6164fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
6147 const tracy = trace(@src());6165 const tracy = trace(@src());
6148 defer tracy.end();6166 defer tracy.end();
61496167
...@@ -6153,11 +6171,7 @@ fn writeCodeSignaturePadding(self: *MachO) !void {...@@ -6153,11 +6171,7 @@ fn writeCodeSignaturePadding(self: *MachO) !void {
6153 // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L2716171 // https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
6154 const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16);6172 const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16);
6155 const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize);6173 const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize);
6156 const needed_size = CodeSignature.calcCodeSignaturePaddingSize(6174 const needed_size = code_sig.estimateSize(fileoff);
6157 self.base.options.emit.?.sub_path,
6158 fileoff,
6159 self.page_size,
6160 );
6161 code_sig_cmd.dataoff = @intCast(u32, fileoff);6175 code_sig_cmd.dataoff = @intCast(u32, fileoff);
6162 code_sig_cmd.datasize = needed_size;6176 code_sig_cmd.datasize = needed_size;
61636177
...@@ -6173,34 +6187,30 @@ fn writeCodeSignaturePadding(self: *MachO) !void {...@@ -6173,34 +6187,30 @@ fn writeCodeSignaturePadding(self: *MachO) !void {
6173 self.load_commands_dirty = true;6187 self.load_commands_dirty = true;
6174}6188}
61756189
6176fn writeCodeSignature(self: *MachO) !void {6190fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void {
6177 const tracy = trace(@src());6191 const tracy = trace(@src());
6178 defer tracy.end();6192 defer tracy.end();
61796193
6180 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment;6194 const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment;
6181 const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data;6195 const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data;
61826196
6183 var code_sig: CodeSignature = .{};6197 var buffer = std.ArrayList(u8).init(self.base.allocator);
6184 defer code_sig.deinit(self.base.allocator);6198 defer buffer.deinit();
61856199 try buffer.ensureTotalCapacityPrecise(code_sig.size());
6186 try code_sig.calcAdhocSignature(6200 try code_sig.writeAdhocSignature(self.base.allocator, .{
6187 self.base.allocator,6201 .file = self.base.file.?,
6188 self.base.file.?,6202 .text_segment = text_segment.inner,
6189 self.base.options.emit.?.sub_path,6203 .code_sig_cmd = code_sig_cmd,
6190 text_segment.inner,6204 .output_mode = self.base.options.output_mode,
6191 code_sig_cmd,6205 }, buffer.writer());
6192 self.base.options.output_mode,6206 assert(buffer.items.len == code_sig.size());
6193 self.page_size,6207
6194 );6208 log.debug("writing code signature from 0x{x} to 0x{x}", .{
61956209 code_sig_cmd.dataoff,
6196 var buffer = try self.base.allocator.alloc(u8, code_sig.size());6210 code_sig_cmd.dataoff + buffer.items.len,
6197 defer self.base.allocator.free(buffer);6211 });
6198 var stream = std.io.fixedBufferStream(buffer);
6199 try code_sig.write(stream.writer());
6200
6201 log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len });
62026212
6203 try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff);6213 try self.base.file.?.pwriteAll(buffer.items, code_sig_cmd.dataoff);
6204}6214}
62056215
6206/// Writes all load commands and section headers.6216/// Writes all load commands and section headers.
src/link/MachO/CodeSignature.zig+315-104
...@@ -12,12 +12,102 @@ const Sha256 = std.crypto.hash.sha2.Sha256;...@@ -12,12 +12,102 @@ const Sha256 = std.crypto.hash.sha2.Sha256;
1212
13const hash_size: u8 = 32;13const hash_size: u8 = 32;
1414
15const Blob = union(enum) {
16 code_directory: *CodeDirectory,
17 requirements: *Requirements,
18 entitlements: *Entitlements,
19 signature: *Signature,
20
21 fn slotType(self: Blob) u32 {
22 return switch (self) {
23 .code_directory => |x| x.slotType(),
24 .requirements => |x| x.slotType(),
25 .entitlements => |x| x.slotType(),
26 .signature => |x| x.slotType(),
27 };
28 }
29
30 fn size(self: Blob) u32 {
31 return switch (self) {
32 .code_directory => |x| x.size(),
33 .requirements => |x| x.size(),
34 .entitlements => |x| x.size(),
35 .signature => |x| x.size(),
36 };
37 }
38
39 fn write(self: Blob, writer: anytype) !void {
40 return switch (self) {
41 .code_directory => |x| x.write(writer),
42 .requirements => |x| x.write(writer),
43 .entitlements => |x| x.write(writer),
44 .signature => |x| x.write(writer),
45 };
46 }
47};
48
15const CodeDirectory = struct {49const CodeDirectory = struct {
16 inner: macho.CodeDirectory,50 inner: macho.CodeDirectory,
17 data: std.ArrayListUnmanaged(u8) = .{},51 ident: []const u8,
52 special_slots: [n_special_slots][hash_size]u8,
53 code_slots: std.ArrayListUnmanaged([hash_size]u8) = .{},
54
55 const n_special_slots: usize = 7;
56
57 fn init(page_size: u16) CodeDirectory {
58 var cdir: CodeDirectory = .{
59 .inner = .{
60 .magic = macho.CSMAGIC_CODEDIRECTORY,
61 .length = @sizeOf(macho.CodeDirectory),
62 .version = macho.CS_SUPPORTSEXECSEG,
63 .flags = macho.CS_ADHOC,
64 .hashOffset = 0,
65 .identOffset = @sizeOf(macho.CodeDirectory),
66 .nSpecialSlots = 0,
67 .nCodeSlots = 0,
68 .codeLimit = 0,
69 .hashSize = hash_size,
70 .hashType = macho.CS_HASHTYPE_SHA256,
71 .platform = 0,
72 .pageSize = @truncate(u8, std.math.log2(page_size)),
73 .spare2 = 0,
74 .scatterOffset = 0,
75 .teamOffset = 0,
76 .spare3 = 0,
77 .codeLimit64 = 0,
78 .execSegBase = 0,
79 .execSegLimit = 0,
80 .execSegFlags = 0,
81 },
82 .ident = undefined,
83 .special_slots = undefined,
84 };
85 comptime var i = 0;
86 inline while (i < n_special_slots) : (i += 1) {
87 cdir.special_slots[i] = [_]u8{0} ** hash_size;
88 }
89 return cdir;
90 }
91
92 fn deinit(self: *CodeDirectory, allocator: Allocator) void {
93 self.code_slots.deinit(allocator);
94 }
95
96 fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
97 assert(index > 0);
98 self.inner.nSpecialSlots = std.math.max(self.inner.nSpecialSlots, index);
99 mem.copy(u8, &self.special_slots[index - 1], &hash);
100 }
101
102 fn slotType(self: CodeDirectory) u32 {
103 _ = self;
104 return macho.CSSLOT_CODEDIRECTORY;
105 }
18106
19 fn size(self: CodeDirectory) u32 {107 fn size(self: CodeDirectory) u32 {
20 return self.inner.length;108 const code_slots = self.inner.nCodeSlots * hash_size;
109 const special_slots = self.inner.nSpecialSlots * hash_size;
110 return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1) + special_slots + code_slots;
21 }111 }
22112
23 fn write(self: CodeDirectory, writer: anytype) !void {113 fn write(self: CodeDirectory, writer: anytype) !void {
...@@ -42,142 +132,263 @@ const CodeDirectory = struct {...@@ -42,142 +132,263 @@ const CodeDirectory = struct {
42 try writer.writeIntBig(u64, self.inner.execSegBase);132 try writer.writeIntBig(u64, self.inner.execSegBase);
43 try writer.writeIntBig(u64, self.inner.execSegLimit);133 try writer.writeIntBig(u64, self.inner.execSegLimit);
44 try writer.writeIntBig(u64, self.inner.execSegFlags);134 try writer.writeIntBig(u64, self.inner.execSegFlags);
45 try writer.writeAll(self.data.items);135
136 try writer.writeAll(self.ident);
137 try writer.writeByte(0);
138
139 var i: isize = @intCast(isize, self.inner.nSpecialSlots);
140 while (i > 0) : (i -= 1) {
141 try writer.writeAll(&self.special_slots[@intCast(usize, i - 1)]);
142 }
143
144 for (self.code_slots.items) |slot| {
145 try writer.writeAll(&slot);
146 }
46 }147 }
47};148};
48149
49/// Code signature blob header.150const Requirements = struct {
50inner: macho.SuperBlob = .{151 fn deinit(self: *Requirements, allocator: Allocator) void {
51 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,152 _ = self;
52 .length = @sizeOf(macho.SuperBlob),153 _ = allocator;
53 .count = 0,154 }
54},
55155
56/// CodeDirectory header which holds the hash of the binary.156 fn slotType(self: Requirements) u32 {
57cdir: ?CodeDirectory = null,157 _ = self;
158 return macho.CSSLOT_REQUIREMENTS;
159 }
58160
59pub fn calcAdhocSignature(161 fn size(self: Requirements) u32 {
60 self: *CodeSignature,162 _ = self;
61 allocator: Allocator,163 return 3 * @sizeOf(u32);
164 }
165
166 fn write(self: Requirements, writer: anytype) !void {
167 try writer.writeIntBig(u32, macho.CSMAGIC_REQUIREMENTS);
168 try writer.writeIntBig(u32, self.size());
169 try writer.writeIntBig(u32, 0);
170 }
171};
172
173const Entitlements = struct {
174 inner: []const u8,
175
176 fn deinit(self: *Entitlements, allocator: Allocator) void {
177 allocator.free(self.inner);
178 }
179
180 fn slotType(self: Entitlements) u32 {
181 _ = self;
182 return macho.CSSLOT_ENTITLEMENTS;
183 }
184
185 fn size(self: Entitlements) u32 {
186 return @intCast(u32, self.inner.len) + 2 * @sizeOf(u32);
187 }
188
189 fn write(self: Entitlements, writer: anytype) !void {
190 try writer.writeIntBig(u32, macho.CSMAGIC_EMBEDDED_ENTITLEMENTS);
191 try writer.writeIntBig(u32, self.size());
192 try writer.writeAll(self.inner);
193 }
194};
195
196const Signature = struct {
197 fn deinit(self: *Signature, allocator: Allocator) void {
198 _ = self;
199 _ = allocator;
200 }
201
202 fn slotType(self: Signature) u32 {
203 _ = self;
204 return macho.CSSLOT_SIGNATURESLOT;
205 }
206
207 fn size(self: Signature) u32 {
208 _ = self;
209 return 2 * @sizeOf(u32);
210 }
211
212 fn write(self: Signature, writer: anytype) !void {
213 try writer.writeIntBig(u32, macho.CSMAGIC_BLOBWRAPPER);
214 try writer.writeIntBig(u32, self.size());
215 }
216};
217
218page_size: u16,
219code_directory: CodeDirectory,
220requirements: ?Requirements = null,
221entitlements: ?Entitlements = null,
222signature: ?Signature = null,
223
224pub fn init(page_size: u16) CodeSignature {
225 return .{
226 .page_size = page_size,
227 .code_directory = CodeDirectory.init(page_size),
228 };
229}
230
231pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
232 self.code_directory.deinit(allocator);
233 if (self.requirements) |*req| {
234 req.deinit(allocator);
235 }
236 if (self.entitlements) |*ents| {
237 ents.deinit(allocator);
238 }
239 if (self.signature) |*sig| {
240 sig.deinit(allocator);
241 }
242}
243
244pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
245 const file = try fs.cwd().openFile(path, .{});
246 defer file.close();
247 const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
248 self.entitlements = .{ .inner = inner };
249}
250
251pub const WriteOpts = struct {
62 file: fs.File,252 file: fs.File,
63 id: []const u8,
64 text_segment: macho.segment_command_64,253 text_segment: macho.segment_command_64,
65 code_sig_cmd: macho.linkedit_data_command,254 code_sig_cmd: macho.linkedit_data_command,
66 output_mode: std.builtin.OutputMode,255 output_mode: std.builtin.OutputMode,
67 page_size: u16,256};
257
258pub fn writeAdhocSignature(
259 self: *CodeSignature,
260 allocator: Allocator,
261 opts: WriteOpts,
262 writer: anytype,
68) !void {263) !void {
69 const execSegBase: u64 = text_segment.fileoff;264 var header: macho.SuperBlob = .{
70 const execSegLimit: u64 = text_segment.filesize;265 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
71 const execSegFlags: u64 = if (output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;266 .length = @sizeOf(macho.SuperBlob),
72 const file_size = code_sig_cmd.dataoff;267 .count = 0,
73 var cdir = CodeDirectory{
74 .inner = .{
75 .magic = macho.CSMAGIC_CODEDIRECTORY,
76 .length = @sizeOf(macho.CodeDirectory),
77 .version = macho.CS_SUPPORTSEXECSEG,
78 .flags = macho.CS_ADHOC,
79 .hashOffset = 0,
80 .identOffset = 0,
81 .nSpecialSlots = 0,
82 .nCodeSlots = 0,
83 .codeLimit = file_size,
84 .hashSize = hash_size,
85 .hashType = macho.CS_HASHTYPE_SHA256,
86 .platform = 0,
87 .pageSize = @truncate(u8, std.math.log2(page_size)),
88 .spare2 = 0,
89 .scatterOffset = 0,
90 .teamOffset = 0,
91 .spare3 = 0,
92 .codeLimit64 = 0,
93 .execSegBase = execSegBase,
94 .execSegLimit = execSegLimit,
95 .execSegFlags = execSegFlags,
96 },
97 };268 };
98269
99 const total_pages = mem.alignForward(file_size, page_size) / page_size;270 var blobs = std.ArrayList(Blob).init(allocator);
271 defer blobs.deinit();
100272
101 var hash: [hash_size]u8 = undefined;273 self.code_directory.inner.execSegBase = opts.text_segment.fileoff;
102 var buffer = try allocator.alloc(u8, page_size);274 self.code_directory.inner.execSegLimit = opts.text_segment.filesize;
103 defer allocator.free(buffer);275 self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
276 const file_size = opts.code_sig_cmd.dataoff;
277 self.code_directory.inner.codeLimit = file_size;
104278
105 try cdir.data.ensureTotalCapacityPrecise(allocator, total_pages * hash_size + id.len + 1);279 const total_pages = mem.alignForward(file_size, self.page_size) / self.page_size;
106280
107 // 1. Save the identifier and update offsets281 var buffer = try allocator.alloc(u8, self.page_size);
108 cdir.inner.identOffset = cdir.inner.length;282 defer allocator.free(buffer);
109 cdir.data.appendSliceAssumeCapacity(id);
110 cdir.data.appendAssumeCapacity(0);
111283
112 // 2. Calculate hash for each page (in file) and write it to the buffer284 try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
113 // TODO figure out how we can cache several hashes since we won't update285
114 // every page during incremental linking286 // Calculate hash for each page (in file) and write it to the buffer
115 cdir.inner.hashOffset = cdir.inner.identOffset + @intCast(u32, id.len) + 1;287 var hash: [hash_size]u8 = undefined;
116 var i: usize = 0;288 var i: usize = 0;
117 while (i < total_pages) : (i += 1) {289 while (i < total_pages) : (i += 1) {
118 const fstart = i * page_size;290 const fstart = i * self.page_size;
119 const fsize = if (fstart + page_size > file_size) file_size - fstart else page_size;291 const fsize = if (fstart + self.page_size > file_size) file_size - fstart else self.page_size;
120 const len = try file.preadAll(buffer, fstart);292 const len = try opts.file.preadAll(buffer, fstart);
121 assert(fsize <= len);293 assert(fsize <= len);
122294
123 Sha256.hash(buffer[0..fsize], &hash, .{});295 Sha256.hash(buffer[0..fsize], &hash, .{});
124296
125 cdir.data.appendSliceAssumeCapacity(&hash);297 self.code_directory.code_slots.appendAssumeCapacity(hash);
126 cdir.inner.nCodeSlots += 1;298 self.code_directory.inner.nCodeSlots += 1;
127 }299 }
128300
129 // 3. Update CodeDirectory length301 try blobs.append(.{ .code_directory = &self.code_directory });
130 cdir.inner.length += @intCast(u32, cdir.data.items.len);302 header.length += @sizeOf(macho.BlobIndex);
303 header.count += 1;
131304
132 self.inner.length += @sizeOf(macho.BlobIndex) + cdir.size();305 if (self.requirements) |*req| {
133 self.inner.count = 1;306 var buf = std.ArrayList(u8).init(allocator);
134 self.cdir = cdir;307 defer buf.deinit();
135}308 try req.write(buf.writer());
309 Sha256.hash(buf.items, &hash, .{});
310 self.code_directory.addSpecialHash(req.slotType(), hash);
136311
137pub fn size(self: CodeSignature) u32 {312 try blobs.append(.{ .requirements = req });
138 return self.inner.length;313 header.count += 1;
139}314 header.length += @sizeOf(macho.BlobIndex) + req.size();
315 }
140316
141pub fn write(self: CodeSignature, writer: anytype) !void {317 if (self.entitlements) |*ents| {
142 try self.writeHeader(writer);318 var buf = std.ArrayList(u8).init(allocator);
143 const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex);319 defer buf.deinit();
144 try writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, writer);320 try ents.write(buf.writer());
145 try self.cdir.?.write(writer);321 Sha256.hash(buf.items, &hash, .{});
146}322 self.code_directory.addSpecialHash(ents.slotType(), hash);
147323
148pub fn deinit(self: *CodeSignature, allocator: Allocator) void {324 try blobs.append(.{ .entitlements = ents });
149 if (self.cdir) |*cdir| {325 header.count += 1;
150 cdir.data.deinit(allocator);326 header.length += @sizeOf(macho.BlobIndex) + ents.size();
151 }327 }
152}
153328
154fn writeHeader(self: CodeSignature, writer: anytype) !void {329 if (self.signature) |*sig| {
155 try writer.writeIntBig(u32, self.inner.magic);330 try blobs.append(.{ .signature = sig });
156 try writer.writeIntBig(u32, self.inner.length);331 header.count += 1;
157 try writer.writeIntBig(u32, self.inner.count);332 header.length += @sizeOf(macho.BlobIndex) + sig.size();
158}333 }
159334
160fn writeBlobIndex(tt: u32, offset: u32, writer: anytype) !void {335 self.code_directory.inner.hashOffset =
161 try writer.writeIntBig(u32, tt);336 @sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1) + self.code_directory.inner.nSpecialSlots * hash_size;
162 try writer.writeIntBig(u32, offset);337 self.code_directory.inner.length = self.code_directory.size();
163}338 header.length += self.code_directory.size();
164339
165test "CodeSignature header" {340 try writer.writeIntBig(u32, header.magic);
166 var code_sig: CodeSignature = .{};341 try writer.writeIntBig(u32, header.length);
167 defer code_sig.deinit(testing.allocator);342 try writer.writeIntBig(u32, header.count);
168343
169 var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined;344 var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @intCast(u32, blobs.items.len);
170 var stream = std.io.fixedBufferStream(&buffer);345 for (blobs.items) |blob| {
171 try code_sig.writeHeader(stream.writer());346 try writer.writeIntBig(u32, blob.slotType());
347 try writer.writeIntBig(u32, offset);
348 offset += blob.size();
349 }
172350
173 const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 };351 for (blobs.items) |blob| {
174 try testing.expect(mem.eql(u8, expected, &buffer));352 try blob.write(writer);
353 }
354}
355
356pub fn size(self: CodeSignature) u32 {
357 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
358 if (self.requirements) |req| {
359 ssize += @sizeOf(macho.BlobIndex) + req.size();
360 }
361 if (self.entitlements) |ent| {
362 ssize += @sizeOf(macho.BlobIndex) + ent.size();
363 }
364 if (self.signature) |sig| {
365 ssize += @sizeOf(macho.BlobIndex) + sig.size();
366 }
367 return ssize;
368}
369
370pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
371 var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
372 // Approx code slots
373 const total_pages = mem.alignForwardGeneric(u64, file_size, self.page_size) / self.page_size;
374 ssize += total_pages * hash_size;
375 var n_special_slots: u32 = 0;
376 if (self.requirements) |req| {
377 ssize += @sizeOf(macho.BlobIndex) + req.size();
378 n_special_slots = std.math.max(n_special_slots, req.slotType());
379 }
380 if (self.entitlements) |ent| {
381 ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
382 n_special_slots = std.math.max(n_special_slots, ent.slotType());
383 }
384 if (self.signature) |sig| {
385 ssize += @sizeOf(macho.BlobIndex) + sig.size();
386 }
387 ssize += n_special_slots * hash_size;
388 return @intCast(u32, mem.alignForwardGeneric(u64, ssize, @sizeOf(u64)));
175}389}
176390
177pub fn calcCodeSignaturePaddingSize(id: []const u8, file_size: u64, page_size: u16) u32 {391pub fn clear(self: *CodeSignature, allocator: Allocator) void {
178 const ident_size = id.len + 1;392 self.code_directory.deinit(allocator);
179 const total_pages = mem.alignForwardGeneric(u64, file_size, page_size) / page_size;393 self.code_directory = CodeDirectory.init(self.page_size);
180 const hashed_size = total_pages * hash_size;
181 const codesig_header = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + @sizeOf(macho.CodeDirectory);
182 return @intCast(u32, mem.alignForwardGeneric(u64, codesig_header + ident_size + hashed_size, @sizeOf(u64)));
183}394}
src/main.zig+7
...@@ -433,6 +433,7 @@ const usage_build_generic =...@@ -433,6 +433,7 @@ const usage_build_generic =
433 \\ -framework [name] (Darwin) link against framework433 \\ -framework [name] (Darwin) link against framework
434 \\ -F[dir] (Darwin) add search path for frameworks434 \\ -F[dir] (Darwin) add search path for frameworks
435 \\ -install_name=[value] (Darwin) add dylib's install name435 \\ -install_name=[value] (Darwin) add dylib's install name
436 \\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature
436 \\ --import-memory (WebAssembly) import memory from the environment437 \\ --import-memory (WebAssembly) import memory from the environment
437 \\ --import-table (WebAssembly) import function table from the host environment438 \\ --import-table (WebAssembly) import function table from the host environment
438 \\ --export-table (WebAssembly) export function table to the host environment439 \\ --export-table (WebAssembly) export function table to the host environment
...@@ -680,6 +681,7 @@ fn buildOutputType(...@@ -680,6 +681,7 @@ fn buildOutputType(
680 var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null;681 var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null;
681 var install_name: ?[]const u8 = null;682 var install_name: ?[]const u8 = null;
682 var hash_style: link.HashStyle = .both;683 var hash_style: link.HashStyle = .both;
684 var entitlements: ?[]const u8 = null;
683685
684 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.686 // e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
685 // This array is populated by zig cc frontend and then has to be converted to zig-style687 // This array is populated by zig cc frontend and then has to be converted to zig-style
...@@ -1036,6 +1038,10 @@ fn buildOutputType(...@@ -1036,6 +1038,10 @@ fn buildOutputType(
1036 } else {1038 } else {
1037 enable_link_snapshots = true;1039 enable_link_snapshots = true;
1038 }1040 }
1041 } else if (mem.eql(u8, arg, "--entitlements")) {
1042 entitlements = args_iter.next() orelse {
1043 fatal("expected parameter after {s}", .{arg});
1044 };
1039 } else if (mem.eql(u8, arg, "-fcompiler-rt")) {1045 } else if (mem.eql(u8, arg, "-fcompiler-rt")) {
1040 want_compiler_rt = true;1046 want_compiler_rt = true;
1041 } else if (mem.eql(u8, arg, "-fno-compiler-rt")) {1047 } else if (mem.eql(u8, arg, "-fno-compiler-rt")) {
...@@ -2729,6 +2735,7 @@ fn buildOutputType(...@@ -2729,6 +2735,7 @@ fn buildOutputType(
2729 .enable_link_snapshots = enable_link_snapshots,2735 .enable_link_snapshots = enable_link_snapshots,
2730 .native_darwin_sdk = native_darwin_sdk,2736 .native_darwin_sdk = native_darwin_sdk,
2731 .install_name = install_name,2737 .install_name = install_name,
2738 .entitlements = entitlements,
2732 }) catch |err| switch (err) {2739 }) catch |err| switch (err) {
2733 error.LibCUnavailable => {2740 error.LibCUnavailable => {
2734 const target = target_info.target;2741 const target = target_info.target;