{"title":"TOCTOU Race Condition in File Permission Checks","language":"Python","severity":"High","cwe":"CWE-367","source_lines":[4],"flow_lines":[4,5,6,7,10],"sink_lines":[10],"vulnerable_code":"import os\nimport boto3\n\ndef sync_credentials_to_s3(cred_file_path, bucket_name):\n    if not os.path.exists(cred_file_path):\n        raise FileNotFoundError(\"Credential file missing\")\n    file_stats = os.stat(cred_file_path)\n    if file_stats.st_mode & 0o077:\n        raise PermissionError(\"Insecure permissions detected\")\n    s3_client = boto3.client('s3')\n    with open(cred_file_path, 'rb') as cred_data:\n        s3_client.upload_fileobj(cred_data, bucket_name, 'secrets/aws_creds.json')\n    return {\"status\": \"uploaded\", \"size\": file_stats.st_size}","explanation":"This code exhibits a Time-of-Check-Time-of-Use (TOCTOU) race condition vulnerability. The file permissions are checked at line 7 using os.stat(), but the file is opened at line 10, creating a window where an attacker can replace the validated file with a malicious one or modify its permissions between the check and use.","remediation":"The fix eliminates the TOCTOU race condition by opening the file first with os.open() using O_NOFOLLOW to reject symlinks, then performing permission checks on the already-opened file descriptor using os.fstat(). The same file descriptor is then used for reading and uploading, ensuring the checked file is the same file that gets uploaded. Additionally, the code verifies the file is a regular file to prevent device file attacks.","secure_code":"import os\nimport boto3\nimport stat\nimport tempfile\nimport shutil\n\ndef sync_credentials_to_s3(cred_file_path, bucket_name):\n    # Open the file first using a file descriptor to eliminate TOCTOU race condition\n    try:\n        fd = os.open(cred_file_path, os.O_RDONLY | os.O_NOFOLLOW)\n    except FileNotFoundError:\n        raise FileNotFoundError(\"Credential file missing\")\n    except OSError as e:\n        raise OSError(f\"Cannot open credential file: {e}\")\n    \n    try:\n        # Check permissions on the already-opened file descriptor\n        file_stats = os.fstat(fd)\n        \n        # Ensure it's a regular file (not a symlink, device, etc.)\n        if not stat.S_ISREG(file_stats.st_mode):\n            raise PermissionError(\"Credential path is not a regular file\")\n        \n        # Check that no group or other permissions are set\n        if file_stats.st_mode & 0o077:\n            raise PermissionError(\"Insecure permissions detected\")\n        \n        # Use the already-opened file descriptor for reading\n        # This ensures we read the same file we checked permissions on\n        cred_data = os.fdopen(fd, 'rb')\n        fd = -1  # Mark fd as consumed by fdopen\n        \n        try:\n            s3_client = boto3.client('s3')\n            s3_client.upload_fileobj(cred_data, bucket_name, 'secrets/aws_creds.json')\n        finally:\n            cred_data.close()\n    finally:\n        if fd >= 0:\n            os.close(fd)\n    \n    return {\"status\": \"uploaded\", \"size\": file_stats.st_size}"}