fix(dotnet): accept legacy small exception section sizes

Apply CoreCLR-compatible clause counting while validating and advancing by the physical section extent. Preserve malformed, reserved, chained and truncation checks.

Validation: managed decoder suite passes; no native compiler or runtime verification claimed.

Fixes #3279
This commit is contained in:
morluto
2026-10-14 16:53:13 +08:00
parent 459c0951f2
commit ee5cfbe05c
2 changed files with 94 additions and 3 deletions
+67
View File
@@ -7,6 +7,7 @@ import {
buildManagedPeFixture,
managedPeFixtureTarget,
} from "./ManagedPe.fixture.js";
import { parseExceptionRegions } from "./ManagedMemberInstructionDecoder.js";
const managedBodyWithSections = (...sections: Buffer[]) => {
const header = Buffer.alloc(12);
@@ -666,6 +667,59 @@ describe("managed exception region flags and filters", () => {
});
describe("managed exception section decoding", () => {
it("reads legacy small EH DataSize without counting its header", () => {
const section = Buffer.alloc(16);
section[0] = 0x01;
section[1] = 12;
expect(managedBodyWithSections(section)).toMatchObject({
status: "present",
issue: null,
exception_regions: [expect.any(Object)],
normalized_il_sha256: expect.any(String),
});
});
it("advances a chained legacy section by its physical size", () => {
const legacy = Buffer.alloc(16);
legacy[0] = 0x81;
legacy[1] = 12;
const conformant = Buffer.alloc(16);
conformant[0] = 0x01;
conformant[1] = 16;
expect(managedBodyWithSections(legacy, conformant)).toMatchObject({
status: "present",
issue: null,
exception_regions: [expect.any(Object), expect.any(Object)],
normalized_il_sha256: expect.any(String),
});
});
it("rejects a legacy-sized clause whose physical bytes leave the artifact", () => {
const section = Buffer.alloc(12);
section[0] = 0x01;
section[1] = 12;
expect(parseExceptionRegions(section, 0, section.length)).toMatchObject({
status: "malformed",
regions: [],
issue: "Exception section size leaves artifact",
});
});
it("still rejects a small EH DataSize that matches neither layout", () => {
const section = Buffer.alloc(17);
section[0] = 0x01;
section[1] = 13;
expect(managedBodyWithSections(section)).toMatchObject({
status: "malformed",
exception_regions: [],
issue: "Exception section size does not contain whole clauses",
});
});
it("marks an exception section with a partial clause as malformed", () => {
const section = Buffer.alloc(17);
section[0] = 0x01;
@@ -695,6 +749,19 @@ describe("managed exception section decoding", () => {
});
});
it("keeps small section reserved bytes invalid", () => {
const section = Buffer.alloc(16);
section[0] = 0x01;
section[1] = 16;
section[2] = 1;
expect(managedBodyWithSections(section)).toMatchObject({
status: "malformed",
exception_regions: [],
issue: "Small exception section reserved bytes are nonzero",
});
});
it("preserves decoded clauses when a chained section has an unsupported kind", () => {
const first = Buffer.alloc(16);
first[0] = 0x81;
+27 -3
View File
@@ -543,11 +543,36 @@ export const parseExceptionRegions = (
"Exception section size leaves artifact",
regions,
);
if ((size - 4) % clauseSize !== 0)
let count: number;
let sectionSize = size;
if (fat) {
if ((size - 4) % clauseSize !== 0)
return malformedExceptionRegions(
"Exception section size does not contain whole clauses",
regions,
);
count = (size - 4) / clauseSize;
} else if ((size - 4) % clauseSize === 0) {
count = (size - 4) / clauseSize;
} else if (size >= clauseSize && size % clauseSize === 0) {
// CoreCLR accommodates legacy VB/MC++ sections whose DataSize omits the
// four-byte section header. DataSize is still not the physical extent.
count = size / clauseSize;
sectionSize = 4 + count * clauseSize;
} else {
return malformedExceptionRegions(
"Exception section size does not contain whole clauses",
regions,
);
}
if (
sectionOffset > methodEnd - sectionSize ||
sectionOffset > bytes.length - sectionSize
)
return malformedExceptionRegions(
"Exception section size leaves artifact",
regions,
);
if (
!fat &&
(bytes[sectionOffset + 2] !== 0 || bytes[sectionOffset + 3] !== 0)
@@ -557,7 +582,6 @@ export const parseExceptionRegions = (
regions,
);
const count = (size - 4) / clauseSize;
// Accumulates with the method IL bound; the body reserves what it retains.
budget?.admitTransient(count * 256, sectionOffset);
for (const region of readExceptionClauses(
@@ -569,7 +593,7 @@ export const parseExceptionRegions = (
regions.push(region);
hasMoreSections = (kind & 0x80) !== 0;
sectionOffset += size;
sectionOffset += sectionSize;
}
return { status: "complete", regions };
};