[GH-ISSUE #4540] [Bug] Empty Objective-C protocol method selectors escape entry recovery and invalidate dispatch metadata #2362

Closed
opened 2026-10-17 21:03:46 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @kevinjosh10 on GitHub (Oct 16, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4540

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

A Mach-O binary containing an Objective-C protocol whose method list contains an entry with an empty (\0) selector causes decodeAppleDispatchMetadata to fail with an unhandled ZodError (too_small: expected string to have >=1 characters at objc_protocols[0].methods[0].selector and objc_protocol_records[0].methods[0].selector).

Rather than rejecting the malformed selector or isolating the failure to the declaring protocol (which would allow sibling protocols, classes, and categories to survive), the entire metadata analysis aborts and discards all valid decoded entities.

Reproduction

  1. In-memory Mach-O reproduction script (repro_proto_selector.mts):
import { decodeAppleDispatchMetadata } from "./src/native/AppleDispatchMetadata.js";

const base = 0x100000000n;
const bytes = Buffer.alloc(4096);
const u32 = (offset: number, value: number) => bytes.writeUInt32LE(value, offset);
const ptr = (offset: number, target: number) => bytes.writeBigUInt64LE(base + BigInt(target), offset);

// Mach-O arm64 header + __DATA segment + __objc_protolist
u32(0, 0xfeedfacf);
u32(4, 0x0100000c);
u32(16, 1);
u32(20, 152);
u32(32, 0x19);
u32(36, 152);
bytes.write("__DATA", 40);
ptr(56, 0);
bytes.writeBigUInt64LE(4096n, 64);
bytes.writeBigUInt64LE(4096n, 80);
u32(92, 5);
u32(96, 1);
bytes.write("__objc_protolist", 104);
bytes.write("__DATA", 120);
ptr(136, 0x180);
bytes.writeBigUInt64LE(8n, 144);
u32(152, 0x180);

// protolist slot pointing to protocol_t at 0x200
ptr(0x180, 0x200);

// protocol_t at 0x200:
ptr(0x208, 0x300); // name pointer -> "MyProtocol\0"
bytes.write("MyProtocol\0", 0x300);
ptr(0x218, 0x400); // instanceMethods pointer -> 0x400
u32(0x240, 72);    // size: 72

// protocol_method_list_t at 0x400: stride 24, count 1
u32(0x400, 24);
u32(0x404, 1);
ptr(0x408, 0x500); // selector pointer -> empty string "\0"
ptr(0x410, 0x520); // types pointer
bytes.write("\0", 0x500);
bytes.write("v16@0:8\0", 0x520);

decodeAppleDispatchMetadata(bytes, 100, {
  path: "/fixture/app",
  sha256: "a".repeat(64),
});
  1. Run with Node / tsx:
npx tsx repro_proto_selector.mts
  1. Observe ZodError thrown during top-level schema parsing:
[
  {
    "origin": "string",
    "code": "too_small",
    "minimum": 1,
    "inclusive": true,
    "path": [
      "objc_protocols",
      0,
      "methods",
      0,
      "selector"
    ],
    "message": "Too small: expected string to have >=1 characters"
  },
  {
    "origin": "string",
    "code": "too_small",
    "minimum": 1,
    "inclusive": true,
    "path": [
      "objc_protocol_records",
      0,
      "methods",
      0,
      "selector"
    ],
    "message": "Too small: expected string to have >=1 characters"
  }
]

Expected behavior

An empty protocol method selector should be rejected or skipped inside the protocol method decode loop (e.g. throwing a TypeError("Objective-C protocol method has an empty selector")). This would allow the per-protocol try/catch in record() to capture the failure, report it under failures, and preserve valid sibling protocols and classes.

Impact and scope

Any Mach-O binary containing stripped or zeroed protocol method selectors crashes decodeAppleDispatchMetadata with an unhandled exception, causing total loss of decoded dispatch metadata.

REA version

8194695144045e0a950f2989190e7337890342f7 (6.5.0)

Runtime and platform

Node v24.13.0; Windows x64

Target and artifact context

Mach-O arm64 binary; Objective-C metadata (__objc_protolist and protocol_method_list_t)

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

In src/native/AppleObjcProtocols.ts:53-60:

output.push({
  selector: read.string(selector),
  method_type: instance ? "instance" : "class",
  address: null,
  is_required: required,
  is_optional: !required,
});

read.string(selector) is pushed directly into output without checking selector.length > 0.
The enclosing record helper only checks the protocol's own name (protocolName.length === 0 at line 73). Consequently, empty method selectors pass through to result.objc_protocols and result.objc_protocol_records, triggering an unhandled exception at objcSwiftMetadataSchema.parse(result).

Typed outcome or recovery expected

The method failure should be recorded, and decodeAppleDispatchMetadata should return a typed ObjcSwiftMetadata result containing all other valid protocols and classes.

Analysis or suspected cause

methods helper in AppleObjcProtocols.ts lacks string length validation before admission into output. Rejecting an empty selector inside methods allows record's try/catch to isolate the defect.

Originally created by @kevinjosh10 on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4540 ### 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 A Mach-O binary containing an Objective-C protocol whose method list contains an entry with an empty (`\0`) selector causes `decodeAppleDispatchMetadata` to fail with an unhandled `ZodError` (`too_small: expected string to have >=1 characters at objc_protocols[0].methods[0].selector` and `objc_protocol_records[0].methods[0].selector`). Rather than rejecting the malformed selector or isolating the failure to the declaring protocol (which would allow sibling protocols, classes, and categories to survive), the entire metadata analysis aborts and discards all valid decoded entities. ### Reproduction 1. In-memory Mach-O reproduction script (`repro_proto_selector.mts`): ```ts import { decodeAppleDispatchMetadata } from "./src/native/AppleDispatchMetadata.js"; const base = 0x100000000n; const bytes = Buffer.alloc(4096); const u32 = (offset: number, value: number) => bytes.writeUInt32LE(value, offset); const ptr = (offset: number, target: number) => bytes.writeBigUInt64LE(base + BigInt(target), offset); // Mach-O arm64 header + __DATA segment + __objc_protolist u32(0, 0xfeedfacf); u32(4, 0x0100000c); u32(16, 1); u32(20, 152); u32(32, 0x19); u32(36, 152); bytes.write("__DATA", 40); ptr(56, 0); bytes.writeBigUInt64LE(4096n, 64); bytes.writeBigUInt64LE(4096n, 80); u32(92, 5); u32(96, 1); bytes.write("__objc_protolist", 104); bytes.write("__DATA", 120); ptr(136, 0x180); bytes.writeBigUInt64LE(8n, 144); u32(152, 0x180); // protolist slot pointing to protocol_t at 0x200 ptr(0x180, 0x200); // protocol_t at 0x200: ptr(0x208, 0x300); // name pointer -> "MyProtocol\0" bytes.write("MyProtocol\0", 0x300); ptr(0x218, 0x400); // instanceMethods pointer -> 0x400 u32(0x240, 72); // size: 72 // protocol_method_list_t at 0x400: stride 24, count 1 u32(0x400, 24); u32(0x404, 1); ptr(0x408, 0x500); // selector pointer -> empty string "\0" ptr(0x410, 0x520); // types pointer bytes.write("\0", 0x500); bytes.write("v16@0:8\0", 0x520); decodeAppleDispatchMetadata(bytes, 100, { path: "/fixture/app", sha256: "a".repeat(64), }); ``` 2. Run with Node / `tsx`: ```sh npx tsx repro_proto_selector.mts ``` 3. Observe `ZodError` thrown during top-level schema parsing: ```json [ { "origin": "string", "code": "too_small", "minimum": 1, "inclusive": true, "path": [ "objc_protocols", 0, "methods", 0, "selector" ], "message": "Too small: expected string to have >=1 characters" }, { "origin": "string", "code": "too_small", "minimum": 1, "inclusive": true, "path": [ "objc_protocol_records", 0, "methods", 0, "selector" ], "message": "Too small: expected string to have >=1 characters" } ] ``` ### Expected behavior An empty protocol method selector should be rejected or skipped inside the protocol method decode loop (e.g. throwing a `TypeError("Objective-C protocol method has an empty selector")`). This would allow the per-protocol `try/catch` in `record()` to capture the failure, report it under `failures`, and preserve valid sibling protocols and classes. ### Impact and scope Any Mach-O binary containing stripped or zeroed protocol method selectors crashes `decodeAppleDispatchMetadata` with an unhandled exception, causing total loss of decoded dispatch metadata. ### REA version `8194695144045e0a950f2989190e7337890342f7` (6.5.0) ### Runtime and platform Node v24.13.0; Windows x64 ### Target and artifact context Mach-O arm64 binary; Objective-C metadata (`__objc_protolist` and `protocol_method_list_t`) ### Interface Other ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence In `src/native/AppleObjcProtocols.ts:53-60`: ```ts output.push({ selector: read.string(selector), method_type: instance ? "instance" : "class", address: null, is_required: required, is_optional: !required, }); ``` `read.string(selector)` is pushed directly into `output` without checking `selector.length > 0`. The enclosing `record` helper only checks the protocol's own name (`protocolName.length === 0` at line 73). Consequently, empty method selectors pass through to `result.objc_protocols` and `result.objc_protocol_records`, triggering an unhandled exception at `objcSwiftMetadataSchema.parse(result)`. ### Typed outcome or recovery expected The method failure should be recorded, and `decodeAppleDispatchMetadata` should return a typed `ObjcSwiftMetadata` result containing all other valid protocols and classes. ### Analysis or suspected cause `methods` helper in `AppleObjcProtocols.ts` lacks string length validation before admission into `output`. Rejecting an empty selector inside `methods` allows `record`'s `try/catch` to isolate the defect.
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#2362
No description provided.