[GH-ISSUE #6686] [Bug] Omitting an unused .NET #Blob heap discards readable assembly identity #4062

Closed
opened 2026-10-31 00:46:29 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

inspect-managed-artifact reports a metadata-only PE/CLI assembly as malformed solely because its unused #Blob heap is omitted. It returns module: null and assembly: null. The control with the same metadata rows and an empty #Blob heap reports assembly Empty and complete coverage.

Reproduction

From the repository at the revision below, with its locked dependencies installed, create the two self-authored, metadata-only PE32 DLL fixtures. The inline data is zlib-compressed base64, included in full; neither fixture contains methods or executable target code. Each has one Module, one <Module> TypeDef, one Assembly, a #Strings heap, and a #GUID heap. All blob indexes are zero. Only the presence of an empty #Blob stream and resulting stream offsets differ.

case_dir=$(mktemp -d)
python3 - "$case_dir" <<'PYFIX'
import base64, pathlib, sys, zlib
fixtures = {
    "empty-with-blob.dll": "eNrzjWIgGzQwUA4CXBkYfBgZUcQeMDApcjNiUawAYzhA2ExY1DjgEEcCAgwDAIDu9UDi6pWkVpSAGCxQf7FgutsB4huaAg+wtawMDIoMDE+AbEaGkQWcgr2cGKG+5gHiMhM9Az1jA2NDS0jkJADJICBWrmNg2AKkpUDs4JKizLz0YpCKC9D0pOwe6ukCTruQMFR2yslPgtoBjFZGVrSwZUTCIBIhxw0XYWlAjw8RCOWaW1BSqZeSk8Ng45ufUpqTagcRAskJKhm7hKaVd8xctfvM3Xf/GUbBKMADAI+oJqI=",
    "empty-without-blob.dll": "eNrzjWIgGzQwUA4CXBkYfBgZUcQeMDApcjNiUawAYzhA2ExY1DjgEEcCAgwDAIDu9UDi6pWkVpSAGCxQf7FgutsB4huaAg+wtawMDIoMDBeAbEaGkQWcgr2cGKG+5gHiMhM9Az1jA2NDS5AIM0MAkAwCYuU6BoYlQFoKxA4uKcrMSy8GqTgATU/K7qGeLlAzgdHIyIoWloxIGEQi5LjhIiwN6OEvAqFccwtKKvVScnIYbHzzU0pzUu0gQiA5QSVjl9C08o6Zq3afufvuP8MoGAUkAAAYTyPa",
}
for name, encoded in fixtures.items():
    pathlib.Path(sys.argv[1], name).write_bytes(zlib.decompress(base64.b64decode(encoded)))
PYFIX
for file in "$case_dir"/*.dll; do
  node --import tsx --input-type=module - inspect-managed-artifact "$file" --format json <<'JS'
import { createCli } from './src/cli.ts';
await createCli({}).serve(process.argv.slice(2));
JS
done

I executed the source CLI through createCli({}).serve(...) with each of these exact fixture byte sequences. Both calls exited 0. One control and one failing CLI invocation were observed with the inline source entrypoint; the failing case was also observed through a source CLI wrapper and the shared production application workflow. No compiled package build or .NET runtime loading was performed.

Expected behavior

Preserve the readable assembly/module metadata when no row requires a blob value. Omission of an unused heap is supported by Microsoft's metadata readers: the released System.Reflection.Metadata reader initializes only streams that are present, and the released Mono reader explicitly accommodates ILasm images without a Blob heap. These are source-derived compatibility evidence; I did not execute either .NET reader.

REA documents assembly identity and metadata inspection through this public CLI command. For this metadata-only input, removing an unused heap should not change readable assembly identity into malformed metadata.

Impact and scope

Observed on the supplied small metadata-only assembly: REA loses every metadata-derived identity field even though the Module, Assembly, strings and GUID bytes are present. Real-world prevalence is unknown. No target execution, native provider, security effect or crash was involved. The empty-heap control shows a format compatibility failure, not a missing analysis provider. A same-artifact workaround was not verified.

REA version

rea-agents 6.7.0; source revision a7f967cf240eba8309cbf4526b2fc6338a52a617. npm latest was checked as 6.7.0 by the parent investigator.

Runtime and platform

Node.js v26.12.1, tsx v4.23.15, Linux 7.2.8-9-omarchy, x86_64. No Hopper, Ghidra or .NET runtime execution is required.

Target and artifact context

Self-authored PE32/CLI DLLs, 1,536 bytes each, containing metadata only. SHA-256: control 8c17fb414d50d33d0172944b5bd53a531e782afc79cb8a3cd2f1027531be811f; omitted-heap input 1a60239c7eb6e78f2e6ed2fa8a7ac424724958437a543964a0f357aaf5b34cf0.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

The source CLI's normalized_result contained:

{"case":"empty-with-blob.dll","classification.status":"managed","module.name":"Empty.dll","assembly.name":"Empty","coverage":{"state":"complete","issues":[]}}
{"case":"empty-without-blob.dll","classification.status":"malformed","module":null,"assembly":null,"coverage":{"state":"partial","issues":[{"code":"missing-stream","scope":"metadata.#Blob","offset":null,"detail":"Required metadata stream #Blob is absent"}]}}

These are focused projections of the observed JSON, with dotted keys naming original field locations. The CLI calls the shared managed analysis operation. Metadata admission unconditionally requires #Blob, then the artifact inspector discards the metadata inventory on that failure.

Typed outcome or recovery expected

Return the observed Module and Assembly identities and complete coverage for this fixture. A missing heap that is actually referenced by a nonzero index should still produce an explicit partial or invalid result; accepting the unused heap case must not hide unavailable referenced data.

Analysis or suspected cause

Source-derived cause: the parser treats #Blob as mandatory before interpreting whether any row needs it. This is separate from the existing truncated-diagnostic report #3364.

Additional context

Duplicate searches covered open and closed issues: dotnet OR IL2CPP OR CIL OR managed, "missing-stream" "Blob", "managed" "heap" in:title, and "managed" "stream" in:title. No matching report was found; #3364 concerns file-offset diagnostics for truncated metadata rather than absent unused heaps. No fix or prior-good release is claimed. Restoration is the same omitted-heap input returning Empty.dll/Empty, while the control remains complete and genuinely referenced missing blob data remains explicit.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A bounded supported metadata-only PE/CLI case loses readable assembly and module identity and is incorrectly classified as malformed; the control isolates the unused heap's omission.
  • Workaround status: unverified
  • Workaround basis: Adding an empty heap changes the artifact bytes and digest. No workaround that inspects the original artifact through REA was verified; alternate metadata readers were inspected as source evidence only.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6686 ### 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 Evidence, artifacts, or reconstruction ### Observed behavior `inspect-managed-artifact` reports a metadata-only PE/CLI assembly as `malformed` solely because its unused `#Blob` heap is omitted. It returns `module: null` and `assembly: null`. The control with the same metadata rows and an empty `#Blob` heap reports assembly `Empty` and complete coverage. ### Reproduction From the repository at the revision below, with its locked dependencies installed, create the two self-authored, metadata-only PE32 DLL fixtures. The inline data is zlib-compressed base64, included in full; neither fixture contains methods or executable target code. Each has one Module, one `<Module>` TypeDef, one Assembly, a `#Strings` heap, and a `#GUID` heap. All blob indexes are zero. Only the presence of an empty `#Blob` stream and resulting stream offsets differ. ```sh case_dir=$(mktemp -d) python3 - "$case_dir" <<'PYFIX' import base64, pathlib, sys, zlib fixtures = { "empty-with-blob.dll": "eNrzjWIgGzQwUA4CXBkYfBgZUcQeMDApcjNiUawAYzhA2ExY1DjgEEcCAgwDAIDu9UDi6pWkVpSAGCxQf7FgutsB4huaAg+wtawMDIoMDE+AbEaGkQWcgr2cGKG+5gHiMhM9Az1jA2NDS0jkJADJICBWrmNg2AKkpUDs4JKizLz0YpCKC9D0pOwe6ukCTruQMFR2yslPgtoBjFZGVrSwZUTCIBIhxw0XYWlAjw8RCOWaW1BSqZeSk8Ng45ufUpqTagcRAskJKhm7hKaVd8xctfvM3Xf/GUbBKMADAI+oJqI=", "empty-without-blob.dll": "eNrzjWIgGzQwUA4CXBkYfBgZUcQeMDApcjNiUawAYzhA2ExY1DjgEEcCAgwDAIDu9UDi6pWkVpSAGCxQf7FgutsB4huaAg+wtawMDIoMDBeAbEaGkQWcgr2cGKG+5gHiMhM9Az1jA2NDS5AIM0MAkAwCYuU6BoYlQFoKxA4uKcrMSy8GqTgATU/K7qGeLlAzgdHIyIoWloxIGEQi5LjhIiwN6OEvAqFccwtKKvVScnIYbHzzU0pzUu0gQiA5QSVjl9C08o6Zq3afufvuP8MoGAUkAAAYTyPa", } for name, encoded in fixtures.items(): pathlib.Path(sys.argv[1], name).write_bytes(zlib.decompress(base64.b64decode(encoded))) PYFIX for file in "$case_dir"/*.dll; do node --import tsx --input-type=module - inspect-managed-artifact "$file" --format json <<'JS' import { createCli } from './src/cli.ts'; await createCli({}).serve(process.argv.slice(2)); JS done ``` I executed the source CLI through `createCli({}).serve(...)` with each of these exact fixture byte sequences. Both calls exited 0. One control and one failing CLI invocation were observed with the inline source entrypoint; the failing case was also observed through a source CLI wrapper and the shared production application workflow. No compiled package build or .NET runtime loading was performed. ### Expected behavior Preserve the readable assembly/module metadata when no row requires a blob value. Omission of an unused heap is supported by Microsoft's metadata readers: the released [System.Reflection.Metadata reader initializes only streams that are present](https://github.com/dotnet/runtime/blob/v10.0.0/src/libraries/System.Reflection.Metadata/src/System/Reflection/Metadata/MetadataReader.cs#L238-L327), and the released [Mono reader explicitly accommodates ILasm images without a Blob heap](https://github.com/dotnet/runtime/blob/v10.0.0/src/mono/mono/metadata/assembly.c#L631-L642). These are source-derived compatibility evidence; I did not execute either .NET reader. REA documents [assembly identity and metadata inspection through this public CLI command](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/docs/managed-code-analysis.md#L3-L22). For this metadata-only input, removing an unused heap should not change readable assembly identity into malformed metadata. ### Impact and scope Observed on the supplied small metadata-only assembly: REA loses every metadata-derived identity field even though the Module, Assembly, strings and GUID bytes are present. Real-world prevalence is unknown. No target execution, native provider, security effect or crash was involved. The empty-heap control shows a format compatibility failure, not a missing analysis provider. A same-artifact workaround was not verified. ### REA version `rea-agents` 6.7.0; source revision `a7f967cf240eba8309cbf4526b2fc6338a52a617`. npm latest was checked as 6.7.0 by the parent investigator. ### Runtime and platform Node.js `v26.12.1`, tsx `v4.23.15`, Linux `7.2.8-9-omarchy`, x86_64. No Hopper, Ghidra or .NET runtime execution is required. ### Target and artifact context Self-authored PE32/CLI DLLs, 1,536 bytes each, containing metadata only. SHA-256: control `8c17fb414d50d33d0172944b5bd53a531e782afc79cb8a3cd2f1027531be811f`; omitted-heap input `1a60239c7eb6e78f2e6ed2fa8a7ac424724958437a543964a0f357aaf5b34cf0`. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence The source CLI's `normalized_result` contained: ```json {"case":"empty-with-blob.dll","classification.status":"managed","module.name":"Empty.dll","assembly.name":"Empty","coverage":{"state":"complete","issues":[]}} {"case":"empty-without-blob.dll","classification.status":"malformed","module":null,"assembly":null,"coverage":{"state":"partial","issues":[{"code":"missing-stream","scope":"metadata.#Blob","offset":null,"detail":"Required metadata stream #Blob is absent"}]}} ``` These are focused projections of the observed JSON, with dotted keys naming original field locations. [The CLI calls the shared managed analysis operation](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/src/cli/managedCommands.ts#L37-L51). [Metadata admission unconditionally requires `#Blob`](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/src/dotnet/ManagedMetadataLayout.ts#L457-L465), then [the artifact inspector discards the metadata inventory on that failure](https://github.com/morluto/rea/blob/a7f967cf240eba8309cbf4526b2fc6338a52a617/src/dotnet/ManagedArtifactInspector.ts#L309-L345). ### Typed outcome or recovery expected Return the observed Module and Assembly identities and complete coverage for this fixture. A missing heap that is actually referenced by a nonzero index should still produce an explicit partial or invalid result; accepting the unused heap case must not hide unavailable referenced data. ### Analysis or suspected cause Source-derived cause: the parser treats `#Blob` as mandatory before interpreting whether any row needs it. This is separate from the existing truncated-diagnostic report #3364. ### Additional context Duplicate searches covered open and closed issues: `dotnet OR IL2CPP OR CIL OR managed`, `"missing-stream" "Blob"`, `"managed" "heap" in:title`, and `"managed" "stream" in:title`. No matching report was found; [#3364](https://github.com/morluto/rea/issues/3364) concerns file-offset diagnostics for truncated metadata rather than absent unused heaps. No fix or prior-good release is claimed. Restoration is the same omitted-heap input returning `Empty.dll`/`Empty`, while the control remains complete and genuinely referenced missing blob data remains explicit. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A bounded supported metadata-only PE/CLI case loses readable assembly and module identity and is incorrectly classified as malformed; the control isolates the unused heap's omission. - Workaround status: unverified - Workaround basis: Adding an empty heap changes the artifact bytes and digest. No workaround that inspects the original artifact through REA was verified; alternate metadata readers were inspected as source evidence only.
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#4062
No description provided.