Virtual Memoryprotectionisolationpermissionsnxsecurity

What Actually Stops One Process Reading Another's Memory

Process isolation is not a promise the operating system makes and enforces in software. It is a consequence of permission bits the MMU checks on every access, and of the simple fact that one process has no way to name another's physical memory at all.

Follow the mechanism

Software view, hardware view

The gap between what you wrote and what the machine does is where this whole domain lives.

The question
What actually stops one process from reading another's memory — and what does that protection not cover?
What you wrote
Processes are isolated. The operating system keeps them apart. This is a rule the system enforces, and a sufficiently clever program might find a way around it.
What the hardware does
Two mechanisms, both in silicon. First, a process's page tables simply contain no entry for another process's frames, so those addresses are unnameable rather than merely forbidden. Second, every entry carries permission bits the MMU validates on every access, so even mapped memory can be refused.
Knowing that isolation is structural rather than procedural changes how you reason about a whole class of problems: why a wild pointer produces a clean fault instead of silent corruption, why shared memory must be explicitly arranged, why W^X is enforceable at all, and why side channels are interesting precisely because they leak *without* violating any of this.
SourceCompilerInstructionsFront EndExecutionRegistersCachesMemoryI/OBehavior

Unnameable, then unpermitted

The first line of defence is not a check that fails — it is the absence of anything to check. A process's page tables describe only its own mappings. Another process's physical frames have no virtual address in this address space, so there is no pointer value that reaches them. You cannot be denied access to something you cannot name.

The second line applies to memory you *can* name. Every entry carries bits saying whether this page may be read, written or executed, and whether user-mode code may touch it at all. The MMU validates the attempted access against those bits on every access, and a violation aborts the instruction and traps (The MMU: Translation and Protection in One Check).

Together these give a property that is easy to state and easy to underestimate: isolation costs nothing at runtime in the common case. There is no per-access software check, no bookkeeping, no supervisor involvement. The check rides along with a translation the hardware had to perform anyway, which is why it can be unconditional.

The two mechanisms, and what each one actually prevents
MechanismPreventsFailure mode when violated
No mapping existsNaming another process's memory at allFault: no valid entry for the address
Read permissionReading a mapped but protected pageFault: access type not permitted
Write permissionWriting to read-only or shared-clean pagesFault, often handled as copy-on-write
Execute permissionExecuting data as instructionsFault: the basis of W^X and NX
User/supervisor bitUser code touching kernel mappingsFault: privilege violation

NX and W^X: separating data from code

The execute permission deserves separate attention because it is the hardware building block under a family of exploit mitigations. Marking a page non-executable means the CPU refuses to fetch instructions from it, so data a program merely *stores* cannot become code the program *runs*.

The policy usually built on top is write-xor-execute: a page may be writable or executable, never both simultaneously. Code pages are executable and read-only; data pages are writable and non-executable. An attacker who manages to write bytes into a buffer therefore cannot simply jump to them, because the fetch itself will fault.

It is important to be precise about what this does and does not accomplish, because overstating it is common. W^X does not prevent memory corruption; it removes one technique for exploiting it. Attacks that reuse code already present and already executable are unaffected by it, which is exactly why they became the standard approach after NX became widespread. The Security Engineering domain carries that story properly.

A process address space under W^X — permissions per region, as the MMU sees them
usedfetched, never readSIMPLIFIED
code (r-x)rodata (r--)data (rw-)heap (rw-)guard (---)stack (rw-)
line 0
64 bytes total1 cache line touched4 bytes fetched and never read

No region is both writable and executable. The guard region is deliberately unmapped so an overrun faults precisely instead of silently entering the next region.

What this protection does not cover

It is worth being explicit about the boundary, because the mechanism is strong in one direction and says nothing at all in another. Permission bits control *architectural* access: whether an instruction may read, write or execute a location. They govern nothing about the microarchitectural traces an access leaves behind.

That gap is precisely where side channels live. A speculatively executed access that is later discarded never architecturally read anything and never violated any permission — but it may have changed cache state in a way that is observable by timing. The protection worked exactly as specified; the specification simply did not cover that channel. Side Channels: When Performance Optimisations Leak and Spectre and Meltdown: When Speculation Crossed a Boundary develop this properly.

The other boundary worth naming is that protection is per-address-space, so anything deliberately shared is deliberately reachable. Shared memory, memory-mapped files and a forked child's copy-on-write pages are all cases where two processes genuinely can reach the same frames, by arrangement. The hardware is enforcing exactly what the tables say — and the tables say sharing is allowed.

  • Covered: naming, reading, writing and executing memory across process boundaries, enforced per access.
  • Covered: user-mode code touching kernel mappings, refused by the privilege bit (Why Kernel Mode Is Actually Privileged).
  • Not covered: microarchitectural side effects of accesses that never architecturally completed.
  • Not covered: timing differences that leak information without any access being permitted.
  • Deliberately permitted: shared mappings, mapped files and copy-on-write pages, which are shared because the tables say so.

Key points

  • Isolation comes first from absence: another process's frames have no virtual address here, so they cannot be named.
  • Permission bits in each page-table entry are validated by the MMU on every access, with no software cost.
  • NX and W^X are hardware execute permission put to use: written bytes cannot be jumped to.
  • W^X removes one exploitation technique; it does not prevent memory corruption or code-reuse attacks.
  • Protection governs architectural access only, which is exactly the gap that side channels exploit.

Follow the mechanism

The path through the machine, hop by hop — and the conclusions it invites that are wrong.

  1. 1
    Process → address: a virtual address is formed that is meaningful only within this address space.
  2. 2
    MMU → page tables: the entry for this process is consulted; another process's frames have no entry to find.
  3. 3
    MMU → permission bits: the requested access type is checked against read/write/execute and user/supervisor bits.
  4. 4
    MMU → fault: a violation aborts the instruction before retirement and traps to the OS with the address and cause.
  5. 5
    OS → handler: the OS decides what the fault means — copy-on-write, demand paging, or a signal to the process.
What people conclude from this — wrongly
  • Believing isolation is enforced by OS code running on each access; it is enforced by hardware on a translation that had to happen anyway.
  • Treating W^X as preventing exploitation rather than removing one technique — code-reuse attacks were the direct response to it.
  • Assuming a segfault indicates corruption. It usually indicates protection working exactly as configured.
  • Expecting permission bits to constrain speculative or microarchitectural behaviour. They govern architectural access only.

Consequences, controls and cost

What it causes
  • • A wild pointer produces a precise fault rather than silently corrupting an unrelated process.
  • • Sharing memory between processes requires explicit arrangement, because the default is mutual unreachability.
  • • JIT compilers must explicitly transition pages between writable and executable, since W^X forbids holding both.
  • • Guard pages turn buffer overruns into immediate faults, at the cost of a page of address space each.
  • • Side-channel attacks remain possible precisely because they never violate any of these checks.
What you can do
  • • Rely on the default: separate address spaces already give strong isolation with no runtime cost.
  • • Use guard pages around buffers and stacks where a precise fault is preferable to silent corruption.
  • • Keep W^X intact — transition JIT pages from writable to executable rather than mapping them both at once.
  • • Make sharing explicit and minimal; every shared mapping is a deliberate hole in the default isolation.
  • • For side-channel exposure, understand that this mechanism does not help and consult the security domain rather than tightening permissions.
How to see it
  • • Count minor faults and inspect their causes; a hot copy-on-write pattern shows up here rather than in any application metric.
  • • Inspect a process's mappings and their permissions (`/proc/<pid>/maps` on Linux) to confirm W^X actually holds in practice.
  • • Watch for pages that are both writable and executable — usually a JIT or a loader doing something worth understanding.
  • • Trace `mprotect` frequency: a hot loop of permission changes costs table edits and cross-core invalidation.
What it costs
  • • Hardware-enforced isolation is nearly free per access but makes every legitimate sharing arrangement explicit work.
  • • W^X costs JIT compilers an extra permission transition per code buffer, and some flexibility in self-modifying designs.
  • • Guard pages consume address space and a page-table entry each, in exchange for turning silent corruption into a clean crash.

Scope

§224 — what these claims are specific to.

What these claims are specific to
  • GENERALPage-granular permission checking is standard on application-class CPUs. MMU-less embedded targets have neither, and run all code in one address space with no protection at all.
  • ISA-SPECIFICBit layouts, the name of the execute-disable bit and the exact privilege encodings differ between x86-64, AArch64 and RISC-V. Additional mechanisms such as memory tagging or pointer authentication exist on some and not others.

Misconceptions

Claim
“The OS checks every memory access to enforce isolation.”
Reality
The OS sets the bits; hardware checks them. If software were involved per access, the cost would be prohibitive — which is exactly why the check is fused with translation.
Claim
“NX prevents buffer-overflow exploitation.”
Reality
It prevents executing injected data. Attacks that reuse code already mapped executable were developed precisely because NX closed the simpler route, and remain effective.
Claim
“Memory protection prevents information leaking between processes.”
Reality
It prevents architectural access. Timing and cache-state side channels leak without any access being permitted, which is why they needed separate mitigations entirely.