lib/closure/src/binary/strict.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

   1 const binary = @import("root.zig");
   2 const read = @import("read.zig");
   3 const std = @import("std");
   4 
   5 pub const loads_max: usize = 32;
   6 pub const relocations_max: usize = 1_024;
   7 
   8 pub const Policy = struct {
   9     kind: binary.Kind,
  10     sections_max: u16,
  11     symbols_max: u32,
  12     executable_bytes_max: u64,
  13     initialized_bytes_max: u64,
  14     memory_bytes_max: u64,
  15     stack_bytes_max: u64,
  16     relocations_max: u32,
  17     pe_directories_allowed: u16 = 0,
  18     pe_timestamp: u32 = 0,
  19     pe_repro_timestamp: ?u32 = null,
  20 };
  21 
  22 pub const Load = struct {
  23     offset: u64,
  24     address: u64,
  25     file_bytes: u64,
  26     content_bytes: u64,
  27     memory_bytes: u64,
  28     readable: bool,
  29     writable: bool,
  30     executable: bool,
  31 
  32     pub fn fileEnd(self: Load) Error!u64 {
  33         return added(self.offset, self.file_bytes);
  34     }
  35 
  36     pub fn addressEnd(self: Load) Error!u64 {
  37         return added(self.address, self.memory_bytes);
  38     }
  39 };
  40 
  41 pub const Relocation = struct {
  42     offset: u64,
  43     address: u64,
  44 };
  45 
  46 pub const Scratch = struct {
  47     loads: []Load,
  48     relocations: []Relocation,
  49 };
  50 
  51 pub const Report = struct {
  52     kind: binary.Kind,
  53     loads: []const Load,
  54     relocations: []const Relocation,
  55     executable_bytes: u64,
  56     initialized_bytes: u64,
  57     memory_bytes: u64,
  58     stack_bytes: u64,
  59     timestamp: u32,
  60     image_base: u64,
  61     work: u64,
  62 
  63     pub fn loadForAddress(
  64         self: *const Report,
  65         address: u64,
  66         length: u64,
  67     ) ?Load {
  68         const end = added(address, length) catch return null;
  69         for (self.loads) |load| {
  70             const load_end = load.addressEnd() catch return null;
  71             if (address >= load.address and end <= load_end) return load;
  72         }
  73         return null;
  74     }
  75 };
  76 
  77 const InspectError = error{
  78     BaseRelocationCapacityExceeded,
  79     DataDirectoryForbidden,
  80     DebugDirectoryPresent,
  81     DynamicLinkagePresent,
  82     EntryPointUnmapped,
  83     ExecutableBudgetExceeded,
  84     ExecutableStack,
  85     InvalidBaseRelocation,
  86     InvalidHeader,
  87     InvalidLoad,
  88     InvalidSection,
  89     LoadCapacityExceeded,
  90     LoadOverlap,
  91     MemoryBudgetExceeded,
  92     MissingStackPolicy,
  93     NonZeroTimestamp,
  94     PeSymbolTablePresent,
  95     RelocationTargetInvalid,
  96     SectionBudgetExceeded,
  97     SymbolBudgetExceeded,
  98     TlsPresent,
  99     UndefinedSymbol,
 100     UnsupportedBinary,
 101     WritableExecutable,
 102 };
 103 
 104 pub const Error = read.Error || InspectError;
 105 
 106 const elf_header_bytes: usize = 64;
 107 const elf_program_bytes: usize = 56;
 108 const elf_section_bytes: usize = 64;
 109 const elf_symbol_bytes: usize = 24;
 110 const elf_machine_x86_64: u16 = 62;
 111 const elf_type_executable: u16 = 2;
 112 const elf_program_load: u32 = 1;
 113 const elf_program_dynamic: u32 = 2;
 114 const elf_program_interpreter: u32 = 3;
 115 const elf_program_tls: u32 = 7;
 116 const elf_program_stack: u32 = 0x6474_e551;
 117 const elf_flag_execute: u32 = 1;
 118 const elf_flag_write: u32 = 2;
 119 const elf_flag_read: u32 = 4;
 120 const elf_section_symbols: u32 = 2;
 121 const elf_section_rela: u32 = 4;
 122 const elf_section_dynamic: u32 = 6;
 123 const elf_section_nobits: u32 = 8;
 124 const elf_section_rel: u32 = 9;
 125 const elf_section_dynamic_symbols: u32 = 11;
 126 const elf_section_relr: u32 = 19;
 127 const elf_section_flag_write: u64 = 1;
 128 const elf_section_flag_alloc: u64 = 2;
 129 const elf_section_flag_execute: u64 = 4;
 130 const elf_section_flag_tls: u64 = 0x400;
 131 
 132 const pe_coff_bytes: usize = 20;
 133 const pe_section_bytes: usize = 40;
 134 const pe_machine_x86_64: u16 = 0x8664;
 135 const pe_optional_magic: u16 = 0x20b;
 136 const pe_executable_image: u16 = 0x0002;
 137 const pe_subsystem_efi_application: u16 = 10;
 138 const pe_nx_compatible: u16 = 0x0100;
 139 const pe_section_code: u32 = 0x0000_0020;
 140 const pe_section_discardable: u32 = 0x0200_0000;
 141 const pe_section_execute: u32 = 0x2000_0000;
 142 const pe_section_read: u32 = 0x4000_0000;
 143 const pe_section_write: u32 = 0x8000_0000;
 144 const pe_directory_base_relocation: usize = 5;
 145 const pe_directory_debug: usize = 6;
 146 const pe_relocation_absolute: u16 = 0;
 147 const pe_relocation_dir64: u16 = 10;
 148 const pe_debug_entry_bytes: u32 = 28;
 149 const pe_debug_type_repro: u32 = 16;
 150 
 151 const Counts = struct {
 152     load_count: usize = 0,
 153     relocation_count: usize = 0,
 154     relocation_entry_count: u32 = 0,
 155     symbol_count: u32 = 0,
 156     executable_bytes: u64 = 0,
 157     initialized_bytes: u64 = 0,
 158     memory_bytes: u64 = 0,
 159     stack_bytes: u64 = 0,
 160     timestamp: u32 = 0,
 161     image_base: u64 = 0,
 162     work: u64 = 1,
 163 };
 164 
 165 const ElfHeader = struct {
 166     entry: u64,
 167     programs: usize,
 168     program_count: usize,
 169     sections: usize,
 170     section_count: usize,
 171 };
 172 
 173 const PeHeader = struct {
 174     coff: usize,
 175     optional: usize,
 176     optional_bytes: usize,
 177     sections: usize,
 178     section_count: usize,
 179     image_base: u64,
 180     entry_rva: u32,
 181     directory_count: usize,
 182 };
 183 
 184 pub fn inspect(
 185     bytes: []const u8,
 186     policy: Policy,
 187     scratch: Scratch,
 188 ) Error!Report {
 189     if (scratch.loads.len > loads_max or
 190         scratch.relocations.len > relocations_max)
 191     {
 192         return error.InvalidHeader;
 193     }
 194     var counts: Counts = .{};
 195     switch (policy.kind) {
 196         .elf64 => try inspectElf(bytes, policy, scratch, &counts),
 197         .pe32_plus => try inspectPe(bytes, policy, scratch, &counts),
 198     }
 199     try inspectLoadOverlap(scratch.loads[0..counts.load_count], &counts.work);
 200     if (counts.executable_bytes > policy.executable_bytes_max or
 201         counts.initialized_bytes > policy.initialized_bytes_max)
 202     {
 203         return error.ExecutableBudgetExceeded;
 204     }
 205     if (counts.memory_bytes > policy.memory_bytes_max) {
 206         return error.MemoryBudgetExceeded;
 207     }
 208     return .{
 209         .kind = policy.kind,
 210         .loads = scratch.loads[0..counts.load_count],
 211         .relocations = scratch.relocations[0..counts.relocation_count],
 212         .executable_bytes = counts.executable_bytes,
 213         .initialized_bytes = counts.initialized_bytes,
 214         .memory_bytes = counts.memory_bytes,
 215         .stack_bytes = counts.stack_bytes,
 216         .timestamp = counts.timestamp,
 217         .image_base = counts.image_base,
 218         .work = counts.work,
 219     };
 220 }
 221 
 222 fn inspectElf(
 223     bytes: []const u8,
 224     policy: Policy,
 225     scratch: Scratch,
 226     counts: *Counts,
 227 ) Error!void {
 228     const header = try elfHeader(bytes, policy);
 229     var stack_count: u8 = 0;
 230     for (0..header.program_count) |index| {
 231         counts.work += 1;
 232         const offset = try read.indexed(
 233             header.programs,
 234             index,
 235             elf_program_bytes,
 236         );
 237         const kind = try read.u32le(bytes, offset);
 238         const flags = try read.u32le(bytes, offset + 4);
 239         switch (kind) {
 240             elf_program_dynamic, elf_program_interpreter => return error.DynamicLinkagePresent,
 241             elf_program_tls => return error.TlsPresent,
 242             elf_program_stack => {
 243                 stack_count += 1;
 244                 if (stack_count != 1 or
 245                     flags & elf_flag_execute != 0 or
 246                     flags & (elf_flag_read | elf_flag_write) !=
 247                         elf_flag_read | elf_flag_write)
 248                 {
 249                     return error.ExecutableStack;
 250                 }
 251                 counts.stack_bytes = try read.u64le(bytes, offset + 40);
 252                 if (counts.stack_bytes > policy.stack_bytes_max) {
 253                     return error.MemoryBudgetExceeded;
 254                 }
 255             },
 256             elf_program_load => try appendElfLoad(
 257                 bytes,
 258                 offset,
 259                 flags,
 260                 scratch.loads,
 261                 counts,
 262             ),
 263             else => {},
 264         }
 265     }
 266     if (stack_count != 1) return error.MissingStackPolicy;
 267     try inspectElfSections(
 268         bytes,
 269         header,
 270         policy,
 271         scratch.loads,
 272         counts,
 273     );
 274     if (!addressExecutable(
 275         scratch.loads[0..counts.load_count],
 276         header.entry,
 277         1,
 278     )) {
 279         return error.EntryPointUnmapped;
 280     }
 281 }
 282 
 283 fn elfHeader(bytes: []const u8, policy: Policy) Error!ElfHeader {
 284     _ = try read.take(bytes, 0, elf_header_bytes);
 285     if (!std.mem.eql(u8, bytes[0..4], "\x7fELF") or
 286         bytes[4] != 2 or
 287         bytes[5] != 1 or
 288         bytes[6] != 1 or
 289         try read.u16le(bytes, 16) != elf_type_executable or
 290         try read.u16le(bytes, 18) != elf_machine_x86_64 or
 291         try read.u32le(bytes, 20) != 1 or
 292         try read.u16le(bytes, 52) != elf_header_bytes or
 293         try read.u16le(bytes, 54) != elf_program_bytes or
 294         try read.u16le(bytes, 58) != elf_section_bytes)
 295     {
 296         return error.UnsupportedBinary;
 297     }
 298     const program_count = try read.u16le(bytes, 56);
 299     const section_count = try read.u16le(bytes, 60);
 300     if (program_count == 0 or
 301         program_count > loads_max or
 302         section_count == 0 or
 303         section_count > policy.sections_max)
 304     {
 305         return error.SectionBudgetExceeded;
 306     }
 307     const programs = try read.toOffset(try read.u64le(bytes, 32));
 308     const sections = try read.toOffset(try read.u64le(bytes, 40));
 309     _ = try read.take(
 310         bytes,
 311         try read.indexed(programs, program_count - 1, elf_program_bytes),
 312         elf_program_bytes,
 313     );
 314     _ = try read.take(
 315         bytes,
 316         try read.indexed(sections, section_count - 1, elf_section_bytes),
 317         elf_section_bytes,
 318     );
 319     return .{
 320         .entry = try read.u64le(bytes, 24),
 321         .programs = programs,
 322         .program_count = program_count,
 323         .sections = sections,
 324         .section_count = section_count,
 325     };
 326 }
 327 
 328 fn appendElfLoad(
 329     bytes: []const u8,
 330     offset: usize,
 331     flags: u32,
 332     loads: []Load,
 333     counts: *Counts,
 334 ) Error!void {
 335     if (counts.load_count == loads.len) {
 336         return error.LoadCapacityExceeded;
 337     }
 338     if (flags & ~(elf_flag_read | elf_flag_write | elf_flag_execute) != 0 or
 339         flags & elf_flag_read == 0 or
 340         flags & (elf_flag_write | elf_flag_execute) ==
 341             elf_flag_write | elf_flag_execute)
 342     {
 343         return error.WritableExecutable;
 344     }
 345     const file_offset = try read.u64le(bytes, offset + 8);
 346     const address = try read.u64le(bytes, offset + 16);
 347     const physical = try read.u64le(bytes, offset + 24);
 348     const file_bytes = try read.u64le(bytes, offset + 32);
 349     const memory_bytes = try read.u64le(bytes, offset + 40);
 350     const alignment = try read.u64le(bytes, offset + 48);
 351     if (physical != address or
 352         file_bytes > memory_bytes or
 353         memory_bytes == 0 or
 354         (flags & elf_flag_execute != 0 and file_bytes != memory_bytes) or
 355         !powerOfTwoOrZero(alignment) or
 356         (alignment > 1 and file_offset % alignment != address % alignment))
 357     {
 358         return error.InvalidLoad;
 359     }
 360     _ = try read.take(
 361         bytes,
 362         try read.toOffset(file_offset),
 363         try read.toLength(file_bytes),
 364     );
 365     loads[counts.load_count] = .{
 366         .offset = file_offset,
 367         .address = address,
 368         .file_bytes = file_bytes,
 369         .content_bytes = file_bytes,
 370         .memory_bytes = memory_bytes,
 371         .readable = true,
 372         .writable = flags & elf_flag_write != 0,
 373         .executable = flags & elf_flag_execute != 0,
 374     };
 375     counts.load_count += 1;
 376     counts.memory_bytes = try added(counts.memory_bytes, memory_bytes);
 377     if (flags & elf_flag_execute != 0) {
 378         counts.executable_bytes =
 379             try added(counts.executable_bytes, file_bytes);
 380     } else {
 381         counts.initialized_bytes =
 382             try added(counts.initialized_bytes, file_bytes);
 383     }
 384 }
 385 
 386 fn inspectElfSections(
 387     bytes: []const u8,
 388     header: ElfHeader,
 389     policy: Policy,
 390     loads: []const Load,
 391     counts: *Counts,
 392 ) Error!void {
 393     for (0..header.section_count) |index| {
 394         counts.work += 1;
 395         const offset = try read.indexed(
 396             header.sections,
 397             index,
 398             elf_section_bytes,
 399         );
 400         const kind = try read.u32le(bytes, offset + 4);
 401         const flags = try read.u64le(bytes, offset + 8);
 402         if (kind == elf_section_dynamic or
 403             kind == elf_section_dynamic_symbols)
 404         {
 405             return error.DynamicLinkagePresent;
 406         }
 407         if (kind == elf_section_rela or
 408             kind == elf_section_rel or
 409             kind == elf_section_relr)
 410         {
 411             return error.InvalidSection;
 412         }
 413         if (flags & elf_section_flag_tls != 0) return error.TlsPresent;
 414         if (kind == elf_section_symbols) {
 415             try inspectElfSymbols(bytes, header, offset, policy, counts);
 416         }
 417         if (flags & elf_section_flag_alloc == 0) continue;
 418         const address = try read.u64le(bytes, offset + 16);
 419         const file_offset = try read.u64le(bytes, offset + 24);
 420         const length = try read.u64le(bytes, offset + 32);
 421         const executable = flags & elf_section_flag_execute != 0;
 422         const writable = flags & elf_section_flag_write != 0;
 423         if (executable and writable) return error.WritableExecutable;
 424         if (length == 0) continue;
 425         if (kind == elf_section_nobits and executable) {
 426             return error.InvalidSection;
 427         }
 428         const admitted = (if (kind == elf_section_nobits)
 429             loadForAddress(loads, address, length)
 430         else
 431             loadForSection(loads, address, file_offset, length)) orelse
 432             return error.InvalidSection;
 433         if (admitted.executable != executable or
 434             (writable and !admitted.writable))
 435         {
 436             return error.InvalidSection;
 437         }
 438     }
 439 }
 440 
 441 fn inspectElfSymbols(
 442     bytes: []const u8,
 443     header: ElfHeader,
 444     section_offset: usize,
 445     policy: Policy,
 446     counts: *Counts,
 447 ) Error!void {
 448     const table_offset = try read.u64le(bytes, section_offset + 24);
 449     const table_bytes = try read.u64le(bytes, section_offset + 32);
 450     const entry_bytes = try read.u64le(bytes, section_offset + 56);
 451     if (entry_bytes != elf_symbol_bytes or
 452         table_bytes % elf_symbol_bytes != 0)
 453     {
 454         return error.InvalidSection;
 455     }
 456     const symbol_count = table_bytes / elf_symbol_bytes;
 457     const total = try added(counts.symbol_count, symbol_count);
 458     if (total > policy.symbols_max) {
 459         return error.SymbolBudgetExceeded;
 460     }
 461     counts.symbol_count = @intCast(total);
 462     const symbols = try read.take(
 463         bytes,
 464         try read.toOffset(table_offset),
 465         try read.toLength(table_bytes),
 466     );
 467     for (0..symbols.len / elf_symbol_bytes) |index| {
 468         counts.work += 1;
 469         if (index == 0) continue;
 470         const offset = index * elf_symbol_bytes;
 471         const binding = symbols[offset + 4] >> 4;
 472         const section_index = std.mem.readInt(
 473             u16,
 474             symbols[offset + 6 ..][0..2],
 475             .little,
 476         );
 477         if (section_index == 0 and (binding == 1 or binding == 2)) {
 478             return error.UndefinedSymbol;
 479         }
 480     }
 481     _ = header;
 482 }
 483 
 484 fn inspectPe(
 485     bytes: []const u8,
 486     policy: Policy,
 487     scratch: Scratch,
 488     counts: *Counts,
 489 ) Error!void {
 490     const header = try peHeader(bytes, policy);
 491     counts.image_base = header.image_base;
 492     counts.timestamp = try read.u32le(bytes, header.coff + 4);
 493     if (counts.timestamp != policy.pe_timestamp) {
 494         return error.NonZeroTimestamp;
 495     }
 496     if (try read.u32le(bytes, header.coff + 8) != 0 or
 497         try read.u32le(bytes, header.coff + 12) != 0)
 498     {
 499         return error.PeSymbolTablePresent;
 500     }
 501     for (0..header.section_count) |index| {
 502         counts.work += 1;
 503         try appendPeLoad(
 504             bytes,
 505             header,
 506             index,
 507             scratch.loads,
 508             counts,
 509         );
 510     }
 511     try inspectPeDirectories(bytes, header, policy, scratch, counts);
 512     const entry = try added(header.image_base, header.entry_rva);
 513     if (!addressExecutable(
 514         scratch.loads[0..counts.load_count],
 515         entry,
 516         1,
 517     )) {
 518         return error.EntryPointUnmapped;
 519     }
 520 }
 521 
 522 fn peHeader(bytes: []const u8, policy: Policy) Error!PeHeader {
 523     _ = try read.take(bytes, 0, 64);
 524     if (!std.mem.eql(u8, bytes[0..2], "MZ")) {
 525         return error.UnsupportedBinary;
 526     }
 527     const pe_offset = try read.toOffset(try read.u32le(bytes, 0x3c));
 528     if (!std.mem.eql(
 529         u8,
 530         try read.take(bytes, pe_offset, 4),
 531         "PE\x00\x00",
 532     )) {
 533         return error.UnsupportedBinary;
 534     }
 535     const coff = try addOffset(pe_offset, 4);
 536     _ = try read.take(bytes, coff, pe_coff_bytes);
 537     if (try read.u16le(bytes, coff) != pe_machine_x86_64 or
 538         try read.u16le(bytes, coff + 18) & pe_executable_image == 0)
 539     {
 540         return error.UnsupportedBinary;
 541     }
 542     const section_count = try read.u16le(bytes, coff + 2);
 543     if (section_count == 0 or section_count > policy.sections_max) {
 544         return error.SectionBudgetExceeded;
 545     }
 546     const optional_bytes = try read.u16le(bytes, coff + 16);
 547     const optional = try addOffset(coff, pe_coff_bytes);
 548     _ = try read.take(bytes, optional, optional_bytes);
 549     if (optional_bytes < 112 or
 550         try read.u16le(bytes, optional) != pe_optional_magic or
 551         try read.u16le(bytes, optional + 68) !=
 552             pe_subsystem_efi_application or
 553         try read.u16le(bytes, optional + 70) & pe_nx_compatible == 0)
 554     {
 555         return error.InvalidHeader;
 556     }
 557     const directory_count = try read.u32le(bytes, optional + 108);
 558     if (directory_count > 16 or
 559         112 + @as(usize, directory_count) * 8 > optional_bytes)
 560     {
 561         return error.InvalidHeader;
 562     }
 563     const sections = try addOffset(optional, optional_bytes);
 564     _ = try read.take(
 565         bytes,
 566         try read.indexed(sections, section_count - 1, pe_section_bytes),
 567         pe_section_bytes,
 568     );
 569     return .{
 570         .coff = coff,
 571         .optional = optional,
 572         .optional_bytes = optional_bytes,
 573         .sections = sections,
 574         .section_count = section_count,
 575         .image_base = try read.u64le(bytes, optional + 24),
 576         .entry_rva = try read.u32le(bytes, optional + 16),
 577         .directory_count = directory_count,
 578     };
 579 }
 580 
 581 fn appendPeLoad(
 582     bytes: []const u8,
 583     header: PeHeader,
 584     index: usize,
 585     loads: []Load,
 586     counts: *Counts,
 587 ) Error!void {
 588     if (counts.load_count == loads.len) {
 589         return error.LoadCapacityExceeded;
 590     }
 591     const offset = try read.indexed(
 592         header.sections,
 593         index,
 594         pe_section_bytes,
 595     );
 596     const virtual_bytes = try read.u32le(bytes, offset + 8);
 597     const virtual_address = try read.u32le(bytes, offset + 12);
 598     const file_bytes = try read.u32le(bytes, offset + 16);
 599     const file_offset = try read.u32le(bytes, offset + 20);
 600     const flags = try read.u32le(bytes, offset + 36);
 601     const executable = flags & pe_section_execute != 0;
 602     const writable = flags & pe_section_write != 0;
 603     const readable = flags & pe_section_read != 0;
 604     if (executable and
 605         (writable or
 606             !readable or
 607             flags & pe_section_code == 0 or
 608             flags & pe_section_discardable != 0 or
 609             virtual_bytes == 0 or
 610             virtual_bytes > file_bytes))
 611     {
 612         return error.WritableExecutable;
 613     }
 614     if (file_bytes == 0 and virtual_bytes == 0) {
 615         return error.InvalidSection;
 616     }
 617     _ = try read.take(bytes, file_offset, file_bytes);
 618     const memory_bytes = @max(virtual_bytes, file_bytes);
 619     loads[counts.load_count] = .{
 620         .offset = file_offset,
 621         .address = try added(header.image_base, virtual_address),
 622         .file_bytes = file_bytes,
 623         .content_bytes = if (executable) virtual_bytes else file_bytes,
 624         .memory_bytes = memory_bytes,
 625         .readable = readable,
 626         .writable = writable,
 627         .executable = executable,
 628     };
 629     counts.load_count += 1;
 630     counts.memory_bytes = try added(counts.memory_bytes, memory_bytes);
 631     if (executable) {
 632         counts.executable_bytes =
 633             try added(counts.executable_bytes, virtual_bytes);
 634     } else {
 635         counts.initialized_bytes =
 636             try added(counts.initialized_bytes, file_bytes);
 637     }
 638 }
 639 
 640 fn inspectPeDirectories(
 641     bytes: []const u8,
 642     header: PeHeader,
 643     policy: Policy,
 644     scratch: Scratch,
 645     counts: *Counts,
 646 ) Error!void {
 647     for (0..header.directory_count) |index| {
 648         counts.work += 1;
 649         const offset = header.optional + 112 + index * 8;
 650         const rva = try read.u32le(bytes, offset);
 651         const length = try read.u32le(bytes, offset + 4);
 652         if ((rva == 0) != (length == 0)) return error.InvalidHeader;
 653         if (rva == 0) continue;
 654         const bit = @as(u16, 1) << @intCast(index);
 655         if (policy.pe_directories_allowed & bit == 0) {
 656             if (index == 6) return error.DebugDirectoryPresent;
 657             return error.DataDirectoryForbidden;
 658         }
 659         if (index == pe_directory_base_relocation) {
 660             try inspectBaseRelocations(
 661                 bytes,
 662                 header,
 663                 rva,
 664                 length,
 665                 policy,
 666                 scratch,
 667                 counts,
 668             );
 669         } else if (index == pe_directory_debug) {
 670             try inspectReproDirectory(
 671                 bytes,
 672                 header,
 673                 rva,
 674                 length,
 675                 policy,
 676                 scratch.loads[0..counts.load_count],
 677             );
 678         } else {
 679             _ = peFileRange(
 680                 scratch.loads[0..counts.load_count],
 681                 header.image_base,
 682                 rva,
 683                 length,
 684             ) orelse return error.InvalidSection;
 685         }
 686     }
 687 }
 688 
 689 fn inspectReproDirectory(
 690     bytes: []const u8,
 691     header: PeHeader,
 692     rva: u32,
 693     length: u32,
 694     policy: Policy,
 695     loads: []const Load,
 696 ) Error!void {
 697     const expected_timestamp = policy.pe_repro_timestamp orelse
 698         return error.DebugDirectoryPresent;
 699     if (length != pe_debug_entry_bytes) {
 700         return error.DebugDirectoryPresent;
 701     }
 702     const offset = peFileRange(
 703         loads,
 704         header.image_base,
 705         rva,
 706         length,
 707     ) orelse return error.DebugDirectoryPresent;
 708     const entry = try read.take(bytes, offset, pe_debug_entry_bytes);
 709     if (std.mem.readInt(u32, entry[0..4], .little) != 0 or
 710         std.mem.readInt(u32, entry[4..8], .little) !=
 711             expected_timestamp or
 712         std.mem.readInt(u16, entry[8..10], .little) != 0 or
 713         std.mem.readInt(u16, entry[10..12], .little) != 0 or
 714         std.mem.readInt(u32, entry[12..16], .little) !=
 715             pe_debug_type_repro or
 716         std.mem.readInt(u32, entry[16..20], .little) != 0 or
 717         std.mem.readInt(u32, entry[20..24], .little) != 0 or
 718         std.mem.readInt(u32, entry[24..28], .little) != 0)
 719     {
 720         return error.DebugDirectoryPresent;
 721     }
 722 }
 723 
 724 fn inspectBaseRelocations(
 725     bytes: []const u8,
 726     header: PeHeader,
 727     rva: u32,
 728     length: u32,
 729     policy: Policy,
 730     scratch: Scratch,
 731     counts: *Counts,
 732 ) Error!void {
 733     const directory_offset = peFileRange(
 734         scratch.loads[0..counts.load_count],
 735         header.image_base,
 736         rva,
 737         length,
 738     ) orelse return error.InvalidBaseRelocation;
 739     const directory = try read.take(bytes, directory_offset, length);
 740     var cursor: usize = 0;
 741     var previous_address: u64 = 0;
 742     while (cursor < directory.len) {
 743         counts.work += 1;
 744         if (cursor + 8 > directory.len) {
 745             return error.InvalidBaseRelocation;
 746         }
 747         const page = std.mem.readInt(
 748             u32,
 749             directory[cursor..][0..4],
 750             .little,
 751         );
 752         const block_bytes = std.mem.readInt(
 753             u32,
 754             directory[cursor + 4 ..][0..4],
 755             .little,
 756         );
 757         if (page % 4096 != 0 or
 758             block_bytes < 8 or
 759             block_bytes % 2 != 0 or
 760             cursor + block_bytes > directory.len)
 761         {
 762             return error.InvalidBaseRelocation;
 763         }
 764         const entry_count = (block_bytes - 8) / 2;
 765         for (0..entry_count) |entry_index| {
 766             counts.work += 1;
 767             if (counts.relocation_entry_count == policy.relocations_max) {
 768                 return error.BaseRelocationCapacityExceeded;
 769             }
 770             counts.relocation_entry_count += 1;
 771             const entry_offset = cursor + 8 + entry_index * 2;
 772             const entry = std.mem.readInt(
 773                 u16,
 774                 directory[entry_offset..][0..2],
 775                 .little,
 776             );
 777             const kind = entry >> 12;
 778             if (kind == pe_relocation_absolute) continue;
 779             if (kind != pe_relocation_dir64) {
 780                 return error.InvalidBaseRelocation;
 781             }
 782             if (counts.relocation_count == scratch.relocations.len or
 783                 counts.relocation_count == policy.relocations_max)
 784             {
 785                 return error.BaseRelocationCapacityExceeded;
 786             }
 787             const target_rva = try added(page, entry & 0x0fff);
 788             const target_address = try added(
 789                 header.image_base,
 790                 target_rva,
 791             );
 792             const load = loadForAddress(
 793                 scratch.loads[0..counts.load_count],
 794                 target_address,
 795                 8,
 796             ) orelse return error.RelocationTargetInvalid;
 797             if (!load.writable or load.executable) {
 798                 return error.RelocationTargetInvalid;
 799             }
 800             if (counts.relocation_count != 0 and
 801                 target_address <= previous_address)
 802             {
 803                 return error.InvalidBaseRelocation;
 804             }
 805             const relative = target_address - load.address;
 806             scratch.relocations[counts.relocation_count] = .{
 807                 .offset = try added(load.offset, relative),
 808                 .address = target_address,
 809             };
 810             counts.relocation_count += 1;
 811             previous_address = target_address;
 812         }
 813         cursor += block_bytes;
 814     }
 815     if (cursor != directory.len) return error.InvalidBaseRelocation;
 816 }
 817 
 818 fn inspectLoadOverlap(loads: []const Load, work: *u64) Error!void {
 819     for (loads, 0..) |left, left_index| {
 820         for (loads[left_index + 1 ..]) |right| {
 821             work.* += 1;
 822             const left_address_end = try left.addressEnd();
 823             const right_address_end = try right.addressEnd();
 824             if (left.address < right_address_end and
 825                 right.address < left_address_end)
 826             {
 827                 return error.LoadOverlap;
 828             }
 829             const left_file_end = try left.fileEnd();
 830             const right_file_end = try right.fileEnd();
 831             if (left.file_bytes != 0 and
 832                 right.file_bytes != 0 and
 833                 left.offset < right_file_end and
 834                 right.offset < left_file_end)
 835             {
 836                 return error.LoadOverlap;
 837             }
 838         }
 839     }
 840 }
 841 
 842 fn loadForSection(
 843     loads: []const Load,
 844     address: u64,
 845     offset: u64,
 846     length: u64,
 847 ) ?Load {
 848     const address_end = added(address, length) catch return null;
 849     const offset_end = added(offset, length) catch return null;
 850     for (loads) |load| {
 851         const load_address_end = load.addressEnd() catch return null;
 852         const load_file_end = load.fileEnd() catch return null;
 853         if (address >= load.address and
 854             address_end <= load_address_end and
 855             offset >= load.offset and
 856             offset_end <= load_file_end)
 857         {
 858             return load;
 859         }
 860     }
 861     return null;
 862 }
 863 
 864 fn loadForAddress(
 865     loads: []const Load,
 866     address: u64,
 867     length: u64,
 868 ) ?Load {
 869     const end = added(address, length) catch return null;
 870     for (loads) |load| {
 871         const load_end = load.addressEnd() catch return null;
 872         if (address >= load.address and end <= load_end) return load;
 873     }
 874     return null;
 875 }
 876 
 877 fn addressExecutable(
 878     loads: []const Load,
 879     address: u64,
 880     length: u64,
 881 ) bool {
 882     const load = loadForAddress(loads, address, length) orelse return false;
 883     return load.executable;
 884 }
 885 
 886 fn peFileRange(
 887     loads: []const Load,
 888     image_base: u64,
 889     rva: u32,
 890     length: u32,
 891 ) ?usize {
 892     const address = added(image_base, rva) catch return null;
 893     const load = loadForAddress(loads, address, length) orelse return null;
 894     const relative = address - load.address;
 895     const relative_end = added(relative, length) catch return null;
 896     if (relative_end > load.file_bytes) return null;
 897     return read.toOffset(added(load.offset, relative) catch return null) catch
 898         return null;
 899 }
 900 
 901 fn powerOfTwoOrZero(value: u64) bool {
 902     return value == 0 or std.math.isPowerOfTwo(value);
 903 }
 904 
 905 fn addOffset(left: usize, right: anytype) Error!usize {
 906     return std.math.add(usize, left, @intCast(right)) catch
 907         error.BinaryArithmeticOverflow;
 908 }
 909 
 910 fn added(left: anytype, right: anytype) Error!u64 {
 911     return std.math.add(
 912         u64,
 913         @intCast(left),
 914         @intCast(right),
 915     ) catch error.BinaryArithmeticOverflow;
 916 }
 917 
 918 fn put16(bytes: []u8, offset: usize, value: u16) void {
 919     std.mem.writeInt(u16, bytes[offset..][0..2], value, .little);
 920 }
 921 
 922 fn put32(bytes: []u8, offset: usize, value: u32) void {
 923     std.mem.writeInt(u32, bytes[offset..][0..4], value, .little);
 924 }
 925 
 926 fn put64(bytes: []u8, offset: usize, value: u64) void {
 927     std.mem.writeInt(u64, bytes[offset..][0..8], value, .little);
 928 }
 929 
 930 fn elfFixture() [512]u8 {
 931     var bytes: [512]u8 = @splat(0);
 932     @memcpy(bytes[0..4], "\x7fELF");
 933     bytes[4] = 2;
 934     bytes[5] = 1;
 935     bytes[6] = 1;
 936     put16(&bytes, 16, elf_type_executable);
 937     put16(&bytes, 18, elf_machine_x86_64);
 938     put32(&bytes, 20, 1);
 939     put64(&bytes, 24, 0x1000);
 940     put64(&bytes, 32, 64);
 941     put64(&bytes, 40, 192);
 942     put16(&bytes, 52, elf_header_bytes);
 943     put16(&bytes, 54, elf_program_bytes);
 944     put16(&bytes, 56, 2);
 945     put16(&bytes, 58, elf_section_bytes);
 946     put16(&bytes, 60, 2);
 947     put32(&bytes, 64, elf_program_load);
 948     put32(&bytes, 68, elf_flag_read | elf_flag_execute);
 949     put64(&bytes, 72, 384);
 950     put64(&bytes, 80, 0x1000);
 951     put64(&bytes, 88, 0x1000);
 952     put64(&bytes, 96, 16);
 953     put64(&bytes, 104, 16);
 954     put64(&bytes, 112, 1);
 955     const stack = 64 + elf_program_bytes;
 956     put32(&bytes, stack, elf_program_stack);
 957     put32(&bytes, stack + 4, elf_flag_read | elf_flag_write);
 958     put64(&bytes, stack + 40, 4096);
 959     const text = 192 + elf_section_bytes;
 960     put32(&bytes, text + 4, 1);
 961     put64(
 962         &bytes,
 963         text + 8,
 964         elf_section_flag_alloc | elf_section_flag_execute,
 965     );
 966     put64(&bytes, text + 16, 0x1000);
 967     put64(&bytes, text + 24, 384);
 968     put64(&bytes, text + 32, 16);
 969     @memset(bytes[384..400], 0xcc);
 970     return bytes;
 971 }
 972 
 973 fn elfPolicy() Policy {
 974     return .{
 975         .kind = .elf64,
 976         .sections_max = 4,
 977         .symbols_max = 16,
 978         .executable_bytes_max = 16,
 979         .initialized_bytes_max = 1,
 980         .memory_bytes_max = 16,
 981         .stack_bytes_max = 4096,
 982         .relocations_max = 0,
 983     };
 984 }
 985 
 986 fn peFixture() [1_024]u8 {
 987     var bytes: [1_024]u8 = @splat(0);
 988     @memcpy(bytes[0..2], "MZ");
 989     put32(&bytes, 0x3c, 64);
 990     @memcpy(bytes[64..68], "PE\x00\x00");
 991     const coff = 68;
 992     put16(&bytes, coff, pe_machine_x86_64);
 993     put16(&bytes, coff + 2, 2);
 994     put16(&bytes, coff + 16, 240);
 995     put16(&bytes, coff + 18, pe_executable_image);
 996     const optional = coff + pe_coff_bytes;
 997     put16(&bytes, optional, pe_optional_magic);
 998     put32(&bytes, optional + 16, 0x1000);
 999     put64(&bytes, optional + 24, 0x400000);
1000     put16(&bytes, optional + 68, pe_subsystem_efi_application);
1001     put16(&bytes, optional + 70, pe_nx_compatible);
1002     put32(&bytes, optional + 108, 16);
1003     const text = optional + 240;
1004     @memcpy(bytes[text..][0..5], ".text");
1005     put32(&bytes, text + 8, 32);
1006     put32(&bytes, text + 12, 0x1000);
1007     put32(&bytes, text + 16, 64);
1008     put32(&bytes, text + 20, 512);
1009     put32(
1010         &bytes,
1011         text + 36,
1012         pe_section_code | pe_section_execute | pe_section_read,
1013     );
1014     const data = text + pe_section_bytes;
1015     @memcpy(bytes[data..][0..5], ".data");
1016     put32(&bytes, data + 8, 64);
1017     put32(&bytes, data + 12, 0x2000);
1018     put32(&bytes, data + 16, 64);
1019     put32(&bytes, data + 20, 576);
1020     put32(&bytes, data + 36, pe_section_read | pe_section_write);
1021     @memset(bytes[512..576], 0xcc);
1022     return bytes;
1023 }
1024 
1025 fn pePolicy() Policy {
1026     return .{
1027         .kind = .pe32_plus,
1028         .sections_max = 4,
1029         .symbols_max = 0,
1030         .executable_bytes_max = 32,
1031         .initialized_bytes_max = 64,
1032         .memory_bytes_max = 128,
1033         .stack_bytes_max = 0,
1034         .relocations_max = 0,
1035     };
1036 }
1037 
1038 test "strict ELF admits a bounded static NX executable" {
1039     const bytes = elfFixture();
1040     var loads: [2]Load = undefined;
1041     var relocations: [1]Relocation = undefined;
1042     const report = try inspect(&bytes, elfPolicy(), .{
1043         .loads = &loads,
1044         .relocations = relocations[0..0],
1045     });
1046     try std.testing.expectEqual(binary.Kind.elf64, report.kind);
1047     try std.testing.expectEqual(@as(usize, 1), report.loads.len);
1048     try std.testing.expect(report.loads[0].executable);
1049     try std.testing.expectEqual(@as(u64, 16), report.executable_bytes);
1050     try std.testing.expectEqual(@as(u64, 4096), report.stack_bytes);
1051 }
1052 
1053 test "strict ELF rejects dynamic linkage and writable code" {
1054     var dynamic = elfFixture();
1055     put32(&dynamic, 64, elf_program_dynamic);
1056     var loads: [2]Load = undefined;
1057     var relocations: [1]Relocation = undefined;
1058     try std.testing.expectError(
1059         error.DynamicLinkagePresent,
1060         inspect(&dynamic, elfPolicy(), .{
1061             .loads = &loads,
1062             .relocations = relocations[0..0],
1063         }),
1064     );
1065     var writable = elfFixture();
1066     put32(
1067         &writable,
1068         68,
1069         elf_flag_read | elf_flag_write | elf_flag_execute,
1070     );
1071     try std.testing.expectError(
1072         error.WritableExecutable,
1073         inspect(&writable, elfPolicy(), .{
1074             .loads = &loads,
1075             .relocations = relocations[0..0],
1076         }),
1077     );
1078 }
1079 
1080 test "strict ELF bounds symbol inspection work" {
1081     var bytes = elfFixture();
1082     const text = 192 + elf_section_bytes;
1083     put32(&bytes, text + 4, elf_section_symbols);
1084     put64(&bytes, text + 24, 384);
1085     put64(&bytes, text + 32, 48);
1086     put64(&bytes, text + 56, elf_symbol_bytes);
1087     var policy = elfPolicy();
1088     policy.symbols_max = 1;
1089     var loads: [2]Load = undefined;
1090     var relocations: [1]Relocation = undefined;
1091     try std.testing.expectError(
1092         error.SymbolBudgetExceeded,
1093         inspect(&bytes, policy, .{
1094             .loads = &loads,
1095             .relocations = relocations[0..0],
1096         }),
1097     );
1098 }
1099 
1100 test "strict PE admits zero-timestamp path-free firmware" {
1101     const bytes = peFixture();
1102     var loads: [2]Load = undefined;
1103     var relocations: [1]Relocation = undefined;
1104     const report = try inspect(&bytes, pePolicy(), .{
1105         .loads = &loads,
1106         .relocations = relocations[0..0],
1107     });
1108     try std.testing.expectEqual(binary.Kind.pe32_plus, report.kind);
1109     try std.testing.expectEqual(@as(usize, 2), report.loads.len);
1110     try std.testing.expectEqual(@as(u64, 32), report.executable_bytes);
1111     try std.testing.expectEqual(@as(u32, 0), report.timestamp);
1112 }
1113 
1114 test "strict PE rejects timestamp debug and import state" {
1115     var timestamp = peFixture();
1116     put32(&timestamp, 68 + 4, 1);
1117     var loads: [2]Load = undefined;
1118     var relocations: [1]Relocation = undefined;
1119     try std.testing.expectError(
1120         error.NonZeroTimestamp,
1121         inspect(&timestamp, pePolicy(), .{
1122             .loads = &loads,
1123             .relocations = relocations[0..0],
1124         }),
1125     );
1126     var imported = peFixture();
1127     const optional = 68 + pe_coff_bytes;
1128     put32(&imported, optional + 112 + 8, 0x2000);
1129     put32(&imported, optional + 112 + 12, 8);
1130     try std.testing.expectError(
1131         error.DataDirectoryForbidden,
1132         inspect(&imported, pePolicy(), .{
1133             .loads = &loads,
1134             .relocations = relocations[0..0],
1135         }),
1136     );
1137     var debug = peFixture();
1138     put32(&debug, optional + 112 + 6 * 8, 0x2000);
1139     put32(&debug, optional + 112 + 6 * 8 + 4, 28);
1140     try std.testing.expectError(
1141         error.DebugDirectoryPresent,
1142         inspect(&debug, pePolicy(), .{
1143             .loads = &loads,
1144             .relocations = relocations[0..0],
1145         }),
1146     );
1147 }
1148 
1149 test "strict PE admits only a matching path-free repro record" {
1150     var bytes = peFixture();
1151     const optional = 68 + pe_coff_bytes;
1152     put32(&bytes, optional + 112 + pe_directory_debug * 8, 0x2000);
1153     put32(
1154         &bytes,
1155         optional + 112 + pe_directory_debug * 8 + 4,
1156         pe_debug_entry_bytes,
1157     );
1158     put32(&bytes, 576 + 4, 0x1234_5678);
1159     put32(&bytes, 576 + 12, pe_debug_type_repro);
1160     var policy = pePolicy();
1161     policy.pe_directories_allowed =
1162         @as(u16, 1) << pe_directory_debug;
1163     policy.pe_repro_timestamp = 0x1234_5678;
1164     var loads: [2]Load = undefined;
1165     var relocations: [1]Relocation = undefined;
1166     _ = try inspect(&bytes, policy, .{
1167         .loads = &loads,
1168         .relocations = relocations[0..0],
1169     });
1170     put32(&bytes, 576 + 16, 1);
1171     try std.testing.expectError(
1172         error.DebugDirectoryPresent,
1173         inspect(&bytes, policy, .{
1174             .loads = &loads,
1175             .relocations = relocations[0..0],
1176         }),
1177     );
1178 }
1179 
1180 test "strict PE base relocations target writable non-executable data" {
1181     var bytes = peFixture();
1182     const optional = 68 + pe_coff_bytes;
1183     put32(
1184         &bytes,
1185         optional + 112 + pe_directory_base_relocation * 8,
1186         0x2000,
1187     );
1188     put32(
1189         &bytes,
1190         optional + 112 + pe_directory_base_relocation * 8 + 4,
1191         12,
1192     );
1193     put32(&bytes, 576, 0x2000);
1194     put32(&bytes, 580, 12);
1195     put16(&bytes, 584, pe_relocation_dir64 << 12);
1196     var policy = pePolicy();
1197     policy.pe_directories_allowed =
1198         @as(u16, 1) << pe_directory_base_relocation;
1199     policy.relocations_max = 2;
1200     var loads: [2]Load = undefined;
1201     var relocations: [1]Relocation = undefined;
1202     const report = try inspect(&bytes, policy, .{
1203         .loads = &loads,
1204         .relocations = &relocations,
1205     });
1206     try std.testing.expectEqual(@as(usize, 1), report.relocations.len);
1207     put32(&bytes, 576, 0x1000);
1208     try std.testing.expectError(
1209         error.RelocationTargetInvalid,
1210         inspect(&bytes, policy, .{
1211             .loads = &loads,
1212             .relocations = &relocations,
1213         }),
1214     );
1215 }