diff --git a/docs/adr/0003-managed-code-evidence-and-provider-boundary.md b/docs/adr/0003-managed-code-evidence-and-provider-boundary.md index 34127a37e9..bc4a513189 100644 --- a/docs/adr/0003-managed-code-evidence-and-provider-boundary.md +++ b/docs/adr/0003-managed-code-evidence-and-provider-boundary.md @@ -203,7 +203,7 @@ artifact: canonical path + byte length + SHA-256 component: container identity + component path/name + component SHA-256, when applicable classification: container + runtime family + implementation form + architecture assembly/module: simple name + version + culture + public-key identity + module name + MVID -producer: provider ID + exact version + parser/profile schema + profile digest +producer: provider ID + exact version + analysis-affecting parameters + profile digest ``` An assembly-qualified name does not replace the artifact digest or MVID. diff --git a/docs/managed-code-analysis.md b/docs/managed-code-analysis.md index 338b62fa2a..c2cb508088 100644 --- a/docs/managed-code-analysis.md +++ b/docs/managed-code-analysis.md @@ -4,9 +4,9 @@ This document turns [ADR-0003](adr/0003-managed-code-evidence-and-provider-boundary.md) into an implementation and verification plan. REA currently ships read-only PE/CLI triage and exact identity through `inspect_managed_artifact` and -`rea inspect-managed-artifact`, plus bounded metadata, signature, method-body -CIL, exception-region, call-edge, and field-access inspection through -`inspect_managed_members` and `rea inspect-managed-members`, and +`rea inspect-managed-artifact`, plus file-backed metadata, signature, +method-body CIL, exception-region, call-edge, and field-access inspection +through `inspect_managed_members` and `rea inspect-managed-members`, and declared ModuleRef/ImplMap/PInvoke and native implementation boundary inventory through `inspect_managed_native_boundaries` and `rea inspect-managed-native-boundaries`, plus obfuscation-resistant member @@ -91,7 +91,7 @@ commitment groups: - module name, generation, and MVID; - target framework and runtime/version strings with their exact metadata locations; -- parser/provider version, profile schema, limits, and profile digest. +- provider identity, analysis-affecting parameters, and their profile digest. CLI `#GUID` heap values are committed as their exact 16-byte GUID text. REA does not impose RFC 4122 UUID version or variant bits on MVID, EncId, or @@ -120,8 +120,8 @@ boundaries: | `normalized_il_sha256` | UTF-8 `JSON.stringify` output for the instruction-order array of `[opcode, operand_kind, operand]` tuples | Instruction offsets except projected scalar branch targets, switch targets, resolved metadata/string identities, method header, locals, and exception regions | The normalized field is available only when the entire CIL stream decodes with -status `present`. A partial, malformed, absent, or over-limit body reports -`null`. The tuple format is defined here. +status `present`. A malformed or absent body reports `null`. The tuple format +is defined here. Tuple operands use these exact projections: @@ -169,7 +169,7 @@ that implement them. The underlying capability slices are: 1. **Triage and inventory**: container/component classification, assembly and module identity, references, files, exported types, resources, attributes, and runtime markers. -2. **Member inventory**: bounded types, methods, fields, properties, events, +2. **Member inventory**: file-backed types, methods, fields, properties, events, interfaces, nesting, generics, and normalized signatures. 3. **Method inspection**: headers, max stack, locals, CIL instructions, constants, metadata operands, exception regions, and implementation flags. @@ -190,9 +190,11 @@ that implement them. The underlying capability slices are: Evidence-backed application graph vocabulary as JavaScript/Electron findings while preserving managed static-analysis authority. -Every list has a deterministic order and hard limits. Search exposes scanned, -matched, returned, and dropped counts. An unresolved indirect call or a failed -signature decode is not silently omitted from a completeness claim. +Inventory lists preserve deterministic order and include every fact available +from the admitted PE/CLI streams. Malformed ranges and unsupported metadata are +reported as coverage issues instead of being silently omitted. Search exposes +scanned, matched, returned, and dropped counts. An unresolved indirect call or +a failed signature decode is not silently omitted from a completeness claim. ## Obfuscation-resistant method slices @@ -372,7 +374,8 @@ manifest supplies those fields. It then pages each declared MethodDef token directly by row, so selected methods do not need to appear in the first member page of a large application. Optional `il_sha256` locks the exact raw CIL bytes in addition to REA's decoded-instruction-tuple digest. That normalized field -has the limits documented above and is not a complete semantic CIL identity. +has the byte boundaries documented above and is not a complete semantic CIL +identity. The optional `application_graph` block reuses those exact-build method commitments. For each referenced MethodDef token, the verifier builds a bounded @@ -420,7 +423,7 @@ The managed-code track advances as reviewable pull requests: 1. accepted evidence/provider boundary (this document and ADR); 2. read-only artifact triage and exact identity (shipped); -3. bounded metadata, signatures, method bodies, raw CIL hashes, and the limited +3. file-backed metadata, signatures, method bodies, raw CIL hashes, and the decoded-instruction-tuple fingerprint (shipped; complete normalized-CIL semantics remain planned); 4. obfuscation-resistant slices and cross-version comparison (shipped for diff --git a/docs/verification/managed-conformance-manifest.json b/docs/verification/managed-conformance-manifest.json index eb51aa63a3..22127d056a 100644 --- a/docs/verification/managed-conformance-manifest.json +++ b/docs/verification/managed-conformance-manifest.json @@ -1,5 +1,5 @@ { - "manifest_id": "ecm_1c73a5b9ae7f6436b096a22f14eab619743ca5d79176de337354da0f51447202", + "manifest_id": "ecm_a1bee6c3a55086b881e35c2b8ce35bf0852d493d356685db7e188dbbdc61ee37", "verifier": { "id": "rea-managed-conformance", "version": "1" @@ -13,7 +13,7 @@ "skill_digests": [ { "skill_id": "reverse-engineer-anything", - "sha256": "58478d036b495a11dcf06753cadbed1b37ddafec6d794421440886e6c117e4cb" + "sha256": "c04163b8a565e95246f22f083f4923efbc4e4499c15b67858bc5649e2d1bc98b" } ], "claims": [ diff --git a/scripts/verify-managed-conformance.mjs b/scripts/verify-managed-conformance.mjs index be3e24a2fa..1081d9d72a 100644 --- a/scripts/verify-managed-conformance.mjs +++ b/scripts/verify-managed-conformance.mjs @@ -154,7 +154,7 @@ try { { id: "ghidra", name: "Ghidra", - version: "12.1.2", + version: "12.1.4", }, { operation: "analyze_function", diff --git a/scripts/verify-package-managed.mjs b/scripts/verify-package-managed.mjs index 6d042aaff6..246490a915 100644 --- a/scripts/verify-package-managed.mjs +++ b/scripts/verify-package-managed.mjs @@ -253,7 +253,7 @@ const verifyManagedNativeVerification = async ({ { id: "ghidra", name: "Ghidra", - version: "12.1.2", + version: "12.1.4", }, { operation: "analyze_function", diff --git a/src/application/ManagedMemberComparisonService.ts b/src/application/ManagedMemberComparisonService.ts index 1a2d038b16..cd894c1acf 100644 --- a/src/application/ManagedMemberComparisonService.ts +++ b/src/application/ManagedMemberComparisonService.ts @@ -1,4 +1,4 @@ -import { readFile, stat } from "node:fs/promises"; +import { readFile } from "node:fs/promises"; import { z } from "zod"; @@ -17,15 +17,11 @@ import { createEvidence, type Evidence } from "../domain/evidence.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { parseBinaryTarget } from "./BinaryTargetResolver.js"; import { err, ok, type Result } from "../domain/result.js"; -import { - type ManagedMemberInspectionLimits, - inspectManagedMembersBytes, -} from "../dotnet/ManagedMemberInspector.js"; +import { inspectManagedMembersBytes } from "../dotnet/ManagedMemberInspector.js"; import { MANAGED_STATIC_PROVIDER, MANAGED_WORKFLOW_PROVIDER, } from "./InvestigationProviders.js"; -import { MANAGED_INSPECTION_DEFAULTS } from "../dotnet/ManagedInspectionDefaults.js"; /** Compare managed members from input parsed by a trusted adapter. */ export const compareManagedMembersEvidenceValidated = ( @@ -52,10 +48,6 @@ export const compareManagedMemberPaths = async (input: { }): Promise> => { const operation = "compare_managed_members"; try { - const memberLimits: ManagedMemberPathInspectionLimits = { - ...MANAGED_INSPECTION_DEFAULTS, - maxFileBytes: MANAGED_INSPECTION_DEFAULTS.maxFileBytes, - }; const [leftTarget, rightTarget] = await Promise.all([ parseBinaryTarget(input.leftPath), parseBinaryTarget(input.rightPath), @@ -68,21 +60,6 @@ export const compareManagedMemberPaths = async (input: { return err( new AnalysisInputError(operation, { cause: rightTarget.error }), ); - const [leftStat, rightStat] = await Promise.all([ - stat(leftTarget.value.path), - stat(rightTarget.value.path), - ]); - if ( - leftStat.size > memberLimits.maxFileBytes || - rightStat.size > memberLimits.maxFileBytes - ) - return err( - new AnalysisInputError(operation, { - cause: new RangeError( - `Managed comparison input exceeds max_file_bytes ${String(memberLimits.maxFileBytes)}`, - ), - }), - ); const [leftBytes, rightBytes] = await Promise.all([ readFile(leftTarget.value.path), readFile(rightTarget.value.path), @@ -90,19 +67,17 @@ export const compareManagedMemberPaths = async (input: { const leftInspection = inspectManagedMembersBytes( leftBytes, leftTarget.value, - memberLimits, ); const rightInspection = inspectManagedMembersBytes( rightBytes, rightTarget.value, - memberLimits, ); const leftEvidence = createEvidence( leftTarget.value, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_members", - parameters: memberLimitParameters(memberLimits), + parameters: {}, result: jsonValueSchema.parse(leftInspection), rawResult: null, limitations: leftInspection.limitations, @@ -114,7 +89,7 @@ export const compareManagedMemberPaths = async (input: { MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_members", - parameters: memberLimitParameters(memberLimits), + parameters: {}, result: jsonValueSchema.parse(rightInspection), rawResult: null, limitations: rightInspection.limitations, @@ -145,11 +120,7 @@ export const compareManagedMemberPaths = async (input: { } }; -/** Byte-inspection limits plus the file admission bound for path comparisons. */ -export interface ManagedMemberPathInspectionLimits - extends ManagedMemberInspectionLimits { - readonly maxFileBytes: number; -} +/** ECMA-335 representable limits for path-based member comparison. */ const createManagedMemberComparisonEvidence = ( parameters: Pick, @@ -171,17 +142,6 @@ const createManagedMemberComparisonEvidence = ( evidenceLinks: result.evidence_links, }); -const memberLimitParameters = ( - limits: ManagedMemberPathInspectionLimits, -): Record> => ({ - max_file_bytes: limits.maxFileBytes, - max_metadata_bytes: limits.maxMetadataBytes, - max_table_rows: limits.maxTableRows, - max_heap_item_bytes: limits.maxHeapItemBytes, - max_method_body_bytes: limits.maxMethodBodyBytes, - max_method_instructions: limits.maxMethodInstructions, -}); - const workflowFailure = ( operation: string, cause: unknown, diff --git a/src/domain/managedArtifact.ts b/src/domain/managedArtifact.ts index a5e1199fec..01298fd845 100644 --- a/src/domain/managedArtifact.ts +++ b/src/domain/managedArtifact.ts @@ -151,7 +151,7 @@ const managedInstructionAnchorSchema = z.object({ }); const managedMethodBodySchema = z.object({ - status: z.enum(["present", "partial", "absent", "malformed", "too-large"]), + status: z.enum(["present", "partial", "absent", "malformed"]), header_format: z.enum(["tiny", "fat", "none", "unknown"]), rva: z.number().int().min(0).max(0xffff_ffff), file_offset: offsetSchema.nullable(), @@ -294,7 +294,6 @@ const managedParseIssueSchema = z.object({ "invalid-blob", "invalid-guid", "invalid-resource", - "limit-exceeded", ]), scope: z.string().min(1), offset: offsetSchema.nullable(), diff --git a/src/domain/managedMemberComparison.test.ts b/src/domain/managedMemberComparison.test.ts index bb1ee885ae..42d00f087a 100644 --- a/src/domain/managedMemberComparison.test.ts +++ b/src/domain/managedMemberComparison.test.ts @@ -10,7 +10,6 @@ import { inspectManagedMembersBytes } from "../dotnet/ManagedMemberInspector.js" import { buildManagedPeFixture, managedPeFixtureTarget, - MANAGED_MEMBER_FIXTURE_LIMITS, } from "../dotnet/ManagedPe.fixture.js"; describe("managed member comparison", () => { @@ -115,35 +114,6 @@ describe("managed member comparison", () => { }).success, ).toBe(false); }); - - it("does not match identical instruction-limited method prefixes", () => { - const limited = { - ...MANAGED_MEMBER_FIXTURE_LIMITS, - maxMethodInstructions: 1, - }; - const left = inspect( - buildManagedPeFixture(), - "/tmp/left-partial.dll", - limited, - ); - const right = inspect( - buildManagedPeFixture(), - "/tmp/right-partial.dll", - limited, - ); - const result = compareManagedMembers( - { evidenceId: left.evidenceId, result: left.result }, - { evidenceId: right.evidenceId, result: right.result }, - ); - - expect(result.matching.exact_il_signature).toBe(0); - expect(result.matching.structural_method_shape).toBe(0); - expect(result.coverage).toMatchObject({ - status: "partial", - left_status: "partial", - right_status: "partial", - }); - }); }); describe("managed member comparison uncertainty", () => { @@ -244,13 +214,9 @@ describe("managed member comparison uncertainty", () => { }); }); -const inspect = ( - bytes: Buffer, - path: string, - limits: typeof MANAGED_MEMBER_FIXTURE_LIMITS = MANAGED_MEMBER_FIXTURE_LIMITS, -) => { +const inspect = (bytes: Buffer, path: string) => { const target = managedPeFixtureTarget(bytes, path); - const result = inspectManagedMembersBytes(bytes, target, limits); + const result = inspectManagedMembersBytes(bytes, target); return { result, evidenceId: `ev_${hash(Buffer.from(path))}`, diff --git a/src/dotnet/ManagedArtifactFailures.test.ts b/src/dotnet/ManagedArtifactFailures.test.ts index c35e7901bb..b80e144632 100644 --- a/src/dotnet/ManagedArtifactFailures.test.ts +++ b/src/dotnet/ManagedArtifactFailures.test.ts @@ -5,16 +5,14 @@ import { buildManagedPeFixture, buildNativePeFixture, managedPeFixtureTarget, - MANAGED_ARTIFACT_FIXTURE_LIMITS, } from "./ManagedPe.fixture.js"; describe("managed artifact failure classification", () => { - it("distinguishes native, malformed, unsupported, and bounded metadata", () => { + it("distinguishes native, malformed, and unsupported metadata", () => { const nativeBytes = buildNativePeFixture(); const native = inspectManagedArtifactBytes( nativeBytes, managedPeFixtureTarget(nativeBytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); expect(native).toMatchObject({ classification: { status: "not-managed" }, @@ -28,7 +26,6 @@ describe("managed artifact failure classification", () => { const malformed = inspectManagedArtifactBytes( malformedBytes, managedPeFixtureTarget(malformedBytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); expect(malformed.classification.status).toBe("malformed"); expect(malformed.coverage.issues).toEqual([ @@ -41,21 +38,9 @@ describe("managed artifact failure classification", () => { const unsupportedTable = inspectManagedArtifactBytes( unsupportedTableBytes, managedPeFixtureTarget(unsupportedTableBytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); expect(unsupportedTable.coverage.issues).toEqual([ expect.objectContaining({ code: "invalid-tables" }), ]); - - const limitedBytes = buildManagedPeFixture(); - const limited = inspectManagedArtifactBytes( - limitedBytes, - managedPeFixtureTarget(limitedBytes), - { ...MANAGED_ARTIFACT_FIXTURE_LIMITS, maxMetadataBytes: 256 }, - ); - expect(limited.metadata.status).toBe("partial"); - expect(limited.coverage.issues).toEqual([ - expect.objectContaining({ code: "limit-exceeded" }), - ]); }); }); diff --git a/src/dotnet/ManagedArtifactInspector.test.ts b/src/dotnet/ManagedArtifactInspector.test.ts index 384536fb8d..106d284b40 100644 --- a/src/dotnet/ManagedArtifactInspector.test.ts +++ b/src/dotnet/ManagedArtifactInspector.test.ts @@ -8,9 +8,6 @@ import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInsp import { buildManagedPeFixture, managedPeFixtureTarget, - MANAGED_ARTIFACT_FIXTURE_LIMITS, - MANAGED_MEMBER_FIXTURE_LIMITS, - MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS, } from "./ManagedPe.fixture.js"; describe("managed artifact inventory", () => { @@ -30,7 +27,6 @@ describe("managed artifact inventory", () => { const result = inspectManagedArtifactBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); expect(result.classification.status).toBe("managed"); @@ -44,7 +40,6 @@ describe("managed artifact inventory", () => { const result = inspectManagedArtifactBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); expect(result.classification).toMatchObject({ @@ -90,7 +85,6 @@ describe("managed artifact inventory", () => { const result = inspectManagedArtifactBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); expect(result.classification.runtime_family).toBe("unity-mono"); @@ -106,17 +100,14 @@ describe("managed artifact inventory", () => { const result = inspectManagedArtifactBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_ARTIFACT_FIXTURE_LIMITS, ); const members = inspectManagedMembersBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); const boundaries = inspectManagedNativeBoundariesBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS, ); expect(result.module?.mvid).toBe("0ec6eb3a-4adc-4b54-1f45-8b4ed40b33b1"); @@ -135,7 +126,6 @@ describe("managed artifact inventory", () => { const result = inspectManagedMembersBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(result.fields[0]?.signature).toMatchObject({ diff --git a/src/dotnet/ManagedArtifactInspector.ts b/src/dotnet/ManagedArtifactInspector.ts index 538a1b60ea..4f9fd75288 100644 --- a/src/dotnet/ManagedArtifactInspector.ts +++ b/src/dotnet/ManagedArtifactInspector.ts @@ -12,7 +12,6 @@ import { import { managedTableRowCounts, readManagedMetadataInventory, - type ManagedInventoryInput, type ManagedMetadataInventory, type ManagedResourceDirectory, } from "./ManagedMetadataInventory.js"; @@ -22,11 +21,6 @@ import { } from "./ManagedMetadataLayout.js"; import { ManagedReaderFailure } from "./ManagedReaderFailure.js"; -export interface ManagedInspectionLimits extends ManagedInventoryInput { - readonly maxMetadataBytes: number; - readonly maxTableRows: number; -} - const flagNames = (flags: number): string[] => { const names: string[] = []; for (const [mask, name] of [ @@ -309,7 +303,7 @@ const partialMetadataResult = ({ ], }, metadata: { - status: issue.code === "limit-exceeded" ? "partial" : "malformed", + status: "malformed", version: null, stream_names: [], table_row_counts: {}, @@ -322,7 +316,7 @@ const partialMetadataResult = ({ attributes: [], coverage: { state: "partial", issues: [issue] }, limitations: [ - "CLI metadata triage stopped at the reported bounded format or resource limit.", + "CLI metadata could not be read because the PE/CLI structure is malformed or unsupported.", ], }); @@ -350,29 +344,14 @@ const mapResources = ( } }; -/** Inspect PE/CLI identity directly from bounded bytes without CLR loading. */ +/** Inspect PE/CLI identity directly from local bytes without CLR loading. */ export const inspectManagedArtifactBytes = ( bytes: Buffer, target: BinaryTarget, - limits: ManagedInspectionLimits, ): ManagedArtifactInspection => { const layout = readManagedPeLayout(bytes); if (layout.cli === null) return unavailableResult(target, bytes, layout); const cli = layout.cli; - if (cli.metadata.size > limits.maxMetadataBytes) - return managedArtifactInspectionSchema.parse( - partialMetadataResult({ - target, - bytes, - layout, - issue: { - code: "limit-exceeded", - scope: "cli.metadata", - offset: cli.headerOffset + 8, - detail: `CLI metadata size exceeds max_metadata_bytes ${String(limits.maxMetadataBytes)}`, - }, - }), - ); let metadata: ManagedMetadataLayout; try { const metadataOffset = layout.rvaToOffset( @@ -384,7 +363,6 @@ export const inspectManagedArtifactBytes = ( bytes, metadataOffset, cli.metadata.size, - limits.maxTableRows, ); } catch (cause: unknown) { if (!(cause instanceof ManagedReaderFailure)) throw cause; @@ -402,7 +380,6 @@ export const inspectManagedArtifactBytes = ( const inventory = readManagedMetadataInventory( bytes, metadata, - limits, resourceDirectory, ); const issues = [...resourceIssues, ...inventory.issues]; @@ -410,7 +387,7 @@ export const inspectManagedArtifactBytes = ( ...(issues.length === 0 ? [] : [ - "At least one bounded metadata or resource item was unavailable; coverage is partial.", + "At least one metadata or resource item could not be read; coverage is partial.", ]), "Static inspection did not load the assembly, resolve dependencies through a CLR, decompile C#, or execute target code.", ]; diff --git a/src/dotnet/ManagedCilDecoding.test.ts b/src/dotnet/ManagedCilDecoding.test.ts index 74a8eb4f7b..3e11146f1a 100644 --- a/src/dotnet/ManagedCilDecoding.test.ts +++ b/src/dotnet/ManagedCilDecoding.test.ts @@ -6,7 +6,6 @@ import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; import { buildManagedPeFixture, managedPeFixtureTarget, - MANAGED_MEMBER_FIXTURE_LIMITS, } from "./ManagedPe.fixture.js"; describe("managed CIL decoding", () => { @@ -26,7 +25,6 @@ describe("managed CIL decoding", () => { const result = inspectManagedMembersBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(result.methods[0]?.body).toMatchObject({ @@ -53,7 +51,6 @@ describe("managed CIL decoding", () => { const result = inspectManagedMembersBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(result.methods[0]?.body).toMatchObject({ @@ -81,12 +78,10 @@ describe("managed CIL decoding", () => { const tiny = inspectManagedMembersBytes( tinyBytes, managedPeFixtureTarget(tinyBytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); const fat = inspectManagedMembersBytes( fatBytes, managedPeFixtureTarget(fatBytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(tiny.methods[0]?.body).toMatchObject({ diff --git a/src/dotnet/ManagedCilLimits.test.ts b/src/dotnet/ManagedCilLimits.test.ts deleted file mode 100644 index 8e0aa0b497..0000000000 --- a/src/dotnet/ManagedCilLimits.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createHash } from "node:crypto"; - -import { describe, expect, it } from "vitest"; - -import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; -import { - buildManagedPeFixture, - managedPeFixtureTarget, - MANAGED_MEMBER_FIXTURE_LIMITS, -} from "./ManagedPe.fixture.js"; - -describe("managed CIL limits", () => { - it("marks instruction-limited CIL as partial without assigning normalized identity", () => { - const bytes = buildManagedPeFixture(); - const result = inspectManagedMembersBytes( - bytes, - managedPeFixtureTarget(bytes), - { - ...MANAGED_MEMBER_FIXTURE_LIMITS, - maxMethodInstructions: 1, - }, - ); - const il = bytes.subarray(0x0a01, 0x0a0d); - - expect(result.metadata.status).toBe("complete"); - expect(result.methods[0]?.body).toMatchObject({ - status: "partial", - il_size: 12, - il_sha256: createHash("sha256").update(il).digest("hex"), - normalized_il_sha256: null, - instruction_count: 1, - decoded_instruction_count: 1, - truncated_instructions: 1, - issue: - "Instruction decode reached max_method_instructions 1 before the end of the method body", - }); - expect(result.coverage).toEqual({ - state: "partial", - issues: [ - { - code: "limit-exceeded", - scope: "method.0x06000001.body.instructions", - offset: 0x0a00, - detail: - "Instruction decode reached max_method_instructions 1 before the end of the method body", - }, - ], - }); - expect(result.limitations.join(" ")).toContain( - "has no normalized CIL identity", - ); - }); -}); diff --git a/src/dotnet/ManagedInspectionDefaults.ts b/src/dotnet/ManagedInspectionDefaults.ts deleted file mode 100644 index 2aa236eb9e..0000000000 --- a/src/dotnet/ManagedInspectionDefaults.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** Internal parser and artifact-admission safeguards for managed PE inspection. */ -export const MANAGED_INSPECTION_DEFAULTS = Object.freeze({ - maxFileBytes: 268_435_456, - maxMetadataBytes: 67_108_864, - maxTableRows: 100_000, - maxHeapItemBytes: 1_048_576, - maxMethodBodyBytes: 1_048_576, - maxMethodInstructions: 10_000, -}); diff --git a/src/dotnet/ManagedMemberInspector.test.ts b/src/dotnet/ManagedMemberInspector.test.ts index b811ddf7db..cc570595c1 100644 --- a/src/dotnet/ManagedMemberInspector.test.ts +++ b/src/dotnet/ManagedMemberInspector.test.ts @@ -4,7 +4,6 @@ import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; import { buildManagedPeFixture, managedPeFixtureTarget, - MANAGED_MEMBER_FIXTURE_LIMITS, } from "./ManagedPe.fixture.js"; describe("managed member inspection", () => { @@ -13,7 +12,6 @@ describe("managed member inspection", () => { const result = inspectManagedMembersBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(result.identity_scope).toEqual({ diff --git a/src/dotnet/ManagedMemberInspector.ts b/src/dotnet/ManagedMemberInspector.ts index a7aa58b2a1..16f2f5c5d9 100644 --- a/src/dotnet/ManagedMemberInspector.ts +++ b/src/dotnet/ManagedMemberInspector.ts @@ -14,15 +14,8 @@ import { readManagedMetadataInventory, } from "./ManagedMetadataInventory.js"; import { readManagedMetadataLayout } from "./ManagedMetadataLayout.js"; -import { - ManagedReaderFailure, - managedFailure, -} from "./ManagedReaderFailure.js"; -import { - parseTypes, - typeRanges, - type ManagedMemberInspectionLimits, -} from "./ManagedMemberInspectorCore.js"; +import { ManagedReaderFailure } from "./ManagedReaderFailure.js"; +import { parseTypes, typeRanges } from "./ManagedMemberInspectorCore.js"; import { edges, parseFields, @@ -30,12 +23,9 @@ import { parseMethods, } from "./ManagedMemberRows.js"; -export type { ManagedMemberInspectionLimits } from "./ManagedMemberInspectorCore.js"; - const unavailable = ( target: BinaryTarget, bytes: Buffer, - limits: ManagedMemberInspectionLimits, issue: ManagedParseIssue | null, ): ManagedMemberInspection => managedMemberInspectionSchema.parse({ @@ -92,30 +82,7 @@ const resourceDirectory = ( }; }; -const collectMethodBodyIssues = ( - methods: ReturnType, - limits: ManagedMemberInspectionLimits, -): ManagedParseIssue[] => - methods.methods.flatMap((method) => - method.body.status === "partial" - ? [ - { - code: "limit-exceeded" as const, - scope: `method.${method.token}.body.instructions`, - offset: method.body.file_offset, - detail: - method.body.issue ?? - `Instruction decode reached max_method_instructions ${String(limits.maxMethodInstructions)}`, - }, - ] - : [], - ); - -const readMemberInventory = ( - bytes: Buffer, - pe: ManagedPeLayout, - limits: ManagedMemberInspectionLimits, -) => { +const readMemberInventory = (bytes: Buffer, pe: ManagedPeLayout) => { const cli = pe.cli; if (cli === null) throw new TypeError("Managed inventory requires CLI metadata"); @@ -124,25 +91,14 @@ const readMemberInventory = ( cli.metadata.size, "cli.metadata", ); - if (cli.metadata.size > limits.maxMetadataBytes) - throw managedFailure( - "limit-exceeded", - "metadata.root", - `CLI metadata size exceeds max_metadata_bytes ${String(limits.maxMetadataBytes)}`, - rootOffset, - ); const layout = readManagedMetadataLayout( bytes, rootOffset, cli.metadata.size, - limits.maxTableRows, ); const inventory = readManagedMetadataInventory( bytes, layout, - { - maxHeapItemBytes: limits.maxHeapItemBytes, - }, resourceDirectory(pe), ); return { layout, inventory }; @@ -152,28 +108,24 @@ const readMemberInventory = ( export const inspectManagedMembersBytes = ( bytes: Buffer, target: BinaryTarget, - limits: ManagedMemberInspectionLimits, ): ManagedMemberInspection => { const pe = readManagedPeLayout(bytes); - if (pe.cli === null) return unavailable(target, bytes, limits, pe.cliIssue); + if (pe.cli === null) return unavailable(target, bytes, pe.cliIssue); const issues: ManagedParseIssue[] = []; try { - const { layout, inventory } = readMemberInventory(bytes, pe, limits); + const { layout, inventory } = readMemberInventory(bytes, pe); issues.push(...inventory.issues); - const ranges = typeRanges(bytes, layout, limits.maxHeapItemBytes); - const types = parseTypes(bytes, layout, ranges, limits.maxHeapItemBytes); - const fields = parseFields(bytes, layout, ranges, limits.maxHeapItemBytes); - const memberRefs = parseMemberRefs(bytes, layout, limits.maxHeapItemBytes); + const ranges = typeRanges(bytes, layout); + const types = parseTypes(bytes, layout, ranges); + const fields = parseFields(bytes, layout, ranges); + const memberRefs = parseMemberRefs(bytes, layout); const methods = parseMethods({ bytes, layout, pe, ranges, - maxBytes: limits.maxHeapItemBytes, - limits, }); - const methodBodyIssues = collectMethodBodyIssues(methods, limits); - const coverageIssues = [...issues, ...methodBodyIssues]; + const coverageIssues = issues; const related = edges( methods.methods, methods.core, @@ -212,16 +164,11 @@ export const inspectManagedMembersBytes = ( "Metadata tokens are build-local coordinates and are only meaningful with the reported artifact SHA-256 and MVID.", "CIL instruction anchors are decoded from file-backed method bodies only; no target assembly is loaded or executed.", "Signatures are decoded for common ECMA-335 primitive, class, valuetype, pointer, byref, array, and generic variable forms; unsupported forms retain raw signature hashes.", - ...(methodBodyIssues.length > 0 - ? [ - "At least one CIL method body reached max_method_instructions; its decoded prefix is partial and has no normalized CIL identity.", - ] - : []), ], }); } catch (cause: unknown) { if (cause instanceof ManagedReaderFailure) - return unavailable(target, bytes, limits, cause.issue); + return unavailable(target, bytes, cause.issue); throw cause; } }; diff --git a/src/dotnet/ManagedMemberInspectorCore.ts b/src/dotnet/ManagedMemberInspectorCore.ts index 83a4d683d4..029b1f3220 100644 --- a/src/dotnet/ManagedMemberInspectorCore.ts +++ b/src/dotnet/ManagedMemberInspectorCore.ts @@ -24,14 +24,6 @@ export type ManagedInstructionAnchor = ManagedMethodBody["anchors"][number]; export type ManagedExceptionRegion = ManagedMethodBody["exception_regions"][number]; -export interface ManagedMemberInspectionLimits { - readonly maxMetadataBytes: number; - readonly maxTableRows: number; - readonly maxHeapItemBytes: number; - readonly maxMethodBodyBytes: number; - readonly maxMethodInstructions: number; -} - export interface TypeRange { readonly token: string; readonly fullName: string; @@ -87,7 +79,6 @@ const readTypeRange = ( bytes: Buffer, layout: ManagedMetadataLayout, row: number, - maxBytes: number, ): TypeRange => { const methods = layout.table(6); const fields = layout.table(4); @@ -97,13 +88,13 @@ const readTypeRange = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); const namespace = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); cursor.readIndex(layout.codedIndexSize("TypeDefOrRef")); const fieldStart = cursor.readIndex(layout.tableIndexSize(4)); @@ -133,12 +124,11 @@ const readTypeRange = ( export const typeRanges = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, ): readonly TypeRange[] => { const typeTable = layout.table(2); const ranges: TypeRange[] = []; for (let row = 1; row <= (typeTable?.rowCount ?? 0); row += 1) - ranges.push(readTypeRange(bytes, layout, row, maxBytes)); + ranges.push(readTypeRange(bytes, layout, row)); return ranges; }; @@ -160,7 +150,6 @@ export const parseTypes = ( bytes: Buffer, layout: ManagedMetadataLayout, ranges: readonly TypeRange[], - maxBytes: number, ): readonly ManagedType[] => { const typeTable = layout.table(2); const fields = layout.table(4); @@ -173,13 +162,13 @@ export const parseTypes = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); const namespace = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); const extendsRaw = cursor.readIndex(layout.codedIndexSize("TypeDefOrRef")); const fieldStart = cursor.readIndex(layout.tableIndexSize(4)); diff --git a/src/dotnet/ManagedMemberInstructionDecoder.ts b/src/dotnet/ManagedMemberInstructionDecoder.ts index 1cc976bad0..d808b914f6 100644 --- a/src/dotnet/ManagedMemberInstructionDecoder.ts +++ b/src/dotnet/ManagedMemberInstructionDecoder.ts @@ -377,7 +377,6 @@ const readOperand = ( export const decodeInstructions = ( il: Buffer, - limit: number, ): { readonly parsed: readonly ParsedInstruction[]; readonly count: number; @@ -388,7 +387,7 @@ export const decodeInstructions = ( let offset = 0; let issue: string | null = null; try { - while (offset < il.length && parsed.length < limit) { + while (offset < il.length) { const start = offset; const first = il.readUInt8(offset); offset += 1; diff --git a/src/dotnet/ManagedMemberRows.ts b/src/dotnet/ManagedMemberRows.ts index 36ef8de8fb..6d8cb2f42c 100644 --- a/src/dotnet/ManagedMemberRows.ts +++ b/src/dotnet/ManagedMemberRows.ts @@ -7,7 +7,6 @@ import { type ManagedCallEdge, type ManagedField, type ManagedFieldAccess, - type ManagedMemberInspectionLimits, type ManagedMemberRef, type ManagedMethod, type MemberRefCore, @@ -27,7 +26,6 @@ export const parseFields = ( bytes: Buffer, layout: ManagedMetadataLayout, ranges: readonly TypeRange[], - maxBytes: number, ): { readonly fields: readonly ManagedField[]; readonly core: ReadonlyMap; @@ -42,13 +40,13 @@ export const parseFields = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); const sig = readMetadataBlob( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + layout.blob.size, ); const declared = declaringType(ranges, "field", row); const token = metadataToken(4, row); @@ -69,7 +67,6 @@ export const parseFields = ( export const parseMemberRefs = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, ): { readonly refs: readonly ManagedMemberRef[]; readonly core: ReadonlyMap; @@ -86,13 +83,13 @@ export const parseMemberRefs = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); const sig = readMetadataBlob( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + layout.blob.size, ); const token = metadataToken(10, row); refs.push({ @@ -112,8 +109,6 @@ interface ParseMethodsInput { readonly layout: ManagedMetadataLayout; readonly pe: ManagedPeLayout; readonly ranges: readonly TypeRange[]; - readonly maxBytes: number; - readonly limits: ManagedMemberInspectionLimits; } export const parseMethods = ({ @@ -121,8 +116,6 @@ export const parseMethods = ({ layout, pe, ranges, - maxBytes, - limits, }: ParseMethodsInput): { readonly methods: readonly ManagedMethod[]; readonly core: ReadonlyMap; @@ -139,13 +132,13 @@ export const parseMethods = ({ bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + layout.strings.size, ); const sig = readMetadataBlob( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + layout.blob.size, ); cursor.readIndex(layout.tableIndexSize(8)); const declared = declaringType(ranges, "method", row); @@ -160,7 +153,7 @@ export const parseMethods = ({ impl_flags: implFlags, flags, signature: signature(sig), - body: methodBody(bytes, pe, rva, limits), + body: methodBody(bytes, pe, rva), }); core.set(token, { token, name, declaringType: declared?.fullName ?? null }); } diff --git a/src/dotnet/ManagedMetadataHeaps.test.ts b/src/dotnet/ManagedMetadataHeaps.test.ts new file mode 100644 index 0000000000..c94ae40553 --- /dev/null +++ b/src/dotnet/ManagedMetadataHeaps.test.ts @@ -0,0 +1,33 @@ +import { describe, expect, it } from "vitest"; + +import { readMetadataBlob } from "./ManagedMetadataHeaps.js"; +import type { ManagedMetadataLayout } from "./ManagedMetadataLayout.js"; + +describe("managed metadata heaps", () => { + it("reads a blob item's compressed length from its ECMA-335 prefix", () => { + const itemLength = 1_048_577; + const bytes = Buffer.alloc(itemLength + 5); + bytes.set([0xc0, 0x10, 0x00, 0x01], 1); + const layout: ManagedMetadataLayout = { + rootOffset: 0, + size: bytes.length, + version: "", + streamNames: ["#Blob"], + strings: { name: "#Strings", offset: 0, size: 0 }, + guid: { name: "#GUID", offset: 0, size: 0 }, + blob: { name: "#Blob", offset: 0, size: bytes.length }, + tables: new Map(), + rowCounts: [], + stringIndexSize: 2, + guidIndexSize: 2, + blobIndexSize: 2, + table: () => undefined, + tableIndexSize: () => 2, + codedIndexSize: () => 2, + }; + + expect(readMetadataBlob(bytes, layout, 1, layout.blob.size)).toHaveLength( + itemLength, + ); + }); +}); diff --git a/src/dotnet/ManagedMetadataHeaps.ts b/src/dotnet/ManagedMetadataHeaps.ts index d53a8a7841..532e9b6371 100644 --- a/src/dotnet/ManagedMetadataHeaps.ts +++ b/src/dotnet/ManagedMetadataHeaps.ts @@ -101,12 +101,12 @@ const compressedLength = ( ); }; -/** Read one bounded UTF-8 value from #Strings. */ +/** Read one UTF-8 value from #Strings within its PE/CLI stream extent. */ export const readMetadataString = ( bytes: Buffer, layout: ManagedMetadataLayout, index: number, - maxBytes: number, + heapExtent: number, ): string => { if (index === 0) return ""; if (index < 0 || index >= layout.strings.size) @@ -119,15 +119,15 @@ export const readMetadataString = ( const start = layout.strings.offset + index; const maximum = Math.min( layout.strings.offset + layout.strings.size, - start + maxBytes + 1, + start + heapExtent + 1, ); let end = start; while (end < maximum && bytes[end] !== 0) end += 1; if (end === maximum) throw managedFailure( - "limit-exceeded", + "invalid-string", "metadata.#Strings", - `String heap item exceeds max_heap_item_bytes ${String(maxBytes)}`, + "String heap item has no terminator within #Strings", start, ); try { @@ -144,12 +144,12 @@ export const readMetadataString = ( } }; -/** Read one bounded item from #Blob without interpreting its contents. */ +/** Read one #Blob item while validating its encoded extent against the heap. */ export const readMetadataBlob = ( bytes: Buffer, layout: ManagedMetadataLayout, index: number, - maxBytes: number, + heapExtent: number, ): Buffer => { if (index === 0) return Buffer.alloc(0); if (index < 0 || index >= layout.blob.size) @@ -161,11 +161,11 @@ export const readMetadataBlob = ( ); const start = layout.blob.offset + index; const decoded = compressedLength(bytes, start, "metadata.#Blob"); - if (decoded.length > maxBytes) + if (decoded.length > heapExtent) throw managedFailure( - "limit-exceeded", + "invalid-blob", "metadata.#Blob", - `Blob heap item exceeds max_heap_item_bytes ${String(maxBytes)}`, + "Blob length exceeds the admitted heap extent", start, ); const content = start + decoded.prefix; diff --git a/src/dotnet/ManagedMetadataInventory.ts b/src/dotnet/ManagedMetadataInventory.ts index bf94107003..ed0d88d9d5 100644 --- a/src/dotnet/ManagedMetadataInventory.ts +++ b/src/dotnet/ManagedMetadataInventory.ts @@ -22,10 +22,6 @@ type AssemblyIdentity = NonNullable; type AssemblyReference = ManagedArtifactInspection["references"][number]; type ManagedResource = ManagedArtifactInspection["resources"][number]; type CustomAttribute = ManagedArtifactInspection["attributes"][number]; -export interface ManagedInventoryInput { - readonly maxHeapItemBytes: number; -} - export interface ManagedResourceDirectory { readonly offset: number; readonly size: number; @@ -74,15 +70,17 @@ const uniqueIssues = ( ...new Map(issues.map((issue) => [JSON.stringify(issue), issue])).values(), ]; +const heapExtent = (layout: ManagedMetadataLayout): number => + Math.max(layout.strings.size, layout.blob.size); + const readReferences = ( bytes: Buffer, layout: ManagedMetadataLayout, - input: ManagedInventoryInput, issues: ManagedParseIssue[], ): readonly AssemblyReference[] => readRows(layout.table(35), (row) => safeRead( - () => readAssemblyReference(bytes, layout, row, input.maxHeapItemBytes), + () => readAssemblyReference(bytes, layout, row, heapExtent(layout)), issues, ), ); @@ -90,13 +88,11 @@ const readReferences = ( const readResources = ({ bytes, layout, - input, resourceDirectory, issues, }: { readonly bytes: Buffer; readonly layout: ManagedMetadataLayout; - readonly input: ManagedInventoryInput; readonly resourceDirectory: ManagedResourceDirectory | null; readonly issues: ManagedParseIssue[]; }): readonly ManagedResource[] => @@ -107,7 +103,6 @@ const readResources = ({ bytes, layout, row, - maxBytes: input.maxHeapItemBytes, directory: resourceDirectory, issues, }), @@ -118,12 +113,11 @@ const readResources = ({ const readAttributes = ( bytes: Buffer, layout: ManagedMetadataLayout, - input: ManagedInventoryInput, issues: ManagedParseIssue[], ): readonly CustomAttribute[] => readRows(layout.table(12), (row) => safeRead( - () => readCustomAttribute(bytes, layout, row, input.maxHeapItemBytes), + () => readCustomAttribute(bytes, layout, row, heapExtent(layout)), issues, ), ); @@ -131,14 +125,13 @@ const readAttributes = ( const collectReferenceNames = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, issues: ManagedParseIssue[], ): readonly string[] => { const names: string[] = []; const referenceTable = layout.table(35); for (let row = 1; row <= (referenceTable?.rowCount ?? 0); row += 1) { const reference = safeRead( - () => readAssemblyReference(bytes, layout, row, maxBytes), + () => readAssemblyReference(bytes, layout, row, heapExtent(layout)), issues, ); if (reference !== undefined) names.push(reference.name); @@ -149,14 +142,13 @@ const collectReferenceNames = ( const collectTargetFrameworks = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, issues: ManagedParseIssue[], ): readonly string[] => { const targetFrameworks: string[] = []; const attributeTable = layout.table(12); for (let row = 1; row <= (attributeTable?.rowCount ?? 0); row += 1) { const attribute = safeRead( - () => readCustomAttribute(bytes, layout, row, maxBytes), + () => readCustomAttribute(bytes, layout, row, heapExtent(layout)), issues, ); if ( @@ -171,43 +163,29 @@ const collectTargetFrameworks = ( return targetFrameworks; }; -/** Inventory bounded identity tables without CLR reflection or execution. */ +/** Inventory identity tables without CLR reflection or execution. */ export const readManagedMetadataInventory = ( bytes: Buffer, layout: ManagedMetadataLayout, - input: ManagedInventoryInput, resourceDirectory: ManagedResourceDirectory | null, ): ManagedMetadataInventory => { const issues: ManagedParseIssue[] = []; const module = - safeRead(() => readModule(bytes, layout, input.maxHeapItemBytes), issues) ?? + safeRead(() => readModule(bytes, layout, heapExtent(layout)), issues) ?? null; const assembly = - safeRead( - () => readAssembly(bytes, layout, input.maxHeapItemBytes), - issues, - ) ?? null; - const references = readReferences(bytes, layout, input, issues); + safeRead(() => readAssembly(bytes, layout, heapExtent(layout)), issues) ?? + null; + const references = readReferences(bytes, layout, issues); const resources = readResources({ bytes, layout, - input, resourceDirectory, issues, }); - const attributes = readAttributes(bytes, layout, input, issues); - const referenceNames = collectReferenceNames( - bytes, - layout, - input.maxHeapItemBytes, - issues, - ); - const targetFrameworks = collectTargetFrameworks( - bytes, - layout, - input.maxHeapItemBytes, - issues, - ); + const attributes = readAttributes(bytes, layout, issues); + const referenceNames = collectReferenceNames(bytes, layout, issues); + const targetFrameworks = collectTargetFrameworks(bytes, layout, issues); return { module, assembly, diff --git a/src/dotnet/ManagedMetadataInventoryRows.ts b/src/dotnet/ManagedMetadataInventoryRows.ts index d06a296fc4..e8fdb5c44c 100644 --- a/src/dotnet/ManagedMetadataInventoryRows.ts +++ b/src/dotnet/ManagedMetadataInventoryRows.ts @@ -45,7 +45,7 @@ const publicKeyIdentity = ( export const readModule = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, + heapExtent: number, ): ModuleIdentity | null => { const table = layout.table(0); if (table === undefined || table.rowCount === 0) return null; @@ -55,7 +55,7 @@ export const readModule = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); const mvid = readMetadataGuid( bytes, @@ -86,7 +86,7 @@ export const readModule = ( export const readAssembly = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, + heapExtent: number, ): AssemblyIdentity | null => { const table = layout.table(32); if (table === undefined || table.rowCount === 0) return null; @@ -103,19 +103,19 @@ export const readAssembly = ( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + heapExtent, ); const name = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); const culture = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); return { name, @@ -136,7 +136,7 @@ export const readAssemblyReference = ( bytes: Buffer, layout: ManagedMetadataLayout, row: number, - maxBytes: number, + heapExtent: number, ): AssemblyReference => { const cursor = metadataRowCursor(bytes, layout, 35, row); const version = [ @@ -150,25 +150,25 @@ export const readAssemblyReference = ( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + heapExtent, ); const name = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); const culture = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); const hashValue = readMetadataBlob( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + heapExtent, ); return { name, @@ -191,7 +191,7 @@ interface ReadTypeNameContext { readonly layout: ManagedMetadataLayout; readonly table: 1 | 2; readonly row: number; - readonly maxBytes: number; + readonly heapExtent: number; } const readTypeName = ({ @@ -199,7 +199,7 @@ const readTypeName = ({ layout, table, row, - maxBytes, + heapExtent, }: ReadTypeNameContext): string | null => { const descriptor = layout.table(table); if (descriptor === undefined || row < 1 || row > descriptor.rowCount) @@ -211,13 +211,13 @@ const readTypeName = ({ bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); const namespace = readMetadataString( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); return namespace.length === 0 ? name : `${namespace}.${name}`; }; @@ -226,7 +226,7 @@ const declaringTypeForMethod = ( bytes: Buffer, layout: ManagedMetadataLayout, methodRow: number, - maxBytes: number, + heapExtent: number, ): string | null => { const types = layout.table(2); const methods = layout.table(6); @@ -244,12 +244,12 @@ const declaringTypeForMethod = ( ? methods.rowCount + 1 : methodListForType(bytes, layout, row + 1); if (methodRow < methodStart || methodRow >= methodEnd) continue; - const name = readMetadataString(bytes, layout, nameIndex, maxBytes); + const name = readMetadataString(bytes, layout, nameIndex, heapExtent); const namespace = readMetadataString( bytes, layout, namespaceIndex, - maxBytes, + heapExtent, ); return namespace.length === 0 ? name : `${namespace}.${name}`; } @@ -274,12 +274,12 @@ const attributeTypeName = ( bytes: Buffer, layout: ManagedMetadataLayout, rawType: number, - maxBytes: number, + heapExtent: number, ): string | null => { const tag = rawType & 7; const row = Math.floor(rawType / 8); if (row === 0) return null; - if (tag === 2) return declaringTypeForMethod(bytes, layout, row, maxBytes); + if (tag === 2) return declaringTypeForMethod(bytes, layout, row, heapExtent); if (tag !== 3) return null; const memberRefs = layout.table(10); if (memberRefs === undefined || row > memberRefs.rowCount) return null; @@ -288,11 +288,23 @@ const attributeTypeName = ( const parentTag = parent & 7; const parentRow = Math.floor(parent / 8); if (parentTag === 0) - return readTypeName({ bytes, layout, table: 2, row: parentRow, maxBytes }); + return readTypeName({ + bytes, + layout, + table: 2, + row: parentRow, + heapExtent, + }); if (parentTag === 1) - return readTypeName({ bytes, layout, table: 1, row: parentRow, maxBytes }); + return readTypeName({ + bytes, + layout, + table: 1, + row: parentRow, + heapExtent, + }); if (parentTag === 3) - return declaringTypeForMethod(bytes, layout, parentRow, maxBytes); + return declaringTypeForMethod(bytes, layout, parentRow, heapExtent); return null; }; @@ -345,7 +357,7 @@ export const readCustomAttribute = ( bytes: Buffer, layout: ManagedMetadataLayout, row: number, - maxBytes: number, + heapExtent: number, ): CustomAttribute => { const cursor = metadataRowCursor(bytes, layout, 12, row); const parentRaw = cursor.readIndex( @@ -358,7 +370,7 @@ export const readCustomAttribute = ( bytes, layout, cursor.readIndex(layout.blobIndexSize), - maxBytes, + heapExtent, ); const parent = metadataCodedToken( parentRaw, @@ -375,7 +387,7 @@ export const readCustomAttribute = ( "CustomAttribute parent coded index is invalid", cursor.start, ); - const typeName = attributeTypeName(bytes, layout, typeRaw, maxBytes); + const typeName = attributeTypeName(bytes, layout, typeRaw, heapExtent); return { parent_token: parent, constructor_token: metadataCodedToken(typeRaw, 3, [ @@ -400,7 +412,6 @@ interface ReadResourceContext { readonly bytes: Buffer; readonly layout: ManagedMetadataLayout; readonly row: number; - readonly maxBytes: number; readonly directory: ManagedResourceDirectory | null; readonly issues: ManagedParseIssue[]; } @@ -409,7 +420,6 @@ export const readResource = ({ bytes, layout, row, - maxBytes, directory, issues, }: ReadResourceContext): ManagedResource => { @@ -420,7 +430,7 @@ export const readResource = ({ bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + Math.max(layout.strings.size, layout.blob.size), ); const implementationRaw = cursor.readIndex( layout.codedIndexSize("Implementation"), @@ -450,13 +460,6 @@ export const readResource = ({ offset: start, detail: "Embedded resource length leaves the CLI resources directory", }); - } else if (dataLength > maxBytes) { - issues.push({ - code: "limit-exceeded", - scope: `metadata.ManifestResource:${metadataToken(40, row)}`, - offset: start + 4, - detail: `Embedded resource exceeds max_heap_item_bytes ${String(maxBytes)}; digest omitted`, - }); } else { dataSha256 = sha256Bytes( bytes.subarray(start + 4, start + 4 + dataLength), diff --git a/src/dotnet/ManagedMetadataLayout.ts b/src/dotnet/ManagedMetadataLayout.ts index 29c6551fd2..19a8740b39 100644 --- a/src/dotnet/ManagedMetadataLayout.ts +++ b/src/dotnet/ManagedMetadataLayout.ts @@ -307,8 +307,6 @@ const readTablesHeader = ( const readRowCounts = ( tableBytes: Buffer, valid: bigint, - maxTableRows: number, - tablesOffset: number, ): readonly number[] => { const rowCounts = Array(METADATA_TABLE_NAMES.length).fill(0); let tableCursor = 24; @@ -316,13 +314,6 @@ const readRowCounts = ( if ((valid & (1n << BigInt(index))) === 0n) continue; requireRange(tableBytes, tableCursor, 4, "metadata.table-row-count"); const count = tableBytes.readUInt32LE(tableCursor); - if (count > maxTableRows) - throw managedFailure( - "limit-exceeded", - `metadata.${METADATA_TABLE_NAMES[index] ?? String(index)}`, - `Metadata table row count ${String(count)} exceeds max_table_rows ${String(maxTableRows)}`, - tablesOffset + tableCursor, - ); rowCounts[index] = count; tableCursor += 4; } @@ -390,7 +381,6 @@ export const readManagedMetadataLayout = ( artifact: Buffer, rootOffset: number, size: number, - maxTableRows: number, ): ManagedMetadataLayout => { if (size < 20 || rootOffset < 0 || rootOffset > artifact.length - size) throw managedFailure( @@ -405,12 +395,7 @@ export const readManagedMetadataLayout = ( artifact, streams, ); - const rowCounts = readRowCounts( - tableBytes, - valid, - maxTableRows, - tablesOffset, - ); + const rowCounts = readRowCounts(tableBytes, valid); const tables = buildMetadataTables({ tableBytes, rowCounts, diff --git a/src/dotnet/ManagedMethodBodyReader.ts b/src/dotnet/ManagedMethodBodyReader.ts index 91495a2402..b89872aa8c 100644 --- a/src/dotnet/ManagedMethodBodyReader.ts +++ b/src/dotnet/ManagedMethodBodyReader.ts @@ -1,8 +1,5 @@ import type { ManagedPeLayout } from "./ManagedPeReader.js"; -import type { - ManagedMemberInspectionLimits, - ManagedMethodBody, -} from "./ManagedMemberInspectorCore.js"; +import type { ManagedMethodBody } from "./ManagedMemberInspectorCore.js"; import { decodeInstructions, parseExceptionRegions, @@ -56,19 +53,16 @@ export const methodBody = ( bytes: Buffer, pe: ManagedPeLayout, rva: number, - limits: ManagedMemberInspectionLimits, ): ManagedMethodBody => { if (rva === 0) return emptyMethodBody(rva, "absent", null); try { const offset = pe.rvaToOffset(rva, 1, "method.body"); const header = readMethodBodyHeader(bytes, offset); - if (header.ilSize > limits.maxMethodBodyBytes) - return oversizedMethodBody(header, rva, offset, limits); const ilOffset = offset + header.size; if (ilOffset > bytes.length - header.ilSize) throw new RangeError("Method IL bytes leave artifact"); const il = bytes.subarray(ilOffset, ilOffset + header.ilSize); - const decoded = decodeInstructions(il, limits.maxMethodInstructions); + const decoded = decodeInstructions(il); const opcodeCounts: Record = {}; for (const instruction of decoded.parsed) opcodeCounts[instruction.opcode] = @@ -126,7 +120,7 @@ export const methodBody = ( issue: decoded.issue ?? (decoded.truncated > 0 - ? `Instruction decode reached max_method_instructions ${String(limits.maxMethodInstructions)} before the end of the method body` + ? "Instruction decoding stopped before the end of the method body" : null), }; } catch (cause: unknown) { @@ -161,31 +155,3 @@ const emptyMethodBody = ( exception_regions: [], issue, }); - -const oversizedMethodBody = ( - header: MethodBodyHeader, - rva: number, - offset: number, - limits: ManagedMemberInspectionLimits, -): ManagedMethodBody => ({ - status: "too-large", - header_format: header.format, - rva, - file_offset: offset, - max_stack: header.maxStack, - init_locals: (header.flags & 0x10) !== 0, - local_var_sig_token: - header.localSig === 0 - ? null - : `0x${header.localSig.toString(16).padStart(8, "0")}`, - il_size: header.ilSize, - il_sha256: null, - normalized_il_sha256: null, - instruction_count: 0, - decoded_instruction_count: 0, - truncated_instructions: 0, - opcode_counts: {}, - anchors: [], - exception_regions: [], - issue: `Method body exceeds max_method_body_bytes ${String(limits.maxMethodBodyBytes)}`, -}); diff --git a/src/dotnet/ManagedNativeBoundaryHelpers.ts b/src/dotnet/ManagedNativeBoundaryHelpers.ts index b5a7140025..3dafe5dac7 100644 --- a/src/dotnet/ManagedNativeBoundaryHelpers.ts +++ b/src/dotnet/ManagedNativeBoundaryHelpers.ts @@ -36,7 +36,7 @@ const flagsHex = (value: number): string => export const parseModuleRefs = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, + heapExtent: number, ): readonly ModuleRef[] => { const refs: ModuleRef[] = []; const table = layout.table(26); @@ -49,7 +49,7 @@ export const parseModuleRefs = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ), }); } @@ -59,7 +59,7 @@ export const parseModuleRefs = ( export const parseFields = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, + heapExtent: number, ): ReadonlyMap => { const fields = new Map(); const table = layout.table(4); @@ -70,7 +70,7 @@ export const parseFields = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); cursor.readIndex(layout.blobIndexSize); const token = metadataToken(4, row); @@ -90,7 +90,7 @@ export const parseFields = ( export const parseMethods = ( bytes: Buffer, layout: ManagedMetadataLayout, - maxBytes: number, + heapExtent: number, ): ReadonlyMap => { const methods = new Map(); const table = layout.table(6); @@ -103,7 +103,7 @@ export const parseMethods = ( bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); cursor.readIndex(layout.blobIndexSize); cursor.readIndex(layout.tableIndexSize(8)); @@ -124,7 +124,7 @@ export const parseMethods = ( interface ParseImplMapsContext { readonly bytes: Buffer; readonly layout: ManagedMetadataLayout; - readonly maxBytes: number; + readonly heapExtent: number; readonly modules: readonly ModuleRef[]; readonly members: ReadonlyMap; } @@ -132,7 +132,7 @@ interface ParseImplMapsContext { export const parseImplMaps = ({ bytes, layout, - maxBytes, + heapExtent, modules, members, }: ParseImplMapsContext): readonly NativeImport[] => { @@ -151,7 +151,7 @@ export const parseImplMaps = ({ bytes, layout, cursor.readIndex(layout.stringIndexSize), - maxBytes, + heapExtent, ); const importScopeRow = cursor.readIndex(layout.tableIndexSize(26)); const importScopeToken = diff --git a/src/dotnet/ManagedNativeBoundaryInspector.test.ts b/src/dotnet/ManagedNativeBoundaryInspector.test.ts index 5b91d17253..a33c803bfa 100644 --- a/src/dotnet/ManagedNativeBoundaryInspector.test.ts +++ b/src/dotnet/ManagedNativeBoundaryInspector.test.ts @@ -6,8 +6,6 @@ import { buildManagedPeFixture, buildNativePeFixture, managedPeFixtureTarget, - MANAGED_MEMBER_FIXTURE_LIMITS, - MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS, } from "./ManagedPe.fixture.js"; describe("managed native boundaries", () => { @@ -23,7 +21,6 @@ describe("managed native boundaries", () => { const result = inspectManagedNativeBoundariesBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS, ); expect(result.cli_native).toMatchObject({ @@ -81,7 +78,6 @@ describe("managed native boundaries", () => { const paged = inspectManagedMembersBytes( bytes, managedPeFixtureTarget(bytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(paged.methods).toHaveLength(1); @@ -89,7 +85,6 @@ describe("managed native boundaries", () => { const native = inspectManagedMembersBytes( nativeBytes, managedPeFixtureTarget(nativeBytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(native.metadata.status).toBe("absent"); expect(native.coverage.state).toBe("unavailable"); @@ -100,7 +95,6 @@ describe("managed native boundaries", () => { const malformed = inspectManagedMembersBytes( malformedBytes, managedPeFixtureTarget(malformedBytes), - MANAGED_MEMBER_FIXTURE_LIMITS, ); expect(malformed.metadata.status).toBe("malformed"); expect(malformed.coverage.issues).toEqual([ diff --git a/src/dotnet/ManagedNativeBoundaryInspector.ts b/src/dotnet/ManagedNativeBoundaryInspector.ts index 39fe71d032..9fd96790bd 100644 --- a/src/dotnet/ManagedNativeBoundaryInspector.ts +++ b/src/dotnet/ManagedNativeBoundaryInspector.ts @@ -27,12 +27,6 @@ import { parseModuleRefs, } from "./ManagedNativeBoundaryHelpers.js"; -export interface ManagedNativeBoundaryInspectionLimits { - readonly maxMetadataBytes: number; - readonly maxTableRows: number; - readonly maxHeapItemBytes: number; -} - type Inventory = ReturnType; const emptyInspection = ( @@ -92,7 +86,6 @@ const readBoundaryInventory = ( bytes: Buffer, pe: ManagedPeLayout, cli: NonNullable, - limits: ManagedNativeBoundaryInspectionLimits, ): { readonly layout: ManagedMetadataLayout; readonly inventory: Inventory; @@ -106,7 +99,6 @@ const readBoundaryInventory = ( bytes, metadataOffset, cli.metadata.size, - limits.maxTableRows, ); const resourceDirectory: ManagedResourceDirectory = { offset: pe.rvaToOffset( @@ -119,29 +111,15 @@ const readBoundaryInventory = ( const inventory = readManagedMetadataInventory( bytes, layout, - { - maxHeapItemBytes: limits.maxHeapItemBytes, - }, resourceDirectory, ); return { layout, inventory }; }; -const metadataLimitIssue = ( - cli: NonNullable, - maxBytes: number, -): ManagedParseIssue => ({ - code: "limit-exceeded", - scope: "cli.metadata", - offset: cli.headerOffset + 8, - detail: `CLI metadata size exceeds max_metadata_bytes ${String(maxBytes)}`, -}); - /** Inspect managed/native boundary declarations from PE metadata without execution. */ export const inspectManagedNativeBoundariesBytes = ( bytes: Buffer, target: BinaryTarget, - limits: ManagedNativeBoundaryInspectionLimits, ): ManagedNativeBoundaryInspection => { let pe: ManagedPeLayout; try { @@ -157,27 +135,24 @@ export const inspectManagedNativeBoundariesBytes = ( pe.cliDirectoryPresent ? "malformed" : "not-managed", pe.cliIssue === null ? [] : [pe.cliIssue], ); - if (pe.cli.metadata.size > limits.maxMetadataBytes) - return emptyInspection(target, bytes, "malformed", [ - metadataLimitIssue(pe.cli, limits.maxMetadataBytes), - ]); let layout: ManagedMetadataLayout; let inventory: Inventory; try { - ({ layout, inventory } = readBoundaryInventory(bytes, pe, pe.cli, limits)); + ({ layout, inventory } = readBoundaryInventory(bytes, pe, pe.cli)); } catch (cause: unknown) { if (!(cause instanceof ManagedReaderFailure)) throw cause; return emptyInspection(target, bytes, "malformed", [cause.issue]); } - const moduleRefs = parseModuleRefs(bytes, layout, limits.maxHeapItemBytes); + const heapExtent = Math.max(layout.strings.size, layout.blob.size); + const moduleRefs = parseModuleRefs(bytes, layout, heapExtent); const members = new Map([ - ...parseFields(bytes, layout, limits.maxHeapItemBytes), - ...parseMethods(bytes, layout, limits.maxHeapItemBytes), + ...parseFields(bytes, layout, heapExtent), + ...parseMethods(bytes, layout, heapExtent), ]); const imports = parseImplMaps({ bytes, layout, - maxBytes: limits.maxHeapItemBytes, + heapExtent, modules: moduleRefs, members, }); diff --git a/src/dotnet/ManagedPe.fixture.ts b/src/dotnet/ManagedPe.fixture.ts index a5aa295df4..076a4cd8ec 100644 --- a/src/dotnet/ManagedPe.fixture.ts +++ b/src/dotnet/ManagedPe.fixture.ts @@ -73,29 +73,6 @@ const DEFAULT_MVID = Buffer.from([ 0xdd, 0xee, 0xff, ]); -/** Generous bounds for inventory assertions over the source-owned PE fixture. */ -export const MANAGED_ARTIFACT_FIXTURE_LIMITS = { - maxMetadataBytes: 1024 * 1024, - maxTableRows: 1_000, - maxHeapItemBytes: 1024 * 1024, -}; - -/** Generous bounds for member assertions over the source-owned PE fixture. */ -export const MANAGED_MEMBER_FIXTURE_LIMITS = { - maxMetadataBytes: 1024 * 1024, - maxTableRows: 1_000, - maxHeapItemBytes: 1024 * 1024, - maxMethodBodyBytes: 1024 * 1024, - maxMethodInstructions: 1_000, -}; - -/** Generous native-boundary bounds for the source-owned PE fixture. */ -export const MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS = { - maxMetadataBytes: 1024 * 1024, - maxTableRows: 1_000, - maxHeapItemBytes: 1024 * 1024, -}; - /** Describe fixture bytes as the managed PE target consumed by static inspectors. */ export const managedPeFixtureTarget = ( bytes: Buffer, diff --git a/src/dotnet/ManagedStaticProvider.ts b/src/dotnet/ManagedStaticProvider.ts index 690a5327b5..fa3bed3847 100644 --- a/src/dotnet/ManagedStaticProvider.ts +++ b/src/dotnet/ManagedStaticProvider.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; import { createReadStream } from "node:fs"; -import { readFile, stat } from "node:fs/promises"; +import { readFile } from "node:fs/promises"; import { createAnalysisExecution, @@ -37,7 +37,6 @@ import { err, ok } from "../domain/result.js"; import { inspectManagedArtifactBytes } from "./ManagedArtifactInspector.js"; import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js"; import { inspectManagedNativeBoundariesBytes } from "./ManagedNativeBoundaryInspector.js"; -import { MANAGED_INSPECTION_DEFAULTS } from "./ManagedInspectionDefaults.js"; const IDENTITY: ProviderIdentity = Object.freeze(MANAGED_STATIC_PROVIDER); @@ -108,29 +107,11 @@ class ManagedStaticClient implements AnalysisClient { try { if (options?.signal?.aborted === true) return err(new AnalysisCancelledError(operation)); - const maxFileBytes = MANAGED_INSPECTION_DEFAULTS.maxFileBytes; - const metadata = await stat(this.target.path); - if (metadata.size > maxFileBytes) - return err( - new AnalysisCapabilityUnavailableError( - IDENTITY.id, - operation, - `Artifact size ${String(metadata.size)} exceeds max_file_bytes ${String(maxFileBytes)}.`, - ), - ); const snapshot = this.#snapshotBytes; const observed = snapshot === undefined ? await readManagedSnapshot(this.target.path, options?.signal) : await hashManagedSource(this.target.path, options?.signal); - if (observed.byteLength > maxFileBytes) - return err( - new AnalysisCapabilityUnavailableError( - IDENTITY.id, - operation, - `Artifact grew beyond max_file_bytes ${String(maxFileBytes)} while it was read.`, - ), - ); if (observed.sha256 !== this.target.sha256) return err( new EvidenceIntegrityError( @@ -202,6 +183,8 @@ const hashManagedSource = async ( for await (const chunk of stream) { if (!Buffer.isBuffer(chunk)) throw new TypeError("Managed source stream returned non-buffer data"); + if (chunk.length > Number.MAX_SAFE_INTEGER - byteLength) + throw new RangeError("Managed source byte length overflowed"); byteLength += chunk.length; digest.update(chunk); } @@ -217,7 +200,7 @@ const limitationsFor = (operation: ManagedToolName): readonly string[] => Object.freeze( operation === "inspect_managed_artifact" ? [ - "This capability inventories PE/CLI identity only; inspect_managed_members admits bounded metadata members, signatures, and CIL anchors.", + "This capability inventories PE/CLI identity only; inspect_managed_members admits metadata members, signatures, and CIL anchors.", "It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code.", ] : operation === "inspect_managed_native_boundaries" @@ -226,7 +209,7 @@ const limitationsFor = (operation: ManagedToolName): readonly string[] => "It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code.", ] : [ - "This capability decodes bounded PE/CLI metadata members, signatures, and file-backed method bodies; decompiled C# reconstruction and cross-build member comparison are separate operations.", + "This capability decodes PE/CLI metadata members, signatures, and file-backed method bodies; decompiled C# reconstruction and cross-build member comparison are separate operations.", "It never loads the target assembly, resolves dependencies through a CLR, decompiles C#, or executes target code.", ], ); @@ -242,28 +225,14 @@ const inspectManagedOperation = ( | ManagedNativeBoundaryInspection => { if (operation === "inspect_managed_artifact") { managedArtifactInputSchema.parse(parameters); - return inspectManagedArtifactBytes(bytes, target, { - maxMetadataBytes: MANAGED_INSPECTION_DEFAULTS.maxMetadataBytes, - maxTableRows: MANAGED_INSPECTION_DEFAULTS.maxTableRows, - maxHeapItemBytes: MANAGED_INSPECTION_DEFAULTS.maxHeapItemBytes, - }); + return inspectManagedArtifactBytes(bytes, target); } if (operation === "inspect_managed_native_boundaries") { managedNativeBoundaryInputSchema.parse(parameters); - return inspectManagedNativeBoundariesBytes(bytes, target, { - maxMetadataBytes: MANAGED_INSPECTION_DEFAULTS.maxMetadataBytes, - maxTableRows: MANAGED_INSPECTION_DEFAULTS.maxTableRows, - maxHeapItemBytes: MANAGED_INSPECTION_DEFAULTS.maxHeapItemBytes, - }); + return inspectManagedNativeBoundariesBytes(bytes, target); } managedMemberInputSchema.parse(parameters); - return inspectManagedMembersBytes(bytes, target, { - maxMetadataBytes: MANAGED_INSPECTION_DEFAULTS.maxMetadataBytes, - maxTableRows: MANAGED_INSPECTION_DEFAULTS.maxTableRows, - maxHeapItemBytes: MANAGED_INSPECTION_DEFAULTS.maxHeapItemBytes, - maxMethodBodyBytes: MANAGED_INSPECTION_DEFAULTS.maxMethodBodyBytes, - maxMethodInstructions: MANAGED_INSPECTION_DEFAULTS.maxMethodInstructions, - }); + return inspectManagedMembersBytes(bytes, target); }; const managedLocations = ( diff --git a/src/server/registerManagedWorkflowTools/compareManagedMembers.ts b/src/server/registerManagedWorkflowTools/compareManagedMembers.ts index b4fefd49da..8e4522cf28 100644 --- a/src/server/registerManagedWorkflowTools/compareManagedMembers.ts +++ b/src/server/registerManagedWorkflowTools/compareManagedMembers.ts @@ -62,7 +62,7 @@ export const registerCompareManagedMembers = ( { operation: "inspect_managed_members", rationale: - "Repeat static member inspection with complete pages and method bodies.", + "Review the inspection Evidence coverage and limit diagnostics; this operation has no page override, so unresolved members must remain unknown.", }, ], relationships: [], diff --git a/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts b/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts index 4e3ed54c17..a5e0b70037 100644 --- a/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts +++ b/src/server/registerManagedWorkflowTools/verifyManagedNativeBoundaries.ts @@ -79,7 +79,7 @@ export const registerVerifyManagedNativeBoundaries = ( { operation: "inspect_managed_native_boundaries", rationale: - "Repeat managed boundary inspection with complete ModuleRef, ImplMap, and native implementation pages.", + "Review the inspection Evidence coverage and limit diagnostics; this operation has no page override, so unresolved declarations must remain unknown.", }, { operation: "analyze_function", diff --git a/tests/boundary/filesystem/managedRuntimeCorrelation.test.ts b/tests/boundary/filesystem/managedRuntimeCorrelation.test.ts index 4e0c218bf2..ffd5826971 100644 --- a/tests/boundary/filesystem/managedRuntimeCorrelation.test.ts +++ b/tests/boundary/filesystem/managedRuntimeCorrelation.test.ts @@ -22,7 +22,6 @@ import { inspectManagedMembersBytes } from "../../../src/dotnet/ManagedMemberIns import { buildManagedPeFixture, managedPeFixtureTarget, - MANAGED_MEMBER_FIXTURE_LIMITS, } from "../../../src/dotnet/ManagedPe.fixture.js"; describe("managed runtime correlation planning", () => { @@ -152,35 +151,6 @@ describe("managed runtime correlation planning", () => { if (result.ok) return; expect(result.error._tag).toBe("AnalysisInputError"); }); - - it("rejects a runtime lock for instruction-limited CIL", async () => { - const directory = await createTestTempDirectory("rea-managed-runtime-"); - const artifactPath = join(directory, "fixture.dll"); - const executablePath = join(directory, "dotnet"); - const bytes = buildManagedPeFixture(); - await writeFile(artifactPath, bytes); - await writeFile(executablePath, "#!/bin/sh\n"); - const fixture = inspect(bytes, artifactPath, { - ...MANAGED_MEMBER_FIXTURE_LIMITS, - maxMethodInstructions: 1, - }); - const authority = await authorityFor(directory, executablePath, true); - const result = await planManagedRuntimeCorrelationEvidence( - { - policy: () => enabledPolicy(directory, executablePath), - authority, - }, - inputFor(fixture.evidence, fixture.method), - ); - - expect(fixture.method.body).toMatchObject({ - status: "partial", - normalized_il_sha256: null, - }); - expect(result.ok).toBe(false); - if (result.ok) return; - expect(result.error._tag).toBe("AnalysisInputError"); - }); }); const enabledPolicy = ( @@ -248,13 +218,9 @@ const inputFor = ( }, }); -const inspect = ( - bytes: Buffer, - path: string, - limits: typeof MANAGED_MEMBER_FIXTURE_LIMITS = MANAGED_MEMBER_FIXTURE_LIMITS, -) => { +const inspect = (bytes: Buffer, path: string) => { const target = managedPeFixtureTarget(bytes, path); - const result = inspectManagedMembersBytes(bytes, target, limits); + const result = inspectManagedMembersBytes(bytes, target); const method = result.methods[0]; if (method === undefined) throw new Error("fixture has no method"); return { diff --git a/tests/composition/analysis-sessions/managedApplicationGraph.test.ts b/tests/composition/analysis-sessions/managedApplicationGraph.test.ts index 52af30050f..59e1f75fb1 100644 --- a/tests/composition/analysis-sessions/managedApplicationGraph.test.ts +++ b/tests/composition/analysis-sessions/managedApplicationGraph.test.ts @@ -14,9 +14,6 @@ import { inspectManagedNativeBoundariesBytes } from "../../../src/dotnet/Managed import { buildManagedPeFixture, managedPeFixtureTarget, - MANAGED_ARTIFACT_FIXTURE_LIMITS, - MANAGED_MEMBER_FIXTURE_LIMITS, - MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS, } from "../../../src/dotnet/ManagedPe.fixture.js"; describe("managed application graph projection", () => { @@ -29,21 +26,9 @@ describe("managed application graph projection", () => { }, }); const binary = managedPeFixtureTarget(bytes, "/fixture/ManagedInterop.exe"); - const managedArtifact = inspectManagedArtifactBytes( - bytes, - binary, - MANAGED_ARTIFACT_FIXTURE_LIMITS, - ); - const members = inspectManagedMembersBytes( - bytes, - binary, - MANAGED_MEMBER_FIXTURE_LIMITS, - ); - const boundaries = inspectManagedNativeBoundariesBytes( - bytes, - binary, - MANAGED_NATIVE_BOUNDARY_FIXTURE_LIMITS, - ); + const managedArtifact = inspectManagedArtifactBytes(bytes, binary); + const members = inspectManagedMembersBytes(bytes, binary); + const boundaries = inspectManagedNativeBoundariesBytes(bytes, binary); const artifactEvidence = createEvidence(binary, MANAGED_STATIC_PROVIDER, { operation: "inspect_managed_artifact", parameters: {}, @@ -185,21 +170,17 @@ describe("managed application graph coverage", () => { it("preserves partial parser coverage in graph and per-fact coverage", () => { const bytes = buildManagedPeFixture(); const binary = managedPeFixtureTarget(bytes, "/fixture/ManagedInterop.exe"); - const members = inspectManagedMembersBytes( - bytes, - binary, - MANAGED_MEMBER_FIXTURE_LIMITS, - ); + const members = inspectManagedMembersBytes(bytes, binary); const parserPartialMembers = { ...members, coverage: { state: "partial" as const, issues: [ { - code: "limit-exceeded" as const, - scope: "method.0x06000001.body.instructions", + code: "invalid-blob" as const, + scope: "metadata.#Blob", offset: 0x0a00, - detail: "instruction limit reached", + detail: "Blob content leaves #Blob", }, ], },