Files
reverse-engineering-anything/src/application/MipsGnuAttributes.ts
T

102 lines
3.7 KiB
TypeScript

import { readElfAttributeUleb } from "../domain/elfAttributeUleb.js";
import type { MipsGnuAttributes } from "../domain/binaryTargetTypes.js";
import { err, ok, type Result } from "../domain/result.js";
/** Bound the section read before its declared size can drive allocation. */
export const MIPS_GNU_ATTRIBUTES_MAX_BYTES = 4096;
const FORMAT_VERSION = 0x41;
const TAG_FILE = 1;
const TAG_GNU_MIPS_ABI_FP = 4;
const TAG_GNU_MIPS_ABI_MSA = 8;
/**
* Read only GNU file-scope MIPS ABI attributes REA understands. Unknown
* vendors, scopes and tags fail closed; their declarations are not skipped.
*/
export const parseMipsGnuAttributes = (
bytes: Buffer,
littleEndian: boolean,
): Result<MipsGnuAttributes, string> => {
if (bytes.length === 0 || bytes[0] !== FORMAT_VERSION)
return err("unsupported MIPS GNU attribute section format version");
const readU32 = (offset: number): number =>
littleEndian ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
let fpAbi: number | null = null;
let msa: number | null = null;
let vendorSeen = false;
let position = 1;
while (position < bytes.length) {
if (bytes.length - position < 4)
return err("truncated MIPS GNU attribute subsection length");
const length = readU32(position);
if (length < 4 || length > bytes.length - position)
return err(
"MIPS GNU attribute subsection length does not fit the section",
);
const end = position + length;
const vendorEnd = bytes.indexOf(0, position + 4);
if (vendorEnd < 0 || vendorEnd >= end)
return err("unterminated MIPS GNU attribute vendor name");
if (bytes.toString("latin1", position + 4, vendorEnd) !== "gnu")
return err(
"MIPS ELF declares non-GNU vendor attributes that REA does not inspect",
);
if (vendorSeen)
return err("duplicate MIPS GNU attribute vendor subsections");
vendorSeen = true;
let cursor = vendorEnd + 1;
while (cursor < end) {
const scopeStart = cursor;
const scope = readElfAttributeUleb(bytes, cursor, end);
if (scope === undefined)
return err("malformed MIPS GNU attribute scope tag");
if (scope.value !== TAG_FILE)
return err(
"MIPS GNU attributes scoped to sections or symbols are not inspected by REA",
);
if (end - scope.next < 4)
return err("truncated MIPS GNU attribute scope length");
const scopeLength = readU32(scope.next);
if (
scopeLength < scope.next + 4 - scopeStart ||
scopeLength > end - scopeStart
)
return err(
"MIPS GNU attribute scope length does not fit its subsection",
);
const scopeEnd = scopeStart + scopeLength;
cursor = scope.next + 4;
while (cursor < scopeEnd) {
const tag = readElfAttributeUleb(bytes, cursor, scopeEnd);
if (tag === undefined) return err("malformed MIPS GNU attribute tag");
if (
tag.value !== TAG_GNU_MIPS_ABI_FP &&
tag.value !== TAG_GNU_MIPS_ABI_MSA
)
return err(
`MIPS ELF declares GNU attribute tag ${tag.value}, which REA does not inspect`,
);
const value = readElfAttributeUleb(bytes, tag.next, scopeEnd);
if (value === undefined)
return err("malformed MIPS GNU attribute value");
if (tag.value === TAG_GNU_MIPS_ABI_FP) {
if (fpAbi !== null)
return err("duplicate MIPS GNU floating-point ABI attributes");
fpAbi = value.value;
} else {
if (msa !== null) return err("duplicate MIPS GNU MSA ABI attributes");
msa = value.value;
}
cursor = value.next;
}
cursor = scopeEnd;
}
position = end;
}
return ok({ fpAbi, msa });
};