import { AnalysisInputError, type AnalysisInputIssue, } from "../analysisErrorCore.js"; import type { AnalysisError } from "../analysisErrorBase.js"; import { uniqueSorted } from "../canonicalOrdering.js"; import type { BinaryLayout } from "../native/binaryLayout.js"; import { err, ok, type Result } from "../result.js"; import type { AnalysisViewParent, AnalysisViewRequest, UnsignedAnalysisView, } from "./analysisView.js"; import { completeWithinViewCoverage as completeCoverage, pageViewCoverage as pageCoverage, } from "./analysisViewCoverage.js"; const OPERATION = "inspect_analysis_view"; type LayoutName = BinaryLayout["sections"][number]["name"]; const inputError = ( issues: readonly AnalysisInputIssue[], ): AnalysisInputError => new AnalysisInputError(OPERATION, undefined, issues); const layoutUnknowns = (layout: BinaryLayout): readonly string[] => { const unknowns = [ "runtime_load_base is unknown", "entry_point.execution_status is unknown", "relocation_inventory_completeness is unknown", "linkage.convenience_maps_completeness is unknown", "linkage.runtime_library_paths are unknown", ]; if (layout.mitigations.nx_indicator === null) unknowns.push("mitigations.nx_indicator is unknown"); if (layout.mitigations.relro === null) unknowns.push("mitigations.relro is unknown"); return unknowns; }; const displayName = (name: LayoutName): string => name.display; const namedMatches = ( rows: readonly Row[], name: string, ): readonly { readonly index: number; readonly row: Row }[] => rows.flatMap((row, index) => row.name.unknown_reason === null && displayName(row.name) === name ? [{ index, row }] : [], ); const selectNamed = ( collection: "sections" | "symbols", rows: readonly Row[], name: string, ): Result => { const matches = namedMatches(rows, name); if (matches.length === 1) { const selected = matches[0]; if (selected !== undefined) return ok(selected.row); } if (matches.length === 0) return err( inputError([ { path: ["view", "selector", "name"], reason: "invalid_value", message: `No ${collection} named ${JSON.stringify(name)}. Collection has ${String(rows.length)} entries; page it to inspect exact indexes and names.`, }, ]), ); return err( inputError([ { path: ["view", "selector", "name"], reason: "invalid_value", message: `Multiple ${collection} named ${JSON.stringify(name)}; select one by index.`, expected: matches.map(({ index }) => ({ index })), }, ]), ); }; const selectIndexed = ( collection: "sections" | "symbols", rows: readonly Row[], index: number, ): Result => { const row = rows[index]; if (row !== undefined) return ok(row); return err( inputError([ { path: ["view", "selector", "index"], reason: "out_of_range", message: `${collection} index ${String(index)} is outside 0..${String(Math.max(0, rows.length - 1))} (${String(rows.length)} entries).`, minimum: 0, maximum: Math.max(0, rows.length - 1), }, ]), ); }; const pageRows = ( rows: readonly Row[], offset: number, limit: number, ): readonly Row[] => rows.slice(offset, offset + limit); const layoutIncompatible = (detail: string): AnalysisError => inputError([ { path: ["view"], reason: "invalid_value", message: detail, }, ]); const parentFields = (parent: AnalysisViewParent, layout: BinaryLayout) => ({ parent_evidence_id: parent.evidenceId, parent_operation: "inspect_binary_layout" as const, parent_digest: parent.evidenceId.slice(3), artifact: { path: layout.artifact.path, sha256: layout.artifact.sha256, }, limitations: uniqueSorted([...parent.limitations, ...layout.limitations]), unknowns: uniqueSorted([...layoutUnknowns(layout)]), }); const layoutSummary = (layout: BinaryLayout) => ({ bytes: layout.artifact.bytes, format: layout.format, architecture: layout.architecture, image_type: layout.image_type, entry: layout.entry_point, counts: { sections: layout.sections.length, segments: layout.segments.length, symbols: layout.symbols.length, relocations: layout.relocations.length, }, limitation_count: layout.limitations.length, }); /** Project one layout view without re-running the decoder. */ export const projectBinaryLayoutView = ( parent: AnalysisViewParent, layout: BinaryLayout, view: Exclude, ): Result => { const shared = parentFields(parent, layout); if (view.kind === "native") return err( layoutIncompatible("Native views require analyze_function Evidence."), ); if (view.kind === "summary") return ok({ kind: "summary", view, summary: layoutSummary(layout), coverage: completeCoverage( layout.sections.length + layout.segments.length + layout.symbols.length + layout.relocations.length, layout.sections.length + layout.segments.length + layout.symbols.length + layout.relocations.length, ), ...shared, }); if (view.kind === "facet") return ok({ kind: "facet", view, facet: view.facet === "mitigations" ? layout.mitigations : layout.linkage, coverage: completeCoverage(1, 1), ...shared, }); if (view.kind === "item") { if (view.collection === "modules") return err( layoutIncompatible( "modules collection applies to analyze_javascript_application Evidence.", ), ); const selected = view.collection === "sections" ? selectLayoutItem(view.collection, layout.sections, view.selector) : selectLayoutItem(view.collection, layout.symbols, view.selector); if (!selected.ok) return selected; return ok({ kind: "item", view, item: selected.value, coverage: completeCoverage( 1, view.collection === "sections" ? layout.sections.length : layout.symbols.length, ), ...shared, }); } if (view.collection === "modules") return err( layoutIncompatible( "modules collection applies to analyze_javascript_application Evidence.", ), ); const rows = view.collection === "sections" ? layout.sections : layout.symbols; const items = view.collection === "sections" ? pageRows(layout.sections, view.offset, view.limit) : pageRows(layout.symbols, view.offset, view.limit); return ok({ kind: "page", view, items: [...items], coverage: pageCoverage(view.offset, items.length, rows.length), ...shared, }); }; const selectLayoutItem = ( collection: "sections" | "symbols", rows: readonly Row[], selector: Extract["selector"], ): Result => { if ("index" in selector) return selectIndexed(collection, rows, selector.index); if ("name" in selector) return selectNamed(collection, rows, selector.name); return err( layoutIncompatible( "Section and symbol items are selected by index or exact name.", ), ); };