Skip to documentation
SLOP

tiny.closure.binary.strict

Reference 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.

No direct callersNo direct callsbinarystrict
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Called byCallsNo direct callersprivate sourcelib.closure.src.binary.strictaddedbinary.strict.LoadaddressEnd
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.closure.src.binary.strictaddedbinary.strict.LoadfileEnd
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.closure.src.binary.strictaddedbinary.strict.ReportloadForAddress
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallstest sourcelib.closure.src.binary.stricttest: strict ELF admits a bounded sta...test sourcelib.closure.src.binary.stricttest: strict ELF bounds symbol inspec...test sourcelib.closure.src.binary.stricttest: strict ELF rejects dynamic link...test sourcelib.closure.src.binary.stricttest: strict PE admits only a matchin...test sourcelib.closure.src.binary.stricttest: strict PE admits zero-timestamp...+2 moreprivate sourcelib.closure.src.binary.strictinspectElfprivate sourcelib.closure.src.binary.strictinspectLoadOverlapprivate sourcelib.closure.src.binary.strictinspectPebinary.strictinspect
Static calls · unresolved targets: 0 · external targets: 0.

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(&timestamp, 68 + 4, 1);    var loads: [2]Load = undefined;    var relocations: [1]Relocation = undefined;    try std.testing.expectError(        error.NonZeroTimestamp,        inspect(&timestamp, 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.

Audit

Definitions13
Public names13
Members33
Version26.7.0
Revisiondaab053ee433