# Tar Slip via unsafe tarfile extraction

Language: Python
Severity: Critical
CWE: CWE-22

## Source
2

## Flow
2-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:
        for member in tar.getmembers():
            tar.extract(member, path=restore_path)
    return {"status": "restored", "device": device_id, "location": restore_path}
```

## Explanation

The code extracts tar archive members without validating their paths, allowing path traversal attacks (Tar Slip). A malicious tar archive can contain members with paths like '../../../etc/passwd' that escape the intended restore_path directory when extracted, potentially overwriting critical system files.

## Remediation

The fix resolves each tar member's full path and verifies it stays within the intended restore directory before extraction. It also validates symlink targets to prevent indirect path traversal through symbolic links. Any member that would escape the target directory raises a ValueError, preventing the extraction entirely.

## 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:
        for member in tar.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"Illegal path in archive: {member.name} resolves outside target directory")
            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"Illegal symlink target in archive: {member.name} -> {member.linkname}")
            tar.extract(member, path=restore_path)
    return {"status": "restored", "device": device_id, "location": restore_path}
```
