Permission Race Condition During Resource Copy

Description

Permission Race Condition During Resource Copy is a composite vulnerability that occurs when software copies or clones a resource but does not set the resource's permissions or access control until the copy operation completes. During the window between creation and permission assignment, the resource exists with insecure default permissions, allowing other processes or users in different security contexts to access or modify the resource. This is particularly dangerous during file extraction from archives or when copying deeply nested directory structures.

Risk

This vulnerability creates a time-of-check to time-of-use (TOCTOU) race condition where attackers can access sensitive resources during the permission gap. Files extracted from archives may be world-readable or world-writable before proper permissions are applied, allowing unauthorized users to read confidential data or inject malicious content. Database objects created before permission assignment may be accessed or modified by unauthorized processes. The risk is especially severe in multi-user systems where other processes actively monitor for exploitable race conditions. Attackers can use automated tools to repeatedly attempt access during the vulnerable window.

Solution

Set proper permissions immediately upon resource creation, before any content is written. Use atomic operations where available—create resources with restrictive permissions from the start rather than creating and then modifying permissions. Apply umask settings that create secure defaults. For file extraction, use secure temporary directories with restricted access. When copying directories, set restrictive permissions on parent directories first. Use platform-specific mechanisms like O_CREAT with proper mode bits. Consider using OS facilities for atomic permission assignment during resource creation.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Application Data - Resources may be read by unauthorized parties during the permission gap window.
IntegrityScope: Integrity

Modify Application Data - Attackers may modify resources before proper permissions are applied.
Access ControlScope: Access Control

Bypass Protection Mechanism - Security controls are ineffective during the vulnerable window.

Example Code

Vulnerable Code

// Vulnerable: Archive extraction with permission race condition
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void vulnerable_extract_file(const char* archive_path,
                             const char* dest_path,
                             mode_t intended_mode) {
    // Vulnerable: File created with default permissions (often 0666 & ~umask)
    int fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, 0666);
    // File now exists with insecure permissions!

    // ... copy file contents from archive ...
    copy_from_archive(archive_path, fd);

    close(fd);

    // Vulnerable: Permission set AFTER file is fully written
    // Race window existed during entire copy operation
    chmod(dest_path, intended_mode);
}

// Vulnerable: Directory copy with permission race
void vulnerable_copy_directory(const char* src, const char* dest) {
    // Create destination directory with default permissions
    mkdir(dest, 0777);  // World accessible initially!

    // Copy all files (each has its own race window)
    copy_directory_contents(src, dest);

    // Set proper permissions AFTER all files copied
    // All files were exposed during copy
    chmod(dest, 0700);
}
# Vulnerable: Python file extraction with race condition
import os
import tarfile
import shutil

def vulnerable_extract_tar(archive_path, dest_dir):
    with tarfile.open(archive_path, 'r') as tar:
        for member in tar.getmembers():
            # Vulnerable: Extract with default permissions first
            tar.extract(member, dest_dir)
            # File exists with insecure permissions

            # Apply intended permissions AFTER extraction
            extracted_path = os.path.join(dest_dir, member.name)
            os.chmod(extracted_path, member.mode)
            # Race condition existed during extraction

def vulnerable_copy_with_permissions(src, dest, mode):
    # Vulnerable: Copy creates file with default permissions
    shutil.copy(src, dest)  # File created with umask

    # Set permissions after copy completes
    os.chmod(dest, mode)  # Race window during copy
// Vulnerable: Java file copy with permission delay
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class VulnerableCopy {

    public void vulnerableCopyFile(Path source, Path dest,
                                   Set<PosixFilePermission> perms)
            throws IOException {

        // Vulnerable: Copy creates file with default permissions
        Files.copy(source, dest);
        // File exists with insecure default permissions!

        // Set permissions after copy completes
        Files.setPosixFilePermissions(dest, perms);
        // Race condition during entire copy operation
    }

    public void vulnerableCreateAndWrite(Path path, byte[] data,
                                         Set<PosixFilePermission> perms)
            throws IOException {

        // Vulnerable: Create file first
        Files.createFile(path);
        // File exists with default permissions

        // Write content
        Files.write(path, data);

        // Set permissions last
        Files.setPosixFilePermissions(path, perms);
        // Content exposed during write
    }
}
// Vulnerable: Database object creation (CVE-2005-2174 pattern)
#include <stdio.h>

typedef struct {
    int id;
    char* data;
    int permissions;
} DbObject;

DbObject* vulnerable_create_object(const char* data) {
    // Insert object into database
    DbObject* obj = db_insert_object(data);
    // Object exists in database with no permissions set!

    // ... other processing ...

    // Set permissions after insertion
    obj->permissions = PERM_OWNER_ONLY;
    db_update_permissions(obj);
    // Race window during processing

    return obj;
}

Fixed Code

// Fixed: Create file with correct permissions from start
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

void secure_extract_file(const char* archive_path,
                         const char* dest_path,
                         mode_t intended_mode) {
    // Fixed: Set restrictive umask before creation
    mode_t old_umask = umask(0077);

    // Fixed: Create with intended permissions from the start
    int fd = open(dest_path, O_CREAT | O_WRONLY | O_TRUNC, intended_mode);

    // Restore umask
    umask(old_umask);

    if (fd < 0) {
        return;  // Handle error
    }

    // Copy contents - file already has correct permissions
    copy_from_archive(archive_path, fd);

    close(fd);
    // No chmod needed - permissions were set at creation
}

// Fixed: Secure directory creation
void secure_copy_directory(const char* src, const char* dest) {
    // Fixed: Create directory with restricted permissions
    if (mkdir(dest, 0700) != 0) {
        return;  // Handle error
    }

    // Copy files (each with proper permissions)
    secure_copy_directory_contents(src, dest);

    // No chmod needed - parent was already secure
}

// Fixed: Use atomic file creation with permissions
int secure_create_file_atomic(const char* path, mode_t mode) {
    // Ensure umask doesn't weaken permissions
    mode_t old_umask = umask(0);

    int fd = open(path,
                  O_CREAT | O_WRONLY | O_EXCL,  // O_EXCL for atomic create
                  mode);

    umask(old_umask);

    return fd;
}
# Fixed: Python secure file extraction
import os
import tarfile
import tempfile
import shutil

def secure_extract_tar(archive_path, dest_dir):
    # Fixed: Set restrictive umask
    old_umask = os.umask(0o077)

    try:
        # Create secure temporary directory first
        with tempfile.TemporaryDirectory(dir=dest_dir) as temp_dir:
            with tarfile.open(archive_path, 'r') as tar:
                for member in tar.getmembers():
                    # Validate member path to prevent path traversal
                    if member.name.startswith('/') or '..' in member.name:
                        continue

                    # Extract with intended mode directly
                    # Some tar implementations support this
                    tar.extract(member, temp_dir, set_attrs=True)

            # Move from temp to final destination atomically
            for item in os.listdir(temp_dir):
                shutil.move(os.path.join(temp_dir, item),
                           os.path.join(dest_dir, item))
    finally:
        os.umask(old_umask)

def secure_copy_with_permissions(src, dest, mode):
    # Fixed: Set umask and create with correct permissions
    old_umask = os.umask(0o077)

    try:
        # Open destination with specific mode
        fd = os.open(dest, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, mode)

        # Copy contents
        with open(src, 'rb') as src_file:
            data = src_file.read()
            os.write(fd, data)

        os.close(fd)
    finally:
        os.umask(old_umask)
// Fixed: Java atomic file creation with permissions
import java.io.*;
import java.nio.file.*;
import java.nio.file.attribute.*;

public class SecureCopy {

    public void secureCopyFile(Path source, Path dest,
                               Set<PosixFilePermission> perms)
            throws IOException {

        // Fixed: Create file with permissions atomically
        FileAttribute<Set<PosixFilePermission>> attr =
            PosixFilePermissions.asFileAttribute(perms);

        // Create with correct permissions from the start
        try (OutputStream out = Files.newOutputStream(dest,
                StandardOpenOption.CREATE_NEW,
                StandardOpenOption.WRITE)) {

            // Set permissions immediately after creation
            Files.setPosixFilePermissions(dest, perms);

            // Copy content - file is already secured
            Files.copy(source, out);
        }
    }

    public void secureCreateAndWrite(Path path, byte[] data,
                                     Set<PosixFilePermission> perms)
            throws IOException {

        // Fixed: Create with permissions atomically
        FileAttribute<Set<PosixFilePermission>> attr =
            PosixFilePermissions.asFileAttribute(perms);

        Files.createFile(path, attr);

        // File created with correct permissions
        // Now safe to write
        Files.write(path, data);
    }
}
// Fixed: Database object with atomic permission assignment
#include <stdio.h>

typedef struct {
    int id;
    char* data;
    int permissions;
} DbObject;

DbObject* secure_create_object(const char* data, int permissions) {
    // Fixed: Begin transaction
    db_begin_transaction();

    // Create object with permissions in single operation
    DbObject* obj = db_insert_object_with_permissions(data, permissions);

    // Commit atomically
    db_commit_transaction();
    // Object never exists without permissions

    return obj;
}

// Alternative: Create in restricted state, then enable
DbObject* secure_create_object_staged(const char* data, int final_perms) {
    // Create with NO permissions initially (deny all)
    DbObject* obj = db_insert_object(data);
    obj->permissions = PERM_DENY_ALL;
    db_update_permissions(obj);

    // Perform setup...

    // Enable final permissions
    obj->permissions = final_perms;
    db_update_permissions(obj);

    return obj;
}

CVE Examples

  • CVE-2002-0760: Archive extractor decompresses files with world-readable permissions, then properly sets permissions later.
  • CVE-2005-2174: Database object is inserted without permission assignment, then permissions set in a separate operation.
  • CVE-2006-5214: Error file had weak permissions before chmod was performed.
  • CVE-2005-2475: Archive permissions race condition involving hard links.
  • CVE-2003-0265: Database files created world-writable before setuid bits are initialized.

References

  1. MITRE Corporation. "CWE-689: Permission Race Condition During Resource Copy." https://cwe.mitre.org/data/definitions/689.html
  2. CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization.
  3. CWE-732: Incorrect Permission Assignment for Critical Resource.