Your process memory is a file: what /proc/[pid]/mem lets you do
On Linux, a running process is not a sealed box. Its entire memory is exposed as an ordinary file at /proc/<pid>/mem, and with the right permissions you can read or write it the same way you would any file on disk. No special debugger, no kernel module, no exotic syscall. Open, lseek, and read.
That sounds alarming the first time you hear it, and it is worth understanding exactly how it works, because the access rules are more subtle than "files have permission bits."
A file that is the address space
/proc/<pid>/mem represents the target process's full virtual address space. The byte at offset N in the file is the byte at virtual address N in the process. So you do not stream it top to bottom like a log file. You lseek to the virtual address you care about, then read (or write) from there.
The Cloudflare engineering team documented the supported operations precisely: open, read, and lseek, with SEEK_END deliberately excluded. There is no meaningful "end" of a 64-bit address space to seek to, so the kernel does not pretend there is one. lcamtuf's writeup makes the same point from the practical side: you can use pread and pwrite with explicit offsets, but you still have to position yourself at a real address first, because "you need to lseek(...) to a specific offset before calling read(...) or write(...)."
The catch is that most of that 64-bit range is not mapped. Seek to a random address and the read fails. You need to know where the real regions live.
Finding the valid regions
That map comes from a sibling file: /proc/<pid>/maps. It lists every mapped region in the process, the address range it covers, and its permissions (read, write, execute, private or shared). Tools that walk process memory read maps first to learn which ranges are writable versus read-only, then seek into /proc/<pid>/mem to dump or patch each one.
lcamtuf's own memfetch, a tool he wrote back in 2002 to take non-destructive "screenshots" of a live process and recently refreshed for 64-bit systems, does exactly this. It parses the memory maps, identifies the writable versus read-only sections, and dumps them region by region. The pair of files is the whole interface: maps tells you the geography, mem gives you the bytes.
Who is allowed to do this
Here is the part people get wrong. The permission check is not a simple file mode, and it does not require that you have attached to the process as a debugger.
When you open the file, the kernel runs proc_mem_open, which calls into __ptrace_may_access with the mode PTRACE_MODE_ATTACH_FSCREDS. That is the same access-mode check ptrace itself uses, but, and this is the key correction, it only checks whether you would be allowed to attach. It does not require that you ever called PTRACE_ATTACH. lcamtuf flags this directly, crediting reviewer Jann Horn: PTRACE_ATTACH is not necessary to access the file.
What the check grants access on, per the kernel's LSM logic and the ptrace(2) manual:
- The caller and target share the same thread group (you reading your own memory, the common case), or
- The caller holds
CAP_SYS_PTRACEin the target's user namespace, or - Credentials match, the target is dumpable, both live in the same user namespace, and the target's permitted capabilities are a subset of the caller's.
PTRACE_MODE_FSCREDS means the kernel uses your filesystem UID and GID for that comparison rather than your effective ones. The short version: you can read another process's memory if it belongs to you and is dumpable, or if you are privileged enough to debug it. Same user, same access. Different user, you need capabilities.
On most distributions there is a second gate on top of this. The Yama LSM exposes /proc/sys/kernel/yama/ptrace_scope, and at the default setting of 1 a process can only be introspected by its own direct parent unless you have CAP_SYS_PTRACE. That is why attaching a debugger to an unrelated process of your own user often fails until you raise privileges or lower the scope. The /proc/<pid>/mem path inherits the same restriction because it routes through the same ptrace_may_access logic.
The checks happen once, at open
One design decision has real consequences: the access checks run when you open the file, not on every read. Cloudflare's analysis spells it out: reading is "quite straightforward" precisely because "all the access checks occur when opening the file." Once you hold a valid file descriptor, the kernel trusts it.
That is convenient and also a footgun. If a privileged process opens the descriptor and then hands it to, or has it inherited by, something less privileged, the read permission travels with the descriptor, not with the current credentials. Descriptor passing across a privilege boundary is exactly the kind of thing to audit for.
Sharp edges worth knowing
A few behaviors will surprise you the first time:
FOLL_FORCEbypasses page protections. The kernel'smem_rwpath usesFOLL_FORCE, which means it can read pages marked non-readable and write pages marked non-writable in the target. The usual page permissions do not stop it.- A dead target reads as empty, not as an error. If the target process exits after you have opened the descriptor, later
readcalls do not fail. They "always succeed with reading 0 bytes." If you are not checking for short reads, you can silently process nothing and never notice the process is gone. - You cannot
mmapit. Early Linux 2.2 allowed mapping/proc/<pid>/memdirectly, but it was removed because it could "crash or hang the entire system due to page tables getting out of sync." It has been read/seek/write only ever since.
Why this is worth your time
This is not a party trick. It is the mechanism underneath a lot of tooling you already use. Debuggers read and patch memory through this interface. Crash dumpers and core-snapshot tools walk maps and mem together. Live-patching and memory-forensics utilities lean on it. Even a logging library, as Cloudflare noted, opened /proc/self/mem to find ELF headers at the start of each mapped region so it could resolve debug symbols.
Knowing the model also sharpens your security instincts. The fact that the permission check is a ptrace-equivalent check, runs once at open, and uses FOLL_FORCE to ignore page protections tells you precisely where the trust boundaries are. If an attacker can read /proc/<pid>/mem of a process holding secrets, page-level memory protections will not save those secrets. It is the inside-out version of the same instinct behind watching your external attack surface, which I wrote about in free website security scanners worth running. That is why ptrace_scope, dumpability flags, and dropping CAP_SYS_PTRACE matter, and why you should treat the ability to open another process's mem file as equivalent to the ability to debug it. Because it is.
The next time you reach for a debugger, you will know it is opening a file, seeking to an address, and reading bytes, the same three operations you have used a thousand times. The magic was never magic.