Lesson 1 of 9 · 9 min read
How the Kernel Isolates Processes
In this lesson
TL;DR: The processor has a privileged mode for the kernel and an unprivileged mode for everything else. Each process sees memory only through page tables the kernel controls, so other processes' memory is simply not addressable. A program asks for privileged work through a system call, a fixed entry point where the kernel validates every argument, especially pointers, before acting with its own authority. Isolation has deliberate doors (debuggers, shared memory, IPC), each with its own access check. All of it depends on the kernel being correct: one kernel bug can break every boundary on the machine.
Before you read
Pick an answer before you read further. The lesson below explains it, and guessing first, right or wrong, helps the explanation stay with you.
1A service runs as root. Does its code run in kernel mode?
Two modes, enforced by hardware
In user mode, instructions that would change page tables, disable interrupts or talk to devices directly cause a fault. Only code running in kernel mode can execute them. On x86 the modes are called ring 3 and ring 0; on ARM, exception levels EL0 and EL1. The names differ; the property is the same: a user program cannot simply decide to be privileged.
Virtual memory is the wall
Programs never use physical addresses. Every load and store goes through the memory management unit, which translates virtual addresses using the current process's page tables. Each page table entry carries permissions: present or not, writable or read-only, executable or not, user-accessible or supervisor-only.
When the kernel switches from process A to process B, it switches page tables. B's addresses now map to B's pages. Nothing B can compute reaches A's memory, because A's pages are not in B's tables. Only the kernel edits page tables, so this wall holds as long as the kernel is correct.
When the wall leaks through the CPU
Page tables are enforced by hardware, but in 2018 Meltdown showed that speculative execution on many processors could read kernel memory mapped into a process's address space before the permission check took effect, leaking it through cache timing. The kernel's correctness was not the problem; the CPU's behaviour was. Linux's fix, kernel page-table isolation (KPTI), keeps two sets of page tables per process: while user code runs, the tables map almost none of the kernel, only the entry and exit code and the interrupt table. Switching tables on every entry and exit costs on the order of a hundred cycles each time, plus about 4 KB of extra memory per process. Newer CPUs fixed the underlying behaviour, so KPTI is not needed on them.
The interview point: an isolation boundary is only as strong as every layer beneath it, including the processor.
The system call is the gate
A program that wants to open a file puts the system call number and arguments in registers and executes a special instruction (syscall on x86-64, svc on ARM64). The CPU switches to kernel mode and jumps to an entry point the kernel registered at boot. The kernel then:
- Validates every argument. Pointers must point into the caller's own user memory, and are copied in once through helpers that handle faults (
copy_from_useron Linux). Reading the same user memory twice is a bug: another thread can change it between reads (a double fetch). - Checks permissions against the process's credentials at the moment of use.
- Does the work with kernel privilege and returns a result.
Hardware helps the kernel avoid mistakes: SMEP stops the kernel executing user-space pages, and SMAP stops it reading or writing user memory except inside the deliberate copy routines.
What the kernel tracks per process
| Item | Why it matters for security |
|---|---|
| Address space | The memory isolation boundary |
| Credentials (user and group IDs, capabilities; on Windows, the access token) | Every permission check uses them |
| Open handles (file descriptors) | Inherited by child processes unless marked close-on-exec |
| Namespaces and cgroups | What the process can see and how much it can consume |
| Seccomp filter | Which system calls it may make at all |
The deliberate doors
Isolation would be useless if processes could never cooperate, so the kernel offers doors, each with a lock:
- Debugging (
ptraceon Linux, process handles with debug rights on Windows) reads and writes another process's memory. Allowed for the same user, or with a capability such asCAP_SYS_PTRACEor WindowsSeDebugPrivilege. Linux's Yama module can restrict even same-user debugging. - Shared memory and memory-mapped files put the same pages in two address spaces, governed by permissions on the shared object.
- IPC (pipes, sockets, message queues, RPC) passes data, which the receiver must treat as untrusted input.
Two consequences. Processes running as the same user on a desktop are weakly isolated from each other, which is why browsers and mobile platforms put each app or site in its own sandboxed identity. And threads inside one process share everything, so there is no isolation between them at all.
Yama's levels for debugging
kernel.yama.ptrace_scope | Who may attach to a process |
|---|---|
| 0 | Any process of the same user (classic behaviour) |
| 1 | Only a parent attaching to its descendants, or a debugger the target has declared |
| 2 | Only processes with CAP_SYS_PTRACE |
| 3 | Nobody; once set, it cannot be changed without a reboot |
Level 1 is a common default on desktop distributions. It means a compromised browser extension host running as you cannot simply attach to your password manager.
fork, exec and what crosses them
A child created with fork gets a copy of the parent's memory, its credentials, its environment variables and all of its open file descriptors. After exec loads a new program, the memory is replaced, but credentials, environment and file descriptors carry over unless a descriptor was marked close-on-exec. Two common leaks follow: a privileged parent passes an open handle to a sensitive file into an untrusted child, and secrets placed in environment variables are inherited by every child process and visible to anything that can read the process's environment. Open sensitive files with O_CLOEXEC, and pass secrets to children deliberately rather than through the environment.
Time of check to time of use in user space
The kernel's double-fetch rule has a user-space twin. A privileged program checks that a path is safe, then opens it. Between the check and the open, an attacker who controls a directory on the path swaps a file for a symbolic link to something sensitive, and the program opens the wrong file with its privileges.
The fix pattern is to check the thing you will use, not a name for it: open first, with flags such as O_NOFOLLOW and relative to a directory handle you already trust (openat), then inspect the open file descriptor with fstat and act only on that descriptor. Newer interfaces such as openat2 with resolve restrictions make this easier.
When the wall falls: the kernel
A user process attacks the kernel through the system calls it can make. A memory-safety bug in kernel code reachable from user space lets the attacker rewrite their own credentials, read any memory or disable security modules. Every container on a host shares the host kernel, so a kernel exploit from inside a container is a container escape.
Protecting: Host kernel and other workloads
- 1
1 · Separate process and user
own page tables and credentials
- Stops: Reads another process's memory
- 2
2 · Seccomp allowlist
fewer kernel entry points
- Stops: Calls a rarely used, buggy system call
- 3
3 · gVisor or microVM
a different kernel in between
- Stops: Exploits a reachable kernel bug
This is the reasoning behind every sandbox design: reduce the kernel surface a workload can reach (seccomp), and for hostile code put another kernel in the way (gVisor's user-space kernel, or a microVM with its own guest kernel behind a hypervisor).
Is it a security boundary?
Interviewers often ask whether some separation "counts". Microsoft publishes the list it services as security boundaries in Windows: the network, kernel, process, AppContainer sandbox, user, session, web browser, virtual machine and virtualisation-based security boundaries. Just as useful is what it says is not a boundary: administrator to kernel. An administrator who loads code into the kernel has not crossed a boundary in Microsoft's model, and User Account Control is a defence-in-depth feature, not a boundary Microsoft commits to service. The Linux equivalent is that root can usually reach the kernel unless lockdown, Secure Boot policy or similar controls are in force.
Measuring how much kernel a workload can reach
Surface reduction can be counted.
- Default container profile. Docker's default seccomp profile is an allowlist that disables around 44 of more than 300 system calls. Everything else stays reachable.
- Concentrated risk. In June 2023 Google reported that 60% of the 42 Linux kernel exploits submitted to its kCTF vulnerability reward programme used io_uring. Google disabled io_uring on ChromeOS and its production servers and blocked Android apps from it with seccomp.
- What seccomp cannot see. A seccomp filter sees the system call number and the raw register values of its arguments, not the memory those arguments point to. It cannot decide which file paths a process may open; that belongs to a Linux security module or Landlock.
- Locking root out of the kernel. The kernel lockdown feature (Linux 5.4) stops even root from modifying the running kernel through interfaces such as
/dev/memor unsigned modules, and many distributions turn it on under Secure Boot.
Worked example: a code execution service
A service runs agent-generated Python in containers on a shared kernel.
| Step | Reachable kernel | Residual risk |
|---|---|---|
| Default container profile | About 256 of 300+ system calls (300 minus about 44) | Any kernel bug in that surface is a full escape |
| Workload allowlist, measured in audit mode, plus io_uring blocked | Tens of system calls the interpreter actually uses | Smaller, but a bug in an allowed call is still an escape |
| A microVM per session | A guest kernel behind a hypervisor | Hypervisor and device bugs; boot on the order of 125 ms |
Surface reduction is measurable and worth doing. Whether a shared kernel is acceptable at all is a separate decision driven by how hostile the code is.
Default container profile
Generated code runs in a container
About 256 system calls reachable
io_uring included
One kernel bug escapes to the host
Outcome, unsafe: Broad kernel exposure
The default profile blocks only around 44 calls.
Allowlist, io_uring blocked, microVM for hostile code
Generated code runs in a microVM
Tens of calls to a guest kernel
measured allowlist
Escape needs a hypervisor bug too
Outcome, safe: Another kernel in the way
Costs about 125 ms per boot and some memory.
Count the surface
A default profile leaves most of the kernel reachable, including the subsystem behind most recent exploits.
- 1
1 · User process
Own address space and credentials
- Threads share everything
- Children inherit handles and environment
Isolation unit
- 2
2 · Debugging
ptrace, debug handles
- Yama levels 0 to 3
- CAP_SYS_PTRACE, SeDebugPrivilege
Reads and writes another process
- 3
3 · System call gate
Copy in once, check at use
- seccomp limits which calls
- LSM and Landlock limit which objects
Where user code meets the kernel
- 4
4 · Kernel
Owns page tables and credentials
- Lockdown keeps root out
- One bug can be a full escape
Shared by every container
- 5
5 · Processor
Modes, MMU, speculation
- SMEP and SMAP
- KPTI against Meltdown
Isolation depends on it too
Doors between processes
Debugging and shared memory are deliberate openings, each with its own lock.
Worked example: a model loader
A model-serving process loads third-party model files with a loader that can execute embedded code. If the loader runs inside the server process, that code gets the server's address space, credentials and network: every secret it holds, every database it can reach. Moving the loader into a separate process, running as an unprivileged user with no network, no secrets and a seccomp allowlist, means a malicious model gets a process that can do almost nothing. The kernel boundary is the same; what changed is which process sits on each side of it.
Misconceptions interviewers listen for
- "Admin or root to kernel is a sandbox escape." On Windows, Microsoft says administrator to kernel is not a security boundary.
- "seccomp can control which files a process opens." It cannot read pointed-to memory; path policy belongs to an LSM or Landlock.
- "Threads are isolated from each other." They share one address space.
- "Page tables alone guarantee isolation." Meltdown read kernel memory across them until KPTI.
- "A container is a lightweight VM." It shares the host kernel.
What if · interview follow-ups
Hold your answer as the constraints change
You are reviewing the isolation design of a platform that runs customer-supplied code and AI-generated scripts on shared Linux hosts. The interviewer starts at the system call and moves down.
Start here
What exactly happens when user code makes a system call?
Mode switch, entry point, copy, check.
The program places the call number and arguments in registers and executes the system call instruction. The CPU switches to kernel mode and jumps to an entry point the kernel registered at boot. The kernel copies pointer arguments in once, checks permissions against the process's credentials at the moment of use, does the work and returns. SMEP and SMAP stop the kernel accidentally executing or touching user pages.
Carry it forwardThe kernel copies once and checks at use; hardware guards the boundary.
Weak answerSaying the program calls a kernel function directly.
Constraint changes
What if a second thread changes a buffer while the kernel is reading it?
Double fetch, and its user-space twin.
If the kernel reads the same user memory twice, the value can change between validation and use: a double fetch. The rule is to copy into kernel memory once and validate the copy. The same pattern appears in user space with file paths: open first, relative to a trusted directory handle and without following links, then check and use that same file descriptor.
Carry it forwardValidate and use the same object, never a name that can change.
Weak answerAdding a second check just before use.
Constraint changes
What if an attacker cannot find a kernel bug but can read kernel memory through the CPU?
Speculative execution and page tables.
That was Meltdown: speculative reads of kernel memory mapped into the process leaked through cache timing. KPTI keeps a separate set of page tables for user mode that maps almost none of the kernel, at a cost of about a hundred cycles per switch. Newer processors fixed the behaviour in hardware, but the lesson stands: isolation depends on the CPU too.
Carry it forwardIsolation is only as strong as the hardware beneath it.
Weak answerSaying page tables make cross-boundary reads impossible.
Constraint changes
You must run untrusted code next to secrets on one host. How do you reduce risk?
Shrink reachable kernel, remove secrets.
Apply a measured seccomp allowlist instead of the default profile, block io_uring, set Yama to at least level 1, enable lockdown, and use Landlock or an LSM for file access. Then take the secrets out of reach: not in the process, not in its environment, not in inherited file descriptors. Finally admit that a bug in an allowed system call is still a full escape on a shared kernel.
Carry it forwardReduce the reachable kernel and remove secrets, then name the residual risk.
Weak answerClaiming a seccomp profile makes a shared kernel safe for hostile code.
Constraint changes
What if the requirement becomes hostile code from thousands of tenants?
Put another kernel in the way.
Use a separate kernel per tenant or session: a user-space kernel such as gVisor or a microVM behind a hypervisor. Accept boot times around 125 ms and memory overhead, keep the per-session environment free of credentials, and include shared devices such as GPUs in the threat model, since they can cross the boundary the VM creates.
Carry it forwardFor hostile multi-tenant code, isolate with a separate kernel.
Weak answerRelying on containers with a stricter profile.
Now defend it
As the defender, what would you monitor on these hosts to spot isolation attacks?
seccomp violations and denied system calls by workload, attempts to use blocked subsystems such as io_uring, ptrace attempts and Yama denials, unexpected kernel module loads or lockdown denials, processes gaining capabilities or changing credentials, and crashes or oopses in kernel logs that can accompany exploitation attempts.
How this shows up in interviews
"What separates kernel space from user space?" and "What happens on a system call?" open many systems interviews, and the follow-ups go straight to containers and sandboxes. Interviewers listen for who enforces the boundary (the CPU and the kernel's page tables), what the kernel must distrust (arguments, especially pointers), and why a shared kernel matters.
Say it in 30 seconds: "The CPU runs apps unprivileged and the kernel privileged. Each process sees memory through page tables only the kernel controls, so other processes' memory isn't addressable. Apps reach privileged operations through system calls, where the kernel validates every argument, especially pointers, and checks credentials. Debuggers, shared memory and IPC are deliberate doors with their own checks. A kernel bug breaks everything at once, which is why containers sharing a kernel are weaker than VMs, and why we use seccomp and gVisor or microVMs for untrusted code."
Check yourself
Say or write your answer first, then reveal. Explaining it from memory, in your own words, is the skill an interviewer is checking.
1Why must the kernel copy a user-supplied structure once and then validate the copy, rather than validating it in place?
2Two processes run as the same user. Can one read the other's memory?
3What did Google's 2023 kCTF data show about io_uring, and what did Google do?
4Why can a seccomp filter not enforce which files a process opens?