# Race Condition via Missing File Locking (TOCTOU)

Language: Python
Severity: High
CWE: CWE-367

## Source
3

## Flow
3-4-5-6-7-8-9-10-11

## Sink
11

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

def provision_iot_device_credentials(device_id, api_key):
    creds_file = f'/var/iot/devices/{device_id}/credentials.json'
    if os.path.exists(creds_file):
        with open(creds_file, 'r') as f:
            existing = json.load(f)
        if existing.get('provisioned'):
            return {'error': 'Device already provisioned'}
    new_creds = {'device_id': device_id, 'api_key': api_key, 'provisioned': True, 'access_level': 'admin'}
    with open(creds_file, 'w') as f:
        json.dump(new_creds, f)
    return {'status': 'success', 'credentials': new_creds}
```

## Explanation

This code contains a Time-of-Check Time-of-Use (TOCTOU) race condition vulnerability. Between checking if the credentials file exists (line 6) and writing new credentials (line 12), another process can simultaneously execute the same code, allowing multiple admin-level credentials to be provisioned for unauthorized devices or the same device multiple times.

## Remediation

The fix uses an exclusive file lock (fcntl.flock with LOCK_EX) to serialize access to the credential file check-and-write operation, eliminating the TOCTOU race window. Additionally, it uses atomic file writes via a temporary file and os.rename to ensure the credentials file is never in a partially-written state. The lock ensures that only one process can check and write credentials for a given device at a time.

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

def provision_iot_device_credentials(device_id, api_key):
    creds_file = f'/var/iot/devices/{device_id}/credentials.json'
    lock_file = f'/var/iot/devices/{device_id}/.credentials.lock'
    
    os.makedirs(os.path.dirname(creds_file), exist_ok=True)
    
    with open(lock_file, 'w') as lock_fd:
        fcntl.flock(lock_fd, fcntl.LOCK_EX)
        try:
            if os.path.exists(creds_file):
                with open(creds_file, 'r') as f:
                    existing = json.load(f)
                if existing.get('provisioned'):
                    return {'error': 'Device already provisioned'}
            
            new_creds = {'device_id': device_id, 'api_key': api_key, 'provisioned': True, 'access_level': 'admin'}
            
            dir_name = os.path.dirname(creds_file)
            fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix='.tmp')
            try:
                with os.fdopen(fd, 'w') as tmp_f:
                    json.dump(new_creds, tmp_f)
                    tmp_f.flush()
                    os.fsync(tmp_f.fileno())
                os.rename(tmp_path, creds_file)
            except Exception:
                os.unlink(tmp_path)
                raise
            
            return {'status': 'success', 'credentials': new_creds}
        finally:
            fcntl.flock(lock_fd, fcntl.LOCK_UN)
```
