While the XDG Base Directory Specification provides excellent defaults for most directories, XDG_RUNTIME_DIR
remains conspicuously undefined in the spec. This creates challenges for developers building applications that require runtime files like named pipes or UNIX sockets.
The common approach of using /tmp/myserver-$USER
has several limitations:
// Problematic implementation example
char *path = "/tmp/myapp-";
char *user = getenv("USER");
// Security risk: race condition during path creation
This violates three key requirements:
- No automatic cleanup (files persist after logout)
- Potential permission issues (world-writable directory)
- No guaranteed exclusivity (other users might predict paths)
Modern Linux systems using systemd typically create /run/user/$UID
with these characteristics:
$ ls -ld /run/user/$(id -u)
drwx------ 3 user user 60 Jan 01 00:00 /run/user/1000
Key properties:
- 0700 permissions (user-exclusive)
- Automatic deletion on logout
- Sticky bit prevents tampering
Here's a recommended fallback implementation in C:
#include <sys/types.h>
#include <pwd.h>
const char *get_runtime_dir() {
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime) return xdg_runtime;
// Fallback 1: Systemd convention
struct passwd *pw = getpwuid(getuid());
char *systemd_path = NULL;
if (asprintf(&systemd_path, "/run/user/%d", getuid()) > 0) {
if (access(systemd_path, F_OK) == 0) {
return systemd_path;
}
free(systemd_path);
}
// Fallback 2: Secure /tmp alternative
char *tmp_path = NULL;
if (asprintf(&tmp_path, "/tmp/.%s-%.10s",
pw->pw_name,
crypt(pw->pw_name, "$6$somesalt$")) > 0) {
mkdir(tmp_path, 0700);
return tmp_path;
}
return "."; // Last resort
}
When implementing your own fallback:
- Always set 0700 permissions
- Use cryptographic hashing for directory names
- Implement proper cleanup hooks
- Consider using
mkdtemp()
for atomic creation
For Python applications, consider this wrapper:
import os
from pathlib import Path
import hashlib
def get_runtime_dir(appname: str) -> Path:
if xdg := os.getenv("XDG_RUNTIME_DIR"):
return Path(xdg)
# Systemd fallback
systemd_path = Path(f"/run/user/{os.getuid()}")
if systemd_path.exists():
return systemd_path
# Secure /tmp fallback
username = os.getenv("USER", "unknown")
salt = "fixed-seed-for-consistency"
hash = hashlib.sha256((username + salt).encode()).hexdigest()[:16]
tmp_path = Path(f"/tmp/.{username}-{hash}")
tmp_path.mkdir(mode=0o700, exist_ok=True)
return tmp_path
The XDG Base Directory Specification provides excellent defaults for most directories, but leaves XDG_RUNTIME_DIR
implementation as an exercise for developers. When creating named pipes or sockets in a client-server architecture, we need a location that satisfies three critical requirements:
1. User-specific isolation
2. Automatic cleanup on logout
3. Secure permissions (0700)
While /tmp/myserver-$USER
might seem like a reasonable choice, it fails to meet the specification's requirements:
- No automatic cleanup - files persist after logout
- Potential security issues if not properly permissioned
- Possible collisions with other applications
Modern Linux systems using systemd provide the ideal solution:
# Systemd creates this automatically for logged-in users
/run/user/$(id -u)
This directory:
- Has strict 0700 permissions
- Is automatically created/destroyed with user sessions
- Is on tmpfs for security
Here's a robust implementation in C that handles all cases:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
const char *get_runtime_dir() {
// 1. Check XDG_RUNTIME_DIR first
const char *xdg_runtime = getenv("XDG_RUNTIME_DIR");
if (xdg_runtime != NULL) {
return xdg_runtime;
}
// 2. Try systemd's /run/user/$UID
char systemd_path[256];
snprintf(systemd_path, sizeof(systemd_path), "/run/user/%d", getuid());
if (access(systemd_path, F_OK) == 0) {
return strdup(systemd_path);
}
// 3. Fallback to secure /tmp alternative
const char *username = getpwuid(getuid())->pw_name;
char *tmp_path = malloc(256);
snprintf(tmp_path, 256, "/tmp/%s-runtime", username);
// Ensure directory exists with correct permissions
mkdir(tmp_path, 0700);
return tmp_path;
}
For systems without systemd, consider using pam_systemd's behavior as inspiration:
- Create
/run/user/$UID
at login via PAM - Set strict permissions (0700)
- Clean up at logout
A shell implementation might look like:
#!/bin/sh
RUNTIME_DIR="/run/user/$(id -u)"
if [ ! -d "$RUNTIME_DIR" ]; then
mkdir -p "$RUNTIME_DIR"
chmod 700 "$RUNTIME_DIR"
chown "$(id -u):$(id -g)" "$RUNTIME_DIR"
fi
export XDG_RUNTIME_DIR="$RUNTIME_DIR"
When implementing your own runtime directory:
- Always use 0700 permissions
- Prefer tmpfs over disk storage
- Validate all path components
- Consider symbolic link attacks