master
1const CodeSignature = @This();
2
3const std = @import("std");
4const assert = std.debug.assert;
5const fs = std.fs;
6const log = std.log.scoped(.link);
7const macho = std.macho;
8const mem = std.mem;
9const testing = std.testing;
10const trace = @import("../../tracy.zig").trace;
11const Allocator = mem.Allocator;
12const Hasher = @import("hasher.zig").ParallelHasher;
13const MachO = @import("../MachO.zig");
14const Sha256 = std.crypto.hash.sha2.Sha256;
15
16const hash_size = Sha256.digest_length;
17
18const Blob = union(enum) {
19 code_directory: *CodeDirectory,
20 requirements: *Requirements,
21 entitlements: *Entitlements,
22 signature: *Signature,
23
24 fn slotType(self: Blob) u32 {
25 return switch (self) {
26 .code_directory => |x| x.slotType(),
27 .requirements => |x| x.slotType(),
28 .entitlements => |x| x.slotType(),
29 .signature => |x| x.slotType(),
30 };
31 }
32
33 fn size(self: Blob) u32 {
34 return switch (self) {
35 .code_directory => |x| x.size(),
36 .requirements => |x| x.size(),
37 .entitlements => |x| x.size(),
38 .signature => |x| x.size(),
39 };
40 }
41
42 fn write(self: Blob, writer: anytype) !void {
43 return switch (self) {
44 .code_directory => |x| x.write(writer),
45 .requirements => |x| x.write(writer),
46 .entitlements => |x| x.write(writer),
47 .signature => |x| x.write(writer),
48 };
49 }
50};
51
52const CodeDirectory = struct {
53 inner: macho.CodeDirectory,
54 ident: []const u8,
55 special_slots: [n_special_slots][hash_size]u8,
56 code_slots: std.ArrayList([hash_size]u8) = .empty,
57
58 const n_special_slots: usize = 7;
59
60 fn init(page_size: u16) CodeDirectory {
61 var cdir: CodeDirectory = .{
62 .inner = .{
63 .magic = macho.CSMAGIC_CODEDIRECTORY,
64 .length = @sizeOf(macho.CodeDirectory),
65 .version = macho.CS_SUPPORTSEXECSEG,
66 .flags = macho.CS_ADHOC | macho.CS_LINKER_SIGNED,
67 .hashOffset = 0,
68 .identOffset = @sizeOf(macho.CodeDirectory),
69 .nSpecialSlots = 0,
70 .nCodeSlots = 0,
71 .codeLimit = 0,
72 .hashSize = hash_size,
73 .hashType = macho.CS_HASHTYPE_SHA256,
74 .platform = 0,
75 .pageSize = @as(u8, @truncate(std.math.log2(page_size))),
76 .spare2 = 0,
77 .scatterOffset = 0,
78 .teamOffset = 0,
79 .spare3 = 0,
80 .codeLimit64 = 0,
81 .execSegBase = 0,
82 .execSegLimit = 0,
83 .execSegFlags = 0,
84 },
85 .ident = undefined,
86 .special_slots = undefined,
87 };
88 comptime var i = 0;
89 inline while (i < n_special_slots) : (i += 1) {
90 cdir.special_slots[i] = [_]u8{0} ** hash_size;
91 }
92 return cdir;
93 }
94
95 fn deinit(self: *CodeDirectory, allocator: Allocator) void {
96 self.code_slots.deinit(allocator);
97 }
98
99 fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
100 assert(index > 0);
101 self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index);
102 @memcpy(&self.special_slots[index - 1], &hash);
103 }
104
105 fn slotType(self: CodeDirectory) u32 {
106 _ = self;
107 return macho.CSSLOT_CODEDIRECTORY;
108 }
109
110 fn size(self: CodeDirectory) u32 {
111 const code_slots = self.inner.nCodeSlots * hash_size;
112 const special_slots = self.inner.nSpecialSlots * hash_size;
113 return @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.ident.len + 1 + special_slots + code_slots));
114 }
115
116 fn write(self: CodeDirectory, writer: anytype) !void {
117 try writer.writeInt(u32, self.inner.magic, .big);
118 try writer.writeInt(u32, self.inner.length, .big);
119 try writer.writeInt(u32, self.inner.version, .big);
120 try writer.writeInt(u32, self.inner.flags, .big);
121 try writer.writeInt(u32, self.inner.hashOffset, .big);
122 try writer.writeInt(u32, self.inner.identOffset, .big);
123 try writer.writeInt(u32, self.inner.nSpecialSlots, .big);
124 try writer.writeInt(u32, self.inner.nCodeSlots, .big);
125 try writer.writeInt(u32, self.inner.codeLimit, .big);
126 try writer.writeByte(self.inner.hashSize);
127 try writer.writeByte(self.inner.hashType);
128 try writer.writeByte(self.inner.platform);
129 try writer.writeByte(self.inner.pageSize);
130 try writer.writeInt(u32, self.inner.spare2, .big);
131 try writer.writeInt(u32, self.inner.scatterOffset, .big);
132 try writer.writeInt(u32, self.inner.teamOffset, .big);
133 try writer.writeInt(u32, self.inner.spare3, .big);
134 try writer.writeInt(u64, self.inner.codeLimit64, .big);
135 try writer.writeInt(u64, self.inner.execSegBase, .big);
136 try writer.writeInt(u64, self.inner.execSegLimit, .big);
137 try writer.writeInt(u64, self.inner.execSegFlags, .big);
138
139 try writer.writeAll(self.ident);
140 try writer.writeByte(0);
141
142 var i: isize = @as(isize, @intCast(self.inner.nSpecialSlots));
143 while (i > 0) : (i -= 1) {
144 try writer.writeAll(&self.special_slots[@as(usize, @intCast(i - 1))]);
145 }
146
147 for (self.code_slots.items) |slot| {
148 try writer.writeAll(&slot);
149 }
150 }
151};
152
153const Requirements = struct {
154 fn deinit(self: *Requirements, allocator: Allocator) void {
155 _ = self;
156 _ = allocator;
157 }
158
159 fn slotType(self: Requirements) u32 {
160 _ = self;
161 return macho.CSSLOT_REQUIREMENTS;
162 }
163
164 fn size(self: Requirements) u32 {
165 _ = self;
166 return 3 * @sizeOf(u32);
167 }
168
169 fn write(self: Requirements, writer: anytype) !void {
170 try writer.writeInt(u32, macho.CSMAGIC_REQUIREMENTS, .big);
171 try writer.writeInt(u32, self.size(), .big);
172 try writer.writeInt(u32, 0, .big);
173 }
174};
175
176const Entitlements = struct {
177 inner: []const u8,
178
179 fn deinit(self: *Entitlements, allocator: Allocator) void {
180 allocator.free(self.inner);
181 }
182
183 fn slotType(self: Entitlements) u32 {
184 _ = self;
185 return macho.CSSLOT_ENTITLEMENTS;
186 }
187
188 fn size(self: Entitlements) u32 {
189 return @as(u32, @intCast(self.inner.len)) + 2 * @sizeOf(u32);
190 }
191
192 fn write(self: Entitlements, writer: anytype) !void {
193 try writer.writeInt(u32, macho.CSMAGIC_EMBEDDED_ENTITLEMENTS, .big);
194 try writer.writeInt(u32, self.size(), .big);
195 try writer.writeAll(self.inner);
196 }
197};
198
199const Signature = struct {
200 fn deinit(self: *Signature, allocator: Allocator) void {
201 _ = self;
202 _ = allocator;
203 }
204
205 fn slotType(self: Signature) u32 {
206 _ = self;
207 return macho.CSSLOT_SIGNATURESLOT;
208 }
209
210 fn size(self: Signature) u32 {
211 _ = self;
212 return 2 * @sizeOf(u32);
213 }
214
215 fn write(self: Signature, writer: anytype) !void {
216 try writer.writeInt(u32, macho.CSMAGIC_BLOBWRAPPER, .big);
217 try writer.writeInt(u32, self.size(), .big);
218 }
219};
220
221page_size: u16,
222code_directory: CodeDirectory,
223requirements: ?Requirements = null,
224entitlements: ?Entitlements = null,
225signature: ?Signature = null,
226
227pub fn init(page_size: u16) CodeSignature {
228 return .{
229 .page_size = page_size,
230 .code_directory = CodeDirectory.init(page_size),
231 };
232}
233
234pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
235 self.code_directory.deinit(allocator);
236 if (self.requirements) |*req| {
237 req.deinit(allocator);
238 }
239 if (self.entitlements) |*ents| {
240 ents.deinit(allocator);
241 }
242 if (self.signature) |*sig| {
243 sig.deinit(allocator);
244 }
245}
246
247pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
248 const inner = try fs.cwd().readFileAlloc(path, allocator, .limited(std.math.maxInt(u32)));
249 self.entitlements = .{ .inner = inner };
250}
251
252pub const WriteOpts = struct {
253 file: fs.File,
254 exec_seg_base: u64,
255 exec_seg_limit: u64,
256 file_size: u32,
257 dylib: bool,
258};
259
260pub fn writeAdhocSignature(
261 self: *CodeSignature,
262 macho_file: *MachO,
263 opts: WriteOpts,
264 writer: *std.Io.Writer,
265) !void {
266 const tracy = trace(@src());
267 defer tracy.end();
268
269 const allocator = macho_file.base.comp.gpa;
270
271 var header: macho.SuperBlob = .{
272 .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
273 .length = @sizeOf(macho.SuperBlob),
274 .count = 0,
275 };
276
277 var blobs = std.array_list.Managed(Blob).init(allocator);
278 defer blobs.deinit();
279
280 self.code_directory.inner.execSegBase = opts.exec_seg_base;
281 self.code_directory.inner.execSegLimit = opts.exec_seg_limit;
282 self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0;
283 self.code_directory.inner.codeLimit = opts.file_size;
284
285 const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size));
286
287 try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
288 self.code_directory.code_slots.items.len = total_pages;
289 self.code_directory.inner.nCodeSlots = total_pages;
290
291 // Calculate hash for each page (in file) and write it to the buffer
292 var hasher = Hasher(Sha256){ .allocator = allocator, .thread_pool = macho_file.base.comp.thread_pool };
293 try hasher.hash(opts.file, self.code_directory.code_slots.items, .{
294 .chunk_size = self.page_size,
295 .max_file_size = opts.file_size,
296 });
297
298 try blobs.append(.{ .code_directory = &self.code_directory });
299 header.length += @sizeOf(macho.BlobIndex);
300 header.count += 1;
301
302 var hash: [hash_size]u8 = undefined;
303
304 if (self.requirements) |*req| {
305 var a: std.Io.Writer.Allocating = .init(allocator);
306 defer a.deinit();
307 try req.write(&a.writer);
308 Sha256.hash(a.written(), &hash, .{});
309 self.code_directory.addSpecialHash(req.slotType(), hash);
310
311 try blobs.append(.{ .requirements = req });
312 header.count += 1;
313 header.length += @sizeOf(macho.BlobIndex) + req.size();
314 }
315
316 if (self.entitlements) |*ents| {
317 var a: std.Io.Writer.Allocating = .init(allocator);
318 defer a.deinit();
319 try ents.write(&a.writer);
320 Sha256.hash(a.written(), &hash, .{});
321 self.code_directory.addSpecialHash(ents.slotType(), hash);
322
323 try blobs.append(.{ .entitlements = ents });
324 header.count += 1;
325 header.length += @sizeOf(macho.BlobIndex) + ents.size();
326 }
327
328 if (self.signature) |*sig| {
329 try blobs.append(.{ .signature = sig });
330 header.count += 1;
331 header.length += @sizeOf(macho.BlobIndex) + sig.size();
332 }
333
334 self.code_directory.inner.hashOffset =
335 @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size));
336 self.code_directory.inner.length = self.code_directory.size();
337 header.length += self.code_directory.size();
338
339 try writer.writeInt(u32, header.magic, .big);
340 try writer.writeInt(u32, header.length, .big);
341 try writer.writeInt(u32, header.count, .big);
342
343 var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len));
344 for (blobs.items) |blob| {
345 try writer.writeInt(u32, blob.slotType(), .big);
346 try writer.writeInt(u32, offset, .big);
347 offset += blob.size();
348 }
349
350 for (blobs.items) |blob| {
351 try blob.write(writer);
352 }
353}
354
355pub fn size(self: CodeSignature) u32 {
356 var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
357 if (self.requirements) |req| {
358 ssize += @sizeOf(macho.BlobIndex) + req.size();
359 }
360 if (self.entitlements) |ent| {
361 ssize += @sizeOf(macho.BlobIndex) + ent.size();
362 }
363 if (self.signature) |sig| {
364 ssize += @sizeOf(macho.BlobIndex) + sig.size();
365 }
366 return ssize;
367}
368
369pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
370 var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
371 // Approx code slots
372 const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size;
373 ssize += total_pages * hash_size;
374 var n_special_slots: u32 = 0;
375 if (self.requirements) |req| {
376 ssize += @sizeOf(macho.BlobIndex) + req.size();
377 n_special_slots = @max(n_special_slots, req.slotType());
378 }
379 if (self.entitlements) |ent| {
380 ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
381 n_special_slots = @max(n_special_slots, ent.slotType());
382 }
383 if (self.signature) |sig| {
384 ssize += @sizeOf(macho.BlobIndex) + sig.size();
385 }
386 ssize += n_special_slots * hash_size;
387 return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64))));
388}
389
390pub fn clear(self: *CodeSignature, allocator: Allocator) void {
391 self.code_directory.deinit(allocator);
392 self.code_directory = CodeDirectory.init(self.page_size);
393}