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}