mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
Merge remote-tracking branch 'origin/main' into fix/nativeaot-pe-virtual-extent
This commit is contained in:
@@ -176,6 +176,9 @@ const startStorageSite = async () => {
|
||||
origin: `http://127.0.0.1:${port}`,
|
||||
close: async () => {
|
||||
server.close();
|
||||
// Chrome can leave speculative TCP connections without an HTTP request;
|
||||
// server.close() alone waits indefinitely for those owned sockets.
|
||||
server.closeAllConnections();
|
||||
await once(server, "close");
|
||||
},
|
||||
};
|
||||
|
||||
@@ -277,11 +277,13 @@ try {
|
||||
throw new Error("Scenario attachment terminated its external browser");
|
||||
|
||||
const profilesBefore = await scenarioProfiles();
|
||||
process.stderr.write("Browser verifier: scenario environment\n");
|
||||
const scenarioEnvironment = await verifyScenarioEnvironment(
|
||||
endpoint,
|
||||
target,
|
||||
site.origin,
|
||||
);
|
||||
process.stderr.write("Browser verifier: scenario storage\n");
|
||||
const scenarioStorage = await verifyScenarioStorage({
|
||||
executable,
|
||||
endpoint,
|
||||
@@ -339,7 +341,9 @@ try {
|
||||
`Browser scenario included unselected structured value: ${unselectedValue}`,
|
||||
);
|
||||
|
||||
process.stderr.write("Browser verifier: large screenshot\n");
|
||||
const largeScreenshot = await verifyLargeScreenshotE2e(endpoint, site.origin);
|
||||
process.stderr.write("Browser verifier: popup coverage\n");
|
||||
const popupEvents = await verifyPopupEventCoverage(executable);
|
||||
const networkEvidence = await verifyBrowserNetworkEvidence(
|
||||
executable,
|
||||
|
||||
@@ -28,6 +28,11 @@ interface Section {
|
||||
readonly size: number;
|
||||
}
|
||||
|
||||
// The image loader maps VirtualSize bytes, using SizeOfRawData only when
|
||||
// VirtualSize is zero. Raw bytes past that extent are file-alignment padding.
|
||||
const virtualExtent = (section: Section): number =>
|
||||
section.virtualSize === 0 ? section.size : section.virtualSize;
|
||||
|
||||
/** Header admission and unambiguous file-backed RVA mapping, independent of section names. */
|
||||
export const readPeResourceLayout = (bytes: Buffer) => {
|
||||
requirePeRange(bytes, 0, 64);
|
||||
@@ -64,7 +69,7 @@ export const readPeResourceLayout = (bytes: Buffer) => {
|
||||
offset: bytes.readUInt32LE(at + 20),
|
||||
size: bytes.readUInt32LE(at + 16),
|
||||
};
|
||||
if (section.rva + Math.max(section.virtualSize, section.size) > 0x100000000)
|
||||
if (section.rva + virtualExtent(section) > 0x100000000)
|
||||
peFailure("PE section RVA range overflows.");
|
||||
if (section.size > 0) {
|
||||
requirePeRange(bytes, section.offset, section.size);
|
||||
@@ -90,15 +95,14 @@ export const readPeResourceLayout = (bytes: Buffer) => {
|
||||
if (rva + size > 0x100000000) peFailure("Resource RVA range overflows.");
|
||||
const candidates = sections.filter(
|
||||
(section) =>
|
||||
rva >= section.rva &&
|
||||
rva - section.rva < Math.max(section.size, section.virtualSize),
|
||||
rva >= section.rva && rva - section.rva < virtualExtent(section),
|
||||
);
|
||||
if (rva < headerSize) {
|
||||
if (
|
||||
sections.some(
|
||||
(section) =>
|
||||
section.rva < rva + size &&
|
||||
section.rva + Math.max(section.size, section.virtualSize) > rva,
|
||||
section.rva + virtualExtent(section) > rva,
|
||||
) ||
|
||||
size > headerSize - rva
|
||||
)
|
||||
@@ -111,6 +115,8 @@ export const readPeResourceLayout = (bytes: Buffer) => {
|
||||
const section = candidates[0];
|
||||
if (section === undefined) return peFailure("Missing resource section.");
|
||||
const within = rva - section.rva;
|
||||
if (size > virtualExtent(section) - within)
|
||||
peFailure("Resource range leaves its section's virtual size.");
|
||||
if (within > section.size || size > section.size - within)
|
||||
peFailure("Resource range is not entirely file-backed in one section.");
|
||||
// A second section starting inside the requested interval is ambiguous too.
|
||||
@@ -119,7 +125,7 @@ export const readPeResourceLayout = (bytes: Buffer) => {
|
||||
(other) =>
|
||||
other !== section &&
|
||||
other.rva < rva + size &&
|
||||
other.rva + Math.max(other.size, other.virtualSize) > rva,
|
||||
other.rva + virtualExtent(other) > rva,
|
||||
)
|
||||
)
|
||||
peFailure("Resource interval intersects overlapping virtual sections.");
|
||||
|
||||
@@ -259,3 +259,62 @@ describe("PE header admission and bounded traversal", () => {
|
||||
await expect(parsing).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("PE resource section virtual extent", () => {
|
||||
const sectionSizes = (fixture: ReturnType<typeof peResourceFixture>) => {
|
||||
const virtualSize = fixture.bytes.readUInt32LE(fixture.sectionAt + 8);
|
||||
const rawSize = fixture.bytes.readUInt32LE(fixture.sectionAt + 16);
|
||||
expect(rawSize - virtualSize).toBeGreaterThanOrEqual(8);
|
||||
return { virtualSize, rawSize };
|
||||
};
|
||||
|
||||
it.each([
|
||||
[0, "Resource RVA has missing or overlapping section mappings."],
|
||||
[-2, "Resource range leaves its section's virtual size."],
|
||||
])(
|
||||
"rejects a payload reaching raw alignment padding past VirtualSize (start %i)",
|
||||
async (start, message) => {
|
||||
const fixture = peResourceFixture();
|
||||
const { virtualSize } = sectionSizes(fixture);
|
||||
const rva = 0x2000 + virtualSize + start;
|
||||
fixture.bytes.writeUInt32LE(rva, fixture.dataOffsets[0]);
|
||||
fixture.bytes.writeUInt32LE(4, (fixture.dataOffsets[0] ?? 0) + 4);
|
||||
await expect(parse(fixture.bytes)).rejects.toMatchObject({
|
||||
reason: "format",
|
||||
message,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("maps raw bytes when VirtualSize is zero, as the image loader does", async () => {
|
||||
const fixture = peResourceFixture();
|
||||
fixture.bytes.writeUInt32LE(0, fixture.sectionAt + 8);
|
||||
expect(await parse(fixture.bytes)).toMatchObject({
|
||||
coverage: { status: "complete", resources: 5 },
|
||||
});
|
||||
});
|
||||
|
||||
it("selects a later section that starts inside earlier raw padding", async () => {
|
||||
const fixture = peResourceFixture();
|
||||
const { virtualSize } = sectionSizes(fixture);
|
||||
const laterRva = 0x2000 + Math.ceil(virtualSize / 4) * 4;
|
||||
const rawOffset = fixture.bytes.length;
|
||||
const payload = Buffer.from("LATE");
|
||||
const bytes = Buffer.concat([fixture.bytes, payload]);
|
||||
bytes.writeUInt16LE(2, 0x86);
|
||||
const later = fixture.sectionAt + 40;
|
||||
bytes.writeUInt32LE(payload.length, later + 8);
|
||||
bytes.writeUInt32LE(laterRva, later + 12);
|
||||
bytes.writeUInt32LE(payload.length, later + 16);
|
||||
bytes.writeUInt32LE(rawOffset, later + 20);
|
||||
bytes.writeUInt32LE(laterRva, fixture.dataOffsets[0]);
|
||||
bytes.writeUInt32LE(payload.length, (fixture.dataOffsets[0] ?? 0) + 4);
|
||||
|
||||
const result = peResourcesSchema.parse(await parse(bytes));
|
||||
expect(result.resources[0]?.payload).toMatchObject({
|
||||
rva: laterRva,
|
||||
location: { offset: rawOffset, bytes: payload.length },
|
||||
sha256: createHash("sha256").update(payload).digest("hex"),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user