flawopen.com/Path Traversal/C/C++
In C, combining strings to open a file is like gluing together pieces of paper. If an attacker puts '../../' into the glue, the OS happily opens the root password file unless you call realpath() to check where it actually points.
In C and C++, constructing paths with snprintf(path, sizeof(path), "%s/%s", base_dir, user_filename) and passing them to open() or fopen() allows traversal tokens to escape the intended directory tree.
High-severity vulnerabilities in embedded firmware, web servers (e.g. Apache CVE-2021-41773), and network daemons frequently originate from path traversal in C code.
CISA Cybersecurity Advisory & MITRE CVE repository.// VULNERABLE: Direct snprintf and open without realpath check
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int serve_user_file(const char *user_filename) {
char full_path[1024];
const char *base_dir = "/var/app/public/files";
// Attacker input: "../../../../etc/shadow"
snprintf(full_path, sizeof(full_path), "%s/%s", base_dir, user_filename);
// Directly opens system shadow file!
int fd = open(full_path, O_RDONLY);
return fd;
}
// HARDENED: Canonicalize with realpath and verify directory prefix bounds
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
int serve_user_file(const char *user_filename) {
const char *base_dir = "/var/app/public/files";
char base_canonical[PATH_MAX];
char raw_path[PATH_MAX];
char target_canonical[PATH_MAX];
// 1. Resolve canonical base directory
if (realpath(base_dir, base_canonical) == NULL) {
return -1;
}
// 2. Safely construct raw path
if (snprintf(raw_path, sizeof(raw_path), "%s/%s", base_canonical, user_filename) >= (int)sizeof(raw_path)) {
return -1; // Path truncated
}
// 3. Resolve canonical target path
if (realpath(raw_path, target_canonical) == NULL) {
return -1; // File does not exist or access error
}
// 4. Verify canonical target begins with base_canonical + '/'
size_t base_len = strlen(base_canonical);
if (strncmp(target_canonical, base_canonical, base_len) != 0 ||
target_canonical[base_len] != '/') {
return -1; // Traversal detected!
}
return open(target_canonical, O_RDONLY | O_CLOEXEC);
}
realpath() resolves all traversal tokens and symbolic links. Checking strncmp(target_canonical, base_canonical, base_len) == 0 and verifying that the character immediately following is a directory slash (/) prevents both traversal and sibling directory access.
Always check the return value of snprintf; if it equals or exceeds sizeof(buffer), truncation occurred, which can truncate file extensions or boundaries.
String checking can be bypassed with URL encoding, null byte injection, or unexpected filesystem encoding sequences.
flawfinder .
scan-build make
realpath(path, resolved) to resolve canonical targetsbase_dir plus '/'O_CLOEXECIf creating a new file, call realpath() on the parent directory to verify boundary containment before opening with O_CREAT.