Skip to content

fix: validate command lock process identity - #286

Closed
UmiSlat wants to merge 1 commit into
ravindu644:mainfrom
UmiSlat:fix/stale-lock-pid-reuse
Closed

fix: validate command lock process identity#286
UmiSlat wants to merge 1 commit into
ravindu644:mainfrom
UmiSlat:fix/stale-lock-pid-reuse

Conversation

@UmiSlat

@UmiSlat UmiSlat commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Problem

External command locks previously stored only the CLI PID and treated
kill(pid, 0) == 0 as proof that the original owner was still running.
This is unsafe when a stale lock survives a host reboot and Android reuses
the PID for an unrelated process.

The issue was reproduced with Droidspaces v6.4.5. The host rebooted while
the Debian13.6 container was running, leaving a lock owned by PID 4806.
After reboot, Android reused PID 4806 for com.android.systemui, whose
/proc/4806/comm value was TB_Client.

Trying to stop the container then failed:

Cannot acquire lock: held by process 4806
Cannot stop 'Debian13.6': another command is managing this container

Solution

  • Store new command locks as <pid> <proc-start-ticks> <boot-id>.
  • Compare the boot ID and /proc/<pid>/stat starttime before accepting an
    existing lock as active.
  • Treat PID-only legacy locks as active only when /proc/<pid>/comm equals
    droidspaces.
  • Continue reading PID-only and PID plus starttime lock formats.
  • Fall back to PID plus starttime, then PID only, when procfs identity
    metadata is unavailable.
  • Preserve conservative behavior when a live process exists but procfs
    identity cannot be read.
  • Apply the same ownership checks during acquisition, release, and monitor
    cleanup.
  • Share the proc starttime parser with the existing socketd code instead of
    maintaining duplicate implementations.

Compatibility

  • Older Droidspaces versions still read the leading PID from the new format.
  • Existing PID-only locks remain readable.
  • Same-PID command handoff behavior is preserved.
  • No new syscall or kernel 5.x interface is required.
  • Lock creation degrades gracefully when boot ID or starttime metadata is
    unavailable.
  • Container PID files, metadata, scripts, and migration behavior are unchanged.

Testing

Regression harness:

36 checks, 0 failures
36 checks, 0 failures (UBSan)

The shared proc starttime parser was also tested with spaces and ) inside
the process command name:

proc starttime parser checks passed
proc starttime parser checks passed (UBSan)

GitHub CI passed on the final commit:

  • musl backend builds for aarch64, x86_64, armhf, x86, and riscv64
  • unified tarball and Debian packages
  • Android release APK
  • artifact staging and upload

CI run:
https://github.com/UmiSlat/Droidspaces-OSS/actions/runs/32350081335

Tested environment

  • Device: Xiaomi Pad 8 Pro, model 25091RP04C
  • SoC: Qualcomm SM8750P
  • Architecture: arm64-v8a
  • Android: Android 16, API 36, HyperOS OS3.0
  • Kernel: 6.6.77-android15-8-g8d05d8f2439f6-ab10030741-4k
  • Userspace: Debian GNU/Linux 13.6 (trixie), aarch64
  • Affected Droidspaces version: v6.4.5

The fixed binary has not yet been installed for a complete live container
and host reboot lifecycle test. Kernel 3.10 real hardware has not been tested.

Command locks previously stored only a PID, so a stale lock could survive a host reboot and block container management after Android reused that PID.

Store process start ticks and the boot ID with new locks, while retaining read compatibility and conservative fallbacks for older lock formats. Share proc starttime parsing with socketd to avoid duplicate logic.

Signed-off-by: UmiSlat <275275677+UmiSlat@users.noreply.github.com>
ravindu644 added a commit that referenced this pull request Aug 22, 2026
External command locks stored only the CLI PID and trusted
kill(pid, 0) == 0 as proof the holder was still running. A lock left
behind by a reboot mid-command survives on persistent storage, and
after boot Android almost always hands that PID to an unrelated
long-lived process, so every later command on the container fails
with "Cannot acquire lock: held by process N" until the file is
deleted by hand. Reported against v6.4.5, where PID 4806 was reused
by com.android.systemui after a reboot (PR #286).

A lock holder is always a CLI process, so no lock can legitimately
outlive a boot. Stamp locks as "<pid> <boot-id>" and accept only
that exact format with a matching boot ID and a live PID. Anything
else, including pid-only locks from older versions, is removed and
the command proceeds, since a bare PID cannot be told apart from
post-reboot PID reuse. Stale lock cleanup is logged with the [DEBUG]
tag, so it reaches the container log file but not the terminal.
Older binaries atoi() the new format and still read the leading PID.

Co-authored-by: UmiSlat <275275677+UmiSlat@users.noreply.github.com>
Signed-off-by: ravindu644 <droidcasts@protonmail.com>
@ravindu644

Copy link
Copy Markdown
Owner

Thanks for this, confirmed real. The lock lives on persistent storage, nothing sweeps it at boot, and as root kill(pid, 0) succeeds against any live process, so a reboot mid-command plus Android's dense PID reuse bricks the container exactly as you described.

I landed a smaller version of your fix on dev as 5d20069, with you as co-author. It keeps your core insight but leans on one invariant: a lock holder is always a CLI process, so no lock can legitimately outlive a boot. Locks are now stamped and only that exact format with a matching boot ID and a live PID counts as held. Anything else, including old pid-only locks, is removed and the command proceeds, since a bare PID cannot be told apart from PID reuse. That also made the starttime and comm checks unnecessary. Old binaries still atoi() the leading PID fine.

Verified on device: the reported scenario now self-heals silently and a live lock still blocks a second command. Closing in favor of 5d20069. Thanks again for the excellent report and compatibility analysis.

@ravindu644 ravindu644 closed this Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants