{"title":"TOCTOU Race Condition in Temporary File Creation","language":"Python","severity":"High","cwe":"CWE-367","source_lines":[9],"flow_lines":[9,10,9,15,9,18],"sink_lines":[10,15,18],"vulnerable_code":"import os\nimport tempfile\nimport json\n\ndef store_iot_sensor_calibration(device_id, calibration_params):\n    temp_dir = tempfile.gettempdir()\n    calib_file = os.path.join(temp_dir, f\"iot_calib_{device_id}.json\")\n    \n    if not os.path.exists(calib_file):\n        with open(calib_file, 'w') as f:\n            json.dump(calibration_params, f)\n        os.chmod(calib_file, 0o600)\n        return calib_file\n    else:\n        with open(calib_file, 'r') as f:\n            existing_data = json.load(f)\n        existing_data.update(calibration_params)\n        with open(calib_file, 'w') as f:\n            json.dump(existing_data, f)\n        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":"import os\nimport stat\nimport tempfile\nimport json\n\ndef store_iot_sensor_calibration(device_id, calibration_params):\n    temp_dir = tempfile.gettempdir()\n    calib_file = os.path.join(temp_dir, f\"iot_calib_{device_id}.json\")\n\n    # Use O_CREAT | O_EXCL for atomic creation, O_NOFOLLOW to reject symlinks\n    try:\n        fd = os.open(calib_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)\n        with os.fdopen(fd, 'w') as f:\n            json.dump(calibration_params, f)\n        return calib_file\n    except FileExistsError:\n        # Verify it's a regular file and not a symlink\n        file_stat = os.lstat(calib_file)\n        if not stat.S_ISREG(file_stat.st_mode):\n            raise ValueError(f\"Calibration file is not a regular file: {calib_file}\")\n\n        # Open using file descriptor to avoid symlink attacks\n        fd = os.open(calib_file, os.O_RDONLY | os.O_NOFOLLOW)\n        with os.fdopen(fd, 'r') as f:\n            existing_data = json.load(f)\n\n        existing_data.update(calibration_params)\n\n        # Write updated data atomically using a secure temp file and rename\n        tmp_fd, tmp_path = tempfile.mkstemp(dir=temp_dir, prefix=f\"iot_calib_{device_id}_\", suffix=\".tmp\")\n        try:\n            with os.fdopen(tmp_fd, 'w') as f:\n                json.dump(existing_data, f)\n            os.chmod(tmp_path, 0o600)\n            os.replace(tmp_path, calib_file)\n        except Exception:\n            if os.path.exists(tmp_path):\n                os.unlink(tmp_path)\n            raise\n\n        return calib_file"}