{"title":"TOCTOU Race Condition in File Existence Check before Open","language":"Python","severity":"High","cwe":"CWE-367","source_lines":[5],"flow_lines":[5,6,9],"sink_lines":[9],"vulnerable_code":"import os\nimport boto3\n\ndef sync_model_weights_to_s3(local_weights_path, bucket_name, s3_key):\n    if os.path.exists(local_weights_path):\n        file_size = os.path.getsize(local_weights_path)\n        print(f\"Uploading model weights: {file_size} bytes\")\n        s3_client = boto3.client('s3')\n        with open(local_weights_path, 'rb') as weight_file:\n            s3_client.upload_fileobj(weight_file, bucket_name, s3_key)\n        return True\n    return False","explanation":"This is a TOCTOU (Time-of-Check-Time-of-Use) race condition vulnerability. The code checks if the file exists at line 5, but doesn't open it until line 9. Between these operations, an attacker could delete the file (causing a crash), replace it with malicious content, or create a symlink to sensitive data that would then be uploaded to S3.","remediation":"The fix eliminates the TOCTOU race condition by using os.open() with O_NOFOLLOW as the first operation, combining the existence check and file open into a single atomic operation. File metadata (size, type) is then retrieved via os.fstat() on the already-opened file descriptor rather than the file path, ensuring consistency. The O_NOFOLLOW flag prevents symlink-based attacks where an attacker could redirect the upload to sensitive files. The incorrect 'os.path.stat.S_ISREG' reference has been corrected to use 'import stat' and 'stat.S_ISREG()'.","secure_code":"import os\nimport stat\nimport boto3\n\ndef sync_model_weights_to_s3(local_weights_path, bucket_name, s3_key):\n    try:\n        # Open the file first, eliminating the TOCTOU window\n        # Use os.open with O_NOFOLLOW to prevent symlink attacks\n        fd = os.open(local_weights_path, os.O_RDONLY | os.O_NOFOLLOW)\n        try:\n            # Get file size from the opened file descriptor, not the path\n            file_stat = os.fstat(fd)\n            file_size = file_stat.st_size\n            # Verify it's a regular file\n            if not stat.S_ISREG(file_stat.st_mode):\n                os.close(fd)\n                print(f\"Error: {local_weights_path} is not a regular file\")\n                return False\n            print(f\"Uploading model weights: {file_size} bytes\")\n            # Wrap the file descriptor in a Python file object\n            with os.fdopen(fd, 'rb') as weight_file:\n                s3_client = boto3.client('s3')\n                s3_client.upload_fileobj(weight_file, bucket_name, s3_key)\n            return True\n        except Exception:\n            os.close(fd)\n            raise\n    except OSError as e:\n        print(f\"Cannot open model weights file: {e}\")\n        return False"}