mirror of
https://github.com/morluto/rea.git
synced 2026-11-04 22:06:10 +00:00
fix(managed): report metadata range failures at artifact file offsets (#3508)
ManagedMetadataLayout's range check reported offsets relative to the buffer it was given: the metadata root for version and stream headers, and the table stream for the tables header, row counts and table data. Other diagnostics in the same reader already use artifact file offsets, so a truncated table stream at file offset 888 was reported at offset 24, inside the DOS header. Pass each buffer's artifact file offset to the range check so every invalid-metadata-root issue carries a file offset. Fixes #3364 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7666a482b6
commit
2a9252e4e6
2 files changed
+58
-10
No files matched your search
@@ -153,7 +153,8 @@ describe("CLR ExtraData index widths and boundaries", () => {
|
||||
|
||||
it("reports a truncated row count before interpreting ExtraData", () => {
|
||||
const bytes = buildManagedPeFixture({ metadataExtraData: 0x1234_5678 });
|
||||
const { streamHeader, extraDataOffset } = fixtureTablesStream(bytes);
|
||||
const { streamHeader, tablesOffset, extraDataOffset } =
|
||||
fixtureTablesStream(bytes);
|
||||
bytes.writeUInt32LE(extraDataOffset - 1, streamHeader + 4);
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
@@ -166,6 +167,8 @@ describe("CLR ExtraData index widths and boundaries", () => {
|
||||
expect.objectContaining({
|
||||
code: "invalid-metadata-root",
|
||||
scope: "metadata.table-row-count",
|
||||
// The last row count's artifact file offset, not its stream offset.
|
||||
offset: tablesOffset + extraDataOffset - 4,
|
||||
}),
|
||||
],
|
||||
});
|
||||
@@ -173,6 +176,24 @@ describe("CLR ExtraData index widths and boundaries", () => {
|
||||
expect(result.methods).toEqual([]);
|
||||
});
|
||||
|
||||
it("reports a stream extent beyond the metadata root at its file offset", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const { streamHeader, tablesOffset } = fixtureTablesStream(bytes);
|
||||
bytes.writeUInt32LE(0xffff_ffff, streamHeader + 4);
|
||||
const result = inspectManagedMembersBytes(
|
||||
bytes,
|
||||
managedPeFixtureTarget(bytes),
|
||||
);
|
||||
|
||||
expect(result.coverage.issues).toEqual([
|
||||
expect.objectContaining({
|
||||
code: "invalid-metadata-root",
|
||||
scope: "metadata.#~",
|
||||
offset: tablesOffset,
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
it.each([0, 1, 3])(
|
||||
"rejects an ExtraData word with only %i declared bytes",
|
||||
(availableBytes) => {
|
||||
|
||||
@@ -116,8 +116,10 @@ const CODED_INDEXES = {
|
||||
|
||||
const SUPPORTED_TABLE_MASK = (1n << BigInt(METADATA_TABLE_NAMES.length)) - 1n;
|
||||
|
||||
/** Check a range within `bytes`, which start at artifact file offset `base`. */
|
||||
const requireRange = (
|
||||
bytes: Buffer,
|
||||
base: number,
|
||||
offset: number,
|
||||
length: number,
|
||||
scope: string,
|
||||
@@ -133,7 +135,7 @@ const requireRange = (
|
||||
"invalid-metadata-root",
|
||||
scope,
|
||||
`${scope} leaves the metadata byte range`,
|
||||
Number.isSafeInteger(offset) && offset >= 0 ? offset : null,
|
||||
Number.isSafeInteger(base + offset) && offset >= 0 ? base + offset : null,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -224,7 +226,7 @@ const readMetadataStreams = (
|
||||
rootOffset,
|
||||
);
|
||||
const versionLength = bytes.readUInt32LE(12);
|
||||
requireRange(bytes, 16, versionLength, "metadata.version");
|
||||
requireRange(bytes, rootOffset, 16, versionLength, "metadata.version");
|
||||
const versionBytes = bytes.subarray(16, 16 + versionLength);
|
||||
const zero = versionBytes.indexOf(0);
|
||||
const decodedVersionLength = zero < 0 ? versionBytes.length : zero;
|
||||
@@ -233,7 +235,7 @@ const readMetadataStreams = (
|
||||
.subarray(0, decodedVersionLength)
|
||||
.toString("utf8");
|
||||
let cursor = align4(16 + versionLength);
|
||||
requireRange(bytes, cursor, 4, "metadata.stream-count");
|
||||
requireRange(bytes, rootOffset, cursor, 4, "metadata.stream-count");
|
||||
const streamCount = bytes.readUInt16LE(cursor + 2);
|
||||
if (streamCount === 0 || streamCount > 64)
|
||||
throw managedFailure(
|
||||
@@ -245,7 +247,7 @@ const readMetadataStreams = (
|
||||
cursor += 4;
|
||||
const streams = new Map<string, MetadataStream>();
|
||||
for (let index = 0; index < streamCount; index += 1) {
|
||||
requireRange(bytes, cursor, 8, "metadata.stream-header");
|
||||
requireRange(bytes, rootOffset, cursor, 8, "metadata.stream-header");
|
||||
const relativeOffset = bytes.readUInt32LE(cursor);
|
||||
const streamSize = bytes.readUInt32LE(cursor + 4);
|
||||
const named = readStreamName(bytes, cursor + 8);
|
||||
@@ -256,7 +258,13 @@ const readMetadataStreams = (
|
||||
`Duplicate metadata stream ${named.name}`,
|
||||
rootOffset + cursor,
|
||||
);
|
||||
requireRange(bytes, relativeOffset, streamSize, `metadata.${named.name}`);
|
||||
requireRange(
|
||||
bytes,
|
||||
rootOffset,
|
||||
relativeOffset,
|
||||
streamSize,
|
||||
`metadata.${named.name}`,
|
||||
);
|
||||
streams.set(named.name, {
|
||||
name: named.name,
|
||||
offset: rootOffset + relativeOffset,
|
||||
@@ -295,7 +303,13 @@ const readTablesHeader = (
|
||||
tablesStream.offset,
|
||||
tablesStream.offset + tablesStream.size,
|
||||
);
|
||||
requireRange(tableBytes, 0, 24, "metadata.tables-header");
|
||||
requireRange(
|
||||
tableBytes,
|
||||
tablesStream.offset,
|
||||
0,
|
||||
24,
|
||||
"metadata.tables-header",
|
||||
);
|
||||
const heapSizes = tableBytes[6] ?? 0;
|
||||
const heaps = {
|
||||
string: (heapSizes & 0x01) === 0 ? (2 as const) : (4 as const),
|
||||
@@ -315,13 +329,20 @@ const readTablesHeader = (
|
||||
|
||||
const readRowCounts = (
|
||||
tableBytes: Buffer,
|
||||
tablesOffset: number,
|
||||
valid: bigint,
|
||||
): readonly number[] => {
|
||||
const rowCounts = Array<number>(METADATA_TABLE_NAMES.length).fill(0);
|
||||
let tableCursor = 24;
|
||||
for (let index = 0; index < METADATA_TABLE_NAMES.length; index += 1) {
|
||||
if ((valid & (1n << BigInt(index))) === 0n) continue;
|
||||
requireRange(tableBytes, tableCursor, 4, "metadata.table-row-count");
|
||||
requireRange(
|
||||
tableBytes,
|
||||
tablesOffset,
|
||||
tableCursor,
|
||||
4,
|
||||
"metadata.table-row-count",
|
||||
);
|
||||
const count = tableBytes.readUInt32LE(tableCursor);
|
||||
rowCounts[index] = count;
|
||||
tableCursor += 4;
|
||||
@@ -384,7 +405,13 @@ const buildMetadataTables = ({
|
||||
"Metadata table byte size overflowed",
|
||||
tablesOffset + tableCursor,
|
||||
);
|
||||
requireRange(tableBytes, tableCursor, byteLength, "metadata.table-data");
|
||||
requireRange(
|
||||
tableBytes,
|
||||
tablesOffset,
|
||||
tableCursor,
|
||||
byteLength,
|
||||
"metadata.table-data",
|
||||
);
|
||||
tables.set(index, {
|
||||
index,
|
||||
name: METADATA_TABLE_NAMES[index] ?? `Table${String(index)}`,
|
||||
@@ -417,7 +444,7 @@ export const readManagedMetadataLayout = (
|
||||
artifact,
|
||||
streams,
|
||||
);
|
||||
const rowCounts = readRowCounts(tableBytes, valid);
|
||||
const rowCounts = readRowCounts(tableBytes, tablesOffset, valid);
|
||||
const tables = buildMetadataTables({
|
||||
tableBytes,
|
||||
rowCounts,
|
||||
|
||||
Reference in new issue
Block a user