| 1 | const CodeSignature = @This(); |
| 2 | |
| 3 | const std = @import("std"); |
| 4 | const Io = std.Io; |
| 5 | const assert = std.debug.assert; |
| 6 | const fs = std.fs; |
| 7 | const log = std.log.scoped(.link); |
| 8 | const macho = std.macho; |
| 9 | const mem = std.mem; |
| 10 | const testing = std.testing; |
| 11 | const Sha256 = std.crypto.hash.sha2.Sha256; |
| 12 | const Allocator = std.mem.Allocator; |
| 13 | |
| 14 | const trace = @import("../../tracy.zig").trace; |
| 15 | const ParallelHasher = @import("hasher.zig").ParallelHasher; |
| 16 | const MachO = @import("../MachO.zig"); |
| 17 | |
| 18 | const hash_size = Sha256.digest_length; |
| 19 | |
| 20 | page_size: u16, |
| 21 | code_directory: CodeDirectory, |
| 22 | requirements: ?Requirements = null, |
| 23 | entitlements: ?Entitlements = null, |
| 24 | signature: ?Signature = null, |
| 25 | |
| 26 | const Blob = union(enum) { |
| 27 | code_directory: *CodeDirectory, |
| 28 | requirements: *Requirements, |
| 29 | entitlements: *Entitlements, |
| 30 | signature: *Signature, |
| 31 | |
| 32 | fn slotType(self: Blob) u32 { |
| 33 | return switch (self) { |
| 34 | .code_directory => |x| x.slotType(), |
| 35 | .requirements => |x| x.slotType(), |
| 36 | .entitlements => |x| x.slotType(), |
| 37 | .signature => |x| x.slotType(), |
| 38 | }; |
| 39 | } |
| 40 | |
| 41 | fn size(self: Blob) u32 { |
| 42 | return switch (self) { |
| 43 | .code_directory => |x| x.size(), |
| 44 | .requirements => |x| x.size(), |
| 45 | .entitlements => |x| x.size(), |
| 46 | .signature => |x| x.size(), |
| 47 | }; |
| 48 | } |
| 49 | |
| 50 | fn write(self: Blob, writer: anytype) !void { |
| 51 | return switch (self) { |
| 52 | .code_directory => |x| x.write(writer), |
| 53 | .requirements => |x| x.write(writer), |
| 54 | .entitlements => |x| x.write(writer), |
| 55 | .signature => |x| x.write(writer), |
| 56 | }; |
| 57 | } |
| 58 | }; |
| 59 | |
| 60 | const CodeDirectory = struct { |
| 61 | inner: macho.CodeDirectory, |
| 62 | ident: []const u8, |
| 63 | special_slots: [n_special_slots][hash_size]u8, |
| 64 | code_slots: std.ArrayList([hash_size]u8) = .empty, |
| 65 | |
| 66 | const n_special_slots: usize = 7; |
| 67 | |
| 68 | fn init(page_size: u16) CodeDirectory { |
| 69 | var cdir: CodeDirectory = .{ |
| 70 | .inner = .{ |
| 71 | .magic = macho.CSMAGIC_CODEDIRECTORY, |
| 72 | .length = @sizeOf(macho.CodeDirectory), |
| 73 | .version = macho.CS_SUPPORTSEXECSEG, |
| 74 | .flags = macho.CS_ADHOC | macho.CS_LINKER_SIGNED, |
| 75 | .hashOffset = 0, |
| 76 | .identOffset = @sizeOf(macho.CodeDirectory), |
| 77 | .nSpecialSlots = 0, |
| 78 | .nCodeSlots = 0, |
| 79 | .codeLimit = 0, |
| 80 | .hashSize = hash_size, |
| 81 | .hashType = macho.CS_HASHTYPE_SHA256, |
| 82 | .platform = 0, |
| 83 | .pageSize = @as(u8, @truncate(std.math.log2(page_size))), |
| 84 | .spare2 = 0, |
| 85 | .scatterOffset = 0, |
| 86 | .teamOffset = 0, |
| 87 | .spare3 = 0, |
| 88 | .codeLimit64 = 0, |
| 89 | .execSegBase = 0, |
| 90 | .execSegLimit = 0, |
| 91 | .execSegFlags = 0, |
| 92 | }, |
| 93 | .ident = undefined, |
| 94 | .special_slots = undefined, |
| 95 | }; |
| 96 | comptime var i = 0; |
| 97 | inline while (i < n_special_slots) : (i += 1) { |
| 98 | cdir.special_slots[i] = @splat(0); |
| 99 | } |
| 100 | return cdir; |
| 101 | } |
| 102 | |
| 103 | fn deinit(self: *CodeDirectory, allocator: Allocator) void { |
| 104 | self.code_slots.deinit(allocator); |
| 105 | } |
| 106 | |
| 107 | fn addSpecialHash(self: *CodeDirectory, index: u32, hash: [hash_size]u8) void { |
| 108 | assert(index > 0); |
| 109 | self.inner.nSpecialSlots = @max(self.inner.nSpecialSlots, index); |
| 110 | @memcpy(&self.special_slots[index - 1], &hash); |
| 111 | } |
| 112 | |
| 113 | fn slotType(self: CodeDirectory) u32 { |
| 114 | _ = self; |
| 115 | return macho.CSSLOT_CODEDIRECTORY; |
| 116 | } |
| 117 | |
| 118 | fn size(self: CodeDirectory) u32 { |
| 119 | const code_slots = self.inner.nCodeSlots * hash_size; |
| 120 | const special_slots = self.inner.nSpecialSlots * hash_size; |
| 121 | return @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.ident.len + 1 + special_slots + code_slots)); |
| 122 | } |
| 123 | |
| 124 | fn write(self: CodeDirectory, writer: anytype) !void { |
| 125 | try writer.writeInt(u32, self.inner.magic, .big); |
| 126 | try writer.writeInt(u32, self.inner.length, .big); |
| 127 | try writer.writeInt(u32, self.inner.version, .big); |
| 128 | try writer.writeInt(u32, self.inner.flags, .big); |
| 129 | try writer.writeInt(u32, self.inner.hashOffset, .big); |
| 130 | try writer.writeInt(u32, self.inner.identOffset, .big); |
| 131 | try writer.writeInt(u32, self.inner.nSpecialSlots, .big); |
| 132 | try writer.writeInt(u32, self.inner.nCodeSlots, .big); |
| 133 | try writer.writeInt(u32, self.inner.codeLimit, .big); |
| 134 | try writer.writeByte(self.inner.hashSize); |
| 135 | try writer.writeByte(self.inner.hashType); |
| 136 | try writer.writeByte(self.inner.platform); |
| 137 | try writer.writeByte(self.inner.pageSize); |
| 138 | try writer.writeInt(u32, self.inner.spare2, .big); |
| 139 | try writer.writeInt(u32, self.inner.scatterOffset, .big); |
| 140 | try writer.writeInt(u32, self.inner.teamOffset, .big); |
| 141 | try writer.writeInt(u32, self.inner.spare3, .big); |
| 142 | try writer.writeInt(u64, self.inner.codeLimit64, .big); |
| 143 | try writer.writeInt(u64, self.inner.execSegBase, .big); |
| 144 | try writer.writeInt(u64, self.inner.execSegLimit, .big); |
| 145 | try writer.writeInt(u64, self.inner.execSegFlags, .big); |
| 146 | |
| 147 | try writer.writeAll(self.ident); |
| 148 | try writer.writeByte(0); |
| 149 | |
| 150 | var i: isize = @as(isize, @intCast(self.inner.nSpecialSlots)); |
| 151 | while (i > 0) : (i -= 1) { |
| 152 | try writer.writeAll(&self.special_slots[@as(usize, @intCast(i - 1))]); |
| 153 | } |
| 154 | |
| 155 | for (self.code_slots.items) |slot| { |
| 156 | try writer.writeAll(&slot); |
| 157 | } |
| 158 | } |
| 159 | }; |
| 160 | |
| 161 | const Requirements = struct { |
| 162 | fn deinit(self: *Requirements, allocator: Allocator) void { |
| 163 | _ = self; |
| 164 | _ = allocator; |
| 165 | } |
| 166 | |
| 167 | fn slotType(self: Requirements) u32 { |
| 168 | _ = self; |
| 169 | return macho.CSSLOT_REQUIREMENTS; |
| 170 | } |
| 171 | |
| 172 | fn size(self: Requirements) u32 { |
| 173 | _ = self; |
| 174 | return 3 * @sizeOf(u32); |
| 175 | } |
| 176 | |
| 177 | fn write(self: Requirements, writer: anytype) !void { |
| 178 | try writer.writeInt(u32, macho.CSMAGIC_REQUIREMENTS, .big); |
| 179 | try writer.writeInt(u32, self.size(), .big); |
| 180 | try writer.writeInt(u32, 0, .big); |
| 181 | } |
| 182 | }; |
| 183 | |
| 184 | const Entitlements = struct { |
| 185 | inner: []const u8, |
| 186 | |
| 187 | fn deinit(self: *Entitlements, allocator: Allocator) void { |
| 188 | allocator.free(self.inner); |
| 189 | } |
| 190 | |
| 191 | fn slotType(self: Entitlements) u32 { |
| 192 | _ = self; |
| 193 | return macho.CSSLOT_ENTITLEMENTS; |
| 194 | } |
| 195 | |
| 196 | fn size(self: Entitlements) u32 { |
| 197 | return @as(u32, @intCast(self.inner.len)) + 2 * @sizeOf(u32); |
| 198 | } |
| 199 | |
| 200 | fn write(self: Entitlements, writer: anytype) !void { |
| 201 | try writer.writeInt(u32, macho.CSMAGIC_EMBEDDED_ENTITLEMENTS, .big); |
| 202 | try writer.writeInt(u32, self.size(), .big); |
| 203 | try writer.writeAll(self.inner); |
| 204 | } |
| 205 | }; |
| 206 | |
| 207 | const Signature = struct { |
| 208 | fn deinit(self: *Signature, allocator: Allocator) void { |
| 209 | _ = self; |
| 210 | _ = allocator; |
| 211 | } |
| 212 | |
| 213 | fn slotType(self: Signature) u32 { |
| 214 | _ = self; |
| 215 | return macho.CSSLOT_SIGNATURESLOT; |
| 216 | } |
| 217 | |
| 218 | fn size(self: Signature) u32 { |
| 219 | _ = self; |
| 220 | return 2 * @sizeOf(u32); |
| 221 | } |
| 222 | |
| 223 | fn write(self: Signature, writer: anytype) !void { |
| 224 | try writer.writeInt(u32, macho.CSMAGIC_BLOBWRAPPER, .big); |
| 225 | try writer.writeInt(u32, self.size(), .big); |
| 226 | } |
| 227 | }; |
| 228 | |
| 229 | pub fn init(page_size: u16) CodeSignature { |
| 230 | return .{ |
| 231 | .page_size = page_size, |
| 232 | .code_directory = CodeDirectory.init(page_size), |
| 233 | }; |
| 234 | } |
| 235 | |
| 236 | pub fn deinit(self: *CodeSignature, allocator: Allocator) void { |
| 237 | self.code_directory.deinit(allocator); |
| 238 | if (self.requirements) |*req| { |
| 239 | req.deinit(allocator); |
| 240 | } |
| 241 | if (self.entitlements) |*ents| { |
| 242 | ents.deinit(allocator); |
| 243 | } |
| 244 | if (self.signature) |*sig| { |
| 245 | sig.deinit(allocator); |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | pub fn addEntitlements(self: *CodeSignature, allocator: Allocator, io: Io, path: std.Build.Cache.Path) !void { |
| 250 | const inner = try path.root_dir.handle.readFileAlloc(io, path.sub_path, allocator, .limited(std.math.maxInt(u32))); |
| 251 | self.entitlements = .{ .inner = inner }; |
| 252 | } |
| 253 | |
| 254 | pub const WriteOpts = struct { |
| 255 | file: Io.File, |
| 256 | exec_seg_base: u64, |
| 257 | exec_seg_limit: u64, |
| 258 | file_size: u32, |
| 259 | dylib: bool, |
| 260 | }; |
| 261 | |
| 262 | pub fn writeAdhocSignature( |
| 263 | self: *CodeSignature, |
| 264 | macho_file: *MachO, |
| 265 | opts: WriteOpts, |
| 266 | writer: *std.Io.Writer, |
| 267 | ) !void { |
| 268 | const tracy = trace(@src()); |
| 269 | defer tracy.end(); |
| 270 | |
| 271 | const comp = macho_file.base.comp; |
| 272 | const gpa = comp.gpa; |
| 273 | const io = comp.io; |
| 274 | |
| 275 | var header: macho.SuperBlob = .{ |
| 276 | .magic = macho.CSMAGIC_EMBEDDED_SIGNATURE, |
| 277 | .length = @sizeOf(macho.SuperBlob), |
| 278 | .count = 0, |
| 279 | }; |
| 280 | |
| 281 | var blobs = std.array_list.Managed(Blob).init(gpa); |
| 282 | defer blobs.deinit(); |
| 283 | |
| 284 | self.code_directory.inner.execSegBase = opts.exec_seg_base; |
| 285 | self.code_directory.inner.execSegLimit = opts.exec_seg_limit; |
| 286 | self.code_directory.inner.execSegFlags = if (!opts.dylib) macho.CS_EXECSEG_MAIN_BINARY else 0; |
| 287 | self.code_directory.inner.codeLimit = opts.file_size; |
| 288 | |
| 289 | const total_pages = @as(u32, @intCast(mem.alignForward(usize, opts.file_size, self.page_size) / self.page_size)); |
| 290 | |
| 291 | try self.code_directory.code_slots.ensureTotalCapacityPrecise(gpa, total_pages); |
| 292 | self.code_directory.code_slots.items.len = total_pages; |
| 293 | self.code_directory.inner.nCodeSlots = total_pages; |
| 294 | |
| 295 | // Calculate hash for each page (in file) and write it to the buffer |
| 296 | try ParallelHasher(Sha256).hash(gpa, io, opts.file, self.code_directory.code_slots.items, .{ |
| 297 | .chunk_size = self.page_size, |
| 298 | .max_file_size = opts.file_size, |
| 299 | }); |
| 300 | |
| 301 | try blobs.append(.{ .code_directory = &self.code_directory }); |
| 302 | header.length += @sizeOf(macho.BlobIndex); |
| 303 | header.count += 1; |
| 304 | |
| 305 | var hash: [hash_size]u8 = undefined; |
| 306 | |
| 307 | if (self.requirements) |*req| { |
| 308 | var a: std.Io.Writer.Allocating = .init(gpa); |
| 309 | defer a.deinit(); |
| 310 | try req.write(&a.writer); |
| 311 | Sha256.hash(a.written(), &hash, .{}); |
| 312 | self.code_directory.addSpecialHash(req.slotType(), hash); |
| 313 | |
| 314 | try blobs.append(.{ .requirements = req }); |
| 315 | header.count += 1; |
| 316 | header.length += @sizeOf(macho.BlobIndex) + req.size(); |
| 317 | } |
| 318 | |
| 319 | if (self.entitlements) |*ents| { |
| 320 | var a: std.Io.Writer.Allocating = .init(gpa); |
| 321 | defer a.deinit(); |
| 322 | try ents.write(&a.writer); |
| 323 | Sha256.hash(a.written(), &hash, .{}); |
| 324 | self.code_directory.addSpecialHash(ents.slotType(), hash); |
| 325 | |
| 326 | try blobs.append(.{ .entitlements = ents }); |
| 327 | header.count += 1; |
| 328 | header.length += @sizeOf(macho.BlobIndex) + ents.size(); |
| 329 | } |
| 330 | |
| 331 | if (self.signature) |*sig| { |
| 332 | try blobs.append(.{ .signature = sig }); |
| 333 | header.count += 1; |
| 334 | header.length += @sizeOf(macho.BlobIndex) + sig.size(); |
| 335 | } |
| 336 | |
| 337 | self.code_directory.inner.hashOffset = |
| 338 | @sizeOf(macho.CodeDirectory) + @as(u32, @intCast(self.code_directory.ident.len + 1 + self.code_directory.inner.nSpecialSlots * hash_size)); |
| 339 | self.code_directory.inner.length = self.code_directory.size(); |
| 340 | header.length += self.code_directory.size(); |
| 341 | |
| 342 | try writer.writeInt(u32, header.magic, .big); |
| 343 | try writer.writeInt(u32, header.length, .big); |
| 344 | try writer.writeInt(u32, header.count, .big); |
| 345 | |
| 346 | var offset: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) * @as(u32, @intCast(blobs.items.len)); |
| 347 | for (blobs.items) |blob| { |
| 348 | try writer.writeInt(u32, blob.slotType(), .big); |
| 349 | try writer.writeInt(u32, offset, .big); |
| 350 | offset += blob.size(); |
| 351 | } |
| 352 | |
| 353 | for (blobs.items) |blob| { |
| 354 | try blob.write(writer); |
| 355 | } |
| 356 | } |
| 357 | |
| 358 | pub fn size(self: CodeSignature) u32 { |
| 359 | var ssize: u32 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); |
| 360 | if (self.requirements) |req| { |
| 361 | ssize += @sizeOf(macho.BlobIndex) + req.size(); |
| 362 | } |
| 363 | if (self.entitlements) |ent| { |
| 364 | ssize += @sizeOf(macho.BlobIndex) + ent.size(); |
| 365 | } |
| 366 | if (self.signature) |sig| { |
| 367 | ssize += @sizeOf(macho.BlobIndex) + sig.size(); |
| 368 | } |
| 369 | return ssize; |
| 370 | } |
| 371 | |
| 372 | pub fn estimateSize(self: CodeSignature, file_size: u64) u32 { |
| 373 | var ssize: u64 = @sizeOf(macho.SuperBlob) + @sizeOf(macho.BlobIndex) + self.code_directory.size(); |
| 374 | // Approx code slots |
| 375 | const total_pages = mem.alignForward(u64, file_size, self.page_size) / self.page_size; |
| 376 | ssize += total_pages * hash_size; |
| 377 | var n_special_slots: u32 = 0; |
| 378 | if (self.requirements) |req| { |
| 379 | ssize += @sizeOf(macho.BlobIndex) + req.size(); |
| 380 | n_special_slots = @max(n_special_slots, req.slotType()); |
| 381 | } |
| 382 | if (self.entitlements) |ent| { |
| 383 | ssize += @sizeOf(macho.BlobIndex) + ent.size() + hash_size; |
| 384 | n_special_slots = @max(n_special_slots, ent.slotType()); |
| 385 | } |
| 386 | if (self.signature) |sig| { |
| 387 | ssize += @sizeOf(macho.BlobIndex) + sig.size(); |
| 388 | } |
| 389 | ssize += n_special_slots * hash_size; |
| 390 | return @as(u32, @intCast(mem.alignForward(u64, ssize, @sizeOf(u64)))); |
| 391 | } |
| 392 | |
| 393 | pub fn clear(self: *CodeSignature, allocator: Allocator) void { |
| 394 | self.code_directory.deinit(allocator); |
| 395 | self.code_directory = CodeDirectory.init(self.page_size); |
| 396 | } |