logo

gVisor vs Normal Containers

To understand why gVisor behaves differently than a "normal" container (like Docker/runc), you have to look at where the Kernel is.

The Fundamental Difference

  • Normal Container (runc): A container is just a regular process on the host, restricted by Linux Namespaces and Cgroups. It shares the Host Kernel.
  • gVisor (runsc): gVisor is an Application Kernel. It provides an isolation layer that sits between the application and the host. The application thinks it’s talking to a Linux kernel, but it’s actually talking to gVisor’s "Sentry."

Why PIDs are not visible on the host

In a normal container, when you run a process (e.g., Python), the Host Kernel is the one scheduling it. If you run ps -ef on the host, you will see the Python process because the host kernel assigned it a PID.

In gVisor, the architecture is different:

  1. The Sentry: This is the heart of gVisor (written in Go). It acts as a guest kernel.
  2. The Application: The application (e.g., Python) runs inside the Sentry.
  3. The Host's View: To the host kernel, the only thing running is the Sentry process (usually seen as runsc).

PID Mapping: Virtual PID (vnr) vs. Host PID (nr)

To understand the difference in how PIDs are handled, we have to look at the relationship between the vnr (Virtual PID Number inside the container) and the nr (Real PID Number on the host).

In Standard Containers: The "Masking" Model

In a standard Linux container (like Docker), the kernel uses PID Namespaces. This is essentially a translation table.

  • How it works: When you start a process in a container, the host kernel creates a single process entry in its system-wide task list. It then assigns that process two different labels.
  • The Mapping: For example, the kernel sees a process and says: "In the global view, you are PID 5021 (the nr), but to anyone inside this specific container namespace, you appear as PID 1 (the vnr)."
  • The Link: Because the host kernel is the one managing the memory, CPU scheduling, and state of that process, there is a 1
    hard link
    between the virtual PID and the host PID. If you kill PID 5021 on the host, "PID 1" inside the container vanishes instantly because they are the exact same entity.

In gVisor: The "Abstraction" Model

gVisor completely breaks this link. The processes inside gVisor do not exist as distinct entities on the host kernel.

  • The Sentry as an Umbrella: On the host, you will see one (or a few) processes representing the Sentry. This Sentry process has its own host PID (e.g., PID 8000).
  • Internal "Ghost" Processes: Inside the Sentry, you might run a web server, a database, and a shell. The Sentry’s internal Go-based scheduler assigns them vnr values (PID 1, PID 2, PID 3).
  • No Mapping: Unlike the standard container, there is no corresponding "nr" on the host for the web server or the database. If you look at the host’s process list, you will not find a "PID 8001" or "8002" that corresponds to those internal tasks.
  • Technical Implementation: The Sentry manages its own "task list" in its own memory space. When an application inside the sandbox calls getpid(), the Sentry doesn't ask the host kernel for an answer. It simply looks at its internal Go struct and returns the value it invented.

Why this matters for Security

  1. Exploit Mitigation: In a standard container, if an attacker can "leak" information from the kernel, they might discover their real nr (host PID). Knowing the host PID is often the first step in a "container breakout" attack. In gVisor, there is no host PID to discover; the process effectively doesn't exist to the host.
  2. Side-Channel Defense: By decoupling the PIDs, gVisor prevents attackers from using process-related side-channels (like timing attacks or resource counting) to figure out what else is running on the host. In gVisor, the "world" ends at the Sentry’s edge.

Why Inodes (INO) are different/invisible

In a normal container, the file system is usually an overlay (like OverlayFS) sitting on the host. When an app inside a container creates a file, the host kernel assigns a physical inode on the disk. If you check the inode number inside the container and on the host, they often match (or are directly mapped).

In gVisor, file system access is strictly isolated using a companion process called the Gofer.

  1. The Gofer: When the application wants to open a file, it asks the Sentry. The Sentry doesn't have permission to touch the disk, so it asks the "Gofer" process via a secure protocol (9P or LISAFS).
  2. Virtual VFS: The Sentry maintains its own Virtual File System (VFS) in memory.
  3. Inode Virtualization: The Sentry generates virtual inode numbers for the application. These numbers are created by the Sentry's Go code and exist only in the Sentry's RAM.

**Technical reason for Inode differences: ** The Sentry intercepts the stat syscall. Instead of passing the host's actual inode number back to the application (which could be used for "side-channel" attacks to learn about the host's disk structure), gVisor returns a virtualized inode. The real inode on the host is hidden behind the Gofer and the Sentry’s VFS abstraction.

How Syscalls are handled (The "How")

The reason gVisor can hide PIDs and Inodes is that it intercepts every single syscall the application makes.

  • In a normal container: The application makes a syscall (e.g., getpid()). The CPU switches to kernel mode, and the Host Kernel answers.
  • In gVisor: The application makes a syscall. gVisor uses a platform (like systrap or KVM) to catch that syscall before it ever reaches the host kernel.
    • If the app calls getpid(), the Sentry catches it, looks at its own internal Go-based table, and returns "1".
    • The host kernel never even knows the getpid call happened.

Summary Comparison

Feature Normal Container (runc) gVisor (runsc)
Kernel Shared Host Kernel "Sentry" (Guest Kernel in Go)
PID Visibility Host sees every process inside. Host sees only runsc (the Sentry).
Inode Numbers Pass-through from host/overlay. Virtualized by Sentry's VFS.
Syscall Path App \to Host Kernel App \to Sentry \to Host Kernel
Security Strategy Restrict access (Namespaces). Replace the interface (Sandbox).

In short: gVisor is essentially a "Virtual Machine" that looks and feels like a container, but because it brings its own kernel (the Sentry), it has its own private "reality" for PIDs and Inodes that the host cannot see.