Jeremy Hefner ยท sysfail.net

Reading /proc without root: what's actually visible

Linux's /proc filesystem is a window into the running system. Here's exactly what you can and can't see as a regular user.

/proc is a virtual filesystem that exposes kernel data structures as files. Every running process gets a directory — /proc/<pid> — and inside that directory are dozens of files exposing process state. It's how tools like ps, top, and lsof actually work: they're all just reading from /proc.

Most documentation treats /proc as if you're always root. But what can a regular user actually see?

The basics: what's always visible

Any user can list /proc and see all PID directories. The PIDs themselves aren't secret — you can enumerate every process on the system just by calling os.listdir("/proc") and filtering for numeric entries.

For each PID directory, a few files are world-readable regardless of process ownership:

  • /proc/<pid>/comm — the short process name (up to 15 chars)
  • /proc/<pid>/cmdline — the full command line, null-delimited
  • /proc/<pid>/stat — process status in a compact fixed format
  • /proc/<pid>/status — the same info, human-readable, including UIDs

That's already quite a lot. With just comm and status you can build a basic process table with usernames — no root required.

Where it gets gated: /proc/<pid>/fd

The fd directory contains a symlink for each open file descriptor, pointing to the actual file path, socket, or pipe. This is the most forensically interesting data in /proc, and it's protected accordingly.

The fd directory is only readable by the process owner or root. Try to list it as a different user and you get EACCES. This is enforced by the kernel — there's no workaround short of privilege escalation.

$ ls /proc/1234/fd
ls: cannot open directory '/proc/1234/fd': Permission denied

This is why tools like lsof ask you to run them as root for full results. They're not being cautious — they literally can't read those entries otherwise.

Network state: /proc/net

Here's where it gets interesting. /proc/net/tcp, /proc/net/udp, and their IPv6 variants are world-readable. They list every socket on the system in a hex-encoded format, indexed by inode number.

$ cat /proc/net/tcp
  sl  local_address rem_address   st tx_queue rx_queue ... inode
   0: 0100007F:0035 00000000:0000 0A 00000000:00000000 ... 23456
   1: 00000000:0016 00000000:0000 0A 00000000:00000000 ... 34567

Addresses are in little-endian hex; ports in hex; state 0A = LISTEN, 01 = ESTABLISHED. The inode field is what links a socket entry back to a specific process via /proc/<pid>/fd.

So: as an unprivileged user you can see every open network socket on the system, including remote addresses and connection state. You just can't always attribute them to a specific process without root.

Putting it together

If you're writing a tool that reads /proc, the practical implication is: design to degrade gracefully when permissions are denied. Catching OSError on fd reads and emitting a placeholder is enough — you still get the process name and owner from comm and status.

The access-denied case is informative in itself. If your scanner is hitting permission errors on every fd read, that's a clear signal it needs elevation. Better to report that explicitly than to silently drop entries.

def proc_open_fds(pid, socket_map):
    fd_dir = f"/proc/{pid}/fd"
    try:
        fds = os.listdir(fd_dir)
    except OSError:
        yield ("?", "<access denied>")
        return
    # ... rest of enumeration

A note on namespaces

On modern kernels with network namespaces, /proc/net only shows sockets in your process's own network namespace. Inside a container you'll see only that container's sockets. This doesn't affect most use cases, but it's worth knowing if you're building monitoring tooling that's supposed to see the full host picture — it needs to run in the root namespace, not inside a container.


The tl;dr: /proc gives you more than most people realize without root, but the most forensically interesting data — open file descriptors — requires either ownership or elevation. Design accordingly, and always handle the permission-denied case explicitly.