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