# TOCTOU Race Condition in File Creation and Permission Checks

Language: Python
Severity: High
CWE: CWE-367

## Source
8

## Flow
8-9-11

## Sink
11

## Vulnerable Code
```python
import os
import boto3

def deploy_lambda_config(config_name, s3_bucket, lambda_arn):
    local_cfg = f"/tmp/lambda_configs/{config_name}.json"
    if not os.path.exists(local_cfg):
        s3 = boto3.client('s3')
        s3.download_file(s3_bucket, f"configs/{config_name}.json", local_cfg)
    if os.stat(local_cfg).st_uid == os.getuid():
        with open(local_cfg, 'r') as f:
            config_data = f.read()
        lambda_client = boto3.client('lambda')
        lambda_client.update_function_configuration(FunctionName=lambda_arn, Environment={'Variables': {'CONFIG': config_data}})
        return True
    return False
```

## Explanation

This is a TOCTOU (Time-of-Check-Time-of-Use) race condition vulnerability. The code checks file ownership at line 8 (os.stat) but reads the file at line 9 (open), creating a window where an attacker can replace the file between the ownership check and the file read operation. The malicious config_data is then deployed to the Lambda function at line 11, allowing configuration injection.

## Remediation

The fix eliminates the TOCTOU race condition by using a freshly created temporary file with restrictive permissions (0o600), downloading directly to it, then performing ownership and file type checks on the open file descriptor (using os.fstat on the fd) rather than on the file path. Reading is done from the same verified file descriptor, ensuring no window exists for an attacker to swap the file between check and use. The O_NOFOLLOW flag prevents symlink attacks.

## Secure Code
```python
import os
import boto3
import tempfile
import stat

def deploy_lambda_config(config_name, s3_bucket, lambda_arn):
    config_dir = "/tmp/lambda_configs"
    os.makedirs(config_dir, mode=0o700, exist_ok=True)
    
    local_cfg = f"/tmp/lambda_configs/{config_name}.json"
    
    fd = None
    tmp_path = None
    try:
        fd = tempfile.mkstemp(dir=config_dir, prefix=f"{config_name}_", suffix=".json")
        tmp_path = fd[1]
        os.fchmod(fd[0], stat.S_IRUSR | stat.S_IWUSR)
        os.close(fd[0])
        fd = None
        
        s3 = boto3.client('s3')
        s3.download_file(s3_bucket, f"configs/{config_name}.json", tmp_path)
        
        file_fd = os.open(tmp_path, os.O_RDONLY | os.O_NOFOLLOW)
        try:
            file_stat = os.fstat(file_fd)
            if file_stat.st_uid != os.getuid():
                return False
            if not stat.S_ISREG(file_stat.st_mode):
                return False
            
            with os.fdopen(os.dup(file_fd), 'r') as f:
                config_data = f.read()
        finally:
            os.close(file_fd)
        
        lambda_client = boto3.client('lambda')
        lambda_client.update_function_configuration(
            FunctionName=lambda_arn,
            Environment={'Variables': {'CONFIG': config_data}}
        )
        return True
    finally:
        if tmp_path and os.path.exists(tmp_path):
            os.unlink(tmp_path)
        if fd is not None:
            try:
                os.close(fd[0])
            except OSError:
                pass
```
