import { createHash } from "node:crypto"; import { execFile } from "node:child_process"; import { constants } from "node:fs"; import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { basename, dirname, isAbsolute, join, resolve } from "node:path"; import assert from "node:assert/strict"; import { promisify } from "node:util"; import { parseBinaryTarget } from "../dist/domain/binaryTarget.js"; import { inspectManagedArtifactBytes } from "../dist/dotnet/ManagedArtifactInspector.js"; import { inspectManagedMembersBytes } from "../dist/dotnet/ManagedMemberInspector.js"; import { inspectManagedNativeBoundariesBytes } from "../dist/dotnet/ManagedNativeBoundaryInspector.js"; import { compareManagedMemberPaths } from "../dist/application/ManagedMemberComparisonService.js"; import { verifyManagedNativeBoundariesEvidence } from "../dist/application/ManagedNativeVerificationService.js"; import { importManagedReconstructionEvidence } from "../dist/application/ManagedReconstructionService.js"; import { planManagedRuntimeCorrelationEvidence } from "../dist/application/ManagedRuntimeCorrelationService.js"; import { projectManagedApplicationGraphEvidence } from "../dist/application/ManagedApplicationGraphService.js"; import { traceApplicationFeatureEvidence } from "../dist/application/JavaScriptApplicationWorkflowService.js"; import { createPermissionAuthority } from "../dist/application/PermissionAuthority.js"; import { MANAGED_STATIC_PROVIDER } from "../dist/application/InvestigationProviders.js"; import { createEvidence } from "../dist/domain/evidence.js"; import { alternateMvid, buildManagedPeFixture, buildNativePeFixture, } from "./lib/managed-pe-fixture.mjs"; const inspectionLimits = { referenceOffset: 0, referenceLimit: 100, resourceOffset: 0, resourceLimit: 100, attributeOffset: 0, attributeLimit: 100, maxMetadataBytes: 1024 * 1024, maxTableRows: 1_000, maxHeapItemBytes: 1024 * 1024, }; const memberLimits = { typeOffset: 0, typeLimit: 100, methodOffset: 0, methodLimit: 100, fieldOffset: 0, fieldLimit: 100, memberRefOffset: 0, memberRefLimit: 100, edgeOffset: 0, edgeLimit: 100, instructionAnchorLimit: 100, maxMetadataBytes: 1024 * 1024, maxTableRows: 1_000, maxHeapItemBytes: 1024 * 1024, maxMethodBodyBytes: 1024 * 1024, maxMethodInstructions: 1_000, }; const nativeBoundaryLimits = { moduleRefOffset: 0, moduleRefLimit: 100, importOffset: 0, importLimit: 100, implementationOffset: 0, implementationLimit: 100, maxMetadataBytes: 1024 * 1024, maxTableRows: 1_000, maxHeapItemBytes: 1024 * 1024, }; const applicationGraphLimits = { max_types: 100, max_methods: 100, max_fields: 100, max_pinvoke_imports: 100, max_native_implementations: 100, }; const appManifestInspectionLimits = { ...inspectionLimits, maxMetadataBytes: 64 * 1024 * 1024, maxTableRows: 250_000, maxHeapItemBytes: 16 * 1024 * 1024, }; const appManifestMemberLimits = { ...memberLimits, typeLimit: 1, methodLimit: 1, fieldLimit: 1, memberRefLimit: 1, edgeLimit: 1, maxMetadataBytes: 64 * 1024 * 1024, maxTableRows: 250_000, maxHeapItemBytes: 16 * 1024 * 1024, maxMethodBodyBytes: 4 * 1024 * 1024, maxMethodInstructions: 20_000, }; const comparisonLimits = { max_method_matches: 100, max_field_matches: 100, max_candidates: 20, }; const defaultIlBody = Buffer.from([ 0x32, 0x02, 0x7b, 0x01, 0x00, 0x00, 0x04, 0x28, 0x01, 0x00, 0x00, 0x0a, 0x2a, ]); const workspace = await mkdtemp(join(tmpdir(), "rea-managed-conformance-")); const execFileAsync = promisify(execFile); try { const modern = await fixture("modern-anycpu.exe", { references: ["System.Runtime"], resourceData: Buffer.from("source-owned managed resource"), }); const modernArtifact = inspectManagedArtifactBytes( modern.bytes, modern.target, inspectionLimits, ); assert.equal(modernArtifact.classification.status, "managed"); assert.equal(modernArtifact.classification.runtime_family, "modern-dotnet"); assert.equal(modernArtifact.classification.managed_architecture, "anycpu"); assert.deepEqual(modernArtifact.target_frameworks, [ ".NETCoreApp,Version=v8.0", ]); assert.equal( modernArtifact.resources.items[0]?.data_sha256, sha256(Buffer.from("source-owned managed resource")), ); const framework = await fixture("framework-x86-pinvoke.exe", { cliFlags: 0x0000_0003, targetFramework: ".NETFramework,Version=v4.8", pinvoke: { moduleName: "user32.dll", importName: "MessageBoxW", mappingFlags: 0x0345, }, }); const frameworkArtifact = inspectManagedArtifactBytes( framework.bytes, framework.target, inspectionLimits, ); assert.equal( frameworkArtifact.classification.runtime_family, "dotnet-framework", ); assert.equal(frameworkArtifact.classification.managed_architecture, "x86"); const frameworkBoundaries = inspectManagedNativeBoundariesBytes( framework.bytes, framework.target, nativeBoundaryLimits, ); assert.equal(frameworkBoundaries.pinvoke_imports.total, 1); assert.equal( frameworkBoundaries.pinvoke_imports.items[0]?.verification, "managed-declaration-only", ); assert.equal( frameworkBoundaries.pinvoke_imports.items[0]?.char_set, "unicode", ); assert.equal( frameworkBoundaries.pinvoke_imports.items[0]?.call_convention, "stdcall", ); const frameworkMembers = inspectManagedMembersBytes( framework.bytes, framework.target, memberLimits, ); const frameworkArtifactEvidence = createEvidence( framework.target, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_artifact", parameters: inspectionLimits, result: frameworkArtifact, rawResult: null, limitations: frameworkArtifact.limitations, locations: [{ kind: "artifact-path", path: framework.target.path }], }, ); const frameworkMemberEvidence = createEvidence( framework.target, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_members", parameters: memberLimits, result: frameworkMembers, rawResult: null, limitations: frameworkMembers.limitations, locations: [{ kind: "artifact-path", path: framework.target.path }], }, ); const frameworkBoundaryEvidence = createEvidence( framework.target, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_native_boundaries", parameters: nativeBoundaryLimits, result: frameworkBoundaries, rawResult: null, limitations: frameworkBoundaries.limitations, locations: [{ kind: "artifact-path", path: framework.target.path }], }, ); const nativeFunctionEvidence = createEvidence( { path: "/system/user32.dll", sha256: "9".repeat(64), format: "pe", architecture: "x86", }, { id: "ghidra", name: "Ghidra", version: "12.1.2", }, { operation: "analyze_function", parameters: { procedure: "MessageBoxW" }, result: functionDossier("MessageBoxW"), rawResult: null, limitations: [], locations: [{ kind: "address", address: "0x401000" }], }, ); const nativeVerification = verifyManagedNativeBoundariesEvidence({ managed_boundaries: frameworkBoundaryEvidence, native_observations: [nativeFunctionEvidence], limits: { max_native_observations: 20, max_candidates_per_import: 25, }, }); assert.equal(nativeVerification.ok, true); assert.equal(nativeVerification.value.normalized_result.summary.verified, 1); const applicationGraph = projectManagedApplicationGraphEvidence({ managed_artifact: frameworkArtifactEvidence, managed_members: frameworkMemberEvidence, managed_native_boundaries: frameworkBoundaryEvidence, limits: { max_types: 100, max_methods: 100, max_fields: 100, max_pinvoke_imports: 100, max_native_implementations: 100, }, }); assert.equal(applicationGraph.ok, true); assert.equal( applicationGraph.value.normalized_result.summary.pinvoke_imports, 1, ); assert.equal( applicationGraph.value.normalized_result.graph.nodes.some( ({ kind }) => kind === "managed-pinvoke-import", ), true, ); const applicationTrace = traceApplicationFeatureEvidence({ application: applicationGraph.value, native_observations: [], seed: { kind: "string", value: "MessageBoxW", match: "exact", case_sensitive: true, }, direction: "incoming", limits: { max_seed_matches: 5, max_depth: 4, max_nodes: 50, max_edges: 100, max_paths: 10, }, }); assert.equal(applicationTrace.ok, true); assert.equal( applicationTrace.value.normalized_result.summary.matched_seeds, 1, ); const readyToRun = await fixture("r2r-x64.exe", { machine: 0x8664, readyToRun: true, methods: [ { name: "NativeBody", implFlags: 0x0001, body: Buffer.from([0x2a]), }, ], }); const r2rArtifact = inspectManagedArtifactBytes( readyToRun.bytes, readyToRun.target, inspectionLimits, ); assert.equal(r2rArtifact.pe.architecture, "x86_64"); assert.equal( r2rArtifact.classification.implementation, "cil-and-ready-to-run", ); const r2rBoundaries = inspectManagedNativeBoundariesBytes( readyToRun.bytes, readyToRun.target, nativeBoundaryLimits, ); assert.equal(r2rBoundaries.summary.ready_to_run, true); assert.equal( r2rBoundaries.native_implementations.items[0]?.code_type, "native", ); assert.equal( r2rBoundaries.native_implementations.items[0]?.boundary_kind, "native-body", ); const obfuscated = await fixture("obfuscated.exe", { typeName: "ꙮType", methodName: "λ⛧", fieldName: "字段", }); const obfuscatedMembers = inspectManagedMembersBytes( obfuscated.bytes, obfuscated.target, memberLimits, ); assert.equal(obfuscatedMembers.types.items[0]?.full_name, "Fixture.ꙮType"); assert.equal(obfuscatedMembers.methods.items[0]?.name, "λ⛧"); assert.equal(obfuscatedMembers.fields.items[0]?.name, "字段"); const runtimeExecutable = join(workspace, "dotnet"); await writeFile(runtimeExecutable, "#!/bin/sh\n"); const runtimeCeiling = { capability: "managed_runtime", roots: [workspace], executables: [runtimeExecutable], environment_names: [], network: "none", mount: false, }; const runtimeAuthority = await createPermissionAuthority( [runtimeCeiling], [ { ...runtimeCeiling, grant_id: "administrator:managed_runtime", lifetime: "administrator", operation_identity: null, expires_at: null, }, ], ); assert.equal(runtimeAuthority.ok, true); const runtimeMethod = obfuscatedMembers.methods.items[0]; assert.ok(runtimeMethod); const obfuscatedMembersEvidence = createEvidence( undefined, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_members", parameters: { path: obfuscated.path }, result: obfuscatedMembers, rawResult: null, limitations: obfuscatedMembers.limitations, }, ); const reconstructionImport = importManagedReconstructionEvidence({ static_members: obfuscatedMembersEvidence, decompiler: { name: "ilspycmd", version: "9.1.0.7988", family: "ilspy", executable_sha256: null, options: ["--type", "Fixture.ꙮType"], }, methods: [ { token: runtimeMethod.token, signature_sha256: runtimeMethod.signature.raw_sha256, normalized_il_sha256: runtimeMethod.body.normalized_il_sha256, reconstruction: { kind: "decompiled-csharp", language: "csharp", text: "internal static void λ⛧() { /* synthetic fixture */ }", }, }, ], notes: ["source-owned synthetic decompiler reconstruction"], }); assert.equal(reconstructionImport.ok, true); assert.equal( reconstructionImport.value.normalized_result.methods[0].validation .canonical_observation, false, ); assert.equal(reconstructionImport.value.confidence, "inferred"); const runtimePlan = await planManagedRuntimeCorrelationEvidence( { policy: { enabled: true, roots: [workspace], executablePath: runtimeExecutable, }, authority: runtimeAuthority.value, }, { static_members: obfuscatedMembersEvidence, method: { token: runtimeMethod.token, signature_sha256: runtimeMethod.signature.raw_sha256, normalized_il_sha256: runtimeMethod.body.normalized_il_sha256, }, requested_effect: "debugger", host: { os: "linux", clr_family: "dotnet", architecture: "x86_64", }, bounds: { timeout_ms: 5_000, max_threads: 32, max_output_bytes: 65_536, allow_network: false, allow_ui: false, }, }, ); assert.equal(runtimePlan.ok, true); assert.equal(runtimePlan.value.normalized_result.executed, false); assert.equal( runtimePlan.value.normalized_result.authority_model.capability, "managed_runtime", ); assert.equal( runtimePlan.value.normalized_result.effect_taxonomy.uses_debugger, true, ); const left = await fixture("token-drift-left.exe", { methodName: "StableSemanticSlice", }); const right = await fixture("token-drift-right.exe", { mvid: alternateMvid, methods: [ { name: "InsertedHelper", body: Buffer.from([0x2a]) }, { name: "RenamedSemanticSlice", body: defaultIlBody }, ], }); const comparison = await compareManagedMemberPaths({ leftPath: left.path, rightPath: right.path, memberLimits: { maxFileBytes: 1024 * 1024, ...memberLimits, }, comparisonLimits, }); assert.equal(comparison.ok, true); const comparisonResult = comparison.value.normalized_result; assert.equal(comparisonResult.algorithm.name_matching, "not-used"); assert.equal(comparisonResult.matching.exact_il_signature, 1); assert.equal( comparisonResult.methods.some( ({ left, right, match }) => left.token === "0x06000001" && right?.token === "0x06000002" && match.basis === "exact-il-signature", ), true, ); const nativeOnly = await fixtureBytes( "native-only.exe", buildNativePeFixture(), ); const nativeResult = inspectManagedArtifactBytes( nativeOnly.bytes, nativeOnly.target, inspectionLimits, ); assert.equal(nativeResult.classification.status, "not-managed"); const malformed = await fixture("malformed-metadata.exe", { corruptMetadataSignature: true, }); const malformedResult = inspectManagedArtifactBytes( malformed.bytes, malformed.target, inspectionLimits, ); assert.equal(malformedResult.classification.status, "malformed"); const manifestSelfTest = await runManagedAppManifestSelfTest(); const operatorManifest = await runOptionalManagedAppManifest(); const ilspyOracle = await runOptionalIlspyOracle(); process.stdout.write( `${JSON.stringify({ verified: 12 + (operatorManifest === null ? 0 : 1) + (ilspyOracle === null ? 0 : 1), managedSurfaces: [ "inspect_managed_artifact", "inspect_managed_members", "inspect_managed_native_boundaries", "compare_managed_members", "verify_managed_native_boundaries", "import_managed_reconstruction", "plan_managed_runtime_correlation", "project_managed_application_graph", ], coverage: [ "modern-dotnet-anycpu", "dotnet-framework-x86-pinvoke", "x64-ready-to-run-native-body", "managed-native-verification", "managed-application-graph-projection", "unicode-obfuscated-identifiers", "decompiler-reconstruction-import", "runtime-correlation-admission-plan", "mvid-and-token-drift", "not-managed", "malformed-metadata", "operator-local-managed-manifest", ...(ilspyOracle === null ? [] : ["ilspy-reconstruction-oracle"]), ], managedAppManifest: { env: "REA_MANAGED_APP_MANIFEST_PATH", selfTest: manifestSelfTest, operator: operatorManifest ?? { configured: false }, }, ilspyOracle: ilspyOracle ?? { env: "REA_ILSPY_CMD_PATH", configured: false, }, })}\n`, ); } finally { await rm(workspace, { recursive: true, force: true }); } async function fixture(name, options) { return fixtureBytes(name, buildManagedPeFixture(options)); } async function fixtureBytes(name, bytes) { const path = join(workspace, name); await writeFile(path, bytes); const parsed = await parseBinaryTarget(path); if (!parsed.ok) throw parsed.error; return { bytes, path, target: parsed.value }; } function sha256(bytes) { return createHash("sha256").update(bytes).digest("hex"); } async function runManagedAppManifestSelfTest() { const sample = await fixture("manifest-self-test.exe", { methodName: "PinnedSemanticSlice", ilBody: defaultIlBody, }); const artifact = inspectManagedArtifactBytes( sample.bytes, sample.target, inspectionLimits, ); const members = inspectManagedMembersBytes( sample.bytes, sample.target, memberLimits, ); const method = members.methods.items[0]; assert.ok(method); const summary = await verifyManagedAppManifest( { schema_version: 1, label: "source-owned manifest verifier self-test", target: { path: sample.path, sha256: sample.target.sha256, mvid: artifact.module?.mvid, assembly_name: artifact.assembly?.name, runtime_family: artifact.classification.runtime_family, managed_architecture: artifact.classification.managed_architecture, }, methods: [ { label: "pinned-semantic-slice", token: method.token, signature_sha256: method.signature.raw_sha256, il_size: method.body.il_size, il_sha256: method.body.il_sha256, normalized_il_sha256: method.body.normalized_il_sha256, }, ], application_graph: { expected_node_kinds: [ "artifact", "managed-assembly", "managed-module", "managed-type", "managed-method", ], feature_traces: [ { label: "trace pinned method by name", method_token: method.token, seed: "PinnedSemanticSlice", match: "exact", min_matched_seeds: 1, }, ], }, }, join(workspace, "source-owned-managed-app-manifest.json"), ); return { verified: true, assertions: summary.assertions, methods: summary.methods.length, applicationGraph: summary.application_graph ?? null, target: summary.target, }; } async function runOptionalManagedAppManifest() { const manifestPath = process.env.REA_MANAGED_APP_MANIFEST_PATH; if (manifestPath === undefined) return null; if (manifestPath.trim().length === 0) throw new Error("REA_MANAGED_APP_MANIFEST_PATH is set but empty"); const resolvedManifestPath = resolve(process.cwd(), manifestPath); const text = await readFile(resolvedManifestPath, "utf8"); let manifest; try { manifest = JSON.parse(text); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error( `managed app manifest ${resolvedManifestPath} is not valid JSON: ${message}`, ); } return { configured: true, ...(await verifyManagedAppManifest(manifest, resolvedManifestPath)), }; } async function runOptionalIlspyOracle() { const ilspyPath = process.env.REA_ILSPY_CMD_PATH; if (ilspyPath === undefined) return null; ensureIlspy( ilspyPath.trim().length > 0, "REA_ILSPY_CMD_PATH is set but empty", ); ensureIlspy( isAbsolute(ilspyPath), "REA_ILSPY_CMD_PATH must be an absolute path", ); await ensureExecutable(ilspyPath); const versionOutput = await runIlspy(ilspyPath, ["--version"], 64 * 1024); const version = ilspyVersion(versionOutput); const executableSha256 = sha256(await readFile(ilspyPath)); const sample = await fixture("ilspy-oracle.exe", { methodName: "PinnedSemanticSlice", }); const artifact = inspectManagedArtifactBytes( sample.bytes, sample.target, inspectionLimits, ); const members = inspectManagedMembersBytes( sample.bytes, sample.target, memberLimits, ); const method = members.methods.items[0]; ensureIlspy(method !== undefined, "source-owned fixture has no method"); const listOutput = await runIlspy( ilspyPath, ["--disable-updatecheck", "-l", "c", sample.path], 256 * 1024, ); ensureIlspy( listOutput.includes("Class Fixture.Program"), "ilspycmd class listing did not include Fixture.Program", ); const csharp = await runIlspy( ilspyPath, ["--disable-updatecheck", "--type", "Fixture.Program", sample.path], 256 * 1024, ); ensureIlspy( csharp.includes("PinnedSemanticSlice"), "ilspycmd C# output did not include the pinned method name", ); ensureIlspy( csharp.length <= 65_536, "ilspycmd C# output exceeded the managed reconstruction import bound", ); const memberEvidence = createEvidence( sample.target, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_members", parameters: memberLimits, result: members, rawResult: null, limitations: members.limitations, locations: [{ kind: "artifact-path", path: sample.target.path }], }, ); const reconstructionImport = importManagedReconstructionEvidence({ static_members: memberEvidence, decompiler: { name: "ilspycmd", version, family: "ilspy", executable_sha256: executableSha256, options: ["--disable-updatecheck", "--type", "Fixture.Program"], }, methods: [ { token: method.token, signature_sha256: method.signature.raw_sha256, normalized_il_sha256: method.body.normalized_il_sha256, reconstruction: { kind: "decompiled-csharp", language: "csharp", text: csharp, }, }, ], notes: [ "source-owned real ilspycmd oracle; output is imported as reconstruction inference", ], }); ensureIlspy(reconstructionImport.ok, "managed reconstruction import failed"); const imported = reconstructionImport.value.normalized_result; ensureIlspy( imported.summary.decompiled_csharp_methods === 1, "managed reconstruction import did not record one decompiled C# method", ); return { env: "REA_ILSPY_CMD_PATH", configured: true, version, executable_sha256: executableSha256, fixture: { file_name: basename(sample.path), sha256: sample.target.sha256, mvid: artifact.module?.mvid ?? null, assembly_name: artifact.assembly?.name ?? null, method: { token: method.token, declaring_type: method.declaring_type, name: method.name, signature_sha256: method.signature.raw_sha256, il_size: method.body.il_size, normalized_il_sha256: method.body.normalized_il_sha256, }, }, output: { class_listing_sha256: sha256(Buffer.from(listOutput)), decompiled_csharp_sha256: sha256(Buffer.from(csharp)), decompiled_csharp_bytes: Buffer.byteLength(csharp), imported_evidence_id: reconstructionImport.value.evidence_id, reconstruction_id: imported.reconstruction_id, canonical_observation: imported.methods[0]?.validation.canonical_observation ?? null, confidence_floor: imported.methods[0]?.validation.confidence_floor ?? null, }, }; } async function ensureExecutable(path) { try { await access(path, constants.X_OK); } catch { throw new Error(`managed ILSpy oracle failed: ${path} is not executable`); } } async function runIlspy(path, args, maxBuffer) { try { const { stdout } = await execFileAsync(path, args, { timeout: 30_000, maxBuffer, env: { ...process.env, DOTNET_CLI_TELEMETRY_OPTOUT: "1", }, }); return stdout; } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`managed ILSpy oracle failed: ${message}`); } } function ilspyVersion(output) { const version = output .split(/\r?\n/u) .map((line) => line.trim()) .find((line) => line.startsWith("ilspycmd:")); ensureIlspy( version !== undefined, "ilspycmd --version did not identify ilspycmd", ); return version; } function ensureIlspy(condition, message) { if (!condition) throw new Error(`managed ILSpy oracle failed: ${message}`); } async function verifyManagedAppManifest(rawManifest, manifestPath) { const manifest = parseManagedAppManifest(rawManifest); const targetPath = isAbsolute(manifest.target.path) ? manifest.target.path : resolve(dirname(manifestPath), manifest.target.path); const parsedTarget = await parseBinaryTarget(targetPath); ensure( parsedTarget.ok, `target ${targetPath} could not be parsed as a binary target`, ); const target = parsedTarget.value; ensure( target.sha256 === manifest.target.sha256, `target sha256 mismatch: expected ${manifest.target.sha256}, observed ${target.sha256}`, ); const bytes = await readFile(targetPath); const artifact = inspectManagedArtifactBytes( bytes, target, appManifestInspectionLimits, ); ensure( artifact.classification.status === "managed", `target is ${artifact.classification.status}, expected managed`, ); if (manifest.target.mvid !== undefined) ensure( artifact.module?.mvid === manifest.target.mvid, `target MVID mismatch: expected ${manifest.target.mvid}, observed ${artifact.module?.mvid ?? "null"}`, ); if (manifest.target.assembly_name !== undefined) ensure( artifact.assembly?.name === manifest.target.assembly_name, `assembly name mismatch: expected ${manifest.target.assembly_name}, observed ${artifact.assembly?.name ?? "null"}`, ); if (manifest.target.runtime_family !== undefined) ensure( artifact.classification.runtime_family === manifest.target.runtime_family, `runtime family mismatch: expected ${manifest.target.runtime_family}, observed ${artifact.classification.runtime_family}`, ); if (manifest.target.managed_architecture !== undefined) ensure( artifact.classification.managed_architecture === manifest.target.managed_architecture, `managed architecture mismatch: expected ${manifest.target.managed_architecture}, observed ${artifact.classification.managed_architecture}`, ); const methods = []; let assertions = 1; const inspectedMethods = new Map(); for (const expectedMethod of manifest.methods) { const method = inspectManagedMethodByToken(bytes, target, expectedMethod); ensure( method.signature.raw_sha256 === expectedMethod.signature_sha256, `method ${expectedMethod.token} signature sha256 mismatch: expected ${expectedMethod.signature_sha256}, observed ${method.signature.raw_sha256}`, ); ensure( method.body.il_size === expectedMethod.il_size, `method ${expectedMethod.token} IL size mismatch: expected ${String(expectedMethod.il_size)}, observed ${String(method.body.il_size)}`, ); if (expectedMethod.il_sha256 !== undefined) ensure( method.body.il_sha256 === expectedMethod.il_sha256, `method ${expectedMethod.token} IL sha256 mismatch: expected ${expectedMethod.il_sha256}, observed ${method.body.il_sha256 ?? "null"}`, ); ensure( method.body.normalized_il_sha256 === expectedMethod.normalized_il_sha256, `method ${expectedMethod.token} normalized IL sha256 mismatch: expected ${expectedMethod.normalized_il_sha256}, observed ${method.body.normalized_il_sha256 ?? "null"}`, ); assertions += expectedMethod.il_sha256 === undefined ? 4 : 5; inspectedMethods.set(expectedMethod.token, method); methods.push({ label: expectedMethod.label ?? null, token: method.token, declaring_type: method.declaring_type, name: method.name, signature_sha256: method.signature.raw_sha256, il_size: method.body.il_size, il_sha256: method.body.il_sha256, normalized_il_sha256: method.body.normalized_il_sha256, }); } const applicationGraph = manifest.application_graph === undefined ? undefined : verifyManagedAppManifestApplicationGraph({ graphManifest: manifest.application_graph, bytes, target, artifact, manifestMethods: manifest.methods, inspectedMethods, }); if (applicationGraph !== undefined) assertions += applicationGraph.assertions; return { label: manifest.label ?? null, assertions, target: { file_name: basename(targetPath), sha256: target.sha256, mvid: artifact.module?.mvid ?? null, assembly_name: artifact.assembly?.name ?? null, runtime_family: artifact.classification.runtime_family, managed_architecture: artifact.classification.managed_architecture, }, methods, ...(applicationGraph === undefined ? {} : { application_graph: applicationGraph.summary }), }; } function verifyManagedAppManifestApplicationGraph({ graphManifest, bytes, target, artifact, manifestMethods, inspectedMethods, }) { const graphByToken = new Map(); const projectionForToken = (token) => { const existing = graphByToken.get(token); if (existing !== undefined) return existing; const method = inspectedMethods.get(token); ensure( method !== undefined, `application graph method ${token} must also be declared in manifest.methods`, ); const members = inspectManagedMethodPageByToken(bytes, target, token); const artifactEvidence = createEvidence(target, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_artifact", parameters: appManifestInspectionLimits, result: artifact, rawResult: null, limitations: artifact.limitations, locations: [{ kind: "artifact-path", path: target.path }], }); const memberEvidence = createEvidence(target, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_members", parameters: { ...appManifestMemberLimits, methodOffset: methodTokenRow(token) - 1, }, result: members, rawResult: null, limitations: members.limitations, locations: [{ kind: "artifact-path", path: target.path }], }); const projected = projectManagedApplicationGraphEvidence({ managed_artifact: artifactEvidence, managed_members: memberEvidence, limits: applicationGraphLimits, }); ensure( projected.ok, `application graph projection failed for method ${token}`, ); const projection = { method, evidence: projected.value, nodeKinds: [ ...new Set( projected.value.normalized_result.graph.nodes.map(({ kind }) => kind), ), ].sort(), summary: projected.value.normalized_result.summary, }; graphByToken.set(token, projection); return projection; }; const firstToken = manifestMethods[0]?.token; ensure( firstToken !== undefined, "application graph verification requires at least one manifest method", ); const baseline = projectionForToken(firstToken); let assertions = 1; for (const expectedKind of graphManifest.expected_node_kinds) { ensure( baseline.nodeKinds.includes(expectedKind), `application graph is missing expected node kind ${expectedKind}`, ); assertions += 1; } const traces = []; for (const expectedTrace of graphManifest.feature_traces) { const projection = projectionForToken(expectedTrace.method_token); const traced = traceApplicationFeatureEvidence({ application: projection.evidence, native_observations: [], seed: { kind: "string", value: expectedTrace.seed, match: expectedTrace.match, case_sensitive: expectedTrace.case_sensitive, }, direction: "incoming", limits: { max_seed_matches: 10, max_depth: 4, max_nodes: 100, max_edges: 200, max_paths: 20, }, }); ensure( traced.ok, `application graph trace failed for method ${expectedTrace.method_token}`, ); const matched = traced.value.normalized_result.summary.matched_seeds; ensure( matched >= expectedTrace.min_matched_seeds, `application graph trace ${expectedTrace.seed} matched ${String(matched)} seeds, expected at least ${String(expectedTrace.min_matched_seeds)}`, ); assertions += 1; traces.push({ label: expectedTrace.label ?? null, method_token: expectedTrace.method_token, seed: expectedTrace.seed, matched_seeds: matched, trace_evidence_id: traced.value.evidence_id, }); } return { assertions, summary: { projections: [...graphByToken.values()].map((projection) => ({ method_token: projection.method.token, graph_evidence_id: projection.evidence.evidence_id, node_kinds: projection.nodeKinds, summary: projection.summary, })), expected_node_kinds: graphManifest.expected_node_kinds, feature_traces: traces, }, }; } function inspectManagedMethodByToken(bytes, target, expectedMethod) { const members = inspectManagedMethodPageByToken( bytes, target, expectedMethod.token, ); const method = members.methods.items.find( ({ token }) => token === expectedMethod.token, ); ensure( method !== undefined, `method ${expectedMethod.token} was not returned by the member inspector`, ); ensure( method.body.status === "present", `method ${expectedMethod.token} body status is ${method.body.status}, expected present`, ); return method; } function inspectManagedMethodPageByToken(bytes, target, token) { const row = methodTokenRow(token); return inspectManagedMembersBytes(bytes, target, { ...appManifestMemberLimits, methodOffset: row - 1, }); } function parseManagedAppManifest(rawManifest) { const manifest = object(rawManifest, "manifest"); ensure( manifest.schema_version === 1, "manifest.schema_version must be exactly 1", ); const target = object(manifest.target, "manifest.target"); const methods = array(manifest.methods, "manifest.methods"); ensure(manifest.methods.length > 0, "manifest.methods must not be empty"); return { schema_version: 1, label: optionalString(manifest.label, "manifest.label"), target: { path: string(target.path, "manifest.target.path"), sha256: digest(target.sha256, "manifest.target.sha256"), mvid: optionalCliMetadataGuid(target.mvid, "manifest.target.mvid"), assembly_name: optionalString( target.assembly_name, "manifest.target.assembly_name", ), runtime_family: optionalString( target.runtime_family, "manifest.target.runtime_family", ), managed_architecture: optionalString( target.managed_architecture, "manifest.target.managed_architecture", ), }, methods: methods.map((method, index) => parseManagedAppManifestMethod(method, index), ), application_graph: parseManagedAppManifestGraph(manifest.application_graph), }; } function parseManagedAppManifestGraph(rawGraph) { if (rawGraph === undefined) return undefined; const graph = object(rawGraph, "manifest.application_graph"); const expectedKinds = graph.expected_node_kinds === undefined ? [] : array( graph.expected_node_kinds, "manifest.application_graph.expected_node_kinds", ).map((kind, index) => string( kind, `manifest.application_graph.expected_node_kinds[${String(index)}]`, ), ); const traces = graph.feature_traces === undefined ? [] : array( graph.feature_traces, "manifest.application_graph.feature_traces", ).map((trace, index) => parseManagedAppManifestTrace(trace, index)); ensure( expectedKinds.length > 0 || traces.length > 0, "manifest.application_graph must declare expected_node_kinds or feature_traces", ); return { expected_node_kinds: expectedKinds, feature_traces: traces, }; } function parseManagedAppManifestTrace(rawTrace, index) { const prefix = `manifest.application_graph.feature_traces[${String(index)}]`; const trace = object(rawTrace, prefix); const match = trace.match ?? "exact"; ensure( match === "exact" || match === "contains", `${prefix}.match must be exact or contains`, ); const caseSensitive = trace.case_sensitive ?? true; ensure( typeof caseSensitive === "boolean", `${prefix}.case_sensitive must be a boolean`, ); const minMatchedSeeds = trace.min_matched_seeds ?? 1; ensure( Number.isInteger(minMatchedSeeds) && minMatchedSeeds > 0, `${prefix}.min_matched_seeds must be a positive integer`, ); return { label: optionalString(trace.label, `${prefix}.label`), method_token: methodToken(trace.method_token, `${prefix}.method_token`), seed: string(trace.seed, `${prefix}.seed`), match, case_sensitive: caseSensitive, min_matched_seeds: minMatchedSeeds, }; } function parseManagedAppManifestMethod(rawMethod, index) { const prefix = `manifest.methods[${String(index)}]`; const method = object(rawMethod, prefix); const token = methodToken(method.token, `${prefix}.token`); return { label: optionalString(method.label, `${prefix}.label`), token, signature_sha256: digest( method.signature_sha256, `${prefix}.signature_sha256`, ), il_size: ilSize(method, prefix), il_sha256: method.il_sha256 === undefined ? undefined : digest(method.il_sha256, `${prefix}.il_sha256`), normalized_il_sha256: digest( method.normalized_il_sha256, `${prefix}.normalized_il_sha256`, ), }; } function ilSize(method, prefix) { const hasIlSize = method.il_size !== undefined; const hasIlLength = method.il_length !== undefined; ensure( hasIlSize || hasIlLength, `${prefix}.il_size is required; il_length is accepted as a legacy alias`, ); if (hasIlSize && hasIlLength) ensure( method.il_size === method.il_length, `${prefix}.il_size and ${prefix}.il_length disagree`, ); const value = hasIlSize ? method.il_size : method.il_length; ensure( Number.isInteger(value) && value >= 0, `${prefix}.il_size must be a non-negative integer`, ); return value; } function object(value, name) { ensure( value !== null && typeof value === "object" && !Array.isArray(value), `${name} must be an object`, ); return value; } function array(value, name) { ensure(Array.isArray(value), `${name} must be an array`); return value; } function string(value, name) { ensure( typeof value === "string" && value.length > 0, `${name} must be a non-empty string`, ); return value; } function optionalString(value, name) { if (value === undefined) return undefined; return string(value, name); } function digest(value, name) { const text = string(value, name); ensure(/^[a-f0-9]{64}$/u.test(text), `${name} must be a lowercase sha256`); return text; } function optionalCliMetadataGuid(value, name) { if (value === undefined) return undefined; if (value === null) return null; const text = string(value, name); ensure( /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.test( text, ), `${name} must be a lowercase GUID`, ); return text; } function metadataToken(value, name) { const text = string(value, name); ensure(/^0x[0-9a-f]{8}$/u.test(text), `${name} must be a metadata token`); return text; } function methodToken(value, name) { const token = metadataToken(value, name); ensure( token.startsWith("0x06"), `${name} must be a MethodDef token beginning with 0x06`, ); return token; } function methodTokenRow(token) { const row = Number.parseInt(token.slice(4), 16); ensure(row > 0, `method token ${token} has an invalid row id`); return row; } function ensure(condition, message) { if (!condition) throw new Error(`managed app manifest verification failed: ${message}`); } function functionDossier(name) { const emptyPage = { items: [], total: 0, returned: 0, truncated: false, next_offset: null, }; return { procedure: { address: "0x401000", name, classification: { external: false, thunk: false, thunk_target: null, provenance: "synthetic-provider", }, signature: null, locals: [], }, pseudocode: { text: "", total_chars: 0, returned_chars: 0, truncated: false, next_offset: null, }, assembly: emptyPage, comments: emptyPage, callers: emptyPage, callees: emptyPage, incoming_references: emptyPage, outgoing_references: emptyPage, referenced_strings: emptyPage, referenced_names: emptyPage, basic_blocks: emptyPage, instruction_scan: { scanned: 0, truncated: false }, limitations: [], }; }