[GH-ISSUE #3336] [Bug] Malformed Objective-C property records discard valid sibling metadata #1204

Closed
opened 2026-10-15 18:31:59 +00:00 by bsakaguchi · 0 comments
Owner

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

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

inspect-native-dispatch-metadata does not isolate failures within an Objective-C property list. An unmapped name pointer in the second property removes the already decoded first property. An empty name in the first property instead fails the entire inspection, including a valid second property and an independent class. The result loses recoverable static metadata instead of retaining it with an explicit partial facet.

The valid control exits 0 and returns Fixture.properties containing first and second, plus Sibling. The unmapped variation exits 0 and retains both classes, but returns Fixture.properties: []. Its objc_properties_categories facet is partial with property_decode_failed and the unmapped pointer 0x100002000; the valid first property is absent.

The empty-name variation exits 1 with execution_failure instead of any metadata result. Its diagnostic is a string-minimum schema violation at ["objc_classes",0,"properties",0,"name"]. Neither Sibling nor the valid second property is returned. All three CLI invocations were executed locally with an existing compiled runtime and installed locked dependencies.

Reproduction

  1. Start from a clean checkout at 8dde6370d0809ad04cda3342d4e6f290f48aa063 on macOS and prepare the runtime from the repository root:
npm ci
npm run build:cached
  1. From that root, create the local files in a disposable directory and isolate client configuration:
rea_entry="$PWD/scripts/rea.mjs"
work=$(mktemp -d)
mkdir "$work/home"
cd "$work"
node --input-type=module <<'JS'
import { writeFileSync } from 'node:fs';
const b = Buffer.alloc(4096), base = 0x100000000n;
const u32 = (at, n) => b.writeUInt32LE(n, at);
const ptr = (at, n) => b.writeBigUInt64LE(base + BigInt(n), at);
u32(0, 0xfeedfacf); u32(4, 0x0100000c); u32(12, 2); u32(16, 1); u32(20, 152);
u32(32, 0x19); u32(36, 152); b.write('__DATA', 40); ptr(56, 0);
b.writeBigUInt64LE(4096n, 64); b.writeBigUInt64LE(4096n, 80);
u32(88, 5); u32(92, 5); u32(96, 1);
b.write('__objc_classlist', 104); b.write('__DATA', 120); ptr(136, 0x180);
b.writeBigUInt64LE(16n, 144); u32(152, 0x180);
for (const [slot, cls, ro, name] of [[0x180, 0x200, 0x300, 0x400], [0x188, 0x700, 0x800, 0x900]]) {
  ptr(slot, cls); ptr(cls + 32, ro); u32(ro, 2); u32(ro + 8, 16); ptr(ro + 24, name);
}
b.write('Fixture\0', 0x400); b.write('Sibling\0', 0x900); ptr(0x340, 0x500);
u32(0x500, 16); u32(0x504, 2);
ptr(0x508, 0x600); ptr(0x510, 0x620); ptr(0x518, 0x640); ptr(0x520, 0x620);
b.write('first\0', 0x600); b.write('Tq,N\0', 0x620); b.write('second\0', 0x640);
writeFileSync('valid.bin', b);
ptr(0x518, 0x2000); writeFileSync('unmapped.bin', b);
ptr(0x518, 0x640); b[0x600] = 0; writeFileSync('empty.bin', b);
JS
  1. Inspect the valid control and the two single-record variations:
HOME="$work/home" node "$rea_entry" inspect-native-dispatch-metadata "$work/valid.bin" --format json
HOME="$work/home" node "$rea_entry" inspect-native-dispatch-metadata "$work/unmapped.bin" --format json
HOME="$work/home" node "$rea_entry" inspect-native-dispatch-metadata "$work/empty.bin" --format json
  1. Compare the returned properties, retained classes, coverage diagnostics, and exit statuses with the observations below. The unmapped variation loses first, and the empty-name variation returns no metadata inventory.

Expected behavior

The valid file returns both classes and both properties. In the unmapped variation, the first property remains available and the unavailable second property has a meaningful partial-coverage diagnostic. In the empty-name variation, the valid second property and independent class remain available while the invalid property is diagnosed. A malformed property must not erase independently decoded metadata.

AGENTS.md:29 requires preservation of partial facts and meaningful failure reasons; docs/tool-design.md:57-59 requires preservation of partial provider facts. src/native/AppleDispatchMetadata.test.ts:426-442 establishes sibling retention for an empty class name in the same metadata reader.

Impact and scope

Impact level P2, supported by the local CLI reproductions. A bounded malformed metadata record causes the loss of valid property observations or the entire static metadata inventory. The reproductions show both loss modes without target mutation; their prevalence in real binaries was not measured.

Workaround status is unverified. No supported REA option was demonstrated to skip one malformed property while preserving the original artifact identity and the remaining inventory. Manual byte inspection is possible in principle but was not verified as a practical substitute for the returned metadata.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64. The reproduction used an existing compiled runtime and installed locked dependencies.

Target and artifact context

The inline fixture is a 4096-byte, 64-bit little-endian Mach-O class list. Fixture has two properties with complete Tq,N attributes, and Sibling is an independent class. The unmapped variation changes only the second property's name pointer. The empty variation restores that pointer and changes only the first property's name to an empty C string. REA inspects the selected files statically; no target code is executed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The three local CLI invocations produced these observations:

valid.bin: exit 0; Fixture.properties contains first and second; Sibling is retained.
unmapped.bin: exit 0; Fixture.properties: []; both classes are retained.
objc_properties_categories: partial; property_decode_failed; unmapped pointer 0x100002000.
empty.bin: exit 1; no metadata result.
{
  "error": "Analysis failed",
  "code": "execution_failure",
  "category": "execution_failure",
  "retryable": false
}
Diagnostic: string-minimum schema violation at ["objc_classes",0,"properties",0,"name"].

Typed outcome or recovery expected

Run the three commands against the inline fixture. Keep the control's two classes and two properties. For the unmapped variation, retain first and diagnose the unavailable second property. For the empty-name variation, retain second and Sibling, and diagnose the invalid first property without a whole-operation schema failure. Preserve the original file bytes and explicit partial coverage.

Analysis or suspected cause

Source-derived reasoning: src/native/AppleObjcProperties.ts:84-95 accumulates properties inside one list-level operation, and src/native/AppleObjcProperties.ts:109-120 returns an empty list after a read failure. This explains why an unmapped second property discards the already decoded first property. src/native/AppleDispatchMetadata.ts:180 validates the whole assembled result, causing the empty property name to reject every retained class. The CLI reproductions establish the loss modes; these source locations explain their suspected cause.

Additional context

  • #2610, closed as completed, concerns an unmapped protocol-list slot; this report concerns per-property isolation inside an otherwise readable property list.
  • #2611, closed as completed, concerns empty class names; this report concerns empty property names and an unmapped property-name pointer, while the existing class-sibling regression check remains in place.
  • #484, closed as completed, introduced the typed dispatch-metadata contract; it does not report the property-record failure reproduced here.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3336 ### 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 `inspect-native-dispatch-metadata` does not isolate failures within an Objective-C property list. An unmapped name pointer in the second property removes the already decoded first property. An empty name in the first property instead fails the entire inspection, including a valid second property and an independent class. The result loses recoverable static metadata instead of retaining it with an explicit partial facet. The valid control exits 0 and returns `Fixture.properties` containing `first` and `second`, plus `Sibling`. The unmapped variation exits 0 and retains both classes, but returns `Fixture.properties: []`. Its `objc_properties_categories` facet is partial with `property_decode_failed` and the unmapped pointer `0x100002000`; the valid `first` property is absent. The empty-name variation exits 1 with `execution_failure` instead of any metadata result. Its diagnostic is a string-minimum schema violation at `["objc_classes",0,"properties",0,"name"]`. Neither `Sibling` nor the valid `second` property is returned. All three CLI invocations were executed locally with an existing compiled runtime and installed locked dependencies. ### Reproduction 1. Start from a clean checkout at `8dde6370d0809ad04cda3342d4e6f290f48aa063` on macOS and prepare the runtime from the repository root: ```sh npm ci npm run build:cached ``` 2. From that root, create the local files in a disposable directory and isolate client configuration: ```sh rea_entry="$PWD/scripts/rea.mjs" work=$(mktemp -d) mkdir "$work/home" cd "$work" node --input-type=module <<'JS' import { writeFileSync } from 'node:fs'; const b = Buffer.alloc(4096), base = 0x100000000n; const u32 = (at, n) => b.writeUInt32LE(n, at); const ptr = (at, n) => b.writeBigUInt64LE(base + BigInt(n), at); u32(0, 0xfeedfacf); u32(4, 0x0100000c); u32(12, 2); u32(16, 1); u32(20, 152); u32(32, 0x19); u32(36, 152); b.write('__DATA', 40); ptr(56, 0); b.writeBigUInt64LE(4096n, 64); b.writeBigUInt64LE(4096n, 80); u32(88, 5); u32(92, 5); u32(96, 1); b.write('__objc_classlist', 104); b.write('__DATA', 120); ptr(136, 0x180); b.writeBigUInt64LE(16n, 144); u32(152, 0x180); for (const [slot, cls, ro, name] of [[0x180, 0x200, 0x300, 0x400], [0x188, 0x700, 0x800, 0x900]]) { ptr(slot, cls); ptr(cls + 32, ro); u32(ro, 2); u32(ro + 8, 16); ptr(ro + 24, name); } b.write('Fixture\0', 0x400); b.write('Sibling\0', 0x900); ptr(0x340, 0x500); u32(0x500, 16); u32(0x504, 2); ptr(0x508, 0x600); ptr(0x510, 0x620); ptr(0x518, 0x640); ptr(0x520, 0x620); b.write('first\0', 0x600); b.write('Tq,N\0', 0x620); b.write('second\0', 0x640); writeFileSync('valid.bin', b); ptr(0x518, 0x2000); writeFileSync('unmapped.bin', b); ptr(0x518, 0x640); b[0x600] = 0; writeFileSync('empty.bin', b); JS ``` 3. Inspect the valid control and the two single-record variations: ```sh HOME="$work/home" node "$rea_entry" inspect-native-dispatch-metadata "$work/valid.bin" --format json HOME="$work/home" node "$rea_entry" inspect-native-dispatch-metadata "$work/unmapped.bin" --format json HOME="$work/home" node "$rea_entry" inspect-native-dispatch-metadata "$work/empty.bin" --format json ``` 4. Compare the returned properties, retained classes, coverage diagnostics, and exit statuses with the observations below. The unmapped variation loses `first`, and the empty-name variation returns no metadata inventory. ### Expected behavior The valid file returns both classes and both properties. In the unmapped variation, the first property remains available and the unavailable second property has a meaningful partial-coverage diagnostic. In the empty-name variation, the valid second property and independent class remain available while the invalid property is diagnosed. A malformed property must not erase independently decoded metadata. `AGENTS.md:29` requires preservation of partial facts and meaningful failure reasons; `docs/tool-design.md:57-59` requires preservation of partial provider facts. `src/native/AppleDispatchMetadata.test.ts:426-442` establishes sibling retention for an empty class name in the same metadata reader. ### Impact and scope Impact level P2, supported by the local CLI reproductions. A bounded malformed metadata record causes the loss of valid property observations or the entire static metadata inventory. The reproductions show both loss modes without target mutation; their prevalence in real binaries was not measured. Workaround status is unverified. No supported REA option was demonstrated to skip one malformed property while preserving the original artifact identity and the remaining inventory. Manual byte inspection is possible in principle but was not verified as a practical substitute for the returned metadata. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64. The reproduction used an existing compiled runtime and installed locked dependencies. ### Target and artifact context The inline fixture is a 4096-byte, 64-bit little-endian Mach-O class list. `Fixture` has two properties with complete `Tq,N` attributes, and `Sibling` is an independent class. The unmapped variation changes only the second property's name pointer. The empty variation restores that pointer and changes only the first property's name to an empty C string. REA inspects the selected files statically; no target code is executed. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The three local CLI invocations produced these observations: ```text valid.bin: exit 0; Fixture.properties contains first and second; Sibling is retained. unmapped.bin: exit 0; Fixture.properties: []; both classes are retained. objc_properties_categories: partial; property_decode_failed; unmapped pointer 0x100002000. empty.bin: exit 1; no metadata result. { "error": "Analysis failed", "code": "execution_failure", "category": "execution_failure", "retryable": false } Diagnostic: string-minimum schema violation at ["objc_classes",0,"properties",0,"name"]. ``` ### Typed outcome or recovery expected Run the three commands against the inline fixture. Keep the control's two classes and two properties. For the unmapped variation, retain `first` and diagnose the unavailable second property. For the empty-name variation, retain `second` and `Sibling`, and diagnose the invalid first property without a whole-operation schema failure. Preserve the original file bytes and explicit partial coverage. ### Analysis or suspected cause Source-derived reasoning: `src/native/AppleObjcProperties.ts:84-95` accumulates properties inside one list-level operation, and `src/native/AppleObjcProperties.ts:109-120` returns an empty list after a read failure. This explains why an unmapped second property discards the already decoded first property. `src/native/AppleDispatchMetadata.ts:180` validates the whole assembled result, causing the empty property name to reject every retained class. The CLI reproductions establish the loss modes; these source locations explain their suspected cause. ### Additional context - [#2610](https://github.com/morluto/rea/issues/2610), closed as completed, concerns an unmapped protocol-list slot; this report concerns per-property isolation inside an otherwise readable property list. - [#2611](https://github.com/morluto/rea/issues/2611), closed as completed, concerns empty class names; this report concerns empty property names and an unmapped property-name pointer, while the existing class-sibling regression check remains in place. - [#484](https://github.com/morluto/rea/issues/484), closed as completed, introduced the typed dispatch-metadata contract; it does not report the property-record failure reproduced here.
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#1204
No description provided.