diff --git a/lib/std/macho.zig b/lib/std/macho.zig index 79378ef5a4..6bd22359f7 100644 --- a/lib/std/macho.zig +++ b/lib/std/macho.zig @@ -1384,10 +1384,10 @@ pub const CSTYPE_INDEX_REQUIREMENTS: u32 = 0x00000002; /// Compat with amfi pub const CSTYPE_INDEX_ENTITLEMENTS: u32 = 0x00000005; -pub const CS_HASHTYPE_SHA1: u32 = 1; -pub const CS_HASHTYPE_SHA256: u32 = 2; -pub const CS_HASHTYPE_SHA256_TRUNCATED: u32 = 3; -pub const CS_HASHTYPE_SHA384: u32 = 4; +pub const CS_HASHTYPE_SHA1: u8 = 1; +pub const CS_HASHTYPE_SHA256: u8 = 2; +pub const CS_HASHTYPE_SHA256_TRUNCATED: u8 = 3; +pub const CS_HASHTYPE_SHA384: u8 = 4; pub const CS_SHA1_LEN: u32 = 20; pub const CS_SHA256_LEN: u32 = 32; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index f77f05fb33..ca5dc894df 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -385,8 +385,6 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { try self.base.file.?.pwriteAll(mem.spanZ(LIB_SYSTEM_PATH), off); self.libsystem_cmd_dirty = false; } - - try self.writecodeSignature(); }, .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, @@ -430,6 +428,11 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { assert(!self.cmd_table_dirty); assert(!self.dylinker_cmd_dirty); assert(!self.libsystem_cmd_dirty); + + switch (self.base.options.output_mode) { + .Exe, .Lib => try self.writeCodeSignature(), // code signing always comes last + else => {}, + } } fn linkWithLLD(self: *MachO, comp: *Compilation) !void { @@ -1486,6 +1489,9 @@ pub fn populateMissingMetadata(self: *MachO) !void { log.debug("found code signature free space 0x{x} to 0x{x}\n", .{ off, off + file_size }); code_sig.dataoff = off; code_sig.datasize = file_size; + + const segment_size = mem.alignForwardGeneric(u64, file_size, self.page_size); + linkedit.vmsize += segment_size; } } if (self.dyld_stub_binder_index == null) { @@ -1752,7 +1758,7 @@ fn writeAllUndefSymbols(self: *MachO) !void { try self.base.file.?.pwriteAll(mem.sliceAsBytes(self.undef_symbols.items), off); } -fn writecodeSignature(self: *MachO) !void { +fn writeCodeSignature(self: *MachO) !void { const code_sig_cmd = &self.load_commands.items[self.code_signature_cmd_index.?].LinkeditData; var code_sig = CodeSignature.init(self.base.allocator); @@ -1766,7 +1772,7 @@ fn writecodeSignature(self: *MachO) !void { code_sig.write(buffer); try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff); - try self.base.file.?.pwriteAll(&[_]u8{ 0 }, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); + try self.base.file.?.pwriteAll(&[_]u8{0}, code_sig_cmd.dataoff + code_sig_cmd.datasize - 1); } fn writeExportTrie(self: *MachO) !void { diff --git a/src/link/MachO/CodeSignature.zig b/src/link/MachO/CodeSignature.zig index 25b1e9e9ff..813993d02c 100644 --- a/src/link/MachO/CodeSignature.zig +++ b/src/link/MachO/CodeSignature.zig @@ -7,19 +7,24 @@ const macho = std.macho; const mem = std.mem; const testing = std.testing; const Allocator = mem.Allocator; +const Sha256 = std.crypto.hash.sha2.Sha256; const MachO = @import("../MachO.zig"); -const Blob = struct { +const hash_size: u8 = 32; +const page_size: u16 = 0x1000; + +const CodeDirectory = struct { inner: macho.CodeDirectory, data: std.ArrayListUnmanaged(u8) = .{}, - fn size(self: Blob) u32 { + fn size(self: CodeDirectory) u32 { return self.inner.length; } - fn write(self: Blob, buffer: []u8) void { + fn write(self: CodeDirectory, buffer: []u8) void { assert(buffer.len >= self.inner.length); + mem.writeIntBig(u32, buffer[0..4], self.inner.magic); mem.writeIntBig(u32, buffer[4..8], self.inner.length); mem.writeIntBig(u32, buffer[8..12], self.inner.version); @@ -29,10 +34,10 @@ const Blob = struct { mem.writeIntBig(u32, buffer[24..28], self.inner.nSpecialSlots); mem.writeIntBig(u32, buffer[28..32], self.inner.nCodeSlots); mem.writeIntBig(u32, buffer[32..36], self.inner.codeLimit); - mem.writeIntBig(u8, buffer[36..37], self.inner.hashSize); - mem.writeIntBig(u8, buffer[37..38], self.inner.hashType); - mem.writeIntBig(u8, buffer[38..39], self.inner.platform); - mem.writeIntBig(u8, buffer[39..40], self.inner.pageSize); + buffer[36] = self.inner.hashSize; + buffer[37] = self.inner.hashType; + buffer[38] = self.inner.platform; + buffer[39] = self.inner.pageSize; mem.writeIntBig(u32, buffer[40..44], self.inner.spare2); mem.writeIntBig(u32, buffer[44..48], self.inner.scatterOffset); mem.writeIntBig(u32, buffer[48..52], self.inner.teamOffset); @@ -41,6 +46,8 @@ const Blob = struct { mem.writeIntBig(u64, buffer[64..72], self.inner.execSegBase); mem.writeIntBig(u64, buffer[72..80], self.inner.execSegLimit); mem.writeIntBig(u64, buffer[80..88], self.inner.execSegFlags); + + mem.copy(u8, buffer[88..], self.data.items); } }; @@ -50,7 +57,7 @@ inner: macho.SuperBlob = .{ .length = @sizeOf(macho.SuperBlob), .count = 0, }, -blob: ?Blob = null, +cdir: ?CodeDirectory = null, pub fn init(alloc: *Allocator) CodeSignature { return .{ @@ -60,10 +67,14 @@ pub fn init(alloc: *Allocator) CodeSignature { 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 data_segment = bin_file.load_commands.items[bin_file.data_segment_cmd_index.?].Segment; + const linkedit_segment = bin_file.load_commands.items[bin_file.linkedit_segment_cmd_index.?].Segment; + const symtab = bin_file.load_commands.items[bin_file.symtab_cmd_index.?].Symtab; + const execSegBase: u64 = text_segment.fileoff; const execSegLimit: u64 = text_segment.filesize; - const execSegFlags: u64 = text_segment.flags; - var blob = Blob{ + const execSegFlags: u64 = if (bin_file.base.options.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0; + var cdir = CodeDirectory{ .inner = .{ .magic = macho.CSMAGIC_CODEDIRECTORY, .length = @sizeOf(macho.CodeDirectory), @@ -74,10 +85,10 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { .nSpecialSlots = 0, .nCodeSlots = 0, .codeLimit = 0, - .hashSize = 0, - .hashType = 0, + .hashSize = hash_size, + .hashType = macho.CS_HASHTYPE_SHA256, .platform = 0, - .pageSize = 0, + .pageSize = @truncate(u8, std.math.log2(page_size)), .spare2 = 0, .scatterOffset = 0, .teamOffset = 0, @@ -88,9 +99,48 @@ pub fn calcAdhocSignature(self: *CodeSignature, bin_file: *const MachO) !void { .execSegFlags = execSegFlags, }, }; - self.inner.length += @sizeOf(macho.BlobIndex) + blob.size(); + + const file_size = symtab.stroff + symtab.strsize; + const total_pages = mem.alignForward(file_size, page_size) / page_size; + log.debug("Total file size: {}; total number of pages: {}\n", .{ file_size, total_pages }); + + var hash: [hash_size]u8 = undefined; + var buffer = try bin_file.base.allocator.alloc(u8, page_size); + defer bin_file.base.allocator.free(buffer); + const macho_file = bin_file.base.file.?; + + const id = bin_file.base.options.emit.?.sub_path; + 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 macho_file.preadAll(buffer, fstart); + assert(fsize <= len); + + Sha256.hash(buffer[0..fsize], &hash, .{}); + log.debug("Calculated hash for page 0x{x}-0x{x}: 0x{x}\n", .{ fstart, fstart + fsize, hash[0..] }); + + 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.blob = blob; + self.cdir = cdir; } pub fn size(self: CodeSignature) u32 { @@ -102,12 +152,12 @@ pub fn write(self: CodeSignature, buffer: []u8) void { self.writeHeader(buffer); const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex); writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, buffer[@sizeOf(macho.SuperBlob)..]); - self.blob.?.write(buffer[offset..]); + self.cdir.?.write(buffer[offset..]); } pub fn deinit(self: *CodeSignature) void { - if (self.blob) |*b| { - b.data.deinit(self.alloc); + if (self.cdir) |*cdir| { + cdir.data.deinit(self.alloc); } }