# TOCTOU Race Condition in Temporary File Creation

Language: Python
Severity: High
CWE: CWE-367

## Source
9

## Flow
9-10, 9-15, 9-18

## Sink
10, 15, 18

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

def store_iot_sensor_calibration(device_id, calibration_params):
    temp_dir = tempfile.gettempdir()
    calib_file = os.path.join(temp_dir, f"iot_calib_{device_id}.json")
    
    if not os.path.exists(calib_file):
        with open(calib_file, 'w') as f:
            json.dump(calibration_params, f)
        os.chmod(calib_file, 0o600)
        return calib_file
    else:
        with open(calib_file, 'r') as f:
            existing_data = json.load(f)
        existing_data.update(calibration_params)
        with open(calib_file, 'w') as f:
            json.dump(existing_data, f)
        return calib_file
```

## Explanation

This code contains a TOCTOU (Time-of-Check-Time-of-Use) race condition vulnerability. Between checking if the file exists (line 9) and opening it (lines 10, 15, 18), an attacker can create a symbolic link pointing to a sensitive file, causing the application to overwrite or read arbitrary files with the privileges of the running process. The chmod operation on line 12 occurs after file creation, leaving a window where the file has insecure permissions.

## Remediation

The fix eliminates the TOCTOU race condition by using os.open() with O_CREAT|O_EXCL flags for atomic file creation with correct permissions set at creation time, and O_NOFOLLOW to refuse to follow symbolic links. For the update case, it verifies the file is not a symlink using lstat() and stat.S_ISREG(), reads via a file descriptor opened with O_NOFOLLOW, and writes updates atomically using mkstemp() followed by os.replace() to prevent partial writes or race conditions during the update. The import of the stat module is added to correctly use stat.S_ISREG(), and double-close issues are avoided by letting os.fdopen() take full ownership of the file descriptor.

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

def store_iot_sensor_calibration(device_id, calibration_params):
    temp_dir = tempfile.gettempdir()
    calib_file = os.path.join(temp_dir, f"iot_calib_{device_id}.json")

    # Use O_CREAT | O_EXCL for atomic creation, O_NOFOLLOW to reject symlinks
    try:
        fd = os.open(calib_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
        with os.fdopen(fd, 'w') as f:
            json.dump(calibration_params, f)
        return calib_file
    except FileExistsError:
        # Verify it's a regular file and not a symlink
        file_stat = os.lstat(calib_file)
        if not stat.S_ISREG(file_stat.st_mode):
            raise ValueError(f"Calibration file is not a regular file: {calib_file}")

        # Open using file descriptor to avoid symlink attacks
        fd = os.open(calib_file, os.O_RDONLY | os.O_NOFOLLOW)
        with os.fdopen(fd, 'r') as f:
            existing_data = json.load(f)

        existing_data.update(calibration_params)

        # Write updated data atomically using a secure temp file and rename
        tmp_fd, tmp_path = tempfile.mkstemp(dir=temp_dir, prefix=f"iot_calib_{device_id}_", suffix=".tmp")
        try:
            with os.fdopen(tmp_fd, 'w') as f:
                json.dump(existing_data, f)
            os.chmod(tmp_path, 0o600)
            os.replace(tmp_path, calib_file)
        except Exception:
            if os.path.exists(tmp_path):
                os.unlink(tmp_path)
            raise

        return calib_file
```
