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
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Resources may be read by unauthorized parties during the permission gap window. |
| Integrity | Scope: Integrity Modify Application Data - Attackers may modify resources before proper permissions are applied. |
| Access Control | Scope: 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
- MITRE Corporation. "CWE-689: Permission Race Condition During Resource Copy." https://cwe.mitre.org/data/definitions/689.html
- CWE-362: Concurrent Execution using Shared Resource with Improper Synchronization.
- CWE-732: Incorrect Permission Assignment for Critical Resource.