# PATH Hijacking via Unsafe subprocess.Popen Search Order

Language: Python
Severity: High
CWE: CWE-426

## Source
3

## Flow
3-5-6-8

## Sink
8

## Vulnerable Code
```python
import subprocess
import os

def deploy_model_to_edge(device_id, model_name, config_path):
    edge_env = os.environ.copy()
    edge_env['DEVICE_TARGET'] = device_id
    edge_env['PATH'] = f"/tmp/edge_tools:{edge_env.get('PATH', '')}"
    deployment_cmd = ['model_optimizer', '--input', model_name, '--config', config_path, '--device', device_id]
    proc = subprocess.Popen(deployment_cmd, env=edge_env, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, errors = proc.communicate(timeout=120)
    if proc.returncode == 0:
        return {'status': 'deployed', 'device': device_id, 'output': output.decode()}
    else:
        return {'status': 'failed', 'error': errors.decode()}
```

## Explanation

The code prepends '/tmp/edge_tools' to the PATH environment variable before executing 'model_optimizer' via subprocess.Popen. An attacker with write access to /tmp/edge_tools can place a malicious 'model_optimizer' executable there, which will be executed instead of the legitimate binary due to PATH search order hijacking.

## Remediation

The fix removes the unsafe PATH modification that prepended the world-writable /tmp/edge_tools directory. Instead, the model_optimizer binary is resolved using an explicit whitelist of trusted absolute paths, with symlink resolution and directory verification to prevent path traversal attacks. This eliminates the PATH hijacking vector entirely.

## Secure Code
```python
import subprocess
import os
import shutil

ALLOWED_OPTIMIZER_PATHS = [
    '/usr/local/bin/model_optimizer',
    '/opt/edge_tools/bin/model_optimizer',
    '/usr/bin/model_optimizer'
]

def _resolve_optimizer_path():
    """Resolve the model_optimizer binary from a whitelist of trusted paths."""
    for path in ALLOWED_OPTIMIZER_PATHS:
        if os.path.isfile(path) and os.access(path, os.X_OK):
            # Resolve symlinks and verify the resolved path is still in a trusted directory
            real_path = os.path.realpath(path)
            trusted_prefixes = ('/usr/local/bin/', '/opt/edge_tools/bin/', '/usr/bin/')
            if real_path.startswith(trusted_prefixes):
                return real_path
    return None

def deploy_model_to_edge(device_id, model_name, config_path):
    # Validate inputs to prevent command injection
    if not all(isinstance(arg, str) for arg in [device_id, model_name, config_path]):
        raise ValueError("All arguments must be strings")
    
    # Resolve the optimizer binary from trusted, absolute paths only
    optimizer_path = _resolve_optimizer_path()
    if optimizer_path is None:
        return {'status': 'failed', 'error': 'model_optimizer binary not found in trusted locations'}
    
    # Use a clean environment without modifying PATH unsafely
    edge_env = os.environ.copy()
    edge_env['DEVICE_TARGET'] = device_id
    # Do NOT prepend writable directories like /tmp to PATH
    
    deployment_cmd = [optimizer_path, '--input', model_name, '--config', config_path, '--device', device_id]
    proc = subprocess.Popen(deployment_cmd, env=edge_env, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    output, errors = proc.communicate(timeout=120)
    if proc.returncode == 0:
        return {'status': 'deployed', 'device': device_id, 'output': output.decode()}
    else:
        return {'status': 'failed', 'error': errors.decode()}
```
