Commit c25b3593e7

Jakub Konka <kubkon@jakubkonka.com>
2020-11-28 14:51:31
lld: add code signature to lld output
1 parent 545721c
Changed files (2)
src/link/MachO/CodeSignature.zig
@@ -2,6 +2,7 @@ const CodeSignature = @This();
 
 const std = @import("std");
 const assert = std.debug.assert;
+const fs = std.fs;
 const log = std.log.scoped(.link);
 const macho = std.macho;
 const mem = std.mem;
@@ -65,6 +66,82 @@ pub fn init(alloc: *Allocator) CodeSignature {
     };
 }
 
+pub fn calcAdhocSignatureFile(
+    self: *CodeSignature,
+    file: fs.File,
+    id: []const u8,
+    text_segment: macho.segment_command_64,
+    code_sig_cmd: macho.linkedit_data_command,
+    output_mode: std.builtin.OutputMode,
+) !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 = @intCast(u32, 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,
+        },
+    };
+
+    const total_pages = mem.alignForward(file_size, page_size) / page_size;
+
+    var hash: [hash_size]u8 = undefined;
+    var buffer = try self.alloc.alloc(u8, page_size);
+    defer self.alloc.free(buffer);
+
+    try cdir.data.ensureCapacity(self.alloc, total_pages * hash_size + id.len + 1);
+
+    // 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;
+    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);
+        assert(fsize <= len);
+
+        Sha256.hash(buffer[0..fsize], &hash, .{});
+
+        cdir.data.appendSliceAssumeCapacity(hash[0..]);
+        cdir.inner.nCodeSlots += 1;
+    }
+
+    // 3. Update CodeDirectory length
+    cdir.inner.length += @intCast(u32, cdir.data.items.len);
+
+    self.inner.length += @sizeOf(macho.BlobIndex) + cdir.size();
+    self.inner.count = 1;
+    self.cdir = cdir;
+}
+
 pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void {
     const text_segment = bin_file.load_commands.items[bin_file.text_segment_cmd_index.?].Segment;
     const code_sig_cmd = bin_file.load_commands.items[bin_file.code_signature_cmd_index.?].LinkeditData;
src/link/MachO.zig
@@ -812,28 +812,23 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             try parser.parseFile(out_file);
             // Pad out space for code signature
             const text_cmd = parser.load_commands.items[parser.text_cmd_index.?].Segment.inner;
-            std.debug.print("end_pos={},text_fileoff={},text_filesize={}\n", .{
-                parser.end_pos,
-                text_cmd.fileoff,
-                text_cmd.filesize,
-            });
             const dataoff = @intCast(u32, mem.alignForward(parser.end_pos.?, @sizeOf(u64)));
+            const datasize = 0x1000;
             const code_sig = macho.linkedit_data_command{
                 .cmd = macho.LC_CODE_SIGNATURE,
                 .cmdsize = @sizeOf(macho.linkedit_data_command),
                 .dataoff = dataoff,
-                .datasize = 0x1000,
+                .datasize = datasize,
             };
-
             const linkedit_seg = parser.load_commands.items[parser.linkedit_cmd_index.?].Segment.inner;
             const linkedit = macho.segment_command_64{
                 .cmd = linkedit_seg.cmd,
                 .cmdsize = linkedit_seg.cmdsize,
                 .segname = linkedit_seg.segname,
                 .vmaddr = linkedit_seg.vmaddr,
-                .vmsize = mem.alignForwardGeneric(u64, linkedit_seg.vmsize + 0x1000, self.page_size),
+                .vmsize = mem.alignForwardGeneric(u64, linkedit_seg.vmsize + datasize, self.page_size),
                 .fileoff = linkedit_seg.fileoff,
-                .filesize = linkedit_seg.filesize + (dataoff - parser.end_pos.?) + 0x1000,
+                .filesize = linkedit_seg.filesize + (dataoff - parser.end_pos.?) + datasize,
                 .maxprot = linkedit_seg.maxprot,
                 .initprot = linkedit_seg.initprot,
                 .nsects = linkedit_seg.nsects,
@@ -854,6 +849,20 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void {
             try out_file.pwriteAll(mem.sliceAsBytes(&[_]macho.linkedit_data_command{code_sig}), parser.code_sig_cmd_offset.?);
             try out_file.pwriteAll(mem.sliceAsBytes(&[_]macho.segment_command_64{linkedit}), parser.linkedit_cmd_offset.?);
             try out_file.pwriteAll(mem.sliceAsBytes(&[_]macho.mach_header_64{header}), 0);
+            // Generate adhoc code signature
+            var signature = CodeSignature.init(self.base.allocator);
+            defer signature.deinit();
+            try signature.calcAdhocSignatureFile(
+                out_file,
+                self.base.options.emit.?.sub_path,
+                text_cmd,
+                code_sig,
+                self.base.options.output_mode,
+            );
+            var buffer = try self.base.allocator.alloc(u8, signature.size());
+            defer self.base.allocator.free(buffer);
+            signature.write(buffer);
+            try out_file.pwriteAll(buffer, code_sig.dataoff);
         }
     }