190 lines
6.8 KiB
Zig
190 lines
6.8 KiB
Zig
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;
|
|
const testing = std.testing;
|
|
const Allocator = mem.Allocator;
|
|
const Sha256 = std.crypto.hash.sha2.Sha256;
|
|
|
|
const hash_size: u8 = 32;
|
|
const page_size: u16 = 0x1000;
|
|
|
|
const CodeDirectory = struct {
|
|
inner: macho.CodeDirectory,
|
|
data: std.ArrayListUnmanaged(u8) = .{},
|
|
|
|
fn size(self: CodeDirectory) u32 {
|
|
return self.inner.length;
|
|
}
|
|
|
|
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);
|
|
mem.writeIntBig(u32, buffer[12..16], self.inner.flags);
|
|
mem.writeIntBig(u32, buffer[16..20], self.inner.hashOffset);
|
|
mem.writeIntBig(u32, buffer[20..24], self.inner.identOffset);
|
|
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);
|
|
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);
|
|
mem.writeIntBig(u32, buffer[52..56], self.inner.spare3);
|
|
mem.writeIntBig(u64, buffer[56..64], self.inner.codeLimit64);
|
|
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);
|
|
}
|
|
};
|
|
|
|
allocator: *Allocator,
|
|
inner: macho.SuperBlob = .{
|
|
.magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
|
|
.length = @sizeOf(macho.SuperBlob),
|
|
.count = 0,
|
|
},
|
|
cdir: ?CodeDirectory = null,
|
|
|
|
pub fn init(allocator: *Allocator) CodeSignature {
|
|
return .{ .allocator = allocator };
|
|
}
|
|
|
|
pub fn calcAdhocSignature(
|
|
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.allocator.alloc(u8, page_size);
|
|
defer self.allocator.free(buffer);
|
|
|
|
try cdir.data.ensureCapacity(self.allocator, 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);
|
|
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 size(self: CodeSignature) u32 {
|
|
return self.inner.length;
|
|
}
|
|
|
|
pub fn write(self: CodeSignature, buffer: []u8) void {
|
|
assert(buffer.len >= self.inner.length);
|
|
self.writeHeader(buffer);
|
|
const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex);
|
|
writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, buffer[@sizeOf(macho.SuperBlob)..]);
|
|
self.cdir.?.write(buffer[offset..]);
|
|
}
|
|
|
|
pub fn deinit(self: *CodeSignature) void {
|
|
if (self.cdir) |*cdir| {
|
|
cdir.data.deinit(self.allocator);
|
|
}
|
|
}
|
|
|
|
fn writeHeader(self: CodeSignature, buffer: []u8) void {
|
|
assert(buffer.len >= @sizeOf(macho.SuperBlob));
|
|
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.count);
|
|
}
|
|
|
|
fn writeBlobIndex(tt: u32, offset: u32, buffer: []u8) void {
|
|
assert(buffer.len >= @sizeOf(macho.BlobIndex));
|
|
mem.writeIntBig(u32, buffer[0..4], tt);
|
|
mem.writeIntBig(u32, buffer[4..8], offset);
|
|
}
|
|
|
|
test "CodeSignature header" {
|
|
var code_sig = CodeSignature.init(testing.allocator);
|
|
defer code_sig.deinit();
|
|
|
|
var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined;
|
|
code_sig.writeHeader(&buffer);
|
|
|
|
const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 };
|
|
testing.expect(mem.eql(u8, expected, &buffer));
|
|
}
|
|
|
|
pub fn calcCodeSignaturePadding(id: []const u8, file_size: u64) u32 {
|
|
const ident_size = id.len + 1;
|
|
const total_pages = mem.alignForwardGeneric(u64, file_size, page_size) / page_size;
|
|
const hashed_size = total_pages * hash_size;
|
|
const codesig_header = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + @sizeOf(macho.CodeDirectory);
|
|
return @intCast(u32, mem.alignForwardGeneric(u64, codesig_header + ident_size + hashed_size, @sizeOf(u64)));
|
|
}
|