Transmission of Private Resources into a New Sphere ('Resource Leak')

Description

Transmission of Private Resources into a New Sphere occurs when a product makes resources available to untrusted parties when those resources are only intended to be accessed by the product itself. This weakness involves the unintended exposure of internal resources such as file descriptors, memory references, database connections, or other system handles to external or less-privileged processes. When private resources leak across trust boundaries, attackers can gain unauthorized access to sensitive data or capabilities.

Risk

Resource leaks have severe implications. Unauthorized access to sensitive files. Privilege escalation through inherited descriptors. Data exposure to untrusted processes. Bypass of access controls. Session hijacking. Database connection theft. Memory disclosure. High likelihood when processes spawn children without proper resource cleanup.

Solution

Close or invalidate all sensitive resources before spawning child processes during implementation phase. Use close-on-exec flags for file descriptors. Implement proper resource lifecycle management. Sanitize the environment before process creation during architecture and design phase. Audit resource handling across trust boundaries.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Read Application Data - Attackers may gain unauthorized access to sensitive information through exposed resources.
IntegrityScope: Integrity

Modification of data through leaked write handles.

Example Code

Vulnerable Code

// Vulnerable: C code leaking file descriptors to child processes

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

// VULNERABLE: File descriptor inherited by child
void vulnerable_spawn_child(const char* program) {
    // Open sensitive file
    int secret_fd = open("/etc/shadow", O_RDONLY);
    if (secret_fd < 0) {
        perror("open");
        return;
    }

    // VULNERABLE: Fork without closing sensitive fd
    pid_t pid = fork();

    if (pid == 0) {
        // Child process - STILL HAS ACCESS to secret_fd!
        // Even though child may be less privileged

        // VULNERABLE: Execute untrusted program with leaked fd
        execl(program, program, NULL);
        exit(1);
    }

    // Parent continues...
    // Even if we close here, child already has the fd
    close(secret_fd);

    waitpid(pid, NULL, 0);
}

// VULNERABLE: Database connection leaked
void vulnerable_db_handler() {
    // Establish privileged database connection
    DB_CONN* conn = db_connect("admin", "password");

    // VULNERABLE: Connection handle accessible to plugins
    load_untrusted_plugin(conn);  // Plugin can use admin connection

    db_disconnect(conn);
}

// VULNERABLE: Socket descriptor leak
void vulnerable_server() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    bind(listen_fd, ...);
    listen(listen_fd, 10);

    while (1) {
        int client_fd = accept(listen_fd, NULL, NULL);

        pid_t pid = fork();
        if (pid == 0) {
            // Child handles client
            // VULNERABLE: listen_fd still open in child
            // Child could accept new connections!

            handle_client(client_fd);
            exit(0);
        }

        // Parent should close client_fd but listen_fd leaks to child
        close(client_fd);
    }
}
# Vulnerable: Python resource leaks

import os
import subprocess

# VULNERABLE: File descriptor leak to subprocess
def vulnerable_run_command(command):
    # Open sensitive file
    secret_file = open('/etc/shadow', 'r')

    # VULNERABLE: subprocess inherits all file descriptors
    result = subprocess.run(command, shell=True, capture_output=True)

    # File descriptor was available to the subprocess
    secret_file.close()

    return result

# VULNERABLE: Database connection leak
class VulnerableApp:
    def __init__(self):
        # Privileged database connection
        self.db_conn = connect_database(admin=True)

    def run_plugin(self, plugin_code):
        # VULNERABLE: Plugin has access to self.db_conn
        exec(plugin_code)  # Can access self.db_conn

# VULNERABLE: Temporary file leak
def vulnerable_temp_file():
    import tempfile

    # Create temp file with sensitive data
    fd, path = tempfile.mkstemp()
    os.write(fd, b"secret data")

    # VULNERABLE: fd still open when calling external process
    os.system("some_command")  # Inherits the fd

    os.close(fd)
    os.unlink(path)

Fixed Code

// Fixed: Proper resource management before spawning children

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

// FIXED: Use close-on-exec flag
void safe_spawn_child(const char* program) {
    // FIXED: Open with O_CLOEXEC flag
    int secret_fd = open("/etc/shadow", O_RDONLY | O_CLOEXEC);
    if (secret_fd < 0) {
        perror("open");
        return;
    }

    // File descriptor will be automatically closed on exec
    pid_t pid = fork();

    if (pid == 0) {
        // Child process
        // secret_fd will be closed when execl is called

        execl(program, program, NULL);
        exit(1);
    }

    close(secret_fd);
    waitpid(pid, NULL, 0);
}

// FIXED: Explicit cleanup before fork
void safe_spawn_with_cleanup(const char* program) {
    int secret_fd = open("/etc/shadow", O_RDONLY);
    if (secret_fd < 0) {
        return;
    }

    // ... use the file ...

    // FIXED: Close sensitive resources before fork
    close(secret_fd);
    secret_fd = -1;

    pid_t pid = fork();
    if (pid == 0) {
        // Child has no access to secret_fd
        execl(program, program, NULL);
        exit(1);
    }

    waitpid(pid, NULL, 0);
}

// FIXED: Set close-on-exec after open
void safe_set_cloexec(int fd) {
    int flags = fcntl(fd, F_GETFD);
    if (flags >= 0) {
        fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
    }
}

// FIXED: Server with proper fd management
void safe_server() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    // FIXED: Set close-on-exec on listen socket
    safe_set_cloexec(listen_fd);

    bind(listen_fd, ...);
    listen(listen_fd, 10);

    while (1) {
        int client_fd = accept(listen_fd, NULL, NULL);

        // FIXED: Set close-on-exec on client fd too
        safe_set_cloexec(client_fd);

        pid_t pid = fork();
        if (pid == 0) {
            // FIXED: Close listen_fd in child
            close(listen_fd);

            handle_client(client_fd);
            close(client_fd);
            exit(0);
        }

        // Parent closes client_fd
        close(client_fd);
    }
}

// FIXED: Close all unnecessary fds before exec
void close_all_fds_except(int keep_fd) {
    int max_fd = sysconf(_SC_OPEN_MAX);
    for (int fd = 3; fd < max_fd; fd++) {
        if (fd != keep_fd) {
            close(fd);
        }
    }
}
# Fixed: Python with proper resource isolation

import os
import subprocess

# FIXED: Close file descriptors before subprocess
def safe_run_command(command):
    secret_file = open('/etc/shadow', 'r')
    secret_fd = secret_file.fileno()

    # ... use the file ...

    # FIXED: Close before running subprocess
    secret_file.close()

    # FIXED: Use close_fds=True (default in Python 3)
    result = subprocess.run(
        command,
        shell=True,
        capture_output=True,
        close_fds=True  # FIXED: Close all fds except stdin/stdout/stderr
    )

    return result

# FIXED: Isolated database connection
class SafeApp:
    def __init__(self):
        self._db_conn = None

    def _get_db_conn(self):
        """FIXED: Lazy initialization, not exposed."""
        if self._db_conn is None:
            self._db_conn = connect_database(admin=True)
        return self._db_conn

    def run_plugin(self, plugin_code):
        # FIXED: Create limited connection for plugin
        plugin_conn = connect_database(admin=False, read_only=True)

        # FIXED: Sandboxed execution without access to admin conn
        sandbox = {'db': plugin_conn}
        exec(plugin_code, sandbox)

        plugin_conn.close()

# FIXED: Secure temporary file handling
def safe_temp_file():
    import tempfile

    # Create temp file
    fd, path = tempfile.mkstemp()

    try:
        os.write(fd, b"secret data")

        # FIXED: Close before external command
        os.close(fd)
        fd = -1

        # Now safe to run external process
        subprocess.run(["some_command"], close_fds=True)
    finally:
        if fd >= 0:
            os.close(fd)
        os.unlink(path)

# FIXED: Context manager for automatic cleanup
class SecureResource:
    def __init__(self, path):
        self.fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC)

    def __enter__(self):
        return self

    def __exit__(self, *args):
        if self.fd >= 0:
            os.close(self.fd)
            self.fd = -1

# Usage
with SecureResource('/etc/shadow') as res:
    # Use resource
    pass
# Automatically closed, safe to spawn processes

CVE Examples

  • CVE-2003-0740: Server leaked a privileged file descriptor enabling attackers to hijack the connection.
  • CVE-2004-1033: File descriptor leak permitted unauthorized file access.
  • CVE-2003-0937: File descriptor leak in daemon allowed privilege escalation.

  • CWE-668: Exposure of Resource to Wrong Sphere (parent)
  • CWE-403: Exposure of File Descriptor to Unintended Control Sphere (child)
  • CWE-619: Dangling Database Cursor (child)

References

  1. MITRE Corporation. "CWE-402: Transmission of Private Resources into a New Sphere." https://cwe.mitre.org/data/definitions/402.html
  2. CERT C. "FIO42-C. Close files when they are no longer needed"
  3. Linux man pages. "open(2) - O_CLOEXEC flag"