[GH-ISSUE #5915] [Bug] MIPS16 shared-load coverage ignores RELR declarations #3593

Closed
opened 2026-10-31 00:42:12 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 21, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/5915

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

ELF32 MIPS16 shared-image source inspection reports complete relocation coverage when a nonempty standard RELR table has not been examined. The MIPS16 admission predicate returns no refusal, and the Ghidra profile retains the complete/zero relocation result.

A focused filesystem-boundary test reproduced this with both byte orders:

  • A RELR table encoding two relocation destinations reports zero relocations, including when an SHT_RELR section describes it.
  • A mixed REL/RELR input reports only its one conventional REL record; the two RELR destinations disappear from the inventory.
  • A standalone SHT_RELR section also bypasses the additional-section-table refusal.

These are executed source-parser, admission-predicate and profile-projection observations. No Ghidra import, CLI command or MCP transport request was executed, and no incorrect pseudocode or relocated pointer value is claimed.

Reproduction

The focused test below ran with one worker: 14 tests passed, recording the current behavior for seven shapes in both byte orders. It includes ordinary REL and empty-table controls, plus a RELA control that is refused as expected. Its assertions deliberately describe the observed behavior at this revision.

For a rerun, save it as tests/boundary/filesystem/mipsRelrCoverage.test.ts beside the existing shared-load tests and run:

./node_modules/.bin/vitest run tests/boundary/filesystem/mipsRelrCoverage.test.ts --maxWorkers 1
import assert from "node:assert/strict";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { it } from "vitest";
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
import { ghidraMipsProfileParameters, ghidraMipsUnsupportedReason } from "../../../src/ghidra/GhidraMipsProfile.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";

for (const little of [true, false]) {
  for (const mode of ["empty", "rel", "rela", "relr", "relr-section", "mixed", "section-only"]) {
    it(`inspects ${mode} ${little ? "LE" : "BE"}`, async () => {
      const bytes = Buffer.alloc(4160);
      const u16 = (value: number, offset: number) => little ? bytes.writeUInt16LE(value, offset) : bytes.writeUInt16BE(value, offset);
      const u32 = (value: number, offset: number) => little ? bytes.writeUInt32LE(value, offset) : bytes.writeUInt32BE(value, offset);
      bytes.set([0x7f, 0x45, 0x4c, 0x46, 1, little ? 1 : 2, 1]);
      u16(3,16); u16(8,18); u32(1,20); u32(52,28); u32(0x74001005,36);
      u16(52,40); u16(32,42); u16(2,44);
      u32(1,52); u32(bytes.length,68); u32(bytes.length,72); u32(7,76); u32(4,80);
      u32(2,84); u32(256,88); u32(256,92); u32(256,96); u32(6,108); u32(4,112);
      const tags: [number,number][] = [];
      if (mode === "rel" || mode === "mixed") tags.push([17,4096],[18,8],[19,8]);
      if (mode === "rela") tags.push([7,4096],[8,12],[9,12]);
      if (["relr", "relr-section", "mixed"].includes(mode)) tags.push([36,4128],[35,8],[37,4]);
      tags.push([0,0]);
      tags.forEach(([tag,value], i) => { u32(tag,256+i*8); u32(value,260+i*8); });
      u32(tags.length*8,100); u32(tags.length*8,104);
      // One conventional REL record. RELR is a separate address + bitmap pair.
      u32(512,4096); u32(3,4100); u32(0x301,512);
      u32(516,4128); u32(3,4132);
      // RELR addends reside in destination slots, including an odd MIPS16 pointer.
      u32(0x305,516); u32(0x309,520);
      if (mode === "relr-section" || mode === "section-only") {
        u32(1024,32); u16(40,46); u16(2,48);
        const sh = 1064;
        u32(19,sh+4); u32(2,sh+8); u32(4128,sh+12); u32(4128,sh+16);
        u32(8,sh+20); u32(4,sh+32); u32(4,sh+36);
      }
      const directory = await createTestTempDirectory("rea-relr-coverage-");
      const path = join(directory,"shared.elf");
      await writeFile(path,bytes);
      const result = await parseBinaryTarget(path);
      assert.ok(result.ok);
      const target = result.value;
      assert.ok(target.kind === "executable" && target.architecture === "mips");
      const reason = ghidraMipsUnsupportedReason(target);
      console.log(JSON.stringify({mode,endian:little?"LE":"BE",sharedLoad:target.mips.sharedLoad,reason,profile:ghidraMipsProfileParameters(target).mips_elf}));
      if (mode === "rela") {
        assert.equal(target.mips.sharedLoad?.status,"unavailable");
        assert.match(reason ?? "",/RELA or PLT/);
      } else {
        const conventional = mode === "rel" || mode === "mixed";
        assert.deepEqual(target.mips.sharedLoad,{status:"complete",relocationCount:conventional?1:0,relocationTypes:conventional?[3]:[],symbolRelativeCount:0});
        assert.equal(reason,null);
      }
    });
  }
}

Expected behavior

Source coverage must remain unavailable, with a useful reason, for nonempty relocation declarations that the bounded MIPS16 profile has not inspected and verified. Alternatively, a supported RELR implementation must examine the declarations and represent their coverage accurately. An additional SHT_RELR section must not silently bypass the section-consistency policy.

The MIPS16 shared-image contract requires complete dynamic relocation inspection and refuses additional section relocation tables. The current profile verifies R_MIPS_NONE and symbol-index-zero R_MIPS_REL32.

Impact and scope

P2: bounded source-coverage and admission error for ELF32 ET_DYN/o32/MIPS32r2/MIPS16 inputs declaring standard RELR relocations. The returned count and completeness claim omit declared relocation destinations, and the admission predicate accepts a declaration family outside its documented verified profile. Mixed REL/RELR inputs are affected as well as RELR-only inputs.

The test establishes this behavior for synthetic fixtures in both byte orders. The frequency in shipped MIPS16 binaries, actual Ghidra RELR handling and any downstream decompilation effect were not tested. Non-shared MIPS lanes are not implicated by this reproduction.

REA version

5241597594 (package version 6.7.0)

Runtime and platform

macOS 27.0.1 build 26A434, arm64; Node v26.12.0; repository-installed Vitest 5.0.3. No native analysis provider was run or simulated.

Target and artifact context

Synthetic ELF32 ET_DYN, machine MIPS, flags 0x74001005, either byte order. PT_LOAD maps the dynamic table, relocation tables and destinations. RELR uses a direct address 516 followed by bitmap 3, encoding four-byte destinations 516 and 520. The destination slots already contain their implicit addends, 0x305 and 0x309. SHT_RELR has type 19 and entry size 4. The mixed case adds one conventional symbol-index-zero R_MIPS_REL32 record.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Executed observations, identical in each byte order:

{"mode":"relr","sharedLoad":{"status":"complete","relocationCount":0,"relocationTypes":[],"symbolRelativeCount":0},"reason":null}
{"mode":"relr-section","sharedLoad":{"status":"complete","relocationCount":0,"relocationTypes":[],"symbolRelativeCount":0},"reason":null}
{"mode":"mixed","sharedLoad":{"status":"complete","relocationCount":1,"relocationTypes":[3],"symbolRelativeCount":0},"reason":null}

The profile projection also reports shared_load.status = "complete" with the corresponding zero or one count. The RELA control instead reports unavailable with MIPS shared RELA or PLT relocation declarations require separate verification.

Public reachability is source-derived: open_binary calls session.open, which uses the production target resolver. The resolver attaches the inspected MIPS metadata. The focused test calls this same filesystem boundary directly.

Typed outcome or recovery expected

Until RELR is verified for this profile, mips.sharedLoad should return status: "unavailable" with a specific RELR declaration reason, and the MIPS16 admission predicate should return a refusal. Any future supported implementation must preserve truthful coverage for mixed tables and section declarations as well.

Analysis or suspected cause

The dynamic-tag filter retains conventional REL/RELA/PLT tags but omits DT_RELRSZ (35), DT_RELR (36) and DT_RELRENT (37). With no conventional REL tags, the empty-table branch claims complete/zero. With a conventional REL table, only that table is counted.

The section collector includes SHT_RELA (4) and SHT_REL (9), omitting SHT_RELR (19). Consequently, an SHT_RELR section cannot trigger the additional-table check. The MIPS16 admission gate then trusts the false complete result. These are source-derived causes consistent with the executed test.

Additional context

RELR stores implicit addends at its destinations. If an image stays at its link address, its zero relocation factor can leave those values unchanged. This report does not infer pointer corruption from a missing relocation write; the separate defect is claiming complete declaration inspection and admitting unexamined load semantics under a bounded verified profile.

The relevant parser, admission, documentation and existing shared-load test files are unchanged on fetched main revision 647717c050. No real shipped MIPS RELR artifact or Ghidra runtime result is asserted.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5915 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior ELF32 MIPS16 shared-image source inspection reports complete relocation coverage when a nonempty standard RELR table has not been examined. The MIPS16 admission predicate returns no refusal, and the Ghidra profile retains the complete/zero relocation result. A focused filesystem-boundary test reproduced this with both byte orders: - A RELR table encoding two relocation destinations reports zero relocations, including when an SHT_RELR section describes it. - A mixed REL/RELR input reports only its one conventional REL record; the two RELR destinations disappear from the inventory. - A standalone SHT_RELR section also bypasses the additional-section-table refusal. These are executed source-parser, admission-predicate and profile-projection observations. No Ghidra import, CLI command or MCP transport request was executed, and no incorrect pseudocode or relocated pointer value is claimed. ### Reproduction The focused test below ran with one worker: **14 tests passed**, recording the current behavior for seven shapes in both byte orders. It includes ordinary REL and empty-table controls, plus a RELA control that is refused as expected. Its assertions deliberately describe the observed behavior at this revision. For a rerun, save it as `tests/boundary/filesystem/mipsRelrCoverage.test.ts` beside the existing shared-load tests and run: ```sh ./node_modules/.bin/vitest run tests/boundary/filesystem/mipsRelrCoverage.test.ts --maxWorkers 1 ``` ```typescript import assert from "node:assert/strict"; import { writeFile } from "node:fs/promises"; import { join } from "node:path"; import { it } from "vitest"; import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js"; import { ghidraMipsProfileParameters, ghidraMipsUnsupportedReason } from "../../../src/ghidra/GhidraMipsProfile.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; for (const little of [true, false]) { for (const mode of ["empty", "rel", "rela", "relr", "relr-section", "mixed", "section-only"]) { it(`inspects ${mode} ${little ? "LE" : "BE"}`, async () => { const bytes = Buffer.alloc(4160); const u16 = (value: number, offset: number) => little ? bytes.writeUInt16LE(value, offset) : bytes.writeUInt16BE(value, offset); const u32 = (value: number, offset: number) => little ? bytes.writeUInt32LE(value, offset) : bytes.writeUInt32BE(value, offset); bytes.set([0x7f, 0x45, 0x4c, 0x46, 1, little ? 1 : 2, 1]); u16(3,16); u16(8,18); u32(1,20); u32(52,28); u32(0x74001005,36); u16(52,40); u16(32,42); u16(2,44); u32(1,52); u32(bytes.length,68); u32(bytes.length,72); u32(7,76); u32(4,80); u32(2,84); u32(256,88); u32(256,92); u32(256,96); u32(6,108); u32(4,112); const tags: [number,number][] = []; if (mode === "rel" || mode === "mixed") tags.push([17,4096],[18,8],[19,8]); if (mode === "rela") tags.push([7,4096],[8,12],[9,12]); if (["relr", "relr-section", "mixed"].includes(mode)) tags.push([36,4128],[35,8],[37,4]); tags.push([0,0]); tags.forEach(([tag,value], i) => { u32(tag,256+i*8); u32(value,260+i*8); }); u32(tags.length*8,100); u32(tags.length*8,104); // One conventional REL record. RELR is a separate address + bitmap pair. u32(512,4096); u32(3,4100); u32(0x301,512); u32(516,4128); u32(3,4132); // RELR addends reside in destination slots, including an odd MIPS16 pointer. u32(0x305,516); u32(0x309,520); if (mode === "relr-section" || mode === "section-only") { u32(1024,32); u16(40,46); u16(2,48); const sh = 1064; u32(19,sh+4); u32(2,sh+8); u32(4128,sh+12); u32(4128,sh+16); u32(8,sh+20); u32(4,sh+32); u32(4,sh+36); } const directory = await createTestTempDirectory("rea-relr-coverage-"); const path = join(directory,"shared.elf"); await writeFile(path,bytes); const result = await parseBinaryTarget(path); assert.ok(result.ok); const target = result.value; assert.ok(target.kind === "executable" && target.architecture === "mips"); const reason = ghidraMipsUnsupportedReason(target); console.log(JSON.stringify({mode,endian:little?"LE":"BE",sharedLoad:target.mips.sharedLoad,reason,profile:ghidraMipsProfileParameters(target).mips_elf})); if (mode === "rela") { assert.equal(target.mips.sharedLoad?.status,"unavailable"); assert.match(reason ?? "",/RELA or PLT/); } else { const conventional = mode === "rel" || mode === "mixed"; assert.deepEqual(target.mips.sharedLoad,{status:"complete",relocationCount:conventional?1:0,relocationTypes:conventional?[3]:[],symbolRelativeCount:0}); assert.equal(reason,null); } }); } } ``` ### Expected behavior Source coverage must remain unavailable, with a useful reason, for nonempty relocation declarations that the bounded MIPS16 profile has not inspected and verified. Alternatively, a supported RELR implementation must examine the declarations and represent their coverage accurately. An additional SHT_RELR section must not silently bypass the section-consistency policy. The [MIPS16 shared-image contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/docs/ghidra-mips.md#L93-L105) requires complete dynamic relocation inspection and refuses additional section relocation tables. The current profile verifies R_MIPS_NONE and symbol-index-zero R_MIPS_REL32. ### Impact and scope P2: bounded source-coverage and admission error for ELF32 ET_DYN/o32/MIPS32r2/MIPS16 inputs declaring standard RELR relocations. The returned count and completeness claim omit declared relocation destinations, and the admission predicate accepts a declaration family outside its documented verified profile. Mixed REL/RELR inputs are affected as well as RELR-only inputs. The test establishes this behavior for synthetic fixtures in both byte orders. The frequency in shipped MIPS16 binaries, actual Ghidra RELR handling and any downstream decompilation effect were not tested. Non-shared MIPS lanes are not implicated by this reproduction. ### REA version 52415975942a4b9c53a817915b313ccc404f280e (package version 6.7.0) ### Runtime and platform macOS 27.0.1 build 26A434, arm64; Node v26.12.0; repository-installed Vitest 5.0.3. No native analysis provider was run or simulated. ### Target and artifact context Synthetic ELF32 ET_DYN, machine MIPS, flags 0x74001005, either byte order. PT_LOAD maps the dynamic table, relocation tables and destinations. RELR uses a direct address 516 followed by bitmap 3, encoding four-byte destinations 516 and 520. The destination slots already contain their implicit addends, 0x305 and 0x309. SHT_RELR has type 19 and entry size 4. The mixed case adds one conventional symbol-index-zero R_MIPS_REL32 record. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Executed observations, identical in each byte order: ```json {"mode":"relr","sharedLoad":{"status":"complete","relocationCount":0,"relocationTypes":[],"symbolRelativeCount":0},"reason":null} {"mode":"relr-section","sharedLoad":{"status":"complete","relocationCount":0,"relocationTypes":[],"symbolRelativeCount":0},"reason":null} {"mode":"mixed","sharedLoad":{"status":"complete","relocationCount":1,"relocationTypes":[3],"symbolRelativeCount":0},"reason":null} ``` The profile projection also reports `shared_load.status = "complete"` with the corresponding zero or one count. The RELA control instead reports unavailable with `MIPS shared RELA or PLT relocation declarations require separate verification`. Public reachability is source-derived: [`open_binary` calls session.open](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/server/registerSessionTools.ts#L179-L205), which uses the production target resolver. The resolver [attaches the inspected MIPS metadata](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/BinaryTargetResolver.ts#L590-L613). The focused test calls this same filesystem boundary directly. ### Typed outcome or recovery expected Until RELR is verified for this profile, `mips.sharedLoad` should return `status: "unavailable"` with a specific RELR declaration reason, and the MIPS16 admission predicate should return a refusal. Any future supported implementation must preserve truthful coverage for mixed tables and section declarations as well. ### Analysis or suspected cause The [dynamic-tag filter](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/MipsElfSharedLoad.ts#L104-L136) retains conventional REL/RELA/PLT tags but omits DT_RELRSZ (35), DT_RELR (36) and DT_RELRENT (37). With no conventional REL tags, the [empty-table branch](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/MipsElfSharedLoad.ts#L161-L173) claims complete/zero. With a conventional REL table, only that table is counted. The [section collector](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/MipsElfMetadata.ts#L182-L188) includes SHT_RELA (4) and SHT_REL (9), omitting SHT_RELR (19). Consequently, an SHT_RELR section cannot trigger the additional-table check. The [MIPS16 admission gate](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/ghidra/GhidraMipsProfile.ts#L173-L179) then trusts the false complete result. These are source-derived causes consistent with the executed test. ### Additional context RELR stores implicit addends at its destinations. If an image stays at its link address, its zero relocation factor can leave those values unchanged. This report does not infer pointer corruption from a missing relocation write; the separate defect is claiming complete declaration inspection and admitting unexamined load semantics under a bounded verified profile. The relevant parser, admission, documentation and existing shared-load test files are unchanged on fetched main revision 647717c050ff4dbaef83fb1c5555ef5c33979cd4. No real shipped MIPS RELR artifact or Ghidra runtime result is asserted.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

At d87e73f2db50be72d33a4b3c7199af35518b441c, the parser retains and refuses RELR dynamic declarations and includes SHT_RELR in section coverage. The regressions assert unavailable coverage and admission refusal for RELR-only, mixed REL/RELR and section declarations in both byte orders. This implements the issue’s conservative refusal outcome.

I ran the cited regression file on this revision; all passed as part of the focused 28-file, 603-test run. Provider-facing checks here use controlled fixtures; this does not claim a run with the real analysis engine. Closing as completed on main.

<!-- gh-comment-id:6380648366 --> @coygeek commented on GitHub (Oct 24, 2026): At `d87e73f2db50be72d33a4b3c7199af35518b441c`, the parser [retains and refuses RELR dynamic declarations](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/MipsElfSharedLoad.ts#L106-L136) and [includes SHT_RELR in section coverage](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/application/MipsElfMetadata.ts#L182-L192). The [regressions](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/filesystem/mipsSharedLoad.test.ts#L136-L204) assert unavailable coverage and admission refusal for RELR-only, mixed REL/RELR and section declarations in both byte orders. This implements the issue’s conservative refusal outcome. I ran the cited regression file on this revision; all passed as part of the focused 28-file, 603-test run. Provider-facing checks here use controlled fixtures; this does not claim a run with the real analysis engine. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#3593
No description provided.