[GH-ISSUE #3346] [Bug] Dart AOT inspection reports complete coverage for conflicting section-backed tables #1215

Closed
opened 2026-10-16 21:02:53 +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/3346

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-dart-aot reports complete structural coverage when section-backed dynamic symbol or string tables have conflicting PT_LOAD interpretations. Conflicts in the snapshot payload itself correctly produce partial coverage, but equivalent conflicts in the metadata selecting those snapshots are accepted without a limitation.

Every CLI call below exited with code 0. The two metadata-conflict results reported complete overall and structural coverage, four snapshot sections, and two snapshot hash sources without a conflict limitation. Their allocated section virtual addresses match the primary load mapping, while the additional mapping points at different bytes. The snapshot-conflict control emitted the expected conflicting-mapping limitation, with partial coverage, three sections, and one hash source.

The public CLI outputs are the primary violation evidence. This is an incorrect completeness claim on deliberately conflicting metadata; the fixture does not establish behavior on ordinary production Flutter APKs.

Reproduction

  1. Start from a clean source checkout and build the baseline runtime.
git clone https://github.com/morluto/rea.git rea
cd rea
git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, create synthetic ELF64 APKs with four exported Dart snapshot symbols and inspect each through the CLI. The control has one identity mapping. The symbols and strings variants add a second file-backed interpretation over the allocated dynamic symbol or string table. The snapshot variant overlaps a snapshot header to exercise the existing conflict behavior. This command creates every required input file inline.
case_dir=$(mktemp -d)
mkdir "$case_dir/home"
python3 - "$case_dir" <<'PYTHON'
import pathlib, struct, sys, zipfile
root = pathlib.Path(sys.argv[1])
image = bytearray(0x2800)
def u16(at, value): struct.pack_into("<H", image, at, value)
def u32(at, value): struct.pack_into("<I", image, at, value)
def u64(at, value): struct.pack_into("<Q", image, at, value)
image[:7] = b"\x7fELF\x02\x01\x01"
u16(0x10, 3); u16(0x12, 62); u32(0x14, 1)
u64(0x20, 64); u64(0x28, 0x100)
u16(0x34, 64); u16(0x36, 56); u16(0x38, 1)
u16(0x3a, 64); u16(0x3c, 4)
u32(64, 1); u32(68, 6)
u64(96, len(image)); u64(104, len(image)); u64(112, 4096)
# Null, allocated dynamic symbols, allocated strings, allocated snapshot data.
u32(0x144, 11); u64(0x148, 2); u64(0x150, 0x300)
u64(0x158, 0x300); u64(0x160, 120); u32(0x168, 2)
u32(0x16c, 1); u64(0x170, 8); u64(0x178, 24)
u32(0x184, 3); u64(0x188, 2); u64(0x190, 0x400)
u64(0x198, 0x400); u64(0x1a0, 256); u64(0x1b0, 1)
u32(0x1c4, 1); u64(0x1c8, 2); u64(0x1d0, 0x1000)
u64(0x1d8, 0x1000); u64(0x1e0, 0x1700); u64(0x1f0, 8)
names = ["_kDartVmSnapshotData", "_kDartVmSnapshotInstructions",
         "_kDartIsolateSnapshotData", "_kDartIsolateSnapshotInstructions"]
name_at = 0x401
for index, (name, address, size) in enumerate(zip(
        names, [0x1000, 0x1800, 0x2000, 0x2600], [52, 16, 52, 16])):
    entry = 0x300 + (index + 1) * 24
    u32(entry, name_at - 0x400); image[entry + 4] = 0x11
    u16(entry + 6, 3); u64(entry + 8, address); u64(entry + 16, size)
    encoded = name.encode()
    image[name_at:name_at + len(encoded)] = encoded
    name_at += len(encoded) + 1
for at in [0x1000, 0x2000]:
    image[at:at + 4] = b"\xf5\xf5\xdc\xdc"
    u64(at + 4, 48); u64(at + 12, 3)
    image[at + 20:at + 52] = b"1" * 32
for name, address in [("control", None), ("symbols", 0x318),
                      ("strings", 0x401), ("snapshot", 0x1000)]:
    variant = bytearray(image)
    if address is not None:
        struct.pack_into("<H", variant, 0x38, 2)
        struct.pack_into("<I", variant, 120, 1)
        struct.pack_into("<I", variant, 124, 4)
        struct.pack_into("<Q", variant, 128, 0x700)
        struct.pack_into("<Q", variant, 136, address)
        struct.pack_into("<Q", variant, 152, 4)
        struct.pack_into("<Q", variant, 160, 4)
    with zipfile.ZipFile(root / (name + ".apk"), "w") as archive:
        archive.writestr("lib/arm64-v8a/libapp.so", variant)
PYTHON
for variant in control symbols strings snapshot; do
  HOME="$case_dir/home" TMPDIR="$case_dir" \
    node scripts/rea.mjs inspect-dart-aot "$case_dir/$variant.apk" \
    --format json > "$case_dir/$variant.result.json"
  python3 - "$variant" "$case_dir/$variant.result.json" <<'PYTHON'
import json, sys
result = json.load(open(sys.argv[2]))["normalized_result"]
print(json.dumps({
    "variant": sys.argv[1],
    "coverage": result["coverage"],
    "structural_coverage": result["libapp"]["coverage"],
    "section_count": len(result["libapp"]["sections"]),
    "snapshot_hash_sources": result["libapp"]["snapshot_hash_sources"]
}))
PYTHON
done
  1. Compare the JSON projections below. The symbols and strings variants retain the control's complete coverage despite conflicting metadata; the snapshot variant qualifies its coverage.

Expected behavior

Dynamic symbol and string tables must receive the documented checks for every overlapping PT_LOAD interpretation in docs/flutter-analysis.md:63. That contract includes partial overlaps, coalesces equivalent mappings, and leaves affected symbols unresolved with qualified coverage when mappings conflict; it explicitly applies the same checks to dynamic hash, symbol, and string tables.

A conflicting table mapping should leave the affected snapshot selection unresolved and qualify structural coverage, or refuse inspection if no recognizable snapshot selection remains. The unambiguous control should retain complete coverage.

Impact and scope

Impact level: P2. Assessment status: supported. Deliberately conflicting section-backed symbol metadata receives a complete structural result without the promised qualification. The observed impact is false completeness and unqualified metadata selection, rather than a demonstrated wrong SDK identification on an ordinary APK.

Workaround status: unverified. No existing REA caller option was verified to apply the missing table-mapping checks while preserving the selected library and complete structural analysis.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64. The reproduced run used checkout dependencies and a compiled runtime that were already present.

Target and artifact context

Synthetic ELF64 little-endian AOT layouts inside ZIP APKs at lib/arm64-v8a/libapp.so, with four exported Dart snapshot symbols, allocated dynamic symbol and string sections, and valid VM and isolate data headers. No external engine runs.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

All four CLI invocations exited with code 0. The observed projections were:

{"variant": "control", "coverage": "complete", "structural_coverage": "complete", "section_count": 4, "snapshot_hash_sources": 2}
{"variant": "symbols", "coverage": "complete", "structural_coverage": "complete", "section_count": 4, "snapshot_hash_sources": 2}
{"variant": "strings", "coverage": "complete", "structural_coverage": "complete", "section_count": 4, "snapshot_hash_sources": 2}
{"variant": "snapshot", "coverage": "partial", "structural_coverage": "partial", "section_count": 3, "snapshot_hash_sources": 1}

Typed outcome or recovery expected

Repeat all four variants. The two metadata-conflict inputs must no longer report complete structural coverage or unqualified snapshot selection. They should expose the conflict or refuse the unresolved selection. Keep complete coverage for the unambiguous control and partial coverage for the snapshot-conflict control.

As a discriminating control, an equivalent overlapping mapping to the same file bytes should remain accepted; rejecting all overlaps would not restore the documented contract. This equivalent-mapping case is a required restoration check, not an additional observed result from the commands above.

Analysis or suspected cause

Source-derived reasoning: src/flutter/DartElf.ts:228 selects the section-backed symbol-table path, and src/flutter/DartElf.ts:272 admits symbol and string file ranges without the load-map checks used by the stripped dynamic-table path. The source behavior explains why snapshot payload conflicts qualify coverage while conflicts in section-backed metadata do not. The documented contract is at docs/flutter-analysis.md:63.

The CLI reproduction establishes the output failure. Evidence provenance is observed, local verification is reproduced, and reproduction completeness is complete.

Additional context

Related completed issue #2525 addressed conflicting load mappings of snapshot payloads; this report concerns section-backed dynamic symbol and string tables, while the snapshot-conflict control already reports partial coverage.

Related proposal #2809 concerns a Blutter Dart reconstruction provider; this report concerns the existing static AOT inspector's structural coverage and metadata selection.

Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3346 ### 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-dart-aot` reports complete structural coverage when section-backed dynamic symbol or string tables have conflicting `PT_LOAD` interpretations. Conflicts in the snapshot payload itself correctly produce partial coverage, but equivalent conflicts in the metadata selecting those snapshots are accepted without a limitation. Every CLI call below exited with code 0. The two metadata-conflict results reported complete overall and structural coverage, four snapshot sections, and two snapshot hash sources without a conflict limitation. Their allocated section virtual addresses match the primary load mapping, while the additional mapping points at different bytes. The snapshot-conflict control emitted the expected conflicting-mapping limitation, with partial coverage, three sections, and one hash source. The public CLI outputs are the primary violation evidence. This is an incorrect completeness claim on deliberately conflicting metadata; the fixture does not establish behavior on ordinary production Flutter APKs. ### Reproduction 1. Start from a clean source checkout and build the baseline runtime. ```sh git clone https://github.com/morluto/rea.git rea cd rea git checkout 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, create synthetic ELF64 APKs with four exported Dart snapshot symbols and inspect each through the CLI. The control has one identity mapping. The symbols and strings variants add a second file-backed interpretation over the allocated dynamic symbol or string table. The snapshot variant overlaps a snapshot header to exercise the existing conflict behavior. This command creates every required input file inline. ```sh case_dir=$(mktemp -d) mkdir "$case_dir/home" python3 - "$case_dir" <<'PYTHON' import pathlib, struct, sys, zipfile root = pathlib.Path(sys.argv[1]) image = bytearray(0x2800) def u16(at, value): struct.pack_into("<H", image, at, value) def u32(at, value): struct.pack_into("<I", image, at, value) def u64(at, value): struct.pack_into("<Q", image, at, value) image[:7] = b"\x7fELF\x02\x01\x01" u16(0x10, 3); u16(0x12, 62); u32(0x14, 1) u64(0x20, 64); u64(0x28, 0x100) u16(0x34, 64); u16(0x36, 56); u16(0x38, 1) u16(0x3a, 64); u16(0x3c, 4) u32(64, 1); u32(68, 6) u64(96, len(image)); u64(104, len(image)); u64(112, 4096) # Null, allocated dynamic symbols, allocated strings, allocated snapshot data. u32(0x144, 11); u64(0x148, 2); u64(0x150, 0x300) u64(0x158, 0x300); u64(0x160, 120); u32(0x168, 2) u32(0x16c, 1); u64(0x170, 8); u64(0x178, 24) u32(0x184, 3); u64(0x188, 2); u64(0x190, 0x400) u64(0x198, 0x400); u64(0x1a0, 256); u64(0x1b0, 1) u32(0x1c4, 1); u64(0x1c8, 2); u64(0x1d0, 0x1000) u64(0x1d8, 0x1000); u64(0x1e0, 0x1700); u64(0x1f0, 8) names = ["_kDartVmSnapshotData", "_kDartVmSnapshotInstructions", "_kDartIsolateSnapshotData", "_kDartIsolateSnapshotInstructions"] name_at = 0x401 for index, (name, address, size) in enumerate(zip( names, [0x1000, 0x1800, 0x2000, 0x2600], [52, 16, 52, 16])): entry = 0x300 + (index + 1) * 24 u32(entry, name_at - 0x400); image[entry + 4] = 0x11 u16(entry + 6, 3); u64(entry + 8, address); u64(entry + 16, size) encoded = name.encode() image[name_at:name_at + len(encoded)] = encoded name_at += len(encoded) + 1 for at in [0x1000, 0x2000]: image[at:at + 4] = b"\xf5\xf5\xdc\xdc" u64(at + 4, 48); u64(at + 12, 3) image[at + 20:at + 52] = b"1" * 32 for name, address in [("control", None), ("symbols", 0x318), ("strings", 0x401), ("snapshot", 0x1000)]: variant = bytearray(image) if address is not None: struct.pack_into("<H", variant, 0x38, 2) struct.pack_into("<I", variant, 120, 1) struct.pack_into("<I", variant, 124, 4) struct.pack_into("<Q", variant, 128, 0x700) struct.pack_into("<Q", variant, 136, address) struct.pack_into("<Q", variant, 152, 4) struct.pack_into("<Q", variant, 160, 4) with zipfile.ZipFile(root / (name + ".apk"), "w") as archive: archive.writestr("lib/arm64-v8a/libapp.so", variant) PYTHON for variant in control symbols strings snapshot; do HOME="$case_dir/home" TMPDIR="$case_dir" \ node scripts/rea.mjs inspect-dart-aot "$case_dir/$variant.apk" \ --format json > "$case_dir/$variant.result.json" python3 - "$variant" "$case_dir/$variant.result.json" <<'PYTHON' import json, sys result = json.load(open(sys.argv[2]))["normalized_result"] print(json.dumps({ "variant": sys.argv[1], "coverage": result["coverage"], "structural_coverage": result["libapp"]["coverage"], "section_count": len(result["libapp"]["sections"]), "snapshot_hash_sources": result["libapp"]["snapshot_hash_sources"] })) PYTHON done ``` 3. Compare the JSON projections below. The symbols and strings variants retain the control's complete coverage despite conflicting metadata; the snapshot variant qualifies its coverage. ### Expected behavior Dynamic symbol and string tables must receive the documented checks for every overlapping `PT_LOAD` interpretation in `docs/flutter-analysis.md:63`. That contract includes partial overlaps, coalesces equivalent mappings, and leaves affected symbols unresolved with qualified coverage when mappings conflict; it explicitly applies the same checks to dynamic hash, symbol, and string tables. A conflicting table mapping should leave the affected snapshot selection unresolved and qualify structural coverage, or refuse inspection if no recognizable snapshot selection remains. The unambiguous control should retain complete coverage. ### Impact and scope Impact level: P2. Assessment status: supported. Deliberately conflicting section-backed symbol metadata receives a complete structural result without the promised qualification. The observed impact is false completeness and unqualified metadata selection, rather than a demonstrated wrong SDK identification on an ordinary APK. Workaround status: unverified. No existing REA caller option was verified to apply the missing table-mapping checks while preserving the selected library and complete structural analysis. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64. The reproduced run used checkout dependencies and a compiled runtime that were already present. ### Target and artifact context Synthetic ELF64 little-endian AOT layouts inside ZIP APKs at `lib/arm64-v8a/libapp.so`, with four exported Dart snapshot symbols, allocated dynamic symbol and string sections, and valid VM and isolate data headers. No external engine runs. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence All four CLI invocations exited with code 0. The observed projections were: ```text {"variant": "control", "coverage": "complete", "structural_coverage": "complete", "section_count": 4, "snapshot_hash_sources": 2} {"variant": "symbols", "coverage": "complete", "structural_coverage": "complete", "section_count": 4, "snapshot_hash_sources": 2} {"variant": "strings", "coverage": "complete", "structural_coverage": "complete", "section_count": 4, "snapshot_hash_sources": 2} {"variant": "snapshot", "coverage": "partial", "structural_coverage": "partial", "section_count": 3, "snapshot_hash_sources": 1} ``` ### Typed outcome or recovery expected Repeat all four variants. The two metadata-conflict inputs must no longer report complete structural coverage or unqualified snapshot selection. They should expose the conflict or refuse the unresolved selection. Keep complete coverage for the unambiguous control and partial coverage for the snapshot-conflict control. As a discriminating control, an equivalent overlapping mapping to the same file bytes should remain accepted; rejecting all overlaps would not restore the documented contract. This equivalent-mapping case is a required restoration check, not an additional observed result from the commands above. ### Analysis or suspected cause Source-derived reasoning: `src/flutter/DartElf.ts:228` selects the section-backed symbol-table path, and `src/flutter/DartElf.ts:272` admits symbol and string file ranges without the load-map checks used by the stripped dynamic-table path. The source behavior explains why snapshot payload conflicts qualify coverage while conflicts in section-backed metadata do not. The documented contract is at `docs/flutter-analysis.md:63`. The CLI reproduction establishes the output failure. Evidence provenance is observed, local verification is reproduced, and reproduction completeness is complete. ### Additional context Related completed issue [#2525](https://github.com/morluto/rea/issues/2525) addressed conflicting load mappings of snapshot payloads; this report concerns section-backed dynamic symbol and string tables, while the snapshot-conflict control already reports partial coverage. Related proposal [#2809](https://github.com/morluto/rea/issues/2809) concerns a Blutter Dart reconstruction provider; this report concerns the existing static AOT inspector's structural coverage and metadata selection.
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#1215
No description provided.