{"title":"TOCTOU Race Condition in File Creation via os.path.exists() Check-Then-Use","language":"Python","severity":"High","cwe":"CWE-367","source_lines":[6],"flow_lines":[6,7,8],"sink_lines":[8],"vulnerable_code":"import os\nimport json\n\ndef provision_iot_device_credentials(device_id, aws_region):\n    cert_path = f\"/var/iot/certs/{device_id}_{aws_region}.pem\"\n    if not os.path.exists(cert_path):\n        device_cert = generate_aws_iot_certificate(device_id)\n        with open(cert_path, 'w') as cert_file:\n            cert_file.write(device_cert)\n        os.chmod(cert_path, 0o600)\n        return {\"status\": \"provisioned\", \"cert_location\": cert_path}\n    return {\"status\": \"exists\", \"cert_location\": cert_path}\n\ndef generate_aws_iot_certificate(device_id):\n    return f\"-----BEGIN CERTIFICATE-----\\nMIIC...{device_id}...\\n-----END CERTIFICATE-----\"","explanation":"This is a Time-of-Check Time-of-Use (TOCTOU) race condition vulnerability. Between checking if the file exists (line 6) and creating it (line 8), an attacker can create a symlink at that path pointing to a sensitive system file, causing the application to overwrite it with certificate data and then set restrictive permissions via a separate os.chmod() call, which itself is another race window.","remediation":"The fix eliminates the TOCTOU race condition by using os.open() with O_CREAT | O_EXCL | O_NOFOLLOW flags, which atomically creates the file only if it doesn't already exist, refuses to follow symlinks, and sets restrictive permissions (0o600) at creation time rather than after writing. This ensures no window exists between checking and creating the file where an attacker could inject a symlink.","secure_code":"import os\nimport json\nimport tempfile\nimport stat\n\ndef provision_iot_device_credentials(device_id, aws_region):\n    cert_dir = \"/var/iot/certs\"\n    cert_filename = f\"{device_id}_{aws_region}.pem\"\n    cert_path = os.path.join(cert_dir, cert_filename)\n\n    # Validate that cert_path resolves within the expected directory\n    real_dir = os.path.realpath(cert_dir)\n    \n    try:\n        # Use O_CREAT | O_EXCL to atomically create the file only if it doesn't exist\n        # O_WRONLY for write-only, O_NOFOLLOW to refuse symlinks\n        fd = os.open(cert_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)\n    except FileExistsError:\n        # File already exists; verify it's not a symlink before returning\n        if os.path.islink(cert_path):\n            raise SecurityError(f\"Symlink detected at {cert_path}, refusing to use\")\n        # Verify the real path is within expected directory\n        real_path = os.path.realpath(cert_path)\n        if not real_path.startswith(real_dir + os.sep):\n            raise SecurityError(f\"Path traversal detected for {cert_path}\")\n        return {\"status\": \"exists\", \"cert_location\": cert_path}\n    except OSError as e:\n        raise RuntimeError(f\"Failed to create certificate file: {e}\")\n\n    try:\n        # Verify the opened file descriptor points to a regular file in expected directory\n        fd_stat = os.fstat(fd)\n        if not stat.S_ISREG(fd_stat.st_mode):\n            os.close(fd)\n            raise SecurityError(f\"Unexpected file type at {cert_path}\")\n\n        device_cert = generate_aws_iot_certificate(device_id)\n        with os.fdopen(fd, 'w') as cert_file:\n            cert_file.write(device_cert)\n        # fd is now closed by os.fdopen context manager\n    except Exception:\n        # Clean up on failure\n        try:\n            os.close(fd)\n        except OSError:\n            pass\n        try:\n            os.unlink(cert_path)\n        except OSError:\n            pass\n        raise\n\n    return {\"status\": \"provisioned\", \"cert_location\": cert_path}\n\n\nclass SecurityError(Exception):\n    pass\n\n\ndef generate_aws_iot_certificate(device_id):\n    return f\"-----BEGIN CERTIFICATE-----\\nMIIC...{device_id}...\\n-----END CERTIFICATE-----\""}