mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
macho: extend CodeSignature to accept entitlements
With this change, we can now bake in entitlements into the binary. Additionally, I see this as the first step towards full code signature support which includes baking in Apple issued certificates for redistribution, etc.
This commit is contained in:
parent
91fd0f42c8
commit
0376fd09bc
6 changed files with 386 additions and 144 deletions
|
|
@ -1570,6 +1570,9 @@ pub const LibExeObjStep = struct {
|
||||||
/// (Darwin) Install name for the dylib
|
/// (Darwin) Install name for the dylib
|
||||||
install_name: ?[]const u8 = null,
|
install_name: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// (Darwin) Path to entitlements file
|
||||||
|
entitlements: ?[]const u8 = null,
|
||||||
|
|
||||||
/// Position Independent Code
|
/// Position Independent Code
|
||||||
force_pic: ?bool = null,
|
force_pic: ?bool = null,
|
||||||
|
|
||||||
|
|
@ -2515,6 +2518,10 @@ pub const LibExeObjStep = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.entitlements) |entitlements| {
|
||||||
|
try zig_args.appendSlice(&[_][]const u8{ "--entitlements", entitlements });
|
||||||
|
}
|
||||||
|
|
||||||
if (self.bundle_compiler_rt) |x| {
|
if (self.bundle_compiler_rt) |x| {
|
||||||
if (x) {
|
if (x) {
|
||||||
try zig_args.append("-fcompiler-rt");
|
try zig_args.append("-fcompiler-rt");
|
||||||
|
|
|
||||||
|
|
@ -815,6 +815,8 @@ pub const InitOptions = struct {
|
||||||
native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
|
native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null,
|
||||||
/// (Darwin) Install name of the dylib
|
/// (Darwin) Install name of the dylib
|
||||||
install_name: ?[]const u8 = null,
|
install_name: ?[]const u8 = null,
|
||||||
|
/// (Darwin) Path to entitlements file
|
||||||
|
entitlements: ?[]const u8 = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn addPackageTableToCacheHash(
|
fn addPackageTableToCacheHash(
|
||||||
|
|
@ -1624,6 +1626,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation {
|
||||||
.enable_link_snapshots = options.enable_link_snapshots,
|
.enable_link_snapshots = options.enable_link_snapshots,
|
||||||
.native_darwin_sdk = options.native_darwin_sdk,
|
.native_darwin_sdk = options.native_darwin_sdk,
|
||||||
.install_name = options.install_name,
|
.install_name = options.install_name,
|
||||||
|
.entitlements = options.entitlements,
|
||||||
});
|
});
|
||||||
errdefer bin_file.destroy();
|
errdefer bin_file.destroy();
|
||||||
comp.* = .{
|
comp.* = .{
|
||||||
|
|
@ -2351,6 +2354,7 @@ fn addNonIncrementalStuffToCacheManifest(comp: *Compilation, man: *Cache.Manifes
|
||||||
// Mach-O specific stuff
|
// Mach-O specific stuff
|
||||||
man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
|
man.hash.addListOfBytes(comp.bin_file.options.framework_dirs);
|
||||||
man.hash.addListOfBytes(comp.bin_file.options.frameworks);
|
man.hash.addListOfBytes(comp.bin_file.options.frameworks);
|
||||||
|
try man.addOptionalFile(comp.bin_file.options.entitlements);
|
||||||
|
|
||||||
// COFF specific stuff
|
// COFF specific stuff
|
||||||
man.hash.addOptional(comp.bin_file.options.subsystem);
|
man.hash.addOptional(comp.bin_file.options.subsystem);
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,9 @@ pub const Options = struct {
|
||||||
/// (Darwin) Install name for the dylib
|
/// (Darwin) Install name for the dylib
|
||||||
install_name: ?[]const u8 = null,
|
install_name: ?[]const u8 = null,
|
||||||
|
|
||||||
|
/// (Darwin) Path to entitlements file
|
||||||
|
entitlements: ?[]const u8 = null,
|
||||||
|
|
||||||
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
|
pub fn effectiveOutputMode(options: Options) std.builtin.OutputMode {
|
||||||
return if (options.use_lld) .Obj else options.output_mode;
|
return if (options.use_lld) .Obj else options.output_mode;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,11 +58,6 @@ d_sym: ?DebugSymbols = null,
|
||||||
/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB.
|
/// For x86_64 that's 4KB, whereas for aarch64, that's 16KB.
|
||||||
page_size: u16,
|
page_size: u16,
|
||||||
|
|
||||||
/// TODO Should we figure out embedding code signatures for other Apple platforms as part of the linker?
|
|
||||||
/// Or should this be a separate tool?
|
|
||||||
/// https://github.com/ziglang/zig/issues/9567
|
|
||||||
requires_adhoc_codesig: bool,
|
|
||||||
|
|
||||||
/// If true, the linker will preallocate several sections and segments before starting the linking
|
/// If true, the linker will preallocate several sections and segments before starting the linking
|
||||||
/// process. This is for example true for stage2 debug builds, however, this is false for stage1
|
/// process. This is for example true for stage2 debug builds, however, this is false for stage1
|
||||||
/// and potentially stage2 release builds in the future.
|
/// and potentially stage2 release builds in the future.
|
||||||
|
|
@ -76,6 +71,9 @@ header_pad: u16 = 0x1000,
|
||||||
/// The absolute address of the entry point.
|
/// The absolute address of the entry point.
|
||||||
entry_addr: ?u64 = null,
|
entry_addr: ?u64 = null,
|
||||||
|
|
||||||
|
/// Code signature (if any)
|
||||||
|
code_signature: ?CodeSignature = null,
|
||||||
|
|
||||||
objects: std.ArrayListUnmanaged(Object) = .{},
|
objects: std.ArrayListUnmanaged(Object) = .{},
|
||||||
archives: std.ArrayListUnmanaged(Archive) = .{},
|
archives: std.ArrayListUnmanaged(Archive) = .{},
|
||||||
|
|
||||||
|
|
@ -402,7 +400,7 @@ pub fn createEmpty(gpa: Allocator, options: link.Options) !*MachO {
|
||||||
.file = null,
|
.file = null,
|
||||||
},
|
},
|
||||||
.page_size = page_size,
|
.page_size = page_size,
|
||||||
.requires_adhoc_codesig = requires_adhoc_codesig,
|
.code_signature = if (requires_adhoc_codesig) CodeSignature.init(page_size) else null,
|
||||||
.needs_prealloc = needs_prealloc,
|
.needs_prealloc = needs_prealloc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -534,6 +532,7 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
|
||||||
}
|
}
|
||||||
link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);
|
link.hashAddSystemLibs(&man.hash, self.base.options.system_libs);
|
||||||
man.hash.addOptionalBytes(self.base.options.sysroot);
|
man.hash.addOptionalBytes(self.base.options.sysroot);
|
||||||
|
try man.addOptionalFile(self.base.options.entitlements);
|
||||||
|
|
||||||
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
|
// We don't actually care whether it's a cache hit or miss; we just need the digest and the lock.
|
||||||
_ = try man.hit();
|
_ = try man.hit();
|
||||||
|
|
@ -859,6 +858,19 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
|
||||||
self.load_commands_dirty = true;
|
self.load_commands_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// code signature and entitlements
|
||||||
|
if (self.base.options.entitlements) |path| {
|
||||||
|
if (self.code_signature) |*csig| {
|
||||||
|
try csig.addEntitlements(self.base.allocator, path);
|
||||||
|
csig.code_directory.ident = self.base.options.emit.?.sub_path;
|
||||||
|
} else {
|
||||||
|
var csig = CodeSignature.init(self.page_size);
|
||||||
|
try csig.addEntitlements(self.base.allocator, path);
|
||||||
|
csig.code_directory.ident = self.base.options.emit.?.sub_path;
|
||||||
|
self.code_signature = csig;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (self.base.options.verbose_link) {
|
if (self.base.options.verbose_link) {
|
||||||
var argv = std.ArrayList([]const u8).init(arena);
|
var argv = std.ArrayList([]const u8).init(arena);
|
||||||
|
|
||||||
|
|
@ -1033,13 +1045,15 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
|
||||||
try d_sym.flushModule(self.base.allocator, self.base.options);
|
try d_sym.flushModule(self.base.allocator, self.base.options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (self.requires_adhoc_codesig) {
|
if (self.code_signature) |*csig| {
|
||||||
|
csig.clear(self.base.allocator);
|
||||||
|
csig.code_directory.ident = self.base.options.emit.?.sub_path;
|
||||||
// Preallocate space for the code signature.
|
// Preallocate space for the code signature.
|
||||||
// We need to do this at this stage so that we have the load commands with proper values
|
// We need to do this at this stage so that we have the load commands with proper values
|
||||||
// written out to the file.
|
// written out to the file.
|
||||||
// The most important here is to have the correct vm and filesize of the __LINKEDIT segment
|
// The most important here is to have the correct vm and filesize of the __LINKEDIT segment
|
||||||
// where the code signature goes into.
|
// where the code signature goes into.
|
||||||
try self.writeCodeSignaturePadding();
|
try self.writeCodeSignaturePadding(csig);
|
||||||
}
|
}
|
||||||
|
|
||||||
try self.writeLoadCommands();
|
try self.writeLoadCommands();
|
||||||
|
|
@ -1055,8 +1069,8 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void {
|
||||||
|
|
||||||
assert(!self.load_commands_dirty);
|
assert(!self.load_commands_dirty);
|
||||||
|
|
||||||
if (self.requires_adhoc_codesig) {
|
if (self.code_signature) |*csig| {
|
||||||
try self.writeCodeSignature(); // code signing always comes last
|
try self.writeCodeSignature(csig); // code signing always comes last
|
||||||
}
|
}
|
||||||
|
|
||||||
if (build_options.enable_link_snapshots) {
|
if (build_options.enable_link_snapshots) {
|
||||||
|
|
@ -3315,7 +3329,7 @@ fn addLoadDylibLC(self: *MachO, id: u16) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn addCodeSignatureLC(self: *MachO) !void {
|
fn addCodeSignatureLC(self: *MachO) !void {
|
||||||
if (self.code_signature_cmd_index != null or !self.requires_adhoc_codesig) return;
|
if (self.code_signature_cmd_index != null or self.code_signature == null) return;
|
||||||
self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
|
self.code_signature_cmd_index = @intCast(u16, self.load_commands.items.len);
|
||||||
try self.load_commands.append(self.base.allocator, .{
|
try self.load_commands.append(self.base.allocator, .{
|
||||||
.linkedit_data = .{
|
.linkedit_data = .{
|
||||||
|
|
@ -3429,6 +3443,10 @@ pub fn deinit(self: *MachO) void {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.atom_by_index_table.deinit(self.base.allocator);
|
self.atom_by_index_table.deinit(self.base.allocator);
|
||||||
|
|
||||||
|
if (self.code_signature) |*csig| {
|
||||||
|
csig.deinit(self.base.allocator);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn closeFiles(self: MachO) void {
|
pub fn closeFiles(self: MachO) void {
|
||||||
|
|
@ -6143,7 +6161,7 @@ fn writeLinkeditSegment(self: *MachO) !void {
|
||||||
seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size);
|
seg.inner.vmsize = mem.alignForwardGeneric(u64, seg.inner.filesize, self.page_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeCodeSignaturePadding(self: *MachO) !void {
|
fn writeCodeSignaturePadding(self: *MachO, code_sig: *CodeSignature) !void {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
|
|
@ -6153,11 +6171,7 @@ fn writeCodeSignaturePadding(self: *MachO) !void {
|
||||||
// https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
|
// https://github.com/opensource-apple/cctools/blob/fdb4825f303fd5c0751be524babd32958181b3ed/libstuff/checkout.c#L271
|
||||||
const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16);
|
const fileoff = mem.alignForwardGeneric(u64, linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize, 16);
|
||||||
const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize);
|
const padding = fileoff - (linkedit_segment.inner.fileoff + linkedit_segment.inner.filesize);
|
||||||
const needed_size = CodeSignature.calcCodeSignaturePaddingSize(
|
const needed_size = code_sig.estimateSize(fileoff);
|
||||||
self.base.options.emit.?.sub_path,
|
|
||||||
fileoff,
|
|
||||||
self.page_size,
|
|
||||||
);
|
|
||||||
code_sig_cmd.dataoff = @intCast(u32, fileoff);
|
code_sig_cmd.dataoff = @intCast(u32, fileoff);
|
||||||
code_sig_cmd.datasize = needed_size;
|
code_sig_cmd.datasize = needed_size;
|
||||||
|
|
||||||
|
|
@ -6173,34 +6187,30 @@ fn writeCodeSignaturePadding(self: *MachO) !void {
|
||||||
self.load_commands_dirty = true;
|
self.load_commands_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeCodeSignature(self: *MachO) !void {
|
fn writeCodeSignature(self: *MachO, code_sig: *CodeSignature) !void {
|
||||||
const tracy = trace(@src());
|
const tracy = trace(@src());
|
||||||
defer tracy.end();
|
defer tracy.end();
|
||||||
|
|
||||||
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment;
|
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].segment;
|
||||||
const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data;
|
const code_sig_cmd = self.load_commands.items[self.code_signature_cmd_index.?].linkedit_data;
|
||||||
|
|
||||||
var code_sig: CodeSignature = .{};
|
var buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||||
defer code_sig.deinit(self.base.allocator);
|
defer buffer.deinit();
|
||||||
|
try buffer.ensureTotalCapacityPrecise(code_sig.size());
|
||||||
|
try code_sig.writeAdhocSignature(self.base.allocator, .{
|
||||||
|
.file = self.base.file.?,
|
||||||
|
.text_segment = text_segment.inner,
|
||||||
|
.code_sig_cmd = code_sig_cmd,
|
||||||
|
.output_mode = self.base.options.output_mode,
|
||||||
|
}, buffer.writer());
|
||||||
|
assert(buffer.items.len == code_sig.size());
|
||||||
|
|
||||||
try code_sig.calcAdhocSignature(
|
log.debug("writing code signature from 0x{x} to 0x{x}", .{
|
||||||
self.base.allocator,
|
code_sig_cmd.dataoff,
|
||||||
self.base.file.?,
|
code_sig_cmd.dataoff + buffer.items.len,
|
||||||
self.base.options.emit.?.sub_path,
|
});
|
||||||
text_segment.inner,
|
|
||||||
code_sig_cmd,
|
|
||||||
self.base.options.output_mode,
|
|
||||||
self.page_size,
|
|
||||||
);
|
|
||||||
|
|
||||||
var buffer = try self.base.allocator.alloc(u8, code_sig.size());
|
try self.base.file.?.pwriteAll(buffer.items, code_sig_cmd.dataoff);
|
||||||
defer self.base.allocator.free(buffer);
|
|
||||||
var stream = std.io.fixedBufferStream(buffer);
|
|
||||||
try code_sig.write(stream.writer());
|
|
||||||
|
|
||||||
log.debug("writing code signature from 0x{x} to 0x{x}", .{ code_sig_cmd.dataoff, code_sig_cmd.dataoff + buffer.len });
|
|
||||||
|
|
||||||
try self.base.file.?.pwriteAll(buffer, code_sig_cmd.dataoff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes all load commands and section headers.
|
/// Writes all load commands and section headers.
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,102 @@ const Sha256 = std.crypto.hash.sha2.Sha256;
|
||||||
|
|
||||||
const hash_size: u8 = 32;
|
const hash_size: u8 = 32;
|
||||||
|
|
||||||
|
const Blob = union(enum) {
|
||||||
|
code_directory: *CodeDirectory,
|
||||||
|
requirements: *Requirements,
|
||||||
|
entitlements: *Entitlements,
|
||||||
|
signature: *Signature,
|
||||||
|
|
||||||
|
fn slotType(self: Blob) u32 {
|
||||||
|
return switch (self) {
|
||||||
|
.code_directory => |x| x.slotType(),
|
||||||
|
.requirements => |x| x.slotType(),
|
||||||
|
.entitlements => |x| x.slotType(),
|
||||||
|
.signature => |x| x.slotType(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self: Blob) u32 {
|
||||||
|
return switch (self) {
|
||||||
|
.code_directory => |x| x.size(),
|
||||||
|
.requirements => |x| x.size(),
|
||||||
|
.entitlements => |x| x.size(),
|
||||||
|
.signature => |x| x.size(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: Blob, writer: anytype) !void {
|
||||||
|
return switch (self) {
|
||||||
|
.code_directory => |x| x.write(writer),
|
||||||
|
.requirements => |x| x.write(writer),
|
||||||
|
.entitlements => |x| x.write(writer),
|
||||||
|
.signature => |x| x.write(writer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const CodeDirectory = struct {
|
const CodeDirectory = struct {
|
||||||
inner: macho.CodeDirectory,
|
inner: macho.CodeDirectory,
|
||||||
data: std.ArrayListUnmanaged(u8) = .{},
|
ident: []const u8,
|
||||||
|
special_slots: [n_special_slots][hash_size]u8,
|
||||||
|
code_slots: std.ArrayListUnmanaged([hash_size]u8) = .{},
|
||||||
|
|
||||||
|
const n_special_slots: usize = 7;
|
||||||
|
|
||||||
|
fn init(page_size: u16) CodeDirectory {
|
||||||
|
var cdir: CodeDirectory = .{
|
||||||
|
.inner = .{
|
||||||
|
.magic = macho.CSMAGIC_CODEDIRECTORY,
|
||||||
|
.length = @sizeOf(macho.CodeDirectory),
|
||||||
|
.version = macho.CS_SUPPORTSEXECSEG,
|
||||||
|
.flags = macho.CS_ADHOC,
|
||||||
|
.hashOffset = 0,
|
||||||
|
.identOffset = @sizeOf(macho.CodeDirectory),
|
||||||
|
.nSpecialSlots = 0,
|
||||||
|
.nCodeSlots = 0,
|
||||||
|
.codeLimit = 0,
|
||||||
|
.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 = 0,
|
||||||
|
.execSegLimit = 0,
|
||||||
|
.execSegFlags = 0,
|
||||||
|
},
|
||||||
|
.ident = undefined,
|
||||||
|
.special_slots = undefined,
|
||||||
|
};
|
||||||
|
comptime var i = 0;
|
||||||
|
inline while (i < n_special_slots) : (i += 1) {
|
||||||
|
cdir.special_slots[i] = [_]u8{0} ** hash_size;
|
||||||
|
}
|
||||||
|
return cdir;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *CodeDirectory, allocator: Allocator) void {
|
||||||
|
self.code_slots.deinit(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void {
|
||||||
|
assert(index > 0);
|
||||||
|
self.inner.nSpecialSlots = std.math.max(self.inner.nSpecialSlots, index);
|
||||||
|
mem.copy(u8, &self.special_slots[index - 1], &hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slotType(self: CodeDirectory) u32 {
|
||||||
|
_ = self;
|
||||||
|
return macho.CSSLOT_CODEDIRECTORY;
|
||||||
|
}
|
||||||
|
|
||||||
fn size(self: CodeDirectory) u32 {
|
fn size(self: CodeDirectory) u32 {
|
||||||
return self.inner.length;
|
const code_slots = self.inner.nCodeSlots * hash_size;
|
||||||
|
const special_slots = self.inner.nSpecialSlots * hash_size;
|
||||||
|
return @sizeOf(macho.CodeDirectory) + @intCast(u32, self.ident.len + 1) + special_slots + code_slots;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(self: CodeDirectory, writer: anytype) !void {
|
fn write(self: CodeDirectory, writer: anytype) !void {
|
||||||
|
|
@ -42,142 +132,263 @@ const CodeDirectory = struct {
|
||||||
try writer.writeIntBig(u64, self.inner.execSegBase);
|
try writer.writeIntBig(u64, self.inner.execSegBase);
|
||||||
try writer.writeIntBig(u64, self.inner.execSegLimit);
|
try writer.writeIntBig(u64, self.inner.execSegLimit);
|
||||||
try writer.writeIntBig(u64, self.inner.execSegFlags);
|
try writer.writeIntBig(u64, self.inner.execSegFlags);
|
||||||
try writer.writeAll(self.data.items);
|
|
||||||
|
try writer.writeAll(self.ident);
|
||||||
|
try writer.writeByte(0);
|
||||||
|
|
||||||
|
var i: isize = @intCast(isize, self.inner.nSpecialSlots);
|
||||||
|
while (i > 0) : (i -= 1) {
|
||||||
|
try writer.writeAll(&self.special_slots[@intCast(usize, i - 1)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.code_slots.items) |slot| {
|
||||||
|
try writer.writeAll(&slot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Code signature blob header.
|
const Requirements = struct {
|
||||||
inner: macho.SuperBlob = .{
|
fn deinit(self: *Requirements, allocator: Allocator) void {
|
||||||
.magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
|
_ = self;
|
||||||
.length = @sizeOf(macho.SuperBlob),
|
_ = allocator;
|
||||||
.count = 0,
|
}
|
||||||
},
|
|
||||||
|
|
||||||
/// CodeDirectory header which holds the hash of the binary.
|
fn slotType(self: Requirements) u32 {
|
||||||
cdir: ?CodeDirectory = null,
|
_ = self;
|
||||||
|
return macho.CSSLOT_REQUIREMENTS;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn calcAdhocSignature(
|
fn size(self: Requirements) u32 {
|
||||||
self: *CodeSignature,
|
_ = self;
|
||||||
allocator: Allocator,
|
return 3 * @sizeOf(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: Requirements, writer: anytype) !void {
|
||||||
|
try writer.writeIntBig(u32, macho.CSMAGIC_REQUIREMENTS);
|
||||||
|
try writer.writeIntBig(u32, self.size());
|
||||||
|
try writer.writeIntBig(u32, 0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Entitlements = struct {
|
||||||
|
inner: []const u8,
|
||||||
|
|
||||||
|
fn deinit(self: *Entitlements, allocator: Allocator) void {
|
||||||
|
allocator.free(self.inner);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slotType(self: Entitlements) u32 {
|
||||||
|
_ = self;
|
||||||
|
return macho.CSSLOT_ENTITLEMENTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self: Entitlements) u32 {
|
||||||
|
return @intCast(u32, self.inner.len) + 2 * @sizeOf(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: Entitlements, writer: anytype) !void {
|
||||||
|
try writer.writeIntBig(u32, macho.CSMAGIC_EMBEDDED_ENTITLEMENTS);
|
||||||
|
try writer.writeIntBig(u32, self.size());
|
||||||
|
try writer.writeAll(self.inner);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Signature = struct {
|
||||||
|
fn deinit(self: *Signature, allocator: Allocator) void {
|
||||||
|
_ = self;
|
||||||
|
_ = allocator;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn slotType(self: Signature) u32 {
|
||||||
|
_ = self;
|
||||||
|
return macho.CSSLOT_SIGNATURESLOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size(self: Signature) u32 {
|
||||||
|
_ = self;
|
||||||
|
return 2 * @sizeOf(u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(self: Signature, writer: anytype) !void {
|
||||||
|
try writer.writeIntBig(u32, macho.CSMAGIC_BLOBWRAPPER);
|
||||||
|
try writer.writeIntBig(u32, self.size());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
page_size: u16,
|
||||||
|
code_directory: CodeDirectory,
|
||||||
|
requirements: ?Requirements = null,
|
||||||
|
entitlements: ?Entitlements = null,
|
||||||
|
signature: ?Signature = null,
|
||||||
|
|
||||||
|
pub fn init(page_size: u16) CodeSignature {
|
||||||
|
return .{
|
||||||
|
.page_size = page_size,
|
||||||
|
.code_directory = CodeDirectory.init(page_size),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
|
||||||
|
self.code_directory.deinit(allocator);
|
||||||
|
if (self.requirements) |*req| {
|
||||||
|
req.deinit(allocator);
|
||||||
|
}
|
||||||
|
if (self.entitlements) |*ents| {
|
||||||
|
ents.deinit(allocator);
|
||||||
|
}
|
||||||
|
if (self.signature) |*sig| {
|
||||||
|
sig.deinit(allocator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, path: []const u8) !void {
|
||||||
|
const file = try fs.cwd().openFile(path, .{});
|
||||||
|
defer file.close();
|
||||||
|
const inner = try file.readToEndAlloc(allocator, std.math.maxInt(u32));
|
||||||
|
self.entitlements = .{ .inner = inner };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const WriteOpts = struct {
|
||||||
file: fs.File,
|
file: fs.File,
|
||||||
id: []const u8,
|
|
||||||
text_segment: macho.segment_command_64,
|
text_segment: macho.segment_command_64,
|
||||||
code_sig_cmd: macho.linkedit_data_command,
|
code_sig_cmd: macho.linkedit_data_command,
|
||||||
output_mode: std.builtin.OutputMode,
|
output_mode: std.builtin.OutputMode,
|
||||||
page_size: u16,
|
};
|
||||||
|
|
||||||
|
pub fn writeAdhocSignature(
|
||||||
|
self: *CodeSignature,
|
||||||
|
allocator: Allocator,
|
||||||
|
opts: WriteOpts,
|
||||||
|
writer: anytype,
|
||||||
) !void {
|
) !void {
|
||||||
const execSegBase: u64 = text_segment.fileoff;
|
var header: macho.SuperBlob = .{
|
||||||
const execSegLimit: u64 = text_segment.filesize;
|
.magic = macho.CSMAGIC_EMBEDDED_SIGNATURE,
|
||||||
const execSegFlags: u64 = if (output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
|
.length = @sizeOf(macho.SuperBlob),
|
||||||
const file_size = code_sig_cmd.dataoff;
|
.count = 0,
|
||||||
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 = 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 blobs = std.ArrayList(Blob).init(allocator);
|
||||||
|
defer blobs.deinit();
|
||||||
|
|
||||||
var hash: [hash_size]u8 = undefined;
|
self.code_directory.inner.execSegBase = opts.text_segment.fileoff;
|
||||||
var buffer = try allocator.alloc(u8, page_size);
|
self.code_directory.inner.execSegLimit = opts.text_segment.filesize;
|
||||||
|
self.code_directory.inner.execSegFlags = if (opts.output_mode == .Exe) macho.CS_EXECSEG_MAIN_BINARY else 0;
|
||||||
|
const file_size = opts.code_sig_cmd.dataoff;
|
||||||
|
self.code_directory.inner.codeLimit = file_size;
|
||||||
|
|
||||||
|
const total_pages = mem.alignForward(file_size, self.page_size) / self.page_size;
|
||||||
|
|
||||||
|
var buffer = try allocator.alloc(u8, self.page_size);
|
||||||
defer allocator.free(buffer);
|
defer allocator.free(buffer);
|
||||||
|
|
||||||
try cdir.data.ensureTotalCapacityPrecise(allocator, total_pages * hash_size + id.len + 1);
|
try self.code_directory.code_slots.ensureTotalCapacityPrecise(allocator, total_pages);
|
||||||
|
|
||||||
// 1. Save the identifier and update offsets
|
// Calculate hash for each page (in file) and write it to the buffer
|
||||||
cdir.inner.identOffset = cdir.inner.length;
|
var hash: [hash_size]u8 = undefined;
|
||||||
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;
|
var i: usize = 0;
|
||||||
while (i < total_pages) : (i += 1) {
|
while (i < total_pages) : (i += 1) {
|
||||||
const fstart = i * page_size;
|
const fstart = i * self.page_size;
|
||||||
const fsize = if (fstart + page_size > file_size) file_size - fstart else page_size;
|
const fsize = if (fstart + self.page_size > file_size) file_size - fstart else self.page_size;
|
||||||
const len = try file.preadAll(buffer, fstart);
|
const len = try opts.file.preadAll(buffer, fstart);
|
||||||
assert(fsize <= len);
|
assert(fsize <= len);
|
||||||
|
|
||||||
Sha256.hash(buffer[0..fsize], &hash, .{});
|
Sha256.hash(buffer[0..fsize], &hash, .{});
|
||||||
|
|
||||||
cdir.data.appendSliceAssumeCapacity(&hash);
|
self.code_directory.code_slots.appendAssumeCapacity(hash);
|
||||||
cdir.inner.nCodeSlots += 1;
|
self.code_directory.inner.nCodeSlots += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Update CodeDirectory length
|
try blobs.append(.{ .code_directory = &self.code_directory });
|
||||||
cdir.inner.length += @intCast(u32, cdir.data.items.len);
|
header.length += @sizeOf(macho.BlobIndex);
|
||||||
|
header.count += 1;
|
||||||
|
|
||||||
self.inner.length += @sizeOf(macho.BlobIndex) + cdir.size();
|
if (self.requirements) |*req| {
|
||||||
self.inner.count = 1;
|
var buf = std.ArrayList(u8).init(allocator);
|
||||||
self.cdir = cdir;
|
defer buf.deinit();
|
||||||
|
try req.write(buf.writer());
|
||||||
|
Sha256.hash(buf.items, &hash, .{});
|
||||||
|
self.code_directory.addSpecialHash(req.slotType(), hash);
|
||||||
|
|
||||||
|
try blobs.append(.{ .requirements = req });
|
||||||
|
header.count += 1;
|
||||||
|
header.length += @sizeOf(macho.BlobIndex) + req.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.entitlements) |*ents| {
|
||||||
|
var buf = std.ArrayList(u8).init(allocator);
|
||||||
|
defer buf.deinit();
|
||||||
|
try ents.write(buf.writer());
|
||||||
|
Sha256.hash(buf.items, &hash, .{});
|
||||||
|
self.code_directory.addSpecialHash(ents.slotType(), hash);
|
||||||
|
|
||||||
|
try blobs.append(.{ .entitlements = ents });
|
||||||
|
header.count += 1;
|
||||||
|
header.length += @sizeOf(macho.BlobIndex) + ents.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.signature) |*sig| {
|
||||||
|
try blobs.append(.{ .signature = sig });
|
||||||
|
header.count += 1;
|
||||||
|
header.length += @sizeOf(macho.BlobIndex) + sig.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.code_directory.inner.hashOffset =
|
||||||
|
@sizeOf(macho.CodeDirectory) + @intCast(u32, self.code_directory.ident.len + 1) + self.code_directory.inner.nSpecialSlots * hash_size;
|
||||||
|
self.code_directory.inner.length = self.code_directory.size();
|
||||||
|
header.length += self.code_directory.size();
|
||||||
|
|
||||||
|
try writer.writeIntBig(u32, header.magic);
|
||||||
|
try writer.writeIntBig(u32, header.length);
|
||||||
|
try writer.writeIntBig(u32, header.count);
|
||||||
|
|
||||||
|
var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @intCast(u32, blobs.items.len);
|
||||||
|
for (blobs.items) |blob| {
|
||||||
|
try writer.writeIntBig(u32, blob.slotType());
|
||||||
|
try writer.writeIntBig(u32, offset);
|
||||||
|
offset += blob.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (blobs.items) |blob| {
|
||||||
|
try blob.write(writer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(self: CodeSignature) u32 {
|
pub fn size(self: CodeSignature) u32 {
|
||||||
return self.inner.length;
|
var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
|
||||||
}
|
if (self.requirements) |req| {
|
||||||
|
ssize += @sizeOf(macho.BlobIndex) + req.size();
|
||||||
pub fn write(self: CodeSignature, writer: anytype) !void {
|
|
||||||
try self.writeHeader(writer);
|
|
||||||
const offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex);
|
|
||||||
try writeBlobIndex(macho.CSSLOT_CODEDIRECTORY, offset, writer);
|
|
||||||
try self.cdir.?.write(writer);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *CodeSignature, allocator: Allocator) void {
|
|
||||||
if (self.cdir) |*cdir| {
|
|
||||||
cdir.data.deinit(allocator);
|
|
||||||
}
|
}
|
||||||
|
if (self.entitlements) |ent| {
|
||||||
|
ssize += @sizeOf(macho.BlobIndex) + ent.size();
|
||||||
|
}
|
||||||
|
if (self.signature) |sig| {
|
||||||
|
ssize += @sizeOf(macho.BlobIndex) + sig.size();
|
||||||
|
}
|
||||||
|
return ssize;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeHeader(self: CodeSignature, writer: anytype) !void {
|
pub fn estimateSize(self: CodeSignature, file_size: u64) u32 {
|
||||||
try writer.writeIntBig(u32, self.inner.magic);
|
var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size();
|
||||||
try writer.writeIntBig(u32, self.inner.length);
|
// Approx code slots
|
||||||
try writer.writeIntBig(u32, self.inner.count);
|
const total_pages = mem.alignForwardGeneric(u64, file_size, self.page_size) / self.page_size;
|
||||||
|
ssize += total_pages * hash_size;
|
||||||
|
var n_special_slots: u32 = 0;
|
||||||
|
if (self.requirements) |req| {
|
||||||
|
ssize += @sizeOf(macho.BlobIndex) + req.size();
|
||||||
|
n_special_slots = std.math.max(n_special_slots, req.slotType());
|
||||||
|
}
|
||||||
|
if (self.entitlements) |ent| {
|
||||||
|
ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size;
|
||||||
|
n_special_slots = std.math.max(n_special_slots, ent.slotType());
|
||||||
|
}
|
||||||
|
if (self.signature) |sig| {
|
||||||
|
ssize += @sizeOf(macho.BlobIndex) + sig.size();
|
||||||
|
}
|
||||||
|
ssize += n_special_slots * hash_size;
|
||||||
|
return @intCast(u32, mem.alignForwardGeneric(u64, ssize, @sizeOf(u64)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn writeBlobIndex(tt: u32, offset: u32, writer: anytype) !void {
|
pub fn clear(self: *CodeSignature, allocator: Allocator) void {
|
||||||
try writer.writeIntBig(u32, tt);
|
self.code_directory.deinit(allocator);
|
||||||
try writer.writeIntBig(u32, offset);
|
self.code_directory = CodeDirectory.init(self.page_size);
|
||||||
}
|
|
||||||
|
|
||||||
test "CodeSignature header" {
|
|
||||||
var code_sig: CodeSignature = .{};
|
|
||||||
defer code_sig.deinit(testing.allocator);
|
|
||||||
|
|
||||||
var buffer: [@sizeOf(macho.SuperBlob)]u8 = undefined;
|
|
||||||
var stream = std.io.fixedBufferStream(&buffer);
|
|
||||||
try code_sig.writeHeader(stream.writer());
|
|
||||||
|
|
||||||
const expected = &[_]u8{ 0xfa, 0xde, 0x0c, 0xc0, 0x0, 0x0, 0x0, 0xc, 0x0, 0x0, 0x0, 0x0 };
|
|
||||||
try testing.expect(mem.eql(u8, expected, &buffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calcCodeSignaturePaddingSize(id: []const u8, file_size: u64, page_size: u16) 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)));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -433,6 +433,7 @@ const usage_build_generic =
|
||||||
\\ -framework [name] (Darwin) link against framework
|
\\ -framework [name] (Darwin) link against framework
|
||||||
\\ -F[dir] (Darwin) add search path for frameworks
|
\\ -F[dir] (Darwin) add search path for frameworks
|
||||||
\\ -install_name=[value] (Darwin) add dylib's install name
|
\\ -install_name=[value] (Darwin) add dylib's install name
|
||||||
|
\\ --entitlements [path] (Darwin) add path to entitlements file for embedding in code signature
|
||||||
\\ --import-memory (WebAssembly) import memory from the environment
|
\\ --import-memory (WebAssembly) import memory from the environment
|
||||||
\\ --import-table (WebAssembly) import function table from the host environment
|
\\ --import-table (WebAssembly) import function table from the host environment
|
||||||
\\ --export-table (WebAssembly) export function table to the host environment
|
\\ --export-table (WebAssembly) export function table to the host environment
|
||||||
|
|
@ -680,6 +681,7 @@ fn buildOutputType(
|
||||||
var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null;
|
var native_darwin_sdk: ?std.zig.system.darwin.DarwinSDK = null;
|
||||||
var install_name: ?[]const u8 = null;
|
var install_name: ?[]const u8 = null;
|
||||||
var hash_style: link.HashStyle = .both;
|
var hash_style: link.HashStyle = .both;
|
||||||
|
var entitlements: ?[]const u8 = null;
|
||||||
|
|
||||||
// e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
|
// e.g. -m3dnow or -mno-outline-atomics. They correspond to std.Target llvm cpu feature names.
|
||||||
// This array is populated by zig cc frontend and then has to be converted to zig-style
|
// This array is populated by zig cc frontend and then has to be converted to zig-style
|
||||||
|
|
@ -1036,6 +1038,10 @@ fn buildOutputType(
|
||||||
} else {
|
} else {
|
||||||
enable_link_snapshots = true;
|
enable_link_snapshots = true;
|
||||||
}
|
}
|
||||||
|
} else if (mem.eql(u8, arg, "--entitlements")) {
|
||||||
|
entitlements = args_iter.next() orelse {
|
||||||
|
fatal("expected parameter after {s}", .{arg});
|
||||||
|
};
|
||||||
} else if (mem.eql(u8, arg, "-fcompiler-rt")) {
|
} else if (mem.eql(u8, arg, "-fcompiler-rt")) {
|
||||||
want_compiler_rt = true;
|
want_compiler_rt = true;
|
||||||
} else if (mem.eql(u8, arg, "-fno-compiler-rt")) {
|
} else if (mem.eql(u8, arg, "-fno-compiler-rt")) {
|
||||||
|
|
@ -2729,6 +2735,7 @@ fn buildOutputType(
|
||||||
.enable_link_snapshots = enable_link_snapshots,
|
.enable_link_snapshots = enable_link_snapshots,
|
||||||
.native_darwin_sdk = native_darwin_sdk,
|
.native_darwin_sdk = native_darwin_sdk,
|
||||||
.install_name = install_name,
|
.install_name = install_name,
|
||||||
|
.entitlements = entitlements,
|
||||||
}) catch |err| switch (err) {
|
}) catch |err| switch (err) {
|
||||||
error.LibCUnavailable => {
|
error.LibCUnavailable => {
|
||||||
const target = target_info.target;
|
const target = target_info.target;
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue