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