Files
rea/src/dotnet/ManagedCilDecoding.test.ts
T
morluto b5e98915c6 fix(tools)!: simplify direct invocation and harden boundary contracts (#555)
Remove REA permission grants, scope ceilings, approval confirmations, and inert settings. Preserve selected local evidence, accept caller-chosen commands and paths, and report actual runtime failures.

Harden provider results, schema serialization, snapshot evidence binding, archive paths, source maps, native screenshots, managed EH parsing, and cancellation cleanup. Regenerate tool metadata and document direct invocation.

BREAKING CHANGE: Removed permission configuration and policy commands, approval fields, filesystem scope inputs, and fixed confirmation options. Process capture inherits the host environment and accepts command names; callers must refresh tool schemas and migrate removed fields.
2026-10-05 20:16:44 +08:00

189 lines
5.6 KiB
TypeScript

import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import { inspectManagedMembersBytes } from "./ManagedMemberInspector.js";
import {
buildManagedPeFixture,
managedPeFixtureTarget,
} from "./ManagedPe.fixture.js";
const managedBodyWithSections = (...sections: Buffer[]) => {
const header = Buffer.alloc(12);
header.writeUInt16LE(0x300b, 0);
header.writeUInt16LE(8, 2);
header.writeUInt32LE(1, 4);
const bytes = buildManagedPeFixture({
ilBody: Buffer.concat([
header,
Buffer.from([0x2a]),
Buffer.alloc(3),
...sections,
]),
});
return inspectManagedMembersBytes(bytes, managedPeFixtureTarget(bytes))
.methods[0]?.body;
};
describe("managed CIL decoding", () => {
it("reads fat method header size from the full flags-and-size word", () => {
const il = Buffer.from([
0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x22, 0x00,
0x00, 0x80, 0x3f, 0x26, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
0x3f, 0x26, 0xfe, 0x06, 0x01, 0x00, 0x00, 0x06, 0x26, 0x2a,
]);
const header = Buffer.alloc(12);
header.writeUInt16LE(0x3013, 0);
header.writeUInt16LE(8, 2);
header.writeUInt32LE(il.length, 4);
const bytes = buildManagedPeFixture({
ilBody: Buffer.concat([header, il]),
});
const result = inspectManagedMembersBytes(
bytes,
managedPeFixtureTarget(bytes),
);
expect(result.methods[0]?.body).toMatchObject({
status: "present",
header_format: "fat",
il_size: il.length,
il_sha256: createHash("sha256").update(il).digest("hex"),
opcode_counts: {
"ldc.i8": 1,
pop: 4,
"ldc.r4": 1,
"ldc.r8": 1,
ldftn: 1,
ret: 1,
},
issue: null,
});
});
it("does not normalize reserved CIL opcodes as operand-free instructions", () => {
const bytes = buildManagedPeFixture({
ilBody: Buffer.from([0x0a, 0x24, 0x2a]),
});
const result = inspectManagedMembersBytes(
bytes,
managedPeFixtureTarget(bytes),
);
expect(result.methods[0]?.body).toMatchObject({
status: "malformed",
il_size: 2,
normalized_il_sha256: null,
decoded_instruction_count: 0,
issue: "Unsupported CIL opcode 0x24 at IL offset 0",
});
});
it("keeps the documented decoded-CIL v1 golden vector stable", () => {
const il = Buffer.from([0x00, 0x2a]);
const tinyBytes = buildManagedPeFixture({
ilBody: Buffer.from([0x0a, ...il]),
});
const fatHeader = Buffer.alloc(12);
fatHeader.writeUInt16LE(0x3013, 0);
fatHeader.writeUInt16LE(32, 2);
fatHeader.writeUInt32LE(il.length, 4);
fatHeader.writeUInt32LE(0x1100_0001, 8);
const fatBytes = buildManagedPeFixture({
ilBody: Buffer.concat([fatHeader, il]),
});
const tiny = inspectManagedMembersBytes(
tinyBytes,
managedPeFixtureTarget(tinyBytes),
);
const fat = inspectManagedMembersBytes(
fatBytes,
managedPeFixtureTarget(fatBytes),
);
expect(tiny.methods[0]?.body).toMatchObject({
status: "present",
header_format: "tiny",
max_stack: 8,
init_locals: false,
local_var_sig_token: null,
il_size: 2,
il_sha256: createHash("sha256").update(il).digest("hex"),
normalized_il_sha256:
"5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636",
opcode_counts: { nop: 1, ret: 1 },
exception_regions: [],
});
expect(fat.methods[0]?.body).toMatchObject({
header_format: "fat",
max_stack: 32,
init_locals: true,
local_var_sig_token: "0x11000001",
il_sha256: tiny.methods[0]?.body.il_sha256,
normalized_il_sha256: tiny.methods[0]?.body.normalized_il_sha256,
});
});
});
describe("managed exception section decoding", () => {
it("marks an exception section with a partial clause as malformed", () => {
const section = Buffer.alloc(17);
section[0] = 0x01;
section[1] = section.length;
expect(managedBodyWithSections(section)).toMatchObject({
status: "malformed",
exception_regions: [],
issue: "Exception section size does not contain whole clauses",
});
});
it("reads every chained exception section", () => {
const first = Buffer.alloc(16);
first[0] = 0x81;
first[1] = first.length;
const second = Buffer.alloc(16);
second[0] = 0x01;
second[1] = second.length;
expect(managedBodyWithSections(first, second)).toMatchObject({
status: "present",
issue: null,
exception_regions: [
{ flags: 0, try_offset: 0, try_length: 0, handler_offset: 0 },
{ flags: 0, try_offset: 0, try_length: 0, handler_offset: 0 },
],
});
});
it("reports malformed data in a chained section", () => {
const first = Buffer.alloc(16);
first[0] = 0x81;
first[1] = first.length;
const second = Buffer.from([0x02, 0x04, 0x00, 0x00]);
expect(managedBodyWithSections(first, second)).toMatchObject({
status: "malformed",
exception_regions: [],
issue: "Unsupported method data section kind 2",
});
});
it.each([
{
name: "unsupported section kind",
section: Buffer.from([0x02, 0x04, 0x00, 0x00]),
issue: "Unsupported method data section kind 2",
},
{
name: "section size outside artifact",
section: Buffer.from([0x41, 0xff, 0xff, 0xff]),
issue: "Exception section size leaves artifact",
},
])("rejects $name", ({ section, issue }) => {
expect(managedBodyWithSections(section)).toMatchObject({
status: "malformed",
exception_regions: [],
issue,
});
});
});