[GH-ISSUE #3186] [Bug] Objective-C ivar word-alignment sentinel returns null instead of 8 #1123

Closed
opened 2026-10-15 18:31:17 +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/3186

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

The inspect-native-dispatch-metadata CLI returns alignment: null for an Objective-C ivar whose raw alignment is 0xffffffff. The ordinary exponent-3 control returns alignment: 8. Both records report decode.status: decoded, and class/method/ivar coverage reports complete. Both CLI runs exited 0 through the native-macos provider, version apple-metadata-reader-1.

The failure reproduces with only the two valid ivars declared. A separate three-ivar control adds raw alignment 0xfffffffe; that invalid non-sentinel value also returns null, while the original results remain unchanged.

Reproduction

  1. Use a clean checkout at 58300885207133422af235e0f2b10658cf958b03 on macOS, follow the repository's runtime prerequisites, and prepare the CLI:
npm ci
npm run build:cached
mkdir -p repro-ivar-alignment/home/config repro-ivar-alignment/home/cache repro-ivar-alignment/home/data
  1. Create this fixture generator. It declares two ivars and stores a third invalid-value control outside the declared list until the final step enables it.
cat > repro-ivar-alignment/repro.cjs <<'EOF'
const fs = require('node:fs');
const b = Buffer.alloc(4096);
const base = 0x100000000n;
const u32 = (o, v) => b.writeUInt32LE(v, o);
const u64 = (o, v) => b.writeBigUInt64LE(BigInt(v), o);
const ptr = (o, target) => u64(o, base + BigInt(target));
u32(0, 0xfeedfacf); u32(4, 0x0100000c); u32(8, 0); u32(12, 2);
u32(16, 1); u32(20, 152);
u32(32, 0x19); u32(36, 152); b.write('__DATA', 40);
u64(56, base); u64(64, 4096); u64(72, 0); u64(80, 4096);
u32(88, 3); u32(92, 3); u32(96, 1);
b.write('__objc_classlist', 104); b.write('__DATA', 120);
ptr(136, 0x180); u64(144, 8); u32(152, 0x180); u32(156, 3);
ptr(0x180, 0x200); ptr(0x220, 0x280);
u32(0x280, 2); u32(0x284, 8); u32(0x288, 40);
ptr(0x298, 0x500); ptr(0x2b0, 0x380); b.write('AlignmentFixture\0', 0x500);
u32(0x380, 32); u32(0x384, 2);
const rows = [
  ['sentinel', 0xffffffff, 8, 3, '[3c]'],
  ['ordinary', 3, 16, 8, 'q'],
  ['invalid', 0xfffffffe, 24, 8, 'q'],
];
rows.forEach(([name, raw, offset, size, type], i) => {
  const entry = 0x388 + 32 * i;
  const storage = 0x480 + 4 * i;
  const strings = 0x540 + 0x40 * i;
  ptr(entry, storage); ptr(entry + 8, strings); ptr(entry + 16, strings + 0x20);
  u32(entry + 24, raw); u32(entry + 28, size); u32(storage, offset);
  b.write(name + '\0', strings); b.write(type + '\0', strings + 0x20);
});
fs.writeFileSync(process.argv[2], b);
EOF
  1. Generate the fixture, run the supported CLI, and print the relevant fields. The environment directories are isolated from existing client configuration.
repro_root="$PWD/repro-ivar-alignment"
repro_run() {
  env HOME="$repro_root/home" \
    XDG_CONFIG_HOME="$repro_root/home/config" \
    XDG_CACHE_HOME="$repro_root/home/cache" \
    XDG_DATA_HOME="$repro_root/home/data" "$@"
}
repro_run node repro-ivar-alignment/repro.cjs repro-ivar-alignment/alignment.macho
repro_run node scripts/rea.mjs inspect-native-dispatch-metadata repro-ivar-alignment/alignment.macho --format json > repro-ivar-alignment/result.json
repro_run node - <<'EOF'
const fs = require('node:fs');
const result = JSON.parse(fs.readFileSync('repro-ivar-alignment/result.json', 'utf8'));
console.log(JSON.stringify(result.normalized_result.result.objc_ivars.map(
  ({name, type_encoding, offset, size, alignment}) => ({name, type_encoding, offset, size, alignment})
)));
EOF
  1. As a discriminating control, expose the stored third record with raw alignment 0xFFFFFFFE, repeat the CLI call, and print the relevant fields:
repro_run node - <<'EOF'
const fs = require('node:fs');
const path = 'repro-ivar-alignment/alignment.macho';
const bytes = fs.readFileSync(path);
bytes.writeUInt32LE(3, 0x384);
fs.writeFileSync(path, bytes);
EOF
repro_run node scripts/rea.mjs inspect-native-dispatch-metadata repro-ivar-alignment/alignment.macho --format json > repro-ivar-alignment/result.json
repro_run node - <<'EOF'
const fs = require('node:fs');
const result = JSON.parse(fs.readFileSync('repro-ivar-alignment/result.json', 'utf8'));
console.log(JSON.stringify(result.normalized_result.result.objc_ivars.map(
  ({name, type_encoding, offset, size, alignment}) => ({name, type_encoding, offset, size, alignment})
)));
EOF

The independent verification used the prepared baseline build. It executed the fixture generator, CLI calls, and output-selection commands for both record-count variants; it did not rerun npm ci or npm run build:cached.

Expected behavior

docs/native-investigation.md:360-364 documents this command as decoding 64-bit little-endian Objective-C ivar offsets, sizes, and alignment. The supplied Apple objc4 header, runtime/objc-runtime-new.h, defines ivar_t::alignment() so that alignment_raw == ~(uint32_t)0 returns 1U << WORD_SHIFT. For the supported 64-bit layout, the sentinel should therefore return alignment: 8. The ordinary exponent-3 ivar should keep alignment 8, and the invalid non-sentinel control should keep alignment null.

The independent verifier read that Apple accessor in a supplied local header. Its exact upstream release provenance was not independently authenticated.

Impact and scope

The supported CLI loses one defined layout field for this bounded ABI encoding while retaining ivar names, type encodings, offsets, sizes, and ordinary alignments. The returned alignment alone cannot distinguish the valid word-alignment sentinel from the invalid control. Treating every null as word alignment would therefore give an incorrect result for the invalid control.

This establishes deterministic loss of an accepted ivar alignment encoding. Compiler-produced occurrence frequency and compiler-version scope were not measured. No target execution or security boundary violation was observed. An analyst workaround that reads and interprets the raw bytes separately was not verified.

REA version

6.4.0 (main at 5830088520)

Runtime and platform

Node.js v26.10.0; macOS arm64

Target and artifact context

Synthetic 4096-byte, 64-bit little-endian arm64 Mach-O executable containing one root Objective-C class and two declared ivar records. A third invalid-value record is stored outside the declared list until the control step enables it. The sentinel ivar has type [3c], offset 8, and size 3; the ordinary ivar has type q, offset 16, and size 8.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Two declared ivars, CLI exit 0:
[{"name":"sentinel","type_encoding":"[3c]","offset":8,"size":3,"alignment":null},{"name":"ordinary","type_encoding":"q","offset":16,"size":8,"alignment":8}]

Three-ivar control adds this record and preserves both earlier results, CLI exit 0:
{"name":"invalid","type_encoding":"q","offset":24,"size":8,"alignment":null}

Typed outcome or recovery expected

Return a schema-valid metadata result with sentinel alignment 8, ordinary alignment 8, and invalid-control alignment null. Preserve each ivar's name, type encoding, offset, and size. Mapping every large raw value to 8 would fail the invalid-value control.

Analysis or suspected cause

Source-derived at the stated baseline: src/native/AppleObjcDispatchFacets.ts:295-312 constructs the ivar result. Lines 301-302 apply exponent conversion only when the raw value is below 31 and return null otherwise, with no exception for 0xffffffff. Lines 304-307 choose decoded status from the offset resolution, so the missing alignment does not make the record partial. The verifier also confirmed the same conversion in dist/native/AppleObjcDispatchFacets.js:218.

Source-derived: src/domain/native/objcSwiftMetadata.ts:133-143 permits alignment as a positive integer or null. The supported CLI route in src/cli/coreNativeCommands.ts:255-290 reaches the native-macos provider; src/native/AppleDispatchMetadata.ts:184 onward bounds and digest-verifies the input before decoding. The verifier's real CLI fixture passed those guards and the 32-byte ivar-stride guard.

Additional context

P2 is supported because this bounded sentinel input loses a layout fact while the command and remaining metadata stay usable. Severe downstream harm was not observed, and no safe output-only workaround was established.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3186 ### 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 The `inspect-native-dispatch-metadata` CLI returns `alignment: null` for an Objective-C ivar whose raw alignment is `0xffffffff`. The ordinary exponent-3 control returns `alignment: 8`. Both records report `decode.status: decoded`, and class/method/ivar coverage reports complete. Both CLI runs exited 0 through the native-macos provider, version apple-metadata-reader-1. The failure reproduces with only the two valid ivars declared. A separate three-ivar control adds raw alignment `0xfffffffe`; that invalid non-sentinel value also returns null, while the original results remain unchanged. ### Reproduction 1. Use a clean checkout at `58300885207133422af235e0f2b10658cf958b03` on macOS, follow the repository's runtime prerequisites, and prepare the CLI: ```sh npm ci npm run build:cached mkdir -p repro-ivar-alignment/home/config repro-ivar-alignment/home/cache repro-ivar-alignment/home/data ``` 2. Create this fixture generator. It declares two ivars and stores a third invalid-value control outside the declared list until the final step enables it. ```sh cat > repro-ivar-alignment/repro.cjs <<'EOF' const fs = require('node:fs'); const b = Buffer.alloc(4096); const base = 0x100000000n; const u32 = (o, v) => b.writeUInt32LE(v, o); const u64 = (o, v) => b.writeBigUInt64LE(BigInt(v), o); const ptr = (o, target) => u64(o, base + BigInt(target)); u32(0, 0xfeedfacf); u32(4, 0x0100000c); u32(8, 0); u32(12, 2); u32(16, 1); u32(20, 152); u32(32, 0x19); u32(36, 152); b.write('__DATA', 40); u64(56, base); u64(64, 4096); u64(72, 0); u64(80, 4096); u32(88, 3); u32(92, 3); u32(96, 1); b.write('__objc_classlist', 104); b.write('__DATA', 120); ptr(136, 0x180); u64(144, 8); u32(152, 0x180); u32(156, 3); ptr(0x180, 0x200); ptr(0x220, 0x280); u32(0x280, 2); u32(0x284, 8); u32(0x288, 40); ptr(0x298, 0x500); ptr(0x2b0, 0x380); b.write('AlignmentFixture\0', 0x500); u32(0x380, 32); u32(0x384, 2); const rows = [ ['sentinel', 0xffffffff, 8, 3, '[3c]'], ['ordinary', 3, 16, 8, 'q'], ['invalid', 0xfffffffe, 24, 8, 'q'], ]; rows.forEach(([name, raw, offset, size, type], i) => { const entry = 0x388 + 32 * i; const storage = 0x480 + 4 * i; const strings = 0x540 + 0x40 * i; ptr(entry, storage); ptr(entry + 8, strings); ptr(entry + 16, strings + 0x20); u32(entry + 24, raw); u32(entry + 28, size); u32(storage, offset); b.write(name + '\0', strings); b.write(type + '\0', strings + 0x20); }); fs.writeFileSync(process.argv[2], b); EOF ``` 3. Generate the fixture, run the supported CLI, and print the relevant fields. The environment directories are isolated from existing client configuration. ```sh repro_root="$PWD/repro-ivar-alignment" repro_run() { env HOME="$repro_root/home" \ XDG_CONFIG_HOME="$repro_root/home/config" \ XDG_CACHE_HOME="$repro_root/home/cache" \ XDG_DATA_HOME="$repro_root/home/data" "$@" } repro_run node repro-ivar-alignment/repro.cjs repro-ivar-alignment/alignment.macho repro_run node scripts/rea.mjs inspect-native-dispatch-metadata repro-ivar-alignment/alignment.macho --format json > repro-ivar-alignment/result.json repro_run node - <<'EOF' const fs = require('node:fs'); const result = JSON.parse(fs.readFileSync('repro-ivar-alignment/result.json', 'utf8')); console.log(JSON.stringify(result.normalized_result.result.objc_ivars.map( ({name, type_encoding, offset, size, alignment}) => ({name, type_encoding, offset, size, alignment}) ))); EOF ``` 4. As a discriminating control, expose the stored third record with raw alignment 0xFFFFFFFE, repeat the CLI call, and print the relevant fields: ```sh repro_run node - <<'EOF' const fs = require('node:fs'); const path = 'repro-ivar-alignment/alignment.macho'; const bytes = fs.readFileSync(path); bytes.writeUInt32LE(3, 0x384); fs.writeFileSync(path, bytes); EOF repro_run node scripts/rea.mjs inspect-native-dispatch-metadata repro-ivar-alignment/alignment.macho --format json > repro-ivar-alignment/result.json repro_run node - <<'EOF' const fs = require('node:fs'); const result = JSON.parse(fs.readFileSync('repro-ivar-alignment/result.json', 'utf8')); console.log(JSON.stringify(result.normalized_result.result.objc_ivars.map( ({name, type_encoding, offset, size, alignment}) => ({name, type_encoding, offset, size, alignment}) ))); EOF ``` The independent verification used the prepared baseline build. It executed the fixture generator, CLI calls, and output-selection commands for both record-count variants; it did not rerun `npm ci` or `npm run build:cached`. ### Expected behavior `docs/native-investigation.md:360-364` documents this command as decoding 64-bit little-endian Objective-C ivar offsets, sizes, and alignment. The supplied Apple objc4 header, `runtime/objc-runtime-new.h`, defines `ivar_t::alignment()` so that `alignment_raw == ~(uint32_t)0` returns `1U << WORD_SHIFT`. For the supported 64-bit layout, the sentinel should therefore return `alignment: 8`. The ordinary exponent-3 ivar should keep alignment 8, and the invalid non-sentinel control should keep alignment null. The independent verifier read that Apple accessor in a supplied local header. Its exact upstream release provenance was not independently authenticated. ### Impact and scope The supported CLI loses one defined layout field for this bounded ABI encoding while retaining ivar names, type encodings, offsets, sizes, and ordinary alignments. The returned alignment alone cannot distinguish the valid word-alignment sentinel from the invalid control. Treating every null as word alignment would therefore give an incorrect result for the invalid control. This establishes deterministic loss of an accepted ivar alignment encoding. Compiler-produced occurrence frequency and compiler-version scope were not measured. No target execution or security boundary violation was observed. An analyst workaround that reads and interprets the raw bytes separately was not verified. ### REA version 6.4.0 (main at 58300885207133422af235e0f2b10658cf958b03) ### Runtime and platform Node.js v26.10.0; macOS arm64 ### Target and artifact context Synthetic 4096-byte, 64-bit little-endian arm64 Mach-O executable containing one root Objective-C class and two declared ivar records. A third invalid-value record is stored outside the declared list until the control step enables it. The sentinel ivar has type `[3c]`, offset 8, and size 3; the ordinary ivar has type `q`, offset 16, and size 8. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence ```text Two declared ivars, CLI exit 0: [{"name":"sentinel","type_encoding":"[3c]","offset":8,"size":3,"alignment":null},{"name":"ordinary","type_encoding":"q","offset":16,"size":8,"alignment":8}] Three-ivar control adds this record and preserves both earlier results, CLI exit 0: {"name":"invalid","type_encoding":"q","offset":24,"size":8,"alignment":null} ``` ### Typed outcome or recovery expected Return a schema-valid metadata result with sentinel alignment 8, ordinary alignment 8, and invalid-control alignment null. Preserve each ivar's name, type encoding, offset, and size. Mapping every large raw value to 8 would fail the invalid-value control. ### Analysis or suspected cause Source-derived at the stated baseline: `src/native/AppleObjcDispatchFacets.ts:295-312` constructs the ivar result. Lines 301-302 apply exponent conversion only when the raw value is below 31 and return null otherwise, with no exception for `0xffffffff`. Lines 304-307 choose decoded status from the offset resolution, so the missing alignment does not make the record partial. The verifier also confirmed the same conversion in `dist/native/AppleObjcDispatchFacets.js:218`. Source-derived: `src/domain/native/objcSwiftMetadata.ts:133-143` permits alignment as a positive integer or null. The supported CLI route in `src/cli/coreNativeCommands.ts:255-290` reaches the native-macos provider; `src/native/AppleDispatchMetadata.ts:184` onward bounds and digest-verifies the input before decoding. The verifier's real CLI fixture passed those guards and the 32-byte ivar-stride guard. ### Additional context P2 is supported because this bounded sentinel input loses a layout fact while the command and remaining metadata stay usable. Severe downstream harm was not observed, and no safe output-only workaround was established.
bsakaguchi 2026-10-15 18:31:17 +00:00
  • closed this issue
  • added the
    bug
    label
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#1123
No description provided.