[GH-ISSUE #6566] [Bug] push-adb-file removes executable bits while staging executable inputs #3957

Closed
opened 2026-10-31 00:45: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/6566

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

Source-backed finding: push_adb_file stages every input with mode 0400, then gives that staged file to ADB. This removes all executable bits from a valid executable input before ADB reads its mode. A direct probe of the published 6.7.0 snapshot helper observed 0755 → 0400; current main has the same logic. No actual ADB/device transfer or public CLI/MCP execution was performed.

Reproduction

The helper probe ran once with an ordinary 24-byte shell-script fixture, mode 0755, a disposable staging directory, and snapshotAdbInput(source, {path: stagingDirectory}, resources, "push_adb_file"). The observed result was {"release":"6.7.0","source_mode":493,"snapshot_mode":256,"snapshot_execute_bits":0,"bytes":24}; modes 493 and 256 are octal 0755 and 0400. The fixture was removed after the probe.

Concrete supported transfer trigger, derived from source and not run: on a connected Android 14 device, with a local executable /tmp/rea-example/tool.sh and an absent writable /data/local/tmp/tool.sh, call push_adb_file with {"serial":"emulator-5554","local_path":"/tmp/rea-example/tool.sh","device_path":"/data/local/tmp/tool.sh","overwrite":false}. The public request schema admits that input, and the provider passes the snapshot to adb push.

Expected behavior

Pushing an executable local file should preserve its executable transfer semantics, as direct ADB push does. REA documents pushing executable tools to /data/local/tmp. Private staging should preserve the needed transfer metadata or explicitly expose a different result, instead of silently changing an executable input into a non-executable device file.

Impact and scope

P2: executable tools and scripts pushed to ordinary executable device storage lose their executable mode even when their bytes and SHA-256 match. For the Android 14 /data/ trigger above, AOSP's mode handling turns staged 0400 into 0444, retaining no execute bits. The source file is unchanged. Other device filesystem policies were not assessed. A manual device-side chmod or direct ADB push is a plausible workaround, not exercised here.

REA version

Current main 6bb4f4bf7d64e5d2f47479d46225a5383b57cdad; published rea-agents@6.7.0, tag rea-agents-6.7.0 at 27dc42d10249f0410904a75c0997842eb03b931c, has the same staging rule and was used for the helper probe.

Runtime and platform

Helper probe: Node.js v26.12.1, Linux x64. Real ADB/adbd runtime: NOT_ENOUGH_INFO; no device or platform-tools binary was used. Android 14 downstream behavior is derived from pinned AOSP source.

Target and artifact context

A valid executable local regular file, pushed to a new writable file under /data/local/tmp/ on Android 14; no target code was executed.

Interface

MCP

Earliest failure stage

Capture or observation

Relevant output or evidence

Typed outcome or recovery expected

The delivered file should retain the source's executable intent, or the result should explicitly identify that execution-related metadata was changed. A successful content digest alone does not establish equivalent file usability.

Analysis or suspected cause

The immutable byte snapshot owns the source bytes but discards the original mode before the external consumer reads it. ADB consumes the staged file's mode, not the caller file's mode. This is independent of byte-identity issue #4252.

Additional context

Open and closed keyword searches for "push_adb_file" "executable" and "push-adb-file" "mode" found no matching report. #5924 concerns checksum-source attribution and #3369 concerns checksum capture overflow; neither addresses lost execute bits. A proposed restoration check is to push a 0755 input to a fresh Android 14 /data/local/tmp/ path and compare execute bits with direct ADB push, while confirming byte digests still match. That check was not run.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The observed helper strips every execute bit, and the reachable provider and pinned AOSP source establish a non-executable delivery for the bounded Android 14 trigger despite unchanged file bytes.
  • Workaround status: unverified
  • Workaround basis: Direct ADB push or applying chmod after transfer should restore executable mode, but neither workaround was exercised on a device.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6566 ### 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 Source-backed finding: `push_adb_file` stages every input with mode `0400`, then gives that staged file to ADB. This removes all executable bits from a valid executable input before ADB reads its mode. A direct probe of the published 6.7.0 snapshot helper observed `0755 → 0400`; current main has the same logic. No actual ADB/device transfer or public CLI/MCP execution was performed. ### Reproduction The helper probe ran once with an ordinary 24-byte shell-script fixture, mode `0755`, a disposable staging directory, and `snapshotAdbInput(source, {path: stagingDirectory}, resources, "push_adb_file")`. The observed result was `{"release":"6.7.0","source_mode":493,"snapshot_mode":256,"snapshot_execute_bits":0,"bytes":24}`; modes 493 and 256 are octal `0755` and `0400`. The fixture was removed after the probe. Concrete supported transfer trigger, derived from source and not run: on a connected Android 14 device, with a local executable `/tmp/rea-example/tool.sh` and an absent writable `/data/local/tmp/tool.sh`, call `push_adb_file` with `{"serial":"emulator-5554","local_path":"/tmp/rea-example/tool.sh","device_path":"/data/local/tmp/tool.sh","overwrite":false}`. The public [request schema](https://github.com/morluto/rea/blob/6bb4f4bf7d64e5d2f47479d46225a5383b57cdad/src/domain/adb/adbDeviceAnalysis.ts#L176-L192) admits that input, and the [provider passes the snapshot to adb push](https://github.com/morluto/rea/blob/6bb4f4bf7d64e5d2f47479d46225a5383b57cdad/src/adb/AdbProvider.ts#L2417-L2453). ### Expected behavior Pushing an executable local file should preserve its executable transfer semantics, as direct ADB push does. REA documents [pushing executable tools to /data/local/tmp](https://github.com/morluto/rea/blob/6bb4f4bf7d64e5d2f47479d46225a5383b57cdad/docs/adb-device-analysis.md#L51-L58). Private staging should preserve the needed transfer metadata or explicitly expose a different result, instead of silently changing an executable input into a non-executable device file. ### Impact and scope P2: executable tools and scripts pushed to ordinary executable device storage lose their executable mode even when their bytes and SHA-256 match. For the Android 14 `/data/` trigger above, AOSP's mode handling turns staged `0400` into `0444`, retaining no execute bits. The source file is unchanged. Other device filesystem policies were not assessed. A manual device-side `chmod` or direct ADB push is a plausible workaround, not exercised here. ### REA version Current main `6bb4f4bf7d64e5d2f47479d46225a5383b57cdad`; published `rea-agents@6.7.0`, tag `rea-agents-6.7.0` at `27dc42d10249f0410904a75c0997842eb03b931c`, has the same staging rule and was used for the helper probe. ### Runtime and platform Helper probe: Node.js v26.12.1, Linux x64. Real ADB/adbd runtime: NOT_ENOUGH_INFO; no device or platform-tools binary was used. Android 14 downstream behavior is derived from pinned AOSP source. ### Target and artifact context A valid executable local regular file, pushed to a new writable file under `/data/local/tmp/` on Android 14; no target code was executed. ### Interface MCP ### Earliest failure stage Capture or observation ### Relevant output or evidence - Expected source: the documented tool-push workflow above and AOSP's [client handoff of source st_mode](https://android.googlesource.com/platform/packages/modules/adb/+/a877adddefdf011bfb5d4045919c6df3d3727ed3/client/file_sync_client.cpp#1534). - Failure source: [snapshotAdbInput unconditionally chmods the snapshot to 0400](https://github.com/morluto/rea/blob/6bb4f4bf7d64e5d2f47479d46225a5383b57cdad/src/adb/AdbInputSnapshot.ts#L124-L139), followed by the provider's snapshot handoff above. The [6.7.0 snapshot rule is identical](https://github.com/morluto/rea/blob/27dc42d10249f0410904a75c0997842eb03b931c/src/adb/AdbInputSnapshot.ts#L124-L139). - Evidence provenance: static main-source proof plus the separately observed published-helper mode result shown under Reproduction. - Local verification: static-proof for the end-to-end finding; runtime transfer reproduction was not run. The direct 6.7.0 helper probe observed the staging premise only. - Reproduction completeness: complete source-derived trigger; actual real-device execution remains unperformed. - Downstream premise: Android 14 adbd [skips filesystem configuration overrides under /data/](https://android.googlesource.com/platform/packages/modules/adb/+/0c88a54d6300ddc98f27bfc3ade96190b53b7c86/daemon/file_sync_service.cpp#71) and [propagates the supplied user permission bits to group and other](https://android.googlesource.com/platform/packages/modules/adb/+/0c88a54d6300ddc98f27bfc3ade96190b53b7c86/daemon/file_sync_service.cpp#524); no execute bit can be recovered from mode `0400`. ### Typed outcome or recovery expected The delivered file should retain the source's executable intent, or the result should explicitly identify that execution-related metadata was changed. A successful content digest alone does not establish equivalent file usability. ### Analysis or suspected cause The immutable byte snapshot owns the source bytes but discards the original mode before the external consumer reads it. ADB consumes the staged file's mode, not the caller file's mode. This is independent of byte-identity issue [#4252](https://github.com/morluto/rea/issues/4252). ### Additional context Open and closed keyword searches for `"push_adb_file" "executable"` and `"push-adb-file" "mode"` found no matching report. [#5924](https://github.com/morluto/rea/issues/5924) concerns checksum-source attribution and [#3369](https://github.com/morluto/rea/issues/3369) concerns checksum capture overflow; neither addresses lost execute bits. A proposed restoration check is to push a `0755` input to a fresh Android 14 `/data/local/tmp/` path and compare execute bits with direct ADB push, while confirming byte digests still match. That check was not run. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The observed helper strips every execute bit, and the reachable provider and pinned AOSP source establish a non-executable delivery for the bounded Android 14 trigger despite unchanged file bytes. - Workaround status: unverified - Workaround basis: Direct ADB push or applying chmod after transfer should restore executable mode, but neither workaround was exercised on a device.
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#3957
No description provided.