[GH-ISSUE #6851] [Bug] inspect_dart_aot rejects DT_BIND_NOW entries with large ignored values #4191

Closed
opened 2026-10-31 00:47:34 +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/6851

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 production shared service rejects a section-stripped ELF64 when the ABI-ignored d_un word of DT_BIND_NOW contains 0xffffffffffffffff. inspect_dart_aot returns unsupported_target with No recognizable Dart snapshot symbols were found: Dynamic entry is not representable.

Changing only that ignored word to zero restores the four snapshot sections, their agreeing hash, a package URI and complete coverage. A high-valued DT_NULL.d_un control succeeds on main, distinguishing this from the already-fixed terminator case.

Reproduction

From the source checkout below with locked dependencies, save this as /tmp/rea-flutter-bind-now.mts and run node_modules/.bin/tsx /tmp/rea-flutter-bind-now.mts. This is the executed probe with its scratch path generalized. It creates inert ZIP APKs, not runnable Flutter applications.

import { Buffer } from 'node:buffer';
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { pathToFileURL } from 'node:url';
import { createRequire } from 'node:module';
const root = '/tmp/rea-flutter-bind-now';
await mkdir(root, { recursive: true });
process.env.TMPDIR = root;
const source = (path: string) => import(pathToFileURL(join(process.cwd(), path)).href);
const require = createRequire(join(process.cwd(), 'package.json'));
const { ZipWriter, Uint8ArrayWriter, Uint8ArrayReader } = require('@zip.js/zip.js');
const { buildStrippedElf64Image, snapshotSection, REFERENCE_HASH } = await source('src/flutter/FlutterBuild.fixture.ts');
const { FlutterBuildProvider } = await source('src/flutter/FlutterBuildProvider.ts');
const { FlutterBuildAnalysisService } = await source('src/application/flutter/FlutterBuildAnalysisService.ts');
const { projectAnalysisError } = await source('src/domain/analysisErrorProjection.ts');
const declarations = [
  { name: '', value: 0, size: 0 },
  { name: '_kDartVmSnapshotData', value: 0x1000, size: 52 },
  { name: '_kDartVmSnapshotInstructions', value: 0x1800, size: 16 },
  { name: '_kDartIsolateSnapshotData', value: 0x2000, size: 0x600 },
  { name: '_kDartIsolateSnapshotInstructions', value: 0x2800, size: 16 },
];
const prefix = buildStrippedElf64Image(declarations);
const image = Buffer.alloc(0x3080);
prefix.copy(image);
const originalDynamic = Number(image.readBigUInt64LE(128));
const symbols = Number(image.readBigUInt64LE(originalDynamic + 8));
const hash = Number(image.readBigUInt64LE(originalDynamic + 4 * 16 + 8));
image.fill(0, symbols, symbols + 24);
image.writeUInt32LE(1, hash + 8);
for (let i = 1; i < declarations.length; i++) {
  image[symbols + i * 24 + 4] = 0x11;
  image.writeUInt32LE(i === declarations.length - 1 ? 0 : i + 1, hash + 12 + i * 4);
}
snapshotSection(REFERENCE_HASH).copy(image, 0x1000);
snapshotSection(REFERENCE_HASH).copy(image, 0x2000);
Buffer.from('\0package:demo/main.dart\0dart:core\0ExampleClass\0').copy(image, 0x2040);
const dynamic = 0x3000;
prefix.copy(image, dynamic, originalDynamic, originalDynamic + 5 * 16);
image.writeUInt32LE(5, 68);
image.writeBigUInt64LE(BigInt(image.length), 96);
image.writeBigUInt64LE(BigInt(image.length), 104);
image.writeBigUInt64LE(4096n, 112);
image.writeUInt32LE(4, 124);
image.writeBigUInt64LE(BigInt(dynamic), 128);
image.writeBigUInt64LE(BigInt(dynamic), 136);
image.writeBigUInt64LE(112n, 152);
image.writeBigUInt64LE(112n, 160);
image.writeBigUInt64LE(8n, 168);
image.writeBigUInt64LE(24n, dynamic + 80); // DT_BIND_NOW
const results = [];
for (const [label, ignored, nullValue] of [
  ['control', 0n, 0n], ['bind-now-high', 0xffffffffffffffffn, 0n],
  ['restored', 0n, 0n], ['null-high-control', 0n, 0xffffffffffffffffn],
] as const) {
  image.writeBigUInt64LE(ignored, dynamic + 88);
  image.writeBigUInt64LE(nullValue, dynamic + 104); // DT_NULL tag remains zero
  await writeFile(join(root, label + '.so'), image);
  const writer = new ZipWriter(new Uint8ArrayWriter(), { useWebWorkers: false });
  await writer.add('lib/x86_64/libapp.so', new Uint8ArrayReader(image), { level: 0 });
  const path = join(root, label + '.apk');
  await writeFile(path, await writer.close());
  const provider = new FlutterBuildProvider();
  try {
    const result = await new FlutterBuildAnalysisService(provider).execute('inspect_dart_aot', { path, abi: 'x86_64' });
    const value = result.ok ? result.value.normalized_result : null;
    const facts = result.ok ? {
      label, ok: true, coverage: value.coverage, structural_coverage: value.libapp.coverage,
      symbols: value.libapp.sections.length, snapshot_hash: value.libapp.snapshot_hash,
      package_uris: value.string_pool.package_uris,
    } : { label, ok: false, error_tag: result.error._tag, code: projectAnalysisError(result.error).code, message: result.error.message };
    results.push(facts);
    console.log(JSON.stringify(facts));
  } finally { await provider.close(); }
}
await writeFile(join(root, 'results.json'), JSON.stringify(results, null, 2) + '\n');

The research worker executed all four cases; its coordinating subsystem researcher independently repeated them in a separate scratch root with matching results. Real CLI/MCP transports and a packaged installation were not run.

Expected behavior

Changing an ignored dynamic-entry payload should not affect snapshot discovery or coverage. The ELF dynamic-section specification, Table 8.1 defines tag 24, DT_BIND_NOW, with d_un ignored. Deprecation does not remove that defined interpretation.

The Flutter analysis guide supports section-stripped little-endian ELF64 with bounded dynamic-symbol tables. Both inputs retain those tables and the four supported snapshot exports.

Impact and scope

A valid ignored metadata word blocks the complete AOT observation, including otherwise available snapshot and string-pool facts. Observed scope is the synthetic ELF64 little-endian x86-64 layout and its controls. Natural APK frequency and other architectures were not measured. No target execution, source modification or data loss occurred.

Zeroing the ignored word restores the parser but changes the selected artifact's identity; no workaround preserving the exact artifact was established.

REA version

Runtime observations: main source 9066444f6b8e9ffe1607a9b02cde3895a37db431, manifest version 6.7.0.

Release applicability was confirmed from source in rea-agents-6.7.0, revision 27dc42d10249f0410904a75c0997842eb03b931c: its loop also reads and rejects the ignored word before skipping unconsumed tags. The published package was not executed.

Runtime and platform

Node.js v26.12.1; Linux x86_64; repository-locked tsx. No external analysis engine was started. readelf -W -d -sD was used as an independent structural check of the failing ELF.

Target and artifact context

Synthetic APKs containing lib/x86_64/libapp.so, with a reserved null symbol, four global OBJECT snapshot exports, linked SysV hash chains, bounded load mappings, DT_BIND_NOW, and a separate DT_NULL terminator.

Interface

Other

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Executed shared-service results, with the fixture-local path omitted from the failure message:

{"label":"control","ok":true,"coverage":"complete","structural_coverage":"complete","symbols":4,"snapshot_hash":"65817c30a78bb44c3dc3771876b6010a","package_uris":["package:demo/main.dart"]}
{"label":"bind-now-high","ok":false,"error_tag":"AnalysisUnsupportedTargetError","code":"unsupported_target"}
{"label":"restored","ok":true,"coverage":"complete","structural_coverage":"complete","symbols":4,"snapshot_hash":"65817c30a78bb44c3dc3771876b6010a","package_uris":["package:demo/main.dart"]}
{"label":"null-high-control","ok":true,"coverage":"complete","structural_coverage":"complete","symbols":4,"snapshot_hash":"65817c30a78bb44c3dc3771876b6010a","package_uris":["package:demo/main.dart"]}

readelf exited 0 and listed seven dynamic entries and all four exports. The compared ELF files were 12,416 bytes and differed only at offsets 12,376–12,383, the ignored word.

Evidence provenance: observed shared-service/provider behavior, plus separately attributed format rules and source reasoning. Local verification: reproduced. Reproduction completeness: complete for these parser fixtures; runnable-app behavior was not tested.

Typed outcome or recovery expected

Both DT_BIND_NOW variants should return successful Evidence with the same snapshot sections, hash, URI facts and coverage, while preserving their distinct artifact identities. The high-valued DT_NULL control should remain successful. Retain bounds and representability checks for entries that actually supply addresses, sizes or counts.

Analysis or suspected cause

Source-derived: the dynamic-entry loop handles the terminator first, but decodes every other payload before filtering tags. An unconsumed DT_BIND_NOW entry therefore fails safe-integer conversion before its ignored payload can be skipped. The release loop has the same issue.

Additional context

Closed #6768 concerns DT_NULL.d_un; main now passes that control while failing this non-terminating entry. #6792 concerns a different export parser and missing section metadata. #3629 concerns renamed exports; these fixtures use the supported legacy names.

NOT_ENOUGH_INFO: the first affected release and natural APK frequency were not established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The demonstrated valid-metadata compatibility failure blocks complete AOT inspection; failure, restoration and an independent ELF inspection support this bounded result.
  • Workaround status: unverified
  • Workaround basis: Zeroing the ignored bytes restores inspection but changes input identity. No practical workaround preserving the exact artifact and complete observation was established.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6851 ### 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 production shared service rejects a section-stripped ELF64 when the ABI-ignored `d_un` word of `DT_BIND_NOW` contains `0xffffffffffffffff`. `inspect_dart_aot` returns `unsupported_target` with `No recognizable Dart snapshot symbols were found: Dynamic entry is not representable`. Changing only that ignored word to zero restores the four snapshot sections, their agreeing hash, a package URI and complete coverage. A high-valued `DT_NULL.d_un` control succeeds on main, distinguishing this from the already-fixed terminator case. ### Reproduction From the source checkout below with locked dependencies, save this as `/tmp/rea-flutter-bind-now.mts` and run `node_modules/.bin/tsx /tmp/rea-flutter-bind-now.mts`. This is the executed probe with its scratch path generalized. It creates inert ZIP APKs, not runnable Flutter applications. ```ts import { Buffer } from 'node:buffer'; import { mkdir, writeFile } from 'node:fs/promises'; import { join } from 'node:path'; import { pathToFileURL } from 'node:url'; import { createRequire } from 'node:module'; const root = '/tmp/rea-flutter-bind-now'; await mkdir(root, { recursive: true }); process.env.TMPDIR = root; const source = (path: string) => import(pathToFileURL(join(process.cwd(), path)).href); const require = createRequire(join(process.cwd(), 'package.json')); const { ZipWriter, Uint8ArrayWriter, Uint8ArrayReader } = require('@zip.js/zip.js'); const { buildStrippedElf64Image, snapshotSection, REFERENCE_HASH } = await source('src/flutter/FlutterBuild.fixture.ts'); const { FlutterBuildProvider } = await source('src/flutter/FlutterBuildProvider.ts'); const { FlutterBuildAnalysisService } = await source('src/application/flutter/FlutterBuildAnalysisService.ts'); const { projectAnalysisError } = await source('src/domain/analysisErrorProjection.ts'); const declarations = [ { name: '', value: 0, size: 0 }, { name: '_kDartVmSnapshotData', value: 0x1000, size: 52 }, { name: '_kDartVmSnapshotInstructions', value: 0x1800, size: 16 }, { name: '_kDartIsolateSnapshotData', value: 0x2000, size: 0x600 }, { name: '_kDartIsolateSnapshotInstructions', value: 0x2800, size: 16 }, ]; const prefix = buildStrippedElf64Image(declarations); const image = Buffer.alloc(0x3080); prefix.copy(image); const originalDynamic = Number(image.readBigUInt64LE(128)); const symbols = Number(image.readBigUInt64LE(originalDynamic + 8)); const hash = Number(image.readBigUInt64LE(originalDynamic + 4 * 16 + 8)); image.fill(0, symbols, symbols + 24); image.writeUInt32LE(1, hash + 8); for (let i = 1; i < declarations.length; i++) { image[symbols + i * 24 + 4] = 0x11; image.writeUInt32LE(i === declarations.length - 1 ? 0 : i + 1, hash + 12 + i * 4); } snapshotSection(REFERENCE_HASH).copy(image, 0x1000); snapshotSection(REFERENCE_HASH).copy(image, 0x2000); Buffer.from('\0package:demo/main.dart\0dart:core\0ExampleClass\0').copy(image, 0x2040); const dynamic = 0x3000; prefix.copy(image, dynamic, originalDynamic, originalDynamic + 5 * 16); image.writeUInt32LE(5, 68); image.writeBigUInt64LE(BigInt(image.length), 96); image.writeBigUInt64LE(BigInt(image.length), 104); image.writeBigUInt64LE(4096n, 112); image.writeUInt32LE(4, 124); image.writeBigUInt64LE(BigInt(dynamic), 128); image.writeBigUInt64LE(BigInt(dynamic), 136); image.writeBigUInt64LE(112n, 152); image.writeBigUInt64LE(112n, 160); image.writeBigUInt64LE(8n, 168); image.writeBigUInt64LE(24n, dynamic + 80); // DT_BIND_NOW const results = []; for (const [label, ignored, nullValue] of [ ['control', 0n, 0n], ['bind-now-high', 0xffffffffffffffffn, 0n], ['restored', 0n, 0n], ['null-high-control', 0n, 0xffffffffffffffffn], ] as const) { image.writeBigUInt64LE(ignored, dynamic + 88); image.writeBigUInt64LE(nullValue, dynamic + 104); // DT_NULL tag remains zero await writeFile(join(root, label + '.so'), image); const writer = new ZipWriter(new Uint8ArrayWriter(), { useWebWorkers: false }); await writer.add('lib/x86_64/libapp.so', new Uint8ArrayReader(image), { level: 0 }); const path = join(root, label + '.apk'); await writeFile(path, await writer.close()); const provider = new FlutterBuildProvider(); try { const result = await new FlutterBuildAnalysisService(provider).execute('inspect_dart_aot', { path, abi: 'x86_64' }); const value = result.ok ? result.value.normalized_result : null; const facts = result.ok ? { label, ok: true, coverage: value.coverage, structural_coverage: value.libapp.coverage, symbols: value.libapp.sections.length, snapshot_hash: value.libapp.snapshot_hash, package_uris: value.string_pool.package_uris, } : { label, ok: false, error_tag: result.error._tag, code: projectAnalysisError(result.error).code, message: result.error.message }; results.push(facts); console.log(JSON.stringify(facts)); } finally { await provider.close(); } } await writeFile(join(root, 'results.json'), JSON.stringify(results, null, 2) + '\n'); ``` The research worker executed all four cases; its coordinating subsystem researcher independently repeated them in a separate scratch root with matching results. Real CLI/MCP transports and a packaged installation were not run. ### Expected behavior Changing an ignored dynamic-entry payload should not affect snapshot discovery or coverage. [The ELF dynamic-section specification, Table 8.1](https://gabi.xinuos.com/elf/08-dynamic.html#dynamic-section) defines tag 24, `DT_BIND_NOW`, with `d_un` ignored. Deprecation does not remove that defined interpretation. The [Flutter analysis guide](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/docs/flutter-analysis.md#L152-L157) supports section-stripped little-endian ELF64 with bounded dynamic-symbol tables. Both inputs retain those tables and the four supported snapshot exports. ### Impact and scope A valid ignored metadata word blocks the complete AOT observation, including otherwise available snapshot and string-pool facts. Observed scope is the synthetic ELF64 little-endian x86-64 layout and its controls. Natural APK frequency and other architectures were not measured. No target execution, source modification or data loss occurred. Zeroing the ignored word restores the parser but changes the selected artifact's identity; no workaround preserving the exact artifact was established. ### REA version Runtime observations: main source `9066444f6b8e9ffe1607a9b02cde3895a37db431`, manifest version `6.7.0`. Release applicability was confirmed from source in `rea-agents-6.7.0`, revision `27dc42d10249f0410904a75c0997842eb03b931c`: its loop also reads and rejects the ignored word before skipping unconsumed tags. The published package was not executed. ### Runtime and platform Node.js v26.12.1; Linux x86_64; repository-locked tsx. No external analysis engine was started. `readelf -W -d -sD` was used as an independent structural check of the failing ELF. ### Target and artifact context Synthetic APKs containing `lib/x86_64/libapp.so`, with a reserved null symbol, four global OBJECT snapshot exports, linked SysV hash chains, bounded load mappings, `DT_BIND_NOW`, and a separate `DT_NULL` terminator. ### Interface Other ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Executed shared-service results, with the fixture-local path omitted from the failure message: ```json {"label":"control","ok":true,"coverage":"complete","structural_coverage":"complete","symbols":4,"snapshot_hash":"65817c30a78bb44c3dc3771876b6010a","package_uris":["package:demo/main.dart"]} {"label":"bind-now-high","ok":false,"error_tag":"AnalysisUnsupportedTargetError","code":"unsupported_target"} {"label":"restored","ok":true,"coverage":"complete","structural_coverage":"complete","symbols":4,"snapshot_hash":"65817c30a78bb44c3dc3771876b6010a","package_uris":["package:demo/main.dart"]} {"label":"null-high-control","ok":true,"coverage":"complete","structural_coverage":"complete","symbols":4,"snapshot_hash":"65817c30a78bb44c3dc3771876b6010a","package_uris":["package:demo/main.dart"]} ``` `readelf` exited 0 and listed seven dynamic entries and all four exports. The compared ELF files were 12,416 bytes and differed only at offsets 12,376–12,383, the ignored word. Evidence provenance: observed shared-service/provider behavior, plus separately attributed format rules and source reasoning. Local verification: reproduced. Reproduction completeness: complete for these parser fixtures; runnable-app behavior was not tested. ### Typed outcome or recovery expected Both `DT_BIND_NOW` variants should return successful Evidence with the same snapshot sections, hash, URI facts and coverage, while preserving their distinct artifact identities. The high-valued `DT_NULL` control should remain successful. Retain bounds and representability checks for entries that actually supply addresses, sizes or counts. ### Analysis or suspected cause Source-derived: [the dynamic-entry loop](https://github.com/morluto/rea/blob/9066444f6b8e9ffe1607a9b02cde3895a37db431/src/flutter/DartElf.ts#L325-L340) handles the terminator first, but decodes every other payload before filtering tags. An unconsumed `DT_BIND_NOW` entry therefore fails safe-integer conversion before its ignored payload can be skipped. [The release loop](https://github.com/morluto/rea/blob/27dc42d10249f0410904a75c0997842eb03b931c/src/flutter/DartElf.ts#L325-L338) has the same issue. ### Additional context Closed [#6768](https://github.com/morluto/rea/issues/6768) concerns `DT_NULL.d_un`; main now passes that control while failing this non-terminating entry. [#6792](https://github.com/morluto/rea/issues/6792) concerns a different export parser and missing section metadata. [#3629](https://github.com/morluto/rea/issues/3629) concerns renamed exports; these fixtures use the supported legacy names. NOT_ENOUGH_INFO: the first affected release and natural APK frequency were not established. ### Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The demonstrated valid-metadata compatibility failure blocks complete AOT inspection; failure, restoration and an independent ELF inspection support this bounded result. - Workaround status: unverified - Workaround basis: Zeroing the ignored bytes restores inspection but changes input identity. No practical workaround preserving the exact artifact and complete observation was established.
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#4191
No description provided.