mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 03:31:35 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be2ac55513 |
No files matched your search
@@ -196,6 +196,28 @@ describe("ELF dependency identity and failures", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it.each(["unmapped strings", "invalid load sizes", "truncated load bytes"])(
|
||||
"does not derive library names from %s",
|
||||
async (failure) => {
|
||||
const valid = elfImage({ needed: ["libc.so.6"] });
|
||||
expect(await dependencies(valid)).toMatchObject({
|
||||
ok: true,
|
||||
value: { needed: ["libc.so.6"] },
|
||||
});
|
||||
const malformed = Buffer.from(valid);
|
||||
if (failure === "unmapped strings")
|
||||
// The NUL at the beginning of DT_STRTAB is mapped; the library name is not.
|
||||
malformed.writeBigUInt64LE(177n, 64 + 32);
|
||||
else if (failure === "invalid load sizes")
|
||||
malformed.writeBigUInt64LE(0n, 64 + 40);
|
||||
else {
|
||||
malformed.writeBigUInt64LE(BigInt(valid.length + 1), 64 + 32);
|
||||
malformed.writeBigUInt64LE(BigInt(valid.length + 1), 64 + 40);
|
||||
}
|
||||
expect(await dependencies(malformed)).toMatchObject({ ok: false });
|
||||
},
|
||||
);
|
||||
|
||||
it("refuses a script, a truncated image and a dynamic table without DT_NULL", async () => {
|
||||
const script = Buffer.from('#!/bin/sh\nexec /opt/app/app "$@"\n');
|
||||
expect(await dependencies(script)).toEqual({
|
||||
@@ -220,3 +242,91 @@ describe("ELF dependency identity and failures", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
"rejects conflicting string-table mappings and accepts identical aliases (reversed: %s)",
|
||||
async (reversed) => {
|
||||
const base = elfImage({ needed: ["libc.so.6"] });
|
||||
const alternate = Buffer.from("\0libevilso\0");
|
||||
const headersAt = base.length + alternate.length;
|
||||
const image = Buffer.concat([base, alternate, Buffer.alloc(3 * 56)]);
|
||||
base.copy(image, headersAt, 64, 64 + 2 * 56);
|
||||
image.writeBigUInt64LE(BigInt(headersAt), 32);
|
||||
image.writeUInt16LE(3, 56);
|
||||
const alias = headersAt + 2 * 56;
|
||||
image.writeUInt32LE(1, alias);
|
||||
image.writeBigUInt64LE(BigInt(base.length), alias + 8);
|
||||
image.writeBigUInt64LE(176n, alias + 16);
|
||||
image.writeBigUInt64LE(BigInt(alternate.length), alias + 32);
|
||||
image.writeBigUInt64LE(BigInt(alternate.length), alias + 40);
|
||||
if (reversed) {
|
||||
const first = Buffer.from(image.subarray(headersAt, headersAt + 56));
|
||||
image.copy(image, headersAt, alias, alias + 56);
|
||||
first.copy(image, alias);
|
||||
}
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
kind: "unreadable",
|
||||
identity: { machine: 183, bits: 64, byte_order: "little" },
|
||||
reason: "ELF dynamic string table has ambiguous file mappings",
|
||||
},
|
||||
});
|
||||
// Two load declarations that translate to the same bytes are not ambiguous.
|
||||
const aliasAt = reversed ? headersAt : alias;
|
||||
image.writeBigUInt64LE(176n, aliasAt + 8);
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: true,
|
||||
value: { needed: ["libc.so.6"] },
|
||||
});
|
||||
// A conflicting mapping of even one string byte keeps the table ambiguous.
|
||||
image.writeBigUInt64LE(177n, aliasAt + 16);
|
||||
image.writeBigUInt64LE(BigInt(base.length + 1), aliasAt + 8);
|
||||
image.writeBigUInt64LE(1n, aliasAt + 32);
|
||||
image.writeBigUInt64LE(1n, aliasAt + 40);
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
kind: "unreadable",
|
||||
identity: { machine: 183, bits: 64, byte_order: "little" },
|
||||
reason: "ELF dynamic string table has ambiguous file mappings",
|
||||
},
|
||||
});
|
||||
image.writeBigUInt64LE(177n, aliasAt + 8);
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: true,
|
||||
value: { needed: ["libc.so.6"] },
|
||||
});
|
||||
// Zero-fill overlaps conflict with the file-backed table as well.
|
||||
image.writeBigUInt64LE(0n, aliasAt + 32);
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
kind: "unreadable",
|
||||
identity: { machine: 183, bits: 64, byte_order: "little" },
|
||||
reason: "ELF dynamic string table has ambiguous file mappings",
|
||||
},
|
||||
});
|
||||
// Consistent adjacent declarations can cover the table together.
|
||||
const originalAt = reversed ? alias : headersAt;
|
||||
image.writeBigUInt64LE(177n, originalAt + 32);
|
||||
image.writeBigUInt64LE(177n, originalAt + 40);
|
||||
image.writeBigUInt64LE(177n, aliasAt + 8);
|
||||
image.writeBigUInt64LE(BigInt(alternate.length - 1), aliasAt + 32);
|
||||
image.writeBigUInt64LE(BigInt(alternate.length - 1), aliasAt + 40);
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: true,
|
||||
value: { needed: ["libc.so.6"] },
|
||||
});
|
||||
image.writeBigUInt64LE(BigInt(base.length), originalAt + 32);
|
||||
image.writeBigUInt64LE(BigInt(base.length), originalAt + 40);
|
||||
// An empty load has no file bytes to admit, regardless of its file offset.
|
||||
image.writeBigUInt64LE(0x100000n, aliasAt + 8);
|
||||
image.writeBigUInt64LE(0n, aliasAt + 32);
|
||||
image.writeBigUInt64LE(0n, aliasAt + 40);
|
||||
expect(await dependencies(image)).toMatchObject({
|
||||
ok: true,
|
||||
value: { needed: ["libc.so.6"] },
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
readElfHeaderFacts,
|
||||
} from "../elfFixedHeader.js";
|
||||
import {
|
||||
admitElfLoadSegment,
|
||||
readElfProgramHeader,
|
||||
type ElfProgramHeader,
|
||||
} from "../elfProgramHeader.js";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
observeElfStringTableEntry,
|
||||
} from "../elfStringTable.js";
|
||||
import { err, ok, type Result } from "../result.js";
|
||||
import { resolveMappedFileOffset } from "../virtualFileMapping.js";
|
||||
|
||||
/** Read up to `length` bytes at `offset`; fewer at the end of the file. */
|
||||
export type ReadAt = (offset: number, length: number) => Promise<Uint8Array>;
|
||||
@@ -79,21 +81,6 @@ const readExactly = async (
|
||||
return bytes.byteLength === Number(length) ? Buffer.from(bytes) : undefined;
|
||||
};
|
||||
|
||||
/** The file offset of a virtual address inside a file-backed PT_LOAD segment. */
|
||||
const fileOffset = (
|
||||
loads: readonly ElfProgramHeader[],
|
||||
address: bigint,
|
||||
): bigint | undefined => {
|
||||
const load = loads.find(
|
||||
(segment) =>
|
||||
address >= segment.virtualAddress &&
|
||||
address < segment.virtualAddress + segment.fileSize,
|
||||
);
|
||||
return load === undefined
|
||||
? undefined
|
||||
: load.offset + address - load.virtualAddress;
|
||||
};
|
||||
|
||||
const programHeaders = async (
|
||||
read: ReadAt,
|
||||
header: Buffer,
|
||||
@@ -130,6 +117,15 @@ const programHeaders = async (
|
||||
little,
|
||||
);
|
||||
if (!parsed.ok) return err(parsed.error.message);
|
||||
if (parsed.value.type === PT_LOAD) {
|
||||
const admitted = admitElfLoadSegment(parsed.value, bits);
|
||||
if (!admitted.ok) return err(admitted.error.message);
|
||||
if (
|
||||
parsed.value.fileSize > 0n &&
|
||||
parsed.value.offset + parsed.value.fileSize > BigInt(size)
|
||||
)
|
||||
return err("ELF load segment is truncated");
|
||||
}
|
||||
headers.push(parsed.value);
|
||||
}
|
||||
return ok(headers);
|
||||
@@ -317,18 +313,36 @@ const readDependencyTables = async (
|
||||
const tags = dynamicTags(table, bits, little, policy);
|
||||
if (!tags.ok) return tags;
|
||||
const { strtab, strsz, needed, runpath, rpath } = tags.value;
|
||||
const stringsAt =
|
||||
strtab === undefined
|
||||
? undefined
|
||||
: fileOffset(
|
||||
headers.value.filter((program) => program.type === PT_LOAD),
|
||||
strtab,
|
||||
);
|
||||
if (stringsAt === undefined || strsz === undefined)
|
||||
return needed.size + runpath.size + rpath.size === 0
|
||||
? ok(none)
|
||||
: err("ELF dynamic string table is not mapped from the file");
|
||||
const strings = { size, at: stringsAt, bytes: strsz };
|
||||
if (needed.size + runpath.size + rpath.size === 0) return ok(none);
|
||||
if (strtab === undefined || strsz === undefined)
|
||||
return err("ELF dynamic string table is not mapped from the file");
|
||||
if (strsz > BigInt(size))
|
||||
return err("ELF dynamic string table is not mapped from the file");
|
||||
const loads = headers.value.filter((program) => program.type === PT_LOAD);
|
||||
const stringsAt = resolveMappedFileOffset(
|
||||
loads
|
||||
.filter((segment) => segment.fileSize > 0n)
|
||||
.map((segment) => ({
|
||||
address: segment.virtualAddress,
|
||||
offset: Number(segment.offset),
|
||||
size: Number(segment.fileSize),
|
||||
})),
|
||||
strtab,
|
||||
Number(strsz),
|
||||
loads
|
||||
.filter((segment) => segment.memorySize > segment.fileSize)
|
||||
.map((segment) => ({
|
||||
address: segment.virtualAddress + segment.fileSize,
|
||||
size: segment.memorySize - segment.fileSize,
|
||||
})),
|
||||
);
|
||||
if (!stringsAt.ok)
|
||||
return err(
|
||||
stringsAt.error.reason === "ambiguous"
|
||||
? "ELF dynamic string table has ambiguous file mappings"
|
||||
: "ELF dynamic string table is not mapped from the file",
|
||||
);
|
||||
const strings = { size, at: BigInt(stringsAt.value), bytes: strsz };
|
||||
const names = await dynamicStrings(read, strings, needed, policy);
|
||||
if (!names.ok) return names;
|
||||
const search = await dynamicStrings(
|
||||
|
||||
Reference in new issue
Block a user