# Insecure Temporary File Creation via Predictable /tmp Filename

Language: Python
Severity: High
CWE: CWE-377

## Source
4

## Flow
4-5-6, 4-12-13

## Sink
5, 6, 13

## Vulnerable Code
```python
import os
import json

def cache_ml_inference_results(model_id, prediction_data):
    cache_file = f"/tmp/ml_inference_{model_id}.json"
    with open(cache_file, 'w') as f:
        json.dump(prediction_data, f)
    os.chmod(cache_file, 0o666)
    return cache_file

def retrieve_cached_prediction(model_id):
    cache_file = f"/tmp/ml_inference_{model_id}.json"
    if os.path.exists(cache_file):
        with open(cache_file, 'r') as f:
            return json.load(f)
    return None
```

## Explanation

The code creates temporary files with predictable filenames based on user-controlled model_id in /tmp directory with world-writable permissions (0o666). An attacker can predict the filename, create symlinks to sensitive files, or replace cached data with malicious content, leading to unauthorized access or data injection attacks.

## Remediation

The fix uses a secure application-specific temporary directory created with tempfile.mkdtemp() with restrictive permissions (0o700), sanitizes model_id using SHA-256 hashing to prevent path traversal, uses os.open() with O_NOFOLLOW flag to prevent symlink attacks, sets restrictive file permissions (0o600), and verifies file ownership before reading cached data.

## Secure Code
```python
import os
import json
import tempfile
import hashlib
import stat

# Use a secure, application-specific cache directory
CACHE_DIR = None

def _get_cache_dir():
    global CACHE_DIR
    if CACHE_DIR is None:
        CACHE_DIR = tempfile.mkdtemp(prefix='ml_inference_cache_')
        os.chmod(CACHE_DIR, stat.S_IRWXU)  # 0o700 - owner only
    return CACHE_DIR

def _sanitize_model_id(model_id):
    """Sanitize model_id to prevent path traversal attacks."""
    # Hash the model_id to create a safe filename
    safe_id = hashlib.sha256(str(model_id).encode()).hexdigest()
    return safe_id

def cache_ml_inference_results(model_id, prediction_data):
    cache_dir = _get_cache_dir()
    safe_id = _sanitize_model_id(model_id)
    cache_file = os.path.join(cache_dir, f"{safe_id}.json")
    
    # Use os.open with exclusive flags to prevent race conditions
    # O_CREAT | O_WRONLY | O_TRUNC | O_NOFOLLOW to avoid symlink attacks
    fd = os.open(cache_file, os.O_CREAT | os.O_WRONLY | os.O_TRUNC | os.O_NOFOLLOW, 0o600)
    try:
        with os.fdopen(fd, 'w') as f:
            json.dump(prediction_data, f)
    except Exception:
        os.close(fd)
        raise
    return cache_file

def retrieve_cached_prediction(model_id):
    cache_dir = _get_cache_dir()
    safe_id = _sanitize_model_id(model_id)
    cache_file = os.path.join(cache_dir, f"{safe_id}.json")
    
    # Verify the file is a regular file and not a symlink
    if os.path.exists(cache_file):
        if os.path.islink(cache_file):
            # Refuse to read symlinks
            os.unlink(cache_file)
            return None
        # Verify ownership
        file_stat = os.stat(cache_file)
        if file_stat.st_uid != os.getuid():
            return None
        with open(cache_file, 'r') as f:
            return json.load(f)
    return None
```
