mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 00:36:35 +00:00
fix(apple): validate complete image mappings with indexed VM lookup
This commit is contained in:
1 parent
b23f8288b4
commit
d39543145e
2 files changed
+53
-11
No files matched your search
@@ -193,6 +193,32 @@ class CacheFile {
|
||||
}
|
||||
}
|
||||
|
||||
interface CacheRegion {
|
||||
readonly file: CacheFile;
|
||||
readonly address: number;
|
||||
readonly size: number;
|
||||
readonly fileOffset: number;
|
||||
}
|
||||
|
||||
/** Validated disjoint regions are sorted once; all VM lookups use logarithmic search. */
|
||||
const cacheRegionAt = (
|
||||
regions: readonly CacheRegion[],
|
||||
address: number,
|
||||
): CacheRegion | undefined => {
|
||||
let low = 0;
|
||||
let high = regions.length;
|
||||
while (low < high) {
|
||||
const middle = Math.floor((low + high) / 2);
|
||||
const region = regions[middle];
|
||||
if (region !== undefined && region.address <= address) low = middle + 1;
|
||||
else high = middle;
|
||||
}
|
||||
const region = regions[low - 1];
|
||||
return region !== undefined && address < region.address + region.size
|
||||
? region
|
||||
: undefined;
|
||||
};
|
||||
|
||||
interface ParsedHeader {
|
||||
readonly magic: string;
|
||||
readonly uuid: string;
|
||||
@@ -314,12 +340,7 @@ export class DyldSharedCache {
|
||||
readonly address: number;
|
||||
}[],
|
||||
private readonly files: readonly CacheFile[],
|
||||
private readonly regions: readonly {
|
||||
readonly file: CacheFile;
|
||||
readonly address: number;
|
||||
readonly size: number;
|
||||
readonly fileOffset: number;
|
||||
}[],
|
||||
private readonly regions: readonly CacheRegion[],
|
||||
) {
|
||||
this.#byPath = new Map(images.map((image) => [image.path, image]));
|
||||
}
|
||||
@@ -418,7 +439,17 @@ export class DyldSharedCache {
|
||||
`Dyld cache VM mappings overlap at ${hex(current.address)} between ${previous.file.suffix || "main"} and ${current.file.suffix || "main"}`,
|
||||
);
|
||||
}
|
||||
return new DyldSharedCache(header, images, files, regions);
|
||||
if (statuses.every(({ status }) => status === "present")) {
|
||||
const unmapped = images.find(
|
||||
({ address }) => cacheRegionAt(ordered, address) === undefined,
|
||||
);
|
||||
if (unmapped !== undefined)
|
||||
throw new ArtifactReaderFailure(
|
||||
"format",
|
||||
`Complete dyld cache has an unmapped image address ${hex(unmapped.address)}: ${unmapped.path}`,
|
||||
);
|
||||
}
|
||||
return new DyldSharedCache(header, images, files, ordered);
|
||||
} catch (cause: unknown) {
|
||||
await Promise.allSettled(files.map(({ handle }) => handle.close()));
|
||||
throw cause;
|
||||
@@ -590,10 +621,7 @@ export class DyldSharedCache {
|
||||
}
|
||||
|
||||
#region(address: number) {
|
||||
return this.regions.find(
|
||||
(region) =>
|
||||
address >= region.address && address < region.address + region.size,
|
||||
);
|
||||
return cacheRegionAt(this.regions, address);
|
||||
}
|
||||
|
||||
/** Parse one cached image's load commands through the cache's VM mappings. */
|
||||
|
||||
@@ -54,6 +54,20 @@ const dylib = (installName: string, dependencies: readonly Uint8Array[] = []) =>
|
||||
commands: [dylibCommand(LC.ID_DYLIB, installName), ...dependencies],
|
||||
});
|
||||
|
||||
it("rejects unmapped images when no unavailable subcache can explain the address", async () => {
|
||||
const fixture = dyldCacheFixture(IMAGES);
|
||||
const main = Buffer.from(fixture.main);
|
||||
main.writeBigUInt64LE(0x700000000n, main.readUInt32LE(0x1c0));
|
||||
const path = await writeCache({ ...fixture, main });
|
||||
expect(await inspectDyldSharedCache({ cache_path: path })).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
reason: "format",
|
||||
detail: expect.stringContaining("unmapped image address"),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
const writeCache = async (
|
||||
fixture: CacheFixture,
|
||||
name = "dyld_shared_cache_arm64e",
|
||||
|
||||
Reference in new issue
Block a user