Symbolic Name not Mapping to Correct Object

Description

Symbolic Name not Mapping to Correct Object is a vulnerability that occurs when a constant symbolic reference to an object is used, even though the reference can resolve to a different object over time. This weakness arises when programs rely on symbolic names (like filenames, symlinks, or object references) that can be manipulated or changed between the time of resolution and the time of use. The actual resource accessed may differ from the intended resource, leading to unauthorized access or unintended operations.

Risk

When symbolic names don't map to the correct objects, attackers can exploit the gap between name resolution and object use. This enables unauthorized resource access through privilege escalation attacks. Data may be read from or written to unintended locations, causing confidentiality and integrity violations. Attackers can hijack operations by redirecting symbolic names to malicious resources. Log files or audit trails may be deleted or modified when file references are manipulated. This vulnerability is closely related to TOCTOU (time-of-check time-of-use) race conditions and symlink attacks.

Solution

Avoid relying on symbolic names that can change over time. Use direct object references (like file descriptors) instead of repeatedly resolving symbolic names. Verify that resolved references point to expected objects immediately before use. Implement proper locking mechanisms when working with shared resources. For file operations, use file descriptor-based operations rather than path-based operations after initial open. Validate that symbolic links point to expected targets. Use atomic operations where possible to eliminate race windows.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Gain Privileges or Assume Identity - Unauthorized resource access becomes possible.
IntegrityScope: Integrity, Confidentiality

Data Modification/Access - Race conditions enable read/write access to restricted resources.
IntegrityScope: Integrity

Data Corruption - Resources may be altered undesirably by malicious actors.
Non-RepudiationScope: Non-Repudiation

Hidden Activities - Activity logging may fail when resources are accessed improperly.
Non-RepudiationScope: Non-Repudiation, Integrity

File Deletion - Unauthorized deletion of files like logs may occur.

Example Code

Vulnerable Code

// Vulnerable: Symbolic name resolved twice with race window
void vulnerable_process_config(const char *config_path) {
    struct stat st;

    // First resolution: Check file ownership
    if (stat(config_path, &st) != 0) {
        return;
    }

    if (st.st_uid != 0) {
        printf("Config must be owned by root\n");
        return;
    }

    // Race window: config_path could change (symlink attack)

    // Second resolution: Open file
    FILE *f = fopen(config_path, "r");  // May open different file!
    if (f) {
        process_config(f);
        fclose(f);
    }
}

// Vulnerable: Class loading by name
// Java example
public class VulnerableClassLoader {
    public Object loadPlugin(String className) throws Exception {
        // Vulnerable: Class name could map to different class
        // if classpath is manipulated between calls
        Class<?> cls = Class.forName(className);

        // Attacker might replace the class file
        return cls.newInstance();
    }
}
# Vulnerable: File path resolved multiple times
import os

def vulnerable_read_file(filepath):
    # First resolution: Security check
    if not os.access(filepath, os.R_OK):
        raise PermissionError("Cannot read file")

    # Race window: filepath could be changed to symlink

    # Second resolution: Read file
    with open(filepath, 'r') as f:  # May read different file!
        return f.read()

# Vulnerable: Symbolic link following
def vulnerable_safe_delete(filepath, safe_dir):
    # Check if file is in safe directory
    if not filepath.startswith(safe_dir):
        raise SecurityError("File not in safe directory")

    # Race window: filepath could become symlink to /etc/passwd

    os.remove(filepath)  # May delete wrong file!

Fixed Code

// Fixed: Use file descriptor to avoid re-resolution
void secure_process_config(const char *config_path) {
    struct stat st;
    int fd;

    // Open file first to get handle
    fd = open(config_path, O_RDONLY | O_NOFOLLOW);
    if (fd < 0) {
        return;
    }

    // Fixed: Use fstat on the file descriptor, not path
    if (fstat(fd, &st) != 0) {
        close(fd);
        return;
    }

    if (st.st_uid != 0) {
        printf("Config must be owned by root\n");
        close(fd);
        return;
    }

    // Fixed: Work with the already-open file descriptor
    FILE *f = fdopen(fd, "r");
    if (f) {
        process_config(f);
        fclose(f);  // Also closes fd
    } else {
        close(fd);
    }
}

// Fixed: Verify resolved path matches expected
void secure_process_file(const char *user_path, const char *safe_dir) {
    char resolved[PATH_MAX];
    int fd;

    // Resolve the path first
    if (realpath(user_path, resolved) == NULL) {
        return;
    }

    // Verify it's in safe directory
    if (strncmp(resolved, safe_dir, strlen(safe_dir)) != 0) {
        return;  // Not in safe directory
    }

    // Fixed: Open with O_NOFOLLOW to prevent symlink following
    fd = open(resolved, O_RDONLY | O_NOFOLLOW);
    if (fd < 0) {
        return;
    }

    // Work with fd
    process_fd(fd);
    close(fd);
}
# Fixed: Use file descriptor to prevent re-resolution
import os
import stat

def secure_read_file(filepath):
    # Open file to get descriptor
    fd = os.open(filepath, os.O_RDONLY | os.O_NOFOLLOW)

    try:
        # Fixed: Check permissions on open descriptor
        st = os.fstat(fd)

        if not (st.st_mode & stat.S_IRUSR):
            raise PermissionError("Cannot read file")

        # Fixed: Read from the descriptor
        with os.fdopen(fd, 'r') as f:
            return f.read()
    except:
        os.close(fd)
        raise

# Fixed: Safe file deletion with path validation
def secure_safe_delete(filepath, safe_dir):
    # Resolve to absolute path
    real_path = os.path.realpath(filepath)
    real_safe = os.path.realpath(safe_dir)

    # Verify file is in safe directory
    if not real_path.startswith(real_safe + os.sep):
        raise SecurityError("File not in safe directory")

    # Check it's not a symlink
    if os.path.islink(filepath):
        raise SecurityError("Cannot delete symlinks")

    # Fixed: Open with O_NOFOLLOW and delete via fd
    fd = os.open(filepath, os.O_RDONLY | os.O_NOFOLLOW)
    try:
        # Verify same file after open
        st = os.fstat(fd)
        lst = os.lstat(filepath)

        if st.st_ino != lst.st_ino or st.st_dev != lst.st_dev:
            raise SecurityError("File changed during operation")

        os.remove(filepath)
    finally:
        os.close(fd)
// Fixed: Verify class identity before use
public class SecureClassLoader {
    private final Map<String, Class<?>> trustedClasses = new HashMap<>();

    public void registerTrustedClass(Class<?> cls) {
        // Pre-register trusted classes with their expected identity
        trustedClasses.put(cls.getName(), cls);
    }

    public Object loadPlugin(String className) throws Exception {
        Class<?> cls = Class.forName(className);

        // Fixed: Verify class identity matches registered version
        Class<?> trusted = trustedClasses.get(className);
        if (trusted == null || trusted != cls) {
            throw new SecurityException("Untrusted or modified class: " + className);
        }

        return cls.getDeclaredConstructor().newInstance();
    }
}

CVE Examples

No specific CVEs are listed for this CWE. The vulnerability pattern appears in:

  • File operations using paths instead of descriptors
  • Class loading systems
  • Resource resolution with symbolic references

References

  1. MITRE Corporation. "CWE-386: Symbolic Name not Mapping to Correct Object." https://cwe.mitre.org/data/definitions/386.html