{"title":"TOCTOU Race Condition in File Existence Check and Open","language":"Python","severity":"High","cwe":"CWE-367","source_lines":[7],"flow_lines":[7,8,9],"sink_lines":[9],"vulnerable_code":"import os\nimport json\nimport time\n\ndef load_iot_device_config(device_id):\n    config_path = f\"/var/iot/devices/{device_id}/config.json\"\n    if os.path.exists(config_path):\n        time.sleep(0.1)\n        with open(config_path, 'r') as cfg:\n            device_settings = json.load(cfg)\n            return device_settings\n    else:\n        return {\"error\": \"Device configuration not found\"}\n\ndef apply_device_settings(device_id):\n    settings = load_iot_device_config(device_id)\n    if \"error\" not in settings:\n        execute_device_commands(settings.get(\"commands\", []))","explanation":"This code has a Time-of-Check Time-of-Use (TOCTOU) race condition. Between checking if the file exists (line 7) and opening it (line 9), an attacker can delete the legitimate file and replace it with a symlink to a sensitive file, causing the application to read unauthorized data or execute malicious commands that get passed to execute_device_commands(). The deliberate time.sleep(0.1) on line 8 further widens the race window, making exploitation significantly easier.","remediation":"The fix eliminates the TOCTOU race condition by removing the separate os.path.exists() check and instead opening the file directly with os.open() using O_NOFOLLOW to reject symlinks. After opening, os.fstat() is used on the file descriptor to verify it is a regular file with proper ownership, ensuring the checks are performed on the actual opened file rather than the path. The deliberate sleep is also removed. Input validation for device_id is added to prevent path traversal attacks.","secure_code":"import os\nimport json\nimport stat\n\ndef load_iot_device_config(device_id):\n    # Sanitize device_id to prevent path traversal\n    if not device_id.isalnum() and not all(c.isalnum() or c in ('-', '_') for c in device_id):\n        return {\"error\": \"Invalid device ID\"}\n\n    config_path = f\"/var/iot/devices/{device_id}/config.json\"\n\n    try:\n        # Open the file directly without a separate existence check (atomic operation)\n        fd = os.open(config_path, os.O_RDONLY | os.O_NOFOLLOW)\n\n        # Verify the opened file descriptor points to a regular file\n        file_stat = os.fstat(fd)\n        if not stat.S_ISREG(file_stat.st_mode):\n            os.close(fd)\n            return {\"error\": \"Device configuration is not a regular file\"}\n\n        # Verify ownership and permissions on the opened file\n        if file_stat.st_uid != 0:\n            os.close(fd)\n            return {\"error\": \"Device configuration has invalid ownership\"}\n\n        with os.fdopen(fd, 'r') as cfg:\n            device_settings = json.load(cfg)\n            return device_settings\n    except (OSError, IOError):\n        return {\"error\": \"Device configuration not found\"}\n    except json.JSONDecodeError:\n        return {\"error\": \"Device configuration is malformed\"}\n\ndef apply_device_settings(device_id):\n    settings = load_iot_device_config(device_id)\n    if \"error\" not in settings:\n        execute_device_commands(settings.get(\"commands\", []))"}