# Tar Slip via Unsafe tarfile Extraction

Language: Python
Severity: Critical
CWE: CWE-22

## Source
1

## Flow
1-4-6-7

## Sink
7

## Vulnerable Code
```python
import tarfile
import os

def restore_iot_firmware_backup(backup_archive, device_id):
    restore_path = f"/opt/iot/devices/{device_id}/firmware"
    os.makedirs(restore_path, exist_ok=True)
    with tarfile.open(backup_archive, 'r:gz') as tar_handle:
        for member in tar_handle.getmembers():
            tar_handle.extract(member, path=restore_path)
    return {"status": "restored", "device": device_id, "location": restore_path}
```

## Explanation

The function accepts an untrusted backup_archive path and extracts tar members without validating their paths. A malicious tar archive can contain path traversal sequences (../) in member names, allowing files to be written outside the intended restore_path directory, potentially overwriting critical system files.

## Remediation

The fix validates each tar member's resolved path to ensure it stays within the intended restore_path directory before extraction. It uses os.path.realpath to resolve any relative path components (like ../) and checks that the resulting absolute path starts with the restore directory prefix. Additionally, it checks symlinks and hardlinks to prevent indirect path traversal attacks.

## Secure Code
```python
import tarfile
import os

def restore_iot_firmware_backup(backup_archive, device_id):
    restore_path = os.path.realpath(f"/opt/iot/devices/{device_id}/firmware")
    os.makedirs(restore_path, exist_ok=True)
    with tarfile.open(backup_archive, 'r:gz') as tar_handle:
        for member in tar_handle.getmembers():
            member_path = os.path.realpath(os.path.join(restore_path, member.name))
            if not member_path.startswith(restore_path + os.sep) and member_path != restore_path:
                raise ValueError(f"Attempted path traversal in tar member: {member.name}")
            if member.issym() or member.islnk():
                link_target = os.path.realpath(os.path.join(restore_path, os.path.dirname(member.name), member.linkname))
                if not link_target.startswith(restore_path + os.sep) and link_target != restore_path:
                    raise ValueError(f"Attempted path traversal via symlink in tar member: {member.name}")
            tar_handle.extract(member, path=restore_path)
    return {"status": "restored", "device": device_id, "location": restore_path}
```
