Use of Path Manipulation Function without Maximum-sized Buffer

Description

Use of Path Manipulation Function without Maximum-sized Buffer is a buffer overflow vulnerability where software uses functions that manipulate file paths or directory names but provides an output buffer smaller than the maximum possible path length. Functions like realpath(), readlink(), PathAppend(), GetFullPathName(), and similar utilities can produce paths up to the system's maximum path length (e.g., PATH_MAX, MAX_PATH). When the output buffer is smaller than this maximum, the function may write beyond the buffer's bounds, causing a buffer overflow.

Risk

Path manipulation buffer overflows can have severe security consequences. Since path lengths depend on user input and file system structure, attackers may be able to craft inputs that produce paths exceeding the undersized buffer. This can corrupt adjacent memory, crash the application, or potentially enable arbitrary code execution. The vulnerability is particularly dangerous because developers often underestimate possible path lengths—paths can include deep directory nesting, long filenames, or symbolic link chains that expand during resolution. Network file systems may have even longer path limits.

Solution

Always allocate output buffers with size equal to or greater than the maximum path length for the target platform. On POSIX systems, use PATH_MAX (typically 4096 bytes). On Windows, use MAX_PATH (260 characters) or extended path handling (32,767 characters with \?\ prefix). Consider that PATH_MAX may not be defined on all systems—use pathconf() to determine limits dynamically. Prefer safer variants of functions when available (e.g., realpath() with NULL argument on systems that allocate the buffer). Validate input paths before processing to reject obviously oversized inputs. Use length-checking wrapper functions.

Common Consequences

ImpactDetails
IntegrityScope: Integrity

Modify Memory - Buffer overflow overwrites adjacent memory with path data.
AvailabilityScope: Availability

DoS: Crash, Exit, or Restart - Memory corruption typically causes crashes.
ConfidentialityScope: Confidentiality, Integrity, Availability

Execute Unauthorized Code or Commands - Exploitable buffer overflows may enable arbitrary code execution.

Example Code

Vulnerable Code

// Vulnerable: Buffer smaller than PATH_MAX
#include <stdlib.h>
#include <string.h>
#include <limits.h>

#ifdef _WIN32
#include <windows.h>
#endif

char* vulnerable_realpath(const char* path) {
    // Vulnerable: Buffer is only 128 bytes, PATH_MAX is typically 4096
    char resolvedPath[128];

    if (realpath(path, resolvedPath) == NULL) {
        return NULL;
    }

    // If actual path > 128 bytes, buffer overflow occurred!
    return strdup(resolvedPath);
}

char* createOutputDirectory(const char* name) {
    // Vulnerable: 128-byte buffer for path operations
    char outputDirectoryName[128];

    if (getCurrentDirectory(128, outputDirectoryName) == 0) {
        return NULL;
    }

    // Vulnerable: PathAppend may exceed buffer
    if (!PathAppend(outputDirectoryName, "output")) {
        return NULL;
    }

    // Vulnerable: Adding 'name' could overflow 128-byte buffer
    if (!PathAppend(outputDirectoryName, name)) {
        return NULL;  // May already have overflowed
    }

    return strdup(outputDirectoryName);
}
// Vulnerable: Windows path manipulation
#include <windows.h>

BOOL vulnerable_get_full_path(const char* relativePath, char* fullPath) {
    // Vulnerable: MAX_PATH is 260, but buffer is only 128
    // Long paths can exceed MAX_PATH with \\?\ prefix
    char buffer[128];

    DWORD result = GetFullPathNameA(relativePath, 128, buffer, NULL);

    if (result == 0 || result > 128) {
        return FALSE;
    }

    strcpy(fullPath, buffer);
    return TRUE;
}

// Vulnerable: readlink without adequate buffer
ssize_t vulnerable_readlink(const char* path, char* buf) {
    // Vulnerable: Arbitrary buffer size
    char linkBuf[256];

    ssize_t len = readlink(path, linkBuf, sizeof(linkBuf) - 1);
    if (len < 0) {
        return -1;
    }

    // If symlink target > 255 chars, it's truncated
    // Worse: some implementations may overflow
    linkBuf[len] = '\0';
    strcpy(buf, linkBuf);
    return len;
}
// Vulnerable: Combining paths without size checking
void vulnerable_combine_paths(const char* dir, const char* file, char* result) {
    // Vulnerable: Fixed-size buffer
    char temp[256];

    strcpy(temp, dir);   // Vulnerable: No bounds check
    strcat(temp, "/");   // Vulnerable: No bounds check
    strcat(temp, file);  // Vulnerable: No bounds check

    // If combined path > 256, buffer overflow
    strcpy(result, temp);
}

Fixed Code

// Fixed: Use PATH_MAX for buffer size
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <errno.h>

#ifndef PATH_MAX
#include <unistd.h>
#define PATH_MAX pathconf("/", _PC_PATH_MAX)
#endif

char* fixed_realpath(const char* path) {
    // Fixed: Allocate buffer of PATH_MAX size
    char* resolvedPath = malloc(PATH_MAX);
    if (resolvedPath == NULL) {
        return NULL;
    }

    if (realpath(path, resolvedPath) == NULL) {
        free(resolvedPath);
        return NULL;
    }

    return resolvedPath;
}

// Even better: Let realpath allocate the buffer (POSIX.1-2008)
char* fixed_realpath_v2(const char* path) {
    // Fixed: NULL buffer causes realpath to allocate
    // Caller must free the returned pointer
    char* resolvedPath = realpath(path, NULL);
    return resolvedPath;  // May be NULL on error
}
// Fixed: Windows path manipulation with proper buffer
#include <windows.h>
#include <stdlib.h>

BOOL fixed_get_full_path(const char* relativePath, char** fullPath) {
    // Fixed: First call to get required size
    DWORD required = GetFullPathNameA(relativePath, 0, NULL, NULL);
    if (required == 0) {
        return FALSE;
    }

    // Fixed: Allocate exact required size
    *fullPath = malloc(required);
    if (*fullPath == NULL) {
        return FALSE;
    }

    DWORD result = GetFullPathNameA(relativePath, required, *fullPath, NULL);
    if (result == 0 || result >= required) {
        free(*fullPath);
        *fullPath = NULL;
        return FALSE;
    }

    return TRUE;
}

// Fixed: Handle long paths on Windows
BOOL fixed_get_full_path_long(const wchar_t* relativePath, wchar_t** fullPath) {
    // Support paths up to 32767 characters
    const DWORD maxPath = 32767;

    *fullPath = malloc(maxPath * sizeof(wchar_t));
    if (*fullPath == NULL) {
        return FALSE;
    }

    DWORD result = GetFullPathNameW(relativePath, maxPath, *fullPath, NULL);
    if (result == 0 || result >= maxPath) {
        free(*fullPath);
        *fullPath = NULL;
        return FALSE;
    }

    return TRUE;
}
// Fixed: readlink with proper buffer
#include <unistd.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>

char* fixed_readlink(const char* path) {
    // Fixed: Use PATH_MAX or determine dynamically
    size_t bufsize = PATH_MAX;
    char* buf = malloc(bufsize);

    if (buf == NULL) {
        return NULL;
    }

    ssize_t len = readlink(path, buf, bufsize - 1);
    if (len < 0) {
        free(buf);
        return NULL;
    }

    // Check if buffer might have been too small
    if ((size_t)len >= bufsize - 1) {
        // Link target may have been truncated
        // Could retry with larger buffer
        free(buf);
        errno = ENAMETOOLONG;
        return NULL;
    }

    buf[len] = '\0';
    return buf;
}
// Fixed: Safe path combination
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>

char* fixed_combine_paths(const char* dir, const char* file) {
    size_t dir_len = strlen(dir);
    size_t file_len = strlen(file);
    size_t total = dir_len + 1 + file_len + 1;  // dir + '/' + file + '\0'

    // Fixed: Check against PATH_MAX
    if (total > PATH_MAX) {
        return NULL;  // Path would be too long
    }

    char* result = malloc(total);
    if (result == NULL) {
        return NULL;
    }

    // Fixed: Use snprintf for bounds checking
    int written = snprintf(result, total, "%s/%s", dir, file);
    if (written < 0 || (size_t)written >= total) {
        free(result);
        return NULL;
    }

    return result;
}

// Alternative: Use a maximum-sized buffer
int fixed_combine_paths_v2(const char* dir, const char* file, char* result, size_t result_size) {
    if (result_size < PATH_MAX) {
        return -1;  // Buffer too small
    }

    int written = snprintf(result, result_size, "%s/%s", dir, file);
    if (written < 0 || (size_t)written >= result_size) {
        return -1;
    }

    return 0;
}

Detection Methods

  • Static Analysis: SAST tools can identify path manipulation calls with undersized buffers.
  • Code Review: Look for hardcoded buffer sizes smaller than PATH_MAX/MAX_PATH.
  • Testing: Test with deeply nested directories and long filenames.

References

  1. MITRE Corporation. "CWE-785: Use of Path Manipulation Function without Maximum-sized Buffer." https://cwe.mitre.org/data/definitions/785.html
  2. CERT C Coding Standard. "FIO02-C. Canonicalize path names originating from tainted sources."
  3. Linux Manual. "realpath(3) - return the canonicalized absolute pathname."