[GH-ISSUE #5853] [Bug] Large JVM class files receive a FAT-specific target refusal #3548

Closed
opened 2026-10-31 00:41:44 +00:00 by bsakaguchi · 0 comments
Owner

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

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

rea analyze explains the refusal of a valid JVM class differently depending on whether it fits the 4096-byte target-admission probe. A 4,060-byte class returns unsupported binary format; a 9,060-byte class returns FAT table does not identify a Mach-O image. The files have the same class structure and differ only in the length of an unused UTF-8 constant.

Both commands exit 1 with target_unavailable / unsupported_target and the same generic remediation. The problem is the FAT-specific diagnostic for a class file, not successful admission as Mach-O. No native analysis engine is involved.

Reproduction

Using the prepared runtime at the revision below, generate these reduced class fixtures. This uses the same minimal class framing as the repository's javaClassFile fixture:

python3 - <<'PYCODE'
from pathlib import Path
import struct
root = Path('repro')
root.mkdir(exist_ok=True)
def utf8(value):
    data = value.encode()
    return b'\x01' + struct.pack('>H', len(data)) + data
def make_class(length):
    pool = [b'\x07\x00\x02', utf8('Hello'),
            b'\x07\x00\x04', utf8('java/lang/Object'),
            utf8('x' * length)]
    return (struct.pack('>I3H', 0xcafebabe, 0, 65, len(pool) + 1)
            + b''.join(pool) + struct.pack('>7H', 0x21, 1, 3, 0, 0, 0, 0))
for name, length in [('Small', 4000), ('Large', 9000)]:
    (root / (name + '.class')).write_bytes(make_class(length))
PYCODE
node scripts/rea.mjs analyze repro/Small.class --format json
node scripts/rea.mjs analyze repro/Large.class --format json

Both paired CLI runs were executed twice with the same refusal reasons. file identifies both inputs as compiled Java class data, version 65.0. REA's built isJvmClassFile also returns true for both complete inputs. Java compilation or execution was not performed.

Expected behavior

Both valid classes should receive the generic unsupported-format refusal, without a FAT-table explanation dependent on the probe size.

The existing class-disambiguation regression expects a JVM class to return not-fat and parseExecutableHeader to return unsupported binary format. The shared parser explicitly excludes complete valid JVM class files from FAT parsing. Java analysis support is not requested.

Impact and scope

P3 diagnostic inconsistency for JVM class inputs larger than the target-admission probe. Both tested inputs are refused safely; the error category, exit status and remediation are unchanged. No false positive Mach-O admission, data loss or downstream analysis failure was demonstrated.

A ZIP inventory comparison returned resource / file for the smaller class and unknown / unknown for the larger class. Neither was labeled as a Mach-O executable. That conservative inventory result is not claimed as an additional defect.

REA version

6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e.

Runtime and platform

Node v26.12.0; macOS 27.0.1 arm64. Used an already prepared runtime. The repository's Node pin is 24.18.0; this reproduction used the available Node version stated here.

Target and artifact context

Synthetic Java 21 class structures, 4,060 and 9,060 bytes, with class name Hello, superclass java/lang/Object, and an unused UTF-8 constant of 4,000 or 9,000 bytes. Both use minor version 0 and major version 65.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Selected fields observed from the production CLI, omitting the input paths:

Small.class (4060 bytes):
exit: 1
code: target_unavailable
category: unsupported_target
message: Cannot open artifact: unsupported binary format.
details.reason: unsupported binary format

Large.class (9060 bytes):
exit: 1
code: target_unavailable
category: unsupported_target
message: Cannot open artifact: FAT table does not identify a Mach-O image.
details.reason: FAT table does not identify a Mach-O image

Both:
remediation.action: Review the reported file-format reason and select a supported, well-formed target.

A supplemental call to the built shared parser returned not-fat for both complete inputs. Passing only the initial 4096 bytes with the actual size returned not-fat for the smaller input and parsed for the larger input. This isolates the size-dependent difference before CLI error presentation.

Typed outcome or recovery expected

Retain the unsupported-target refusal and generic remediation for both inputs. The larger class should use the same unsupported binary format reason as the smaller class.

Analysis or suspected cause

Source-derived: BinaryTargetResolver reads a 4096-byte prefix and later passes it to target parsing. The JVM exclusion in readFatContainerHeader requires bytes.byteLength === fileSize, so it is skipped for the larger class. Version 65 is interpreted as a 65-entry FAT table and constant-pool bytes become candidate slice declarations.

Target dispatch enters FAT parsing, and the negative identity result produces the observed FAT-specific reason. A fix should preserve bounded reads and existing malformed-FAT checks; version bytes alone should not establish JVM identity.

Additional context

Related to #3176, which fixed JVM lookalikes producing false malformed-image records and partial coverage in dylib tracing. Its whole-file class scan in the Mach-O image reader is present at this revision. Target admission still uses the bounded resolver path reproduced above.

This differs from #2317's false Mach-O inventory labels and #2458's Go-reader slice-selection remedy. Neither incorrect result was observed here. MCP runtime behavior was not replayed. After fetching origin/main at f801b2fbfd28f4e3713adefddc7bd73db44c284c, the relevant resolver and FAT/JVM parser files were unchanged from the tested revision.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5853 ### 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 `rea analyze` explains the refusal of a valid JVM class differently depending on whether it fits the 4096-byte target-admission probe. A 4,060-byte class returns `unsupported binary format`; a 9,060-byte class returns `FAT table does not identify a Mach-O image`. The files have the same class structure and differ only in the length of an unused UTF-8 constant. Both commands exit 1 with `target_unavailable` / `unsupported_target` and the same generic remediation. The problem is the FAT-specific diagnostic for a class file, not successful admission as Mach-O. No native analysis engine is involved. ### Reproduction Using the prepared runtime at the revision below, generate these reduced class fixtures. This uses the same minimal class framing as the repository's `javaClassFile` fixture: ```sh python3 - <<'PYCODE' from pathlib import Path import struct root = Path('repro') root.mkdir(exist_ok=True) def utf8(value): data = value.encode() return b'\x01' + struct.pack('>H', len(data)) + data def make_class(length): pool = [b'\x07\x00\x02', utf8('Hello'), b'\x07\x00\x04', utf8('java/lang/Object'), utf8('x' * length)] return (struct.pack('>I3H', 0xcafebabe, 0, 65, len(pool) + 1) + b''.join(pool) + struct.pack('>7H', 0x21, 1, 3, 0, 0, 0, 0)) for name, length in [('Small', 4000), ('Large', 9000)]: (root / (name + '.class')).write_bytes(make_class(length)) PYCODE node scripts/rea.mjs analyze repro/Small.class --format json node scripts/rea.mjs analyze repro/Large.class --format json ``` Both paired CLI runs were executed twice with the same refusal reasons. `file` identifies both inputs as compiled Java class data, version 65.0. REA's built `isJvmClassFile` also returns true for both complete inputs. Java compilation or execution was not performed. ### Expected behavior Both valid classes should receive the generic unsupported-format refusal, without a FAT-table explanation dependent on the probe size. The [existing class-disambiguation regression](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/artifacts/apple/machoContainer.test.ts#L159-L172) expects a JVM class to return `not-fat` and `parseExecutableHeader` to return `unsupported binary format`. The [shared parser](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/apple/machoContainer.ts#L114-L141) explicitly excludes complete valid JVM class files from FAT parsing. Java analysis support is not requested. ### Impact and scope P3 diagnostic inconsistency for JVM class inputs larger than the target-admission probe. Both tested inputs are refused safely; the error category, exit status and remediation are unchanged. No false positive Mach-O admission, data loss or downstream analysis failure was demonstrated. A ZIP inventory comparison returned `resource` / `file` for the smaller class and `unknown` / `unknown` for the larger class. Neither was labeled as a Mach-O executable. That conservative inventory result is not claimed as an additional defect. ### REA version 6.7.0; source revision `52415975942a4b9c53a817915b313ccc404f280e`. ### Runtime and platform Node v26.12.0; macOS 27.0.1 arm64. Used an already prepared runtime. The repository's Node pin is 24.18.0; this reproduction used the available Node version stated here. ### Target and artifact context Synthetic Java 21 class structures, 4,060 and 9,060 bytes, with class name `Hello`, superclass `java/lang/Object`, and an unused UTF-8 constant of 4,000 or 9,000 bytes. Both use minor version 0 and major version 65. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Selected fields observed from the production CLI, omitting the input paths: ```text Small.class (4060 bytes): exit: 1 code: target_unavailable category: unsupported_target message: Cannot open artifact: unsupported binary format. details.reason: unsupported binary format Large.class (9060 bytes): exit: 1 code: target_unavailable category: unsupported_target message: Cannot open artifact: FAT table does not identify a Mach-O image. details.reason: FAT table does not identify a Mach-O image Both: remediation.action: Review the reported file-format reason and select a supported, well-formed target. ``` A supplemental call to the built shared parser returned `not-fat` for both complete inputs. Passing only the initial 4096 bytes with the actual size returned `not-fat` for the smaller input and `parsed` for the larger input. This isolates the size-dependent difference before CLI error presentation. ### Typed outcome or recovery expected Retain the unsupported-target refusal and generic remediation for both inputs. The larger class should use the same `unsupported binary format` reason as the smaller class. ### Analysis or suspected cause Source-derived: [BinaryTargetResolver](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/BinaryTargetResolver.ts#L502-L515) reads a 4096-byte prefix and later [passes it to target parsing](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/BinaryTargetResolver.ts#L571-L583). The JVM exclusion in `readFatContainerHeader` requires `bytes.byteLength === fileSize`, so it is skipped for the larger class. Version 65 is interpreted as a 65-entry FAT table and constant-pool bytes become candidate slice declarations. [Target dispatch](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/binaryTarget.ts#L100-L104) enters FAT parsing, and the [negative identity result](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/binaryTarget.ts#L270-L274) produces the observed FAT-specific reason. A fix should preserve bounded reads and existing malformed-FAT checks; version bytes alone should not establish JVM identity. ### Additional context Related to #3176, which fixed JVM lookalikes producing false malformed-image records and partial coverage in dylib tracing. Its [whole-file class scan in the Mach-O image reader](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/artifacts/apple/MachoLoadCommandReader.ts#L169-L176) is present at this revision. Target admission still uses the bounded resolver path reproduced above. This differs from #2317's false Mach-O inventory labels and #2458's Go-reader slice-selection remedy. Neither incorrect result was observed here. MCP runtime behavior was not replayed. After fetching origin/main at `f801b2fbfd28f4e3713adefddc7bd73db44c284c`, the relevant resolver and FAT/JVM parser files were unchanged from the tested revision.
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#3548
No description provided.