# TOCTOU Race Condition via os.path.exists() Before File Creation

Language: Python
Severity: High
CWE: CWE-367

## Source
5

## Flow
5-6-7

## Sink
7

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

def provision_iot_device_cert(device_id, cert_data):
    cert_path = f"/var/iot/certs/{device_id}.pem"
    if not os.path.exists(cert_path):
        time.sleep(0.1)
        with open(cert_path, 'w') as cert_file:
            cert_file.write(cert_data)
        os.chmod(cert_path, 0o600)
        return {"status": "provisioned", "device": device_id}
    return {"status": "already_exists", "device": device_id}
```

## Explanation

This is a TOCTOU (Time-of-check to time-of-use) race condition vulnerability. Between checking if the file exists (line 5) and creating it (line 7), an attacker can create a malicious symlink at cert_path pointing to a sensitive file, causing the application to overwrite that target file with certificate data. The deliberate time.sleep(0.1) widens the attack window.

## Remediation

The fix replaces the vulnerable check-then-act pattern (os.path.exists followed by open) with an atomic os.open() call using O_CREAT | O_EXCL | O_NOFOLLOW flags. O_EXCL ensures the operation fails atomically if the file already exists, eliminating the race window. O_NOFOLLOW prevents symlink following, and permissions are set atomically at creation time rather than after the fact with chmod.

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

def provision_iot_device_cert(device_id, cert_data):
    cert_dir = "/var/iot/certs"
    cert_path = f"{cert_dir}/{device_id}.pem"
    
    # Validate device_id to prevent path traversal
    if not device_id.isalnum() and not all(c.isalnum() or c in ('-', '_') for c in device_id):
        return {"status": "invalid_device_id", "device": device_id}
    
    # Ensure the resolved path is within the expected directory
    real_cert_dir = os.path.realpath(cert_dir)
    
    try:
        # Use os.open with O_CREAT | O_EXCL to atomically create the file
        # O_EXCL ensures the call fails if the file already exists (no race window)
        # O_NOFOLLOW prevents following symlinks
        fd = os.open(cert_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
        
        # Verify the created file is within the expected directory (defense in depth)
        real_path = os.path.realpath(f"/proc/self/fd/{fd}")
        if not real_path.startswith(real_cert_dir + os.sep):
            os.close(fd)
            os.unlink(cert_path)
            return {"status": "error_path_traversal", "device": device_id}
        
        # Write cert data using the file descriptor
        with os.fdopen(fd, 'w') as cert_file:
            cert_file.write(cert_data)
        
        return {"status": "provisioned", "device": device_id}
    except FileExistsError:
        return {"status": "already_exists", "device": device_id}
    except OSError as e:
        return {"status": "error", "device": device_id, "message": str(e)}
```
