tiny.closure.binary.strict
Defined in binary.
API (12)
Actions
Public operations.
Types and contracts
Public types and contracts.
Values and defaults
Public values and defaults.
Source
Source: lib/closure/src/binary/root.zig:6
zig
pub const strict = @import("strict.zig");Source: lib/closure/src/binary/strict.zig
zig
const binary = @import("root.zig");const read = @import("read.zig");const std = @import("std");pub const loads_max: usize = 32;pub const relocations_max: usize = 1_024;pub const Policy = struct { kind: binary.Kind, sections_max: u16, symbols_max: u32, executable_bytes_max: u64, initialized_bytes_max: u64, memory_bytes_max: u64, stack_bytes_max: u64, relocations_max: u32, pe_directories_allowed: u16 = 0, pe_timestamp: u32 = 0, pe_repro_timestamp: ?u32 = null,};pub const Load = struct { offset: u64, address: u64, file_bytes: u64, content_bytes: u64, memory_bytes: u64, readable: bool, writable: bool, executable: bool, pub fn fileEnd(self: Load) Error!u64 { return added(self.offset, self.file_bytes); } pub fn addressEnd(self: Load) Error!u64 { return added(self.address, self.memory_bytes); }};pub const Relocation = struct { offset: u64, address: u64,};pub const Scratch = struct { loads: []Load, relocations: []Relocation,};pub const Report = struct { kind: binary.Kind, loads: []const Load, relocations: []const Relocation, executable_bytes: u64, initialized_bytes: u64, memory_bytes: u64, stack_bytes: u64, timestamp: u32, image_base: u64, work: u64, pub fn loadForAddress( self: *const Report, address: u64, length: u64, ) ?Load { const end = added(address, length) catch return null; for (self.loads) |load| { const load_end = load.addressEnd() catch return null; if (address >= load.address and end <= load_end) return load; } return null; }};const InspectError = error{ BaseRelocationCapacityExceeded, DataDirectoryForbidden, DebugDirectoryPresent, DynamicLinkagePresent, EntryPointUnmapped, ExecutableBudgetExceeded, ExecutableStack, InvalidBaseRelocation, InvalidHeader, InvalidLoad, InvalidSection, LoadCapacityExceeded, LoadOverlap, MemoryBudgetExceeded, MissingStackPolicy, NonZeroTimestamp, PeSymbolTablePresent, RelocationTargetInvalid, SectionBudgetExceeded, SymbolBudgetExceeded, TlsPresent, UndefinedSymbol, UnsupportedBinary, WritableExecutable,};pub const Error = read.Error || InspectError;const elf_header_bytes: usize = 64;const elf_program_bytes: usize = 56;const elf_section_bytes: usize = 64;const elf_symbol_bytes: usize = 24;const elf_machine_x86_64: u16 = 62;const elf_type_executable: u16 = 2;const elf_program_load: u32 = 1;const elf_program_dynamic: u32 = 2;const elf_program_interpreter: u32 = 3;const elf_program_tls: u32 = 7;const elf_program_stack: u32 = 0x6474_e551;const elf_flag_execute: u32 = 1;const elf_flag_write: u32 = 2;const elf_flag_read: u32 = 4;const elf_section_symbols: u32 = 2;const elf_section_rela: u32 = 4;const elf_section_dynamic: u32 = 6;const elf_section_nobits: u32 = 8;const elf_section_rel: u32 = 9;const elf_section_dynamic_symbols: u32 = 11;const elf_section_relr: u32 = 19;const elf_section_flag_write: u64 = 1;const elf_section_flag_alloc: u64 = 2;const elf_section_flag_execute: u64 = 4;const elf_section_flag_tls: u64 = 0x400;const pe_coff_bytes: usize = 20;const pe_section_bytes: usize = 40;const pe_machine_x86_64: u16 = 0x8664;const pe_optional_magic: u16 = 0x20b;const pe_executable_image: u16 = 0x0002;const pe_subsystem_efi_application: u16 = 10;const pe_nx_compatible: u16 = 0x0100;const pe_section_code: u32 = 0x0000_0020;const pe_section_discardable: u32 = 0x0200_0000;const pe_section_execute: u32 = 0x2000_0000;const pe_section_read: u32 = 0x4000_0000;const pe_section_write: u32 = 0x8000_0000;const pe_directory_base_relocation: usize = 5;const pe_directory_debug: usize = 6;const pe_relocation_absolute: u16 = 0;const pe_relocation_dir64: u16 = 10;const pe_debug_entry_bytes: u32 = 28;const pe_debug_type_repro: u32 = 16;const Counts = struct { load_count: usize = 0, relocation_count: usize = 0, relocation_entry_count: u32 = 0, symbol_count: u32 = 0, executable_bytes: u64 = 0, initialized_bytes: u64 = 0, memory_bytes: u64 = 0, stack_bytes: u64 = 0, timestamp: u32 = 0, image_base: u64 = 0, work: u64 = 1,};const ElfHeader = struct { entry: u64, programs: usize, program_count: usize, sections: usize, section_count: usize,};const PeHeader = struct { coff: usize, optional: usize, optional_bytes: usize, sections: usize, section_count: usize, image_base: u64, entry_rva: u32, directory_count: usize,};pub fn inspect( bytes: []const u8, policy: Policy, scratch: Scratch,) Error!Report { if (scratch.loads.len > loads_max or scratch.relocations.len > relocations_max) { return error.InvalidHeader; } var counts: Counts = .{}; switch (policy.kind) { .elf64 => try inspectElf(bytes, policy, scratch, &counts), .pe32_plus => try inspectPe(bytes, policy, scratch, &counts), } try inspectLoadOverlap(scratch.loads[0..counts.load_count], &counts.work); if (counts.executable_bytes > policy.executable_bytes_max or counts.initialized_bytes > policy.initialized_bytes_max) { return error.ExecutableBudgetExceeded; } if (counts.memory_bytes > policy.memory_bytes_max) { return error.MemoryBudgetExceeded; } return .{ .kind = policy.kind, .loads = scratch.loads[0..counts.load_count], .relocations = scratch.relocations[0..counts.relocation_count], .executable_bytes = counts.executable_bytes, .initialized_bytes = counts.initialized_bytes, .memory_bytes = counts.memory_bytes, .stack_bytes = counts.stack_bytes, .timestamp = counts.timestamp, .image_base = counts.image_base, .work = counts.work, };}fn inspectElf( bytes: []const u8, policy: Policy, scratch: Scratch, counts: *Counts,) Error!void { const header = try elfHeader(bytes, policy); var stack_count: u8 = 0; for (0..header.program_count) |index| { counts.work += 1; const offset = try read.indexed( header.programs, index, elf_program_bytes, ); const kind = try read.u32le(bytes, offset); const flags = try read.u32le(bytes, offset + 4); switch (kind) { elf_program_dynamic, elf_program_interpreter => return error.DynamicLinkagePresent, elf_program_tls => return error.TlsPresent, elf_program_stack => { stack_count += 1; if (stack_count != 1 or flags & elf_flag_execute != 0 or flags & (elf_flag_read | elf_flag_write) != elf_flag_read | elf_flag_write) { return error.ExecutableStack; } counts.stack_bytes = try read.u64le(bytes, offset + 40); if (counts.stack_bytes > policy.stack_bytes_max) { return error.MemoryBudgetExceeded; } }, elf_program_load => try appendElfLoad( bytes, offset, flags, scratch.loads, counts, ), else => {}, } } if (stack_count != 1) return error.MissingStackPolicy; try inspectElfSections( bytes, header, policy, scratch.loads, counts, ); if (!addressExecutable( scratch.loads[0..counts.load_count], header.entry, 1, )) { return error.EntryPointUnmapped; }}fn elfHeader(bytes: []const u8, policy: Policy) Error!ElfHeader { _ = try read.take(bytes, 0, elf_header_bytes); if (!std.mem.eql(u8, bytes[0..4], "\x7fELF") or bytes[4] != 2 or bytes[5] != 1 or bytes[6] != 1 or try read.u16le(bytes, 16) != elf_type_executable or try read.u16le(bytes, 18) != elf_machine_x86_64 or try read.u32le(bytes, 20) != 1 or try read.u16le(bytes, 52) != elf_header_bytes or try read.u16le(bytes, 54) != elf_program_bytes or try read.u16le(bytes, 58) != elf_section_bytes) { return error.UnsupportedBinary; } const program_count = try read.u16le(bytes, 56); const section_count = try read.u16le(bytes, 60); if (program_count == 0 or program_count > loads_max or section_count == 0 or section_count > policy.sections_max) { return error.SectionBudgetExceeded; } const programs = try read.toOffset(try read.u64le(bytes, 32)); const sections = try read.toOffset(try read.u64le(bytes, 40)); _ = try read.take( bytes, try read.indexed(programs, program_count - 1, elf_program_bytes), elf_program_bytes, ); _ = try read.take( bytes, try read.indexed(sections, section_count - 1, elf_section_bytes), elf_section_bytes, ); return .{ .entry = try read.u64le(bytes, 24), .programs = programs, .program_count = program_count, .sections = sections, .section_count = section_count, };}fn appendElfLoad( bytes: []const u8, offset: usize, flags: u32, loads: []Load, counts: *Counts,) Error!void { if (counts.load_count == loads.len) { return error.LoadCapacityExceeded; } if (flags & ~(elf_flag_read | elf_flag_write | elf_flag_execute) != 0 or flags & elf_flag_read == 0 or flags & (elf_flag_write | elf_flag_execute) == elf_flag_write | elf_flag_execute) { return error.WritableExecutable; } const file_offset = try read.u64le(bytes, offset + 8); const address = try read.u64le(bytes, offset + 16); const physical = try read.u64le(bytes, offset + 24); const file_bytes = try read.u64le(bytes, offset + 32); const memory_bytes = try read.u64le(bytes, offset + 40); const alignment = try read.u64le(bytes, offset + 48); if (physical != address or file_bytes > memory_bytes or memory_bytes == 0 or (flags & elf_flag_execute != 0 and file_bytes != memory_bytes) or !powerOfTwoOrZero(alignment) or (alignment > 1 and file_offset % alignment != address % alignment)) { return error.InvalidLoad; } _ = try read.take( bytes, try read.toOffset(file_offset), try read.toLength(file_bytes), ); loads[counts.load_count] = .{ .offset = file_offset, .address = address, .file_bytes = file_bytes, .content_bytes = file_bytes, .memory_bytes = memory_bytes, .readable = true, .writable = flags & elf_flag_write != 0, .executable = flags & elf_flag_execute != 0, }; counts.load_count += 1; counts.memory_bytes = try added(counts.memory_bytes, memory_bytes); if (flags & elf_flag_execute != 0) { counts.executable_bytes = try added(counts.executable_bytes, file_bytes); } else { counts.initialized_bytes = try added(counts.initialized_bytes, file_bytes); }}fn inspectElfSections( bytes: []const u8, header: ElfHeader, policy: Policy, loads: []const Load, counts: *Counts,) Error!void { for (0..header.section_count) |index| { counts.work += 1; const offset = try read.indexed( header.sections, index, elf_section_bytes, ); const kind = try read.u32le(bytes, offset + 4); const flags = try read.u64le(bytes, offset + 8); if (kind == elf_section_dynamic or kind == elf_section_dynamic_symbols) { return error.DynamicLinkagePresent; } if (kind == elf_section_rela or kind == elf_section_rel or kind == elf_section_relr) { return error.InvalidSection; } if (flags & elf_section_flag_tls != 0) return error.TlsPresent; if (kind == elf_section_symbols) { try inspectElfSymbols(bytes, header, offset, policy, counts); } if (flags & elf_section_flag_alloc == 0) continue; const address = try read.u64le(bytes, offset + 16); const file_offset = try read.u64le(bytes, offset + 24); const length = try read.u64le(bytes, offset + 32); const executable = flags & elf_section_flag_execute != 0; const writable = flags & elf_section_flag_write != 0; if (executable and writable) return error.WritableExecutable; if (length == 0) continue; if (kind == elf_section_nobits and executable) { return error.InvalidSection; } const admitted = (if (kind == elf_section_nobits) loadForAddress(loads, address, length) else loadForSection(loads, address, file_offset, length)) orelse return error.InvalidSection; if (admitted.executable != executable or (writable and !admitted.writable)) { return error.InvalidSection; } }}fn inspectElfSymbols( bytes: []const u8, header: ElfHeader, section_offset: usize, policy: Policy, counts: *Counts,) Error!void { const table_offset = try read.u64le(bytes, section_offset + 24); const table_bytes = try read.u64le(bytes, section_offset + 32); const entry_bytes = try read.u64le(bytes, section_offset + 56); if (entry_bytes != elf_symbol_bytes or table_bytes % elf_symbol_bytes != 0) { return error.InvalidSection; } const symbol_count = table_bytes / elf_symbol_bytes; const total = try added(counts.symbol_count, symbol_count); if (total > policy.symbols_max) { return error.SymbolBudgetExceeded; } counts.symbol_count = @intCast(total); const symbols = try read.take( bytes, try read.toOffset(table_offset), try read.toLength(table_bytes), ); for (0..symbols.len / elf_symbol_bytes) |index| { counts.work += 1; if (index == 0) continue; const offset = index * elf_symbol_bytes; const binding = symbols[offset + 4] >> 4; const section_index = std.mem.readInt( u16, symbols[offset + 6 ..][0..2], .little, ); if (section_index == 0 and (binding == 1 or binding == 2)) { return error.UndefinedSymbol; } } _ = header;}fn inspectPe( bytes: []const u8, policy: Policy, scratch: Scratch, counts: *Counts,) Error!void { const header = try peHeader(bytes, policy); counts.image_base = header.image_base; counts.timestamp = try read.u32le(bytes, header.coff + 4); if (counts.timestamp != policy.pe_timestamp) { return error.NonZeroTimestamp; } if (try read.u32le(bytes, header.coff + 8) != 0 or try read.u32le(bytes, header.coff + 12) != 0) { return error.PeSymbolTablePresent; } for (0..header.section_count) |index| { counts.work += 1; try appendPeLoad( bytes, header, index, scratch.loads, counts, ); } try inspectPeDirectories(bytes, header, policy, scratch, counts); const entry = try added(header.image_base, header.entry_rva); if (!addressExecutable( scratch.loads[0..counts.load_count], entry, 1, )) { return error.EntryPointUnmapped; }}fn peHeader(bytes: []const u8, policy: Policy) Error!PeHeader { _ = try read.take(bytes, 0, 64); if (!std.mem.eql(u8, bytes[0..2], "MZ")) { return error.UnsupportedBinary; } const pe_offset = try read.toOffset(try read.u32le(bytes, 0x3c)); if (!std.mem.eql( u8, try read.take(bytes, pe_offset, 4), "PE\x00\x00", )) { return error.UnsupportedBinary; } const coff = try addOffset(pe_offset, 4); _ = try read.take(bytes, coff, pe_coff_bytes); if (try read.u16le(bytes, coff) != pe_machine_x86_64 or try read.u16le(bytes, coff + 18) & pe_executable_image == 0) { return error.UnsupportedBinary; } const section_count = try read.u16le(bytes, coff + 2); if (section_count == 0 or section_count > policy.sections_max) { return error.SectionBudgetExceeded; } const optional_bytes = try read.u16le(bytes, coff + 16); const optional = try addOffset(coff, pe_coff_bytes); _ = try read.take(bytes, optional, optional_bytes); if (optional_bytes < 112 or try read.u16le(bytes, optional) != pe_optional_magic or try read.u16le(bytes, optional + 68) != pe_subsystem_efi_application or try read.u16le(bytes, optional + 70) & pe_nx_compatible == 0) { return error.InvalidHeader; } const directory_count = try read.u32le(bytes, optional + 108); if (directory_count > 16 or 112 + @as(usize, directory_count) * 8 > optional_bytes) { return error.InvalidHeader; } const sections = try addOffset(optional, optional_bytes); _ = try read.take( bytes, try read.indexed(sections, section_count - 1, pe_section_bytes), pe_section_bytes, ); return .{ .coff = coff, .optional = optional, .optional_bytes = optional_bytes, .sections = sections, .section_count = section_count, .image_base = try read.u64le(bytes, optional + 24), .entry_rva = try read.u32le(bytes, optional + 16), .directory_count = directory_count, };}fn appendPeLoad( bytes: []const u8, header: PeHeader, index: usize, loads: []Load, counts: *Counts,) Error!void { if (counts.load_count == loads.len) { return error.LoadCapacityExceeded; } const offset = try read.indexed( header.sections, index, pe_section_bytes, ); const virtual_bytes = try read.u32le(bytes, offset + 8); const virtual_address = try read.u32le(bytes, offset + 12); const file_bytes = try read.u32le(bytes, offset + 16); const file_offset = try read.u32le(bytes, offset + 20); const flags = try read.u32le(bytes, offset + 36); const executable = flags & pe_section_execute != 0; const writable = flags & pe_section_write != 0; const readable = flags & pe_section_read != 0; if (executable and (writable or !readable or flags & pe_section_code == 0 or flags & pe_section_discardable != 0 or virtual_bytes == 0 or virtual_bytes > file_bytes)) { return error.WritableExecutable; } if (file_bytes == 0 and virtual_bytes == 0) { return error.InvalidSection; } _ = try read.take(bytes, file_offset, file_bytes); const memory_bytes = @max(virtual_bytes, file_bytes); loads[counts.load_count] = .{ .offset = file_offset, .address = try added(header.image_base, virtual_address), .file_bytes = file_bytes, .content_bytes = if (executable) virtual_bytes else file_bytes, .memory_bytes = memory_bytes, .readable = readable, .writable = writable, .executable = executable, }; counts.load_count += 1; counts.memory_bytes = try added(counts.memory_bytes, memory_bytes); if (executable) { counts.executable_bytes = try added(counts.executable_bytes, virtual_bytes); } else { counts.initialized_bytes = try added(counts.initialized_bytes, file_bytes); }}fn inspectPeDirectories( bytes: []const u8, header: PeHeader, policy: Policy, scratch: Scratch, counts: *Counts,) Error!void { for (0..header.directory_count) |index| { counts.work += 1; const offset = header.optional + 112 + index * 8; const rva = try read.u32le(bytes, offset); const length = try read.u32le(bytes, offset + 4); if ((rva == 0) != (length == 0)) return error.InvalidHeader; if (rva == 0) continue; const bit = @as(u16, 1) << @intCast(index); if (policy.pe_directories_allowed & bit == 0) { if (index == 6) return error.DebugDirectoryPresent; return error.DataDirectoryForbidden; } if (index == pe_directory_base_relocation) { try inspectBaseRelocations( bytes, header, rva, length, policy, scratch, counts, ); } else if (index == pe_directory_debug) { try inspectReproDirectory( bytes, header, rva, length, policy, scratch.loads[0..counts.load_count], ); } else { _ = peFileRange( scratch.loads[0..counts.load_count], header.image_base, rva, length, ) orelse return error.InvalidSection; } }}fn inspectReproDirectory( bytes: []const u8, header: PeHeader, rva: u32, length: u32, policy: Policy, loads: []const Load,) Error!void { const expected_timestamp = policy.pe_repro_timestamp orelse return error.DebugDirectoryPresent; if (length != pe_debug_entry_bytes) { return error.DebugDirectoryPresent; } const offset = peFileRange( loads, header.image_base, rva, length, ) orelse return error.DebugDirectoryPresent; const entry = try read.take(bytes, offset, pe_debug_entry_bytes); if (std.mem.readInt(u32, entry[0..4], .little) != 0 or std.mem.readInt(u32, entry[4..8], .little) != expected_timestamp or std.mem.readInt(u16, entry[8..10], .little) != 0 or std.mem.readInt(u16, entry[10..12], .little) != 0 or std.mem.readInt(u32, entry[12..16], .little) != pe_debug_type_repro or std.mem.readInt(u32, entry[16..20], .little) != 0 or std.mem.readInt(u32, entry[20..24], .little) != 0 or std.mem.readInt(u32, entry[24..28], .little) != 0) { return error.DebugDirectoryPresent; }}fn inspectBaseRelocations( bytes: []const u8, header: PeHeader, rva: u32, length: u32, policy: Policy, scratch: Scratch, counts: *Counts,) Error!void { const directory_offset = peFileRange( scratch.loads[0..counts.load_count], header.image_base, rva, length, ) orelse return error.InvalidBaseRelocation; const directory = try read.take(bytes, directory_offset, length); var cursor: usize = 0; var previous_address: u64 = 0; while (cursor < directory.len) { counts.work += 1; if (cursor + 8 > directory.len) { return error.InvalidBaseRelocation; } const page = std.mem.readInt( u32, directory[cursor..][0..4], .little, ); const block_bytes = std.mem.readInt( u32, directory[cursor + 4 ..][0..4], .little, ); if (page % 4096 != 0 or block_bytes < 8 or block_bytes % 2 != 0 or cursor + block_bytes > directory.len) { return error.InvalidBaseRelocation; } const entry_count = (block_bytes - 8) / 2; for (0..entry_count) |entry_index| { counts.work += 1; if (counts.relocation_entry_count == policy.relocations_max) { return error.BaseRelocationCapacityExceeded; } counts.relocation_entry_count += 1; const entry_offset = cursor + 8 + entry_index * 2; const entry = std.mem.readInt( u16, directory[entry_offset..][0..2], .little, ); const kind = entry >> 12; if (kind == pe_relocation_absolute) continue; if (kind != pe_relocation_dir64) { return error.InvalidBaseRelocation; } if (counts.relocation_count == scratch.relocations.len or counts.relocation_count == policy.relocations_max) { return error.BaseRelocationCapacityExceeded; } const target_rva = try added(page, entry & 0x0fff); const target_address = try added( header.image_base, target_rva, ); const load = loadForAddress( scratch.loads[0..counts.load_count], target_address, 8, ) orelse return error.RelocationTargetInvalid; if (!load.writable or load.executable) { return error.RelocationTargetInvalid; } if (counts.relocation_count != 0 and target_address <= previous_address) { return error.InvalidBaseRelocation; } const relative = target_address - load.address; scratch.relocations[counts.relocation_count] = .{ .offset = try added(load.offset, relative), .address = target_address, }; counts.relocation_count += 1; previous_address = target_address; } cursor += block_bytes; } if (cursor != directory.len) return error.InvalidBaseRelocation;}fn inspectLoadOverlap(loads: []const Load, work: *u64) Error!void { for (loads, 0..) |left, left_index| { for (loads[left_index + 1 ..]) |right| { work.* += 1; const left_address_end = try left.addressEnd(); const right_address_end = try right.addressEnd(); if (left.address < right_address_end and right.address < left_address_end) { return error.LoadOverlap; } const left_file_end = try left.fileEnd(); const right_file_end = try right.fileEnd(); if (left.file_bytes != 0 and right.file_bytes != 0 and left.offset < right_file_end and right.offset < left_file_end) { return error.LoadOverlap; } } }}fn loadForSection( loads: []const Load, address: u64, offset: u64, length: u64,) ?Load { const address_end = added(address, length) catch return null; const offset_end = added(offset, length) catch return null; for (loads) |load| { const load_address_end = load.addressEnd() catch return null; const load_file_end = load.fileEnd() catch return null; if (address >= load.address and address_end <= load_address_end and offset >= load.offset and offset_end <= load_file_end) { return load; } } return null;}fn loadForAddress( loads: []const Load, address: u64, length: u64,) ?Load { const end = added(address, length) catch return null; for (loads) |load| { const load_end = load.addressEnd() catch return null; if (address >= load.address and end <= load_end) return load; } return null;}fn addressExecutable( loads: []const Load, address: u64, length: u64,) bool { const load = loadForAddress(loads, address, length) orelse return false; return load.executable;}fn peFileRange( loads: []const Load, image_base: u64, rva: u32, length: u32,) ?usize { const address = added(image_base, rva) catch return null; const load = loadForAddress(loads, address, length) orelse return null; const relative = address - load.address; const relative_end = added(relative, length) catch return null; if (relative_end > load.file_bytes) return null; return read.toOffset(added(load.offset, relative) catch return null) catch return null;}fn powerOfTwoOrZero(value: u64) bool { return value == 0 or std.math.isPowerOfTwo(value);}fn addOffset(left: usize, right: anytype) Error!usize { return std.math.add(usize, left, @intCast(right)) catch error.BinaryArithmeticOverflow;}fn added(left: anytype, right: anytype) Error!u64 { return std.math.add( u64, @intCast(left), @intCast(right), ) catch error.BinaryArithmeticOverflow;}fn put16(bytes: []u8, offset: usize, value: u16) void { std.mem.writeInt(u16, bytes[offset..][0..2], value, .little);}fn put32(bytes: []u8, offset: usize, value: u32) void { std.mem.writeInt(u32, bytes[offset..][0..4], value, .little);}fn put64(bytes: []u8, offset: usize, value: u64) void { std.mem.writeInt(u64, bytes[offset..][0..8], value, .little);}fn elfFixture() [512]u8 { var bytes: [512]u8 = @splat(0); @memcpy(bytes[0..4], "\x7fELF"); bytes[4] = 2; bytes[5] = 1; bytes[6] = 1; put16(&bytes, 16, elf_type_executable); put16(&bytes, 18, elf_machine_x86_64); put32(&bytes, 20, 1); put64(&bytes, 24, 0x1000); put64(&bytes, 32, 64); put64(&bytes, 40, 192); put16(&bytes, 52, elf_header_bytes); put16(&bytes, 54, elf_program_bytes); put16(&bytes, 56, 2); put16(&bytes, 58, elf_section_bytes); put16(&bytes, 60, 2); put32(&bytes, 64, elf_program_load); put32(&bytes, 68, elf_flag_read | elf_flag_execute); put64(&bytes, 72, 384); put64(&bytes, 80, 0x1000); put64(&bytes, 88, 0x1000); put64(&bytes, 96, 16); put64(&bytes, 104, 16); put64(&bytes, 112, 1); const stack = 64 + elf_program_bytes; put32(&bytes, stack, elf_program_stack); put32(&bytes, stack + 4, elf_flag_read | elf_flag_write); put64(&bytes, stack + 40, 4096); const text = 192 + elf_section_bytes; put32(&bytes, text + 4, 1); put64( &bytes, text + 8, elf_section_flag_alloc | elf_section_flag_execute, ); put64(&bytes, text + 16, 0x1000); put64(&bytes, text + 24, 384); put64(&bytes, text + 32, 16); @memset(bytes[384..400], 0xcc); return bytes;}fn elfPolicy() Policy { return .{ .kind = .elf64, .sections_max = 4, .symbols_max = 16, .executable_bytes_max = 16, .initialized_bytes_max = 1, .memory_bytes_max = 16, .stack_bytes_max = 4096, .relocations_max = 0, };}fn peFixture() [1_024]u8 { var bytes: [1_024]u8 = @splat(0); @memcpy(bytes[0..2], "MZ"); put32(&bytes, 0x3c, 64); @memcpy(bytes[64..68], "PE\x00\x00"); const coff = 68; put16(&bytes, coff, pe_machine_x86_64); put16(&bytes, coff + 2, 2); put16(&bytes, coff + 16, 240); put16(&bytes, coff + 18, pe_executable_image); const optional = coff + pe_coff_bytes; put16(&bytes, optional, pe_optional_magic); put32(&bytes, optional + 16, 0x1000); put64(&bytes, optional + 24, 0x400000); put16(&bytes, optional + 68, pe_subsystem_efi_application); put16(&bytes, optional + 70, pe_nx_compatible); put32(&bytes, optional + 108, 16); const text = optional + 240; @memcpy(bytes[text..][0..5], ".text"); put32(&bytes, text + 8, 32); put32(&bytes, text + 12, 0x1000); put32(&bytes, text + 16, 64); put32(&bytes, text + 20, 512); put32( &bytes, text + 36, pe_section_code | pe_section_execute | pe_section_read, ); const data = text + pe_section_bytes; @memcpy(bytes[data..][0..5], ".data"); put32(&bytes, data + 8, 64); put32(&bytes, data + 12, 0x2000); put32(&bytes, data + 16, 64); put32(&bytes, data + 20, 576); put32(&bytes, data + 36, pe_section_read | pe_section_write); @memset(bytes[512..576], 0xcc); return bytes;}fn pePolicy() Policy { return .{ .kind = .pe32_plus, .sections_max = 4, .symbols_max = 0, .executable_bytes_max = 32, .initialized_bytes_max = 64, .memory_bytes_max = 128, .stack_bytes_max = 0, .relocations_max = 0, };}test "strict ELF admits a bounded static NX executable" { const bytes = elfFixture(); var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; const report = try inspect(&bytes, elfPolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }); try std.testing.expectEqual(binary.Kind.elf64, report.kind); try std.testing.expectEqual(@as(usize, 1), report.loads.len); try std.testing.expect(report.loads[0].executable); try std.testing.expectEqual(@as(u64, 16), report.executable_bytes); try std.testing.expectEqual(@as(u64, 4096), report.stack_bytes);}test "strict ELF rejects dynamic linkage and writable code" { var dynamic = elfFixture(); put32(&dynamic, 64, elf_program_dynamic); var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; try std.testing.expectError( error.DynamicLinkagePresent, inspect(&dynamic, elfPolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }), ); var writable = elfFixture(); put32( &writable, 68, elf_flag_read | elf_flag_write | elf_flag_execute, ); try std.testing.expectError( error.WritableExecutable, inspect(&writable, elfPolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }), );}test "strict ELF bounds symbol inspection work" { var bytes = elfFixture(); const text = 192 + elf_section_bytes; put32(&bytes, text + 4, elf_section_symbols); put64(&bytes, text + 24, 384); put64(&bytes, text + 32, 48); put64(&bytes, text + 56, elf_symbol_bytes); var policy = elfPolicy(); policy.symbols_max = 1; var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; try std.testing.expectError( error.SymbolBudgetExceeded, inspect(&bytes, policy, .{ .loads = &loads, .relocations = relocations[0..0], }), );}test "strict PE admits zero-timestamp path-free firmware" { const bytes = peFixture(); var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; const report = try inspect(&bytes, pePolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }); try std.testing.expectEqual(binary.Kind.pe32_plus, report.kind); try std.testing.expectEqual(@as(usize, 2), report.loads.len); try std.testing.expectEqual(@as(u64, 32), report.executable_bytes); try std.testing.expectEqual(@as(u32, 0), report.timestamp);}test "strict PE rejects timestamp debug and import state" { var timestamp = peFixture(); put32(×tamp, 68 + 4, 1); var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; try std.testing.expectError( error.NonZeroTimestamp, inspect(×tamp, pePolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }), ); var imported = peFixture(); const optional = 68 + pe_coff_bytes; put32(&imported, optional + 112 + 8, 0x2000); put32(&imported, optional + 112 + 12, 8); try std.testing.expectError( error.DataDirectoryForbidden, inspect(&imported, pePolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }), ); var debug = peFixture(); put32(&debug, optional + 112 + 6 * 8, 0x2000); put32(&debug, optional + 112 + 6 * 8 + 4, 28); try std.testing.expectError( error.DebugDirectoryPresent, inspect(&debug, pePolicy(), .{ .loads = &loads, .relocations = relocations[0..0], }), );}test "strict PE admits only a matching path-free repro record" { var bytes = peFixture(); const optional = 68 + pe_coff_bytes; put32(&bytes, optional + 112 + pe_directory_debug * 8, 0x2000); put32( &bytes, optional + 112 + pe_directory_debug * 8 + 4, pe_debug_entry_bytes, ); put32(&bytes, 576 + 4, 0x1234_5678); put32(&bytes, 576 + 12, pe_debug_type_repro); var policy = pePolicy(); policy.pe_directories_allowed = @as(u16, 1) << pe_directory_debug; policy.pe_repro_timestamp = 0x1234_5678; var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; _ = try inspect(&bytes, policy, .{ .loads = &loads, .relocations = relocations[0..0], }); put32(&bytes, 576 + 16, 1); try std.testing.expectError( error.DebugDirectoryPresent, inspect(&bytes, policy, .{ .loads = &loads, .relocations = relocations[0..0], }), );}test "strict PE base relocations target writable non-executable data" { var bytes = peFixture(); const optional = 68 + pe_coff_bytes; put32( &bytes, optional + 112 + pe_directory_base_relocation * 8, 0x2000, ); put32( &bytes, optional + 112 + pe_directory_base_relocation * 8 + 4, 12, ); put32(&bytes, 576, 0x2000); put32(&bytes, 580, 12); put16(&bytes, 584, pe_relocation_dir64 << 12); var policy = pePolicy(); policy.pe_directories_allowed = @as(u16, 1) << pe_directory_base_relocation; policy.relocations_max = 2; var loads: [2]Load = undefined; var relocations: [1]Relocation = undefined; const report = try inspect(&bytes, policy, .{ .loads = &loads, .relocations = &relocations, }); try std.testing.expectEqual(@as(usize, 1), report.relocations.len); put32(&bytes, 576, 0x1000); try std.testing.expectError( error.RelocationTargetInvalid, inspect(&bytes, policy, .{ .loads = &loads, .relocations = &relocations, }), );}Complete caller list for binary.strict.inspect
7 direct callers.
lib.closure.src.binary.strict.test_strict_ELF_admits_a_bounded_static_NX_executable[function] — test source atlib/closure/src/binary/strict.zig:1038in nearest public ownertiny.closure.binary.strictlib.closure.src.binary.strict.test_strict_ELF_bounds_symbol_inspection_work[function] — test source atlib/closure/src/binary/strict.zig:1080in nearest public ownertiny.closure.binary.strictlib.closure.src.binary.strict.test_strict_ELF_rejects_dynamic_linkage_and_writable_code[function] — test source atlib/closure/src/binary/strict.zig:1053in nearest public ownertiny.closure.binary.strictlib.closure.src.binary.strict.test_strict_PE_admits_only_a_matching_path-free_repro_record[function] — test source atlib/closure/src/binary/strict.zig:1149in nearest public ownertiny.closure.binary.strictlib.closure.src.binary.strict.test_strict_PE_admits_zero-timestamp_path-free_firmware[function] — test source atlib/closure/src/binary/strict.zig:1100in nearest public ownertiny.closure.binary.strictlib.closure.src.binary.strict.test_strict_PE_base_relocations_target_writable_non-executable_data[function] — test source atlib/closure/src/binary/strict.zig:1180in nearest public ownertiny.closure.binary.strictlib.closure.src.binary.strict.test_strict_PE_rejects_timestamp_debug_and_import_state[function] — test source atlib/closure/src/binary/strict.zig:1114in nearest public ownertiny.closure.binary.strict
Audit
| Definitions | 13 |
|---|---|
| Public names | 13 |
| Members | 33 |
| Version | 26.7.0 |
| Revision | daab053ee433 |