{"title":"Race Condition via Missing File Locking (TOCTOU)","language":"Python","severity":"High","cwe":"CWE-367","source_lines":[3],"flow_lines":[3,4,5,6,7,8,9,10,11],"sink_lines":[11],"vulnerable_code":"import os\nimport json\n\ndef provision_iot_device_credentials(device_id, api_key):\n    creds_file = f'/var/iot/devices/{device_id}/credentials.json'\n    if os.path.exists(creds_file):\n        with open(creds_file, 'r') as f:\n            existing = json.load(f)\n        if existing.get('provisioned'):\n            return {'error': 'Device already provisioned'}\n    new_creds = {'device_id': device_id, 'api_key': api_key, 'provisioned': True, 'access_level': 'admin'}\n    with open(creds_file, 'w') as f:\n        json.dump(new_creds, f)\n    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":"import os\nimport json\nimport fcntl\nimport tempfile\n\ndef provision_iot_device_credentials(device_id, api_key):\n    creds_file = f'/var/iot/devices/{device_id}/credentials.json'\n    lock_file = f'/var/iot/devices/{device_id}/.credentials.lock'\n    \n    os.makedirs(os.path.dirname(creds_file), exist_ok=True)\n    \n    with open(lock_file, 'w') as lock_fd:\n        fcntl.flock(lock_fd, fcntl.LOCK_EX)\n        try:\n            if os.path.exists(creds_file):\n                with open(creds_file, 'r') as f:\n                    existing = json.load(f)\n                if existing.get('provisioned'):\n                    return {'error': 'Device already provisioned'}\n            \n            new_creds = {'device_id': device_id, 'api_key': api_key, 'provisioned': True, 'access_level': 'admin'}\n            \n            dir_name = os.path.dirname(creds_file)\n            fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix='.tmp')\n            try:\n                with os.fdopen(fd, 'w') as tmp_f:\n                    json.dump(new_creds, tmp_f)\n                    tmp_f.flush()\n                    os.fsync(tmp_f.fileno())\n                os.rename(tmp_path, creds_file)\n            except Exception:\n                os.unlink(tmp_path)\n                raise\n            \n            return {'status': 'success', 'credentials': new_creds}\n        finally:\n            fcntl.flock(lock_fd, fcntl.LOCK_UN)"}