mirror of
https://github.com/morluto/rea.git
synced 2026-10-31 03:45:39 +00:00
fix(il2cpp): validate structural record extents (#7861)
This commit is contained in:
1 parent
5119d08daf
commit
183e2e2d1e
6 files changed
+227
-81
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
checkRange,
|
||||
checkedSectionCount,
|
||||
readIndex,
|
||||
Il2cppMetadataFormatFailure,
|
||||
} from "./Il2cppMetadataFormat.js";
|
||||
@@ -236,16 +237,6 @@ const recordBytes = (section: Il2cppMetadataSection): number | null => {
|
||||
return section.size / count;
|
||||
};
|
||||
|
||||
/** Entry count of a section whose header records only its size. */
|
||||
const entriesIn = (section: Il2cppMetadataSection, entryBytes: number) => {
|
||||
if (section.size % entryBytes !== 0)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section ${section.name} size ${section.size} is not a whole multiple of its ${entryBytes}-byte records`,
|
||||
);
|
||||
return section.size / entryBytes;
|
||||
};
|
||||
|
||||
const FIXED_WIDTHS: Il2cppMetadataDecoded["index_widths"] = {
|
||||
type_definition: 4,
|
||||
generic_container: 4,
|
||||
@@ -260,15 +251,6 @@ const resolveWidths = (
|
||||
section: (name: SectionName) => Il2cppMetadataSection,
|
||||
): Il2cppMetadataDecoded["index_widths"] => {
|
||||
const genericContainers = section("genericContainers");
|
||||
const genericContainerBytes = recordBytes(genericContainers);
|
||||
if (
|
||||
genericContainerBytes !== null &&
|
||||
genericContainerBytes !== GENERIC_CONTAINER_RECORD_BYTES
|
||||
)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section genericContainers records are ${genericContainerBytes} bytes, but each record is ${GENERIC_CONTAINER_RECORD_BYTES}`,
|
||||
);
|
||||
const type_definition = il2cppIndexWidth(
|
||||
headerCount(section("typeDefinitions")),
|
||||
);
|
||||
@@ -308,36 +290,54 @@ const recordCounts = (
|
||||
version: Il2cppMetadataVersion,
|
||||
section: (name: SectionName) => Il2cppMetadataSection,
|
||||
expected: Readonly<Record<RecordSection, number>>,
|
||||
): Readonly<Record<RecordSection | "nestedTypes", number>> => {
|
||||
widths: Il2cppMetadataDecoded["index_widths"],
|
||||
): Readonly<
|
||||
Record<
|
||||
RecordSection | "nestedTypes" | "genericContainers" | "genericParameters",
|
||||
number
|
||||
>
|
||||
> => {
|
||||
const genericContainers = checkedSectionCount(
|
||||
section("genericContainers"),
|
||||
GENERIC_CONTAINER_RECORD_BYTES,
|
||||
);
|
||||
const genericParameters = checkedSectionCount(
|
||||
section("genericParameters"),
|
||||
widths.generic_container + 12,
|
||||
);
|
||||
const names = ["typeDefinitions", "images", "methods"] as const;
|
||||
if (VERSION_LAYOUTS[version].indexing === "fixed") {
|
||||
checkedSectionCount(section("interfaceOffsets"), widths.type + 4);
|
||||
const sized = Object.fromEntries(
|
||||
names.map((name) => [name, entriesIn(section(name), expected[name])]),
|
||||
names.map((name) => [
|
||||
name,
|
||||
checkedSectionCount(section(name), expected[name]),
|
||||
]),
|
||||
) as Record<RecordSection, number>;
|
||||
return {
|
||||
...sized,
|
||||
nestedTypes: entriesIn(section("nestedTypes"), NESTED_TYPE_ENTRY_BYTES),
|
||||
nestedTypes: checkedSectionCount(
|
||||
section("nestedTypes"),
|
||||
NESTED_TYPE_ENTRY_BYTES,
|
||||
),
|
||||
genericContainers,
|
||||
genericParameters,
|
||||
};
|
||||
}
|
||||
for (const name of names) {
|
||||
const actual = recordBytes(section(name));
|
||||
if (actual !== null && actual !== expected[name])
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section ${name} records are ${actual} bytes, but the index widths imply ${expected[name]}`,
|
||||
);
|
||||
}
|
||||
const nestedTypes = section("nestedTypes");
|
||||
if (nestedTypes.size !== headerCount(nestedTypes) * NESTED_TYPE_ENTRY_BYTES)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section nestedTypes size ${nestedTypes.size} does not hold ${String(nestedTypes.count)} 4-byte entries`,
|
||||
);
|
||||
const counted = Object.fromEntries(
|
||||
names.map((name) => [
|
||||
name,
|
||||
checkedSectionCount(section(name), expected[name]),
|
||||
]),
|
||||
) as Record<RecordSection, number>;
|
||||
return {
|
||||
typeDefinitions: headerCount(section("typeDefinitions")),
|
||||
images: headerCount(section("images")),
|
||||
methods: headerCount(section("methods")),
|
||||
nestedTypes: headerCount(nestedTypes),
|
||||
...counted,
|
||||
nestedTypes: checkedSectionCount(
|
||||
section("nestedTypes"),
|
||||
NESTED_TYPE_ENTRY_BYTES,
|
||||
),
|
||||
genericContainers,
|
||||
genericParameters,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -522,9 +522,7 @@ function* readTypes(tables: MetadataTables): Generator<
|
||||
events: [] as MetadataOwnerRange[],
|
||||
};
|
||||
const genericContainerCount =
|
||||
tables.linkFacts === undefined
|
||||
? 0
|
||||
: entriesIn(section("genericContainers"), GENERIC_CONTAINER_RECORD_BYTES);
|
||||
tables.linkFacts === undefined ? 0 : tables.counts.genericContainers;
|
||||
for (let index = 0; index < counts.typeDefinitions; index++) {
|
||||
if (index % 256 === 0) yield;
|
||||
const at = typeDefinitions.offset + index * sizes.typeDefinitions;
|
||||
@@ -810,11 +808,8 @@ const readGenericContainer = (tables: MetadataTables, index: number) => {
|
||||
/** Type and method container indices and container records, read on demand. */
|
||||
const createGenericContainerReaders = (tables: MetadataTables) => {
|
||||
const typeCount = tables.counts.typeDefinitions;
|
||||
const containers = tables.section("genericContainers");
|
||||
const containerCount =
|
||||
tables.indexing === "counted"
|
||||
? 0
|
||||
: Math.floor(containers.size / GENERIC_CONTAINER_RECORD_BYTES);
|
||||
tables.indexing === "counted" ? 0 : tables.counts.genericContainers;
|
||||
const inRange = (index: number, count: number) =>
|
||||
Number.isInteger(index) && index >= 0 && index < count;
|
||||
const methods = tables.section("methods"),
|
||||
@@ -845,12 +840,7 @@ const createGenericContainerReaders = (tables: MetadataTables) => {
|
||||
const createGenericParameterReader = (tables: MetadataTables) => {
|
||||
const parameters = tables.section("genericParameters"),
|
||||
width = tables.widths.generic_container + 12;
|
||||
const count = entriesIn(parameters, width);
|
||||
if (parameters.count !== null && count !== parameters.count)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
"generic parameter count disagrees with its extent",
|
||||
);
|
||||
const count = tables.counts.genericParameters;
|
||||
return (index: number): string | undefined => {
|
||||
if (!Number.isInteger(index) || index < 0 || index >= count)
|
||||
return undefined;
|
||||
@@ -911,7 +901,7 @@ function* readMetadataCoreSteps(
|
||||
section,
|
||||
widths,
|
||||
recordBytes: expected,
|
||||
counts: recordCounts(version, section, expected),
|
||||
counts: recordCounts(version, section, expected, widths),
|
||||
typeDefinitionTypeIndices: layout.typeDefinitionTypeIndices,
|
||||
indexing: layout.indexing,
|
||||
methodReturnParameterToken: layout.methodReturnParameterToken,
|
||||
|
||||
@@ -38,6 +38,25 @@ export class Il2cppMetadataFormatFailure extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
/** Validate a section against a known serialized record width once. */
|
||||
export const checkedSectionCount = (
|
||||
section: Il2cppMetadataSection,
|
||||
recordBytes: number,
|
||||
): number => {
|
||||
if (section.size % recordBytes !== 0)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section ${section.name} size ${section.size} is not a whole multiple of its ${recordBytes}-byte records`,
|
||||
);
|
||||
const count = section.size / recordBytes;
|
||||
if (section.count !== null && count !== section.count)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section ${section.name} size ${section.size} does not hold ${section.count} ${recordBytes}-byte records`,
|
||||
);
|
||||
return count;
|
||||
};
|
||||
|
||||
export const checkRange = (
|
||||
label: string,
|
||||
start: number,
|
||||
@@ -64,3 +83,4 @@ export const readIndex = (bytes: Buffer, offset: number, width: 1 | 2 | 4) => {
|
||||
}
|
||||
return bytes.readInt32LE(offset);
|
||||
};
|
||||
import type { Il2cppMetadataSection } from "../domain/il2cpp/il2cppMetadata.js";
|
||||
@@ -1,9 +1,7 @@
|
||||
import type {
|
||||
Il2cppMetadata,
|
||||
Il2cppMetadataSection,
|
||||
} from "../domain/il2cpp/il2cppMetadata.js";
|
||||
import type { Il2cppMetadata } from "../domain/il2cpp/il2cppMetadata.js";
|
||||
import {
|
||||
checkRange,
|
||||
checkedSectionCount,
|
||||
readIndex,
|
||||
Il2cppMetadataFormatFailure,
|
||||
} from "./Il2cppMetadataFormat.js";
|
||||
@@ -24,21 +22,6 @@ export interface MetadataMemberOwners {
|
||||
readonly parameters: readonly MetadataOwnerRange[];
|
||||
}
|
||||
|
||||
const entries = (
|
||||
indexing: MetadataTables["indexing"],
|
||||
section: Il2cppMetadataSection,
|
||||
recordBytes: number,
|
||||
) => {
|
||||
const count =
|
||||
indexing === "counted" ? (section.count ?? 0) : section.size / recordBytes;
|
||||
if (!Number.isInteger(count) || section.size !== count * recordBytes)
|
||||
throw new Il2cppMetadataFormatFailure(
|
||||
"record-size-mismatch",
|
||||
`section ${section.name} size ${section.size} does not hold whole ${recordBytes}-byte records`,
|
||||
);
|
||||
return count;
|
||||
};
|
||||
|
||||
/** Own member record widths and validate all counts before any row allocation. */
|
||||
export const readIl2cppMemberTables = ({
|
||||
widths,
|
||||
@@ -52,8 +35,7 @@ export const readIl2cppMemberTables = ({
|
||||
parameters: 8 + widths.type,
|
||||
stringLiterals: indexing === "counted" ? 4 : 8,
|
||||
};
|
||||
const literalRecords = entries(
|
||||
indexing,
|
||||
const literalRecords = checkedSectionCount(
|
||||
section("stringLiterals"),
|
||||
recordBytes.stringLiterals,
|
||||
);
|
||||
@@ -61,15 +43,13 @@ export const readIl2cppMemberTables = ({
|
||||
recordBytes,
|
||||
literalRecords,
|
||||
counts: {
|
||||
fields: entries(indexing, section("fields"), recordBytes.fields),
|
||||
properties: entries(
|
||||
indexing,
|
||||
fields: checkedSectionCount(section("fields"), recordBytes.fields),
|
||||
properties: checkedSectionCount(
|
||||
section("properties"),
|
||||
recordBytes.properties,
|
||||
),
|
||||
events: entries(indexing, section("events"), recordBytes.events),
|
||||
parameters: entries(
|
||||
indexing,
|
||||
events: checkedSectionCount(section("events"), recordBytes.events),
|
||||
parameters: checkedSectionCount(
|
||||
section("parameters"),
|
||||
recordBytes.parameters,
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user