isolationprobe

package
v0.0.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package isolationprobe verifies what a sandbox runtime is from what the kernel serving a workload says about itself (Task 20439).

executor.IsVirtualizedRuntime and IsUserspaceKernelRuntime read a runtime's *name*, and placement honoured a project's demand for a virtual machine or a userspace kernel on that name alone. A daemon.json alias, or a RuntimeClass whose handler is runc, ran such a project on the host kernel with nothing to say so. This package is the half that looks: the probe a sandbox runs under the runtime it claims, and the rules that turn what the probe saw into a verdict.

The probe

`cloop isolation-probe` reads, from inside the sandbox, what only the kernel serving it can answer: uname(2), /proc/version, the boot ID that kernel drew when it booted, its uptime, the CPU's hypervisor flag, the machine's DMI vendor and product, and gVisor's markers — its fixed kernel build and the boot log only the Sentry writes. The binary is the hub's or the agent's own, bound into the sandbox read-only, so an image cannot substitute a probe that says what it likes: the image supplies a root filesystem, and nothing the probe reads comes from it.

The verdict

Classify compares that evidence with the executing host's own kernel, which the hub reads directly for its own container executor and an agent reports for its device. The boot ID settles most cases outright: a runc container reads its host's, because there is only one kernel, while a VM and a gVisor sandbox each draw their own. What a different kernel is — a VM's or gVisor's — is then read from markers neither can hide, and anything else is "unknown", which is never mistaken for isolation.

A hypervisor flag or a cloud vendor's DMI string is not evidence on its own. Both hosts this was built against are virtual machines themselves, so a runc container on either shows both, and only the comparison with the host's own kernel tells that container from a Kata guest.

Index

Constants

View Source
const Arg = "isolation-probe"

Arg is the argument that puts the cloop binary into probe mode: `cloop isolation-probe`. main dispatches on it before anything else in the binary runs, so the probe needs no configuration, no home directory and no writable filesystem — a sandbox gives it none of those.

View Source
const GVisorBuildString = "#1 SMP Sun Jan 10 15:06:54 PST 2016"

GVisorBuildString is the kernel build string gVisor's Sentry reports as uname -v, in every release: the Sentry is not a Linux build and stamps the same one regardless of the release it claims to be.

View Source
const MaxOutput = 64 << 10

MaxOutput bounds how much of a probe's output a reader takes. Evidence is a few hundred bytes; the bound is for a sandbox that prints something else.

View Source
const Schema = 1

Schema is the version of Evidence this build writes. A reader accepts any version it can decode; fields are only ever added.

Variables

View Source
var ErrNoEvidence = errors.New("isolationprobe: the probe printed no evidence")

ErrNoEvidence is returned by Decode when the output holds no probe line.

Functions

func Encode

func Encode(e Evidence) ([]byte, error)

Encode renders evidence as the probe's output line, newline included.

func IsInvocation

func IsInvocation(args []string) bool

IsInvocation reports whether args — os.Args — ask for probe mode: exactly `cloop isolation-probe`, nothing before or after it.

Exact on purpose. The mode is entered before the CLI parses anything, so it must not swallow an invocation meant for a command: anything more than the bare argument goes to the CLI, which knows no such command and says so.

func Main

func Main(stdout, stderr io.Writer) int

Main is probe mode: collect the evidence the kernel gives this process and write it to stdout as one line. It returns the process's exit code.

It touches nothing but the kernel interfaces Collect reads. A sandbox runs it with a read-only root filesystem, no network, no capabilities and an unknown user — and as a binary bound in from outside an image whose own files it never opens.

Types

type Evidence

type Evidence struct {
	// Schema is the version of this struct the writer knew.
	Schema int `json:"schema"`
	// Release, Version and Machine are uname(2)'s fields: "6.8.0-79-generic",
	// "#79-Ubuntu SMP PREEMPT_DYNAMIC Tue Aug 12 14:42:46 UTC 2025", "x86_64".
	Release string `json:"release,omitempty"`
	Version string `json:"version,omitempty"`
	Machine string `json:"machine,omitempty"`
	// ProcVersion is /proc/version, which names the compiler and the builder
	// as well — two kernels can share a release and differ here.
	ProcVersion string `json:"proc_version,omitempty"`
	// BootID is /proc/sys/kernel/random/boot_id: a random UUID a Linux kernel
	// draws once per boot. A container sharing its host's kernel reads the
	// host's; a VM's guest kernel and gVisor's Sentry each draw their own.
	BootID string `json:"boot_id,omitempty"`
	// UptimeSeconds is /proc/uptime's first field: how long the kernel serving
	// the reader has been up. A sandbox's own kernel was booted for it.
	UptimeSeconds float64 `json:"uptime_seconds,omitempty"`
	// Hypervisor reports whether /proc/cpuinfo's flags include "hypervisor",
	// the CPUID bit a hypervisor sets for its guests. A guest of any VM sets
	// it — including a runc container on a host that is itself a cloud VM.
	Hypervisor bool `json:"hypervisor,omitempty"`
	// HypervisorType is /sys/hypervisor/type ("xen") where the kernel has one.
	HypervisorType string `json:"hypervisor_type,omitempty"`
	// DMIVendor and DMIProduct are /sys/class/dmi/id/sys_vendor and
	// product_name: "QEMU" and "Standard PC (Q35 + ICH9, 2009)" in a QEMU
	// guest, "Cloud Hypervisor", or the cloud's name on a cloud VM.
	DMIVendor  string `json:"dmi_vendor,omitempty"`
	DMIProduct string `json:"dmi_product,omitempty"`
	// GVisor names the gVisor markers the probe found: gVisor's fixed kernel
	// build string, a release naming it, a command line naming it, and the
	// boot log only the Sentry writes ("Starting gVisor...").
	GVisor []string `json:"gvisor,omitempty"`
	// Unreadable lists what the probe could not read and why, for the
	// essential fields only — a sandbox that hides its kernel says so.
	Unreadable []string `json:"unreadable,omitempty"`
}

Evidence is what one kernel says about itself: read by the probe from inside a sandbox, or by a hub or an agent about the kernel it runs on.

func Collect

func Collect() Evidence

Collect gathers the evidence the running kernel gives this process.

func Decode

func Decode(out []byte) (Evidence, error)

Decode finds the probe's line in a sandbox's output and returns the evidence on it, sanitized. Only the first MaxOutput bytes are read.

The last probe line wins, and anything around it is ignored: a runtime may write a warning to the same stream, and a probe that cannot run at all — "exec format error", a cloop too old to know the mode — prints none, which the error then quotes so the reader learns which.

func (Evidence) IsZero

func (e Evidence) IsZero() bool

IsZero reports whether the evidence names no kernel at all.

func (Evidence) Kernel

func (e Evidence) Kernel() string

Kernel renders the kernel the evidence names, as uname -rv would.

func (Evidence) Sanitize

func (e Evidence) Sanitize() Evidence

Sanitize bounds every field and drops control characters, so evidence a device or a sandbox wrote cannot carry more than it describes into the database or the dashboard.

type Kind

type Kind string

Kind is what a probe found serving a sandbox's system calls.

const (
	// KindHostKernel: the executing host's own kernel — the sandbox is a
	// container, whatever its runtime is called.
	KindHostKernel Kind = "host-kernel"
	// KindUserspaceKernel: gVisor's Sentry, a kernel implemented in a host
	// process.
	KindUserspaceKernel Kind = "userspace-kernel"
	// KindVM: a guest kernel of the sandbox's own, behind a hypervisor.
	KindVM Kind = "vm"
	// KindUnknown: nothing the probe saw settles it. Never read as isolation.
	KindUnknown Kind = "unknown"
)

func (Kind) Phrase

func (k Kind) Phrase() string

Phrase renders k as the object of "runs on" or "runs in": "the host kernel", "gVisor's userspace kernel", "a virtual machine", "an unidentified kernel".

func (Kind) Valid

func (k Kind) Valid() bool

Valid reports whether k is one of the four kinds.

type Reference

type Reference struct {
	// Kernel is the host's evidence. Nil when there is none.
	Kernel *Evidence `json:"kernel,omitempty"`
	// Source says who read it.
	Source ReferenceSource `json:"source,omitempty"`
	// EngineKernel is the kernel release the container engine reports for its
	// own machine (`docker info`'s KernelVersion), when it was asked. It must
	// match Kernel's release: an engine on another machine — DOCKER_HOST at a
	// remote daemon — runs its containers on a kernel the reference does not
	// describe.
	EngineKernel string `json:"engine_kernel,omitempty"`
}

Reference is the executing host's own kernel, which a probe's evidence is compared with.

type ReferenceSource

type ReferenceSource string

ReferenceSource says where the host's own kernel evidence came from.

const (
	// SourceHub: the hub read its own kernel, for a container engine on the
	// hub's machine.
	SourceHub ReferenceSource = "hub"
	// SourceAgent: a device's agent read the kernel of the machine it runs on.
	SourceAgent ReferenceSource = "agent"
	// SourceClusterNode: a Pod on the same node, under the cluster's default
	// runtime, read the node's kernel. Within the executor Role's verbs a hub
	// cannot read a Node object, so this is how it learns a node's kernel —
	// and it is the one reference that could itself be a sandbox, if the
	// cluster's default runtime is one. Classify's uptime rule is what keeps
	// such a reference from making the host kernel look like a VM.
	SourceClusterNode ReferenceSource = "node"
)

type Status

type Status string

Status is how a runtime's claim stands against the evidence.

const (
	// StatusVerified: the evidence shows at least the isolation claimed.
	StatusVerified Status = "verified"
	// StatusContradicted: the evidence shows less than the claim — a runtime
	// named for a VM serving from the host kernel or from gVisor, one named
	// for gVisor serving from the host kernel.
	StatusContradicted Status = "contradicted"
	// StatusUnverified: nothing settles it — never probed, or an unknown
	// verdict.
	StatusUnverified Status = "unverified"
)

func Judge

func Judge(claim, verdict Kind) Status

Judge compares what a runtime claims with what a probe found.

"At least the isolation claimed" is the rule, so the comparison is not symmetric: a runtime named for gVisor found to be a VM is verified, because a guest kernel keeps syscalls off the host kernel too, while a runtime named for a VM found to be gVisor is contradicted, because there is no hypervisor. A runtime that claims nothing — a plain container — is verified by any verdict but unknown: it promised nothing a verdict could break.

type Verdict

type Verdict struct {
	Kind Kind `json:"kind"`
	// Detail names the evidence in a sentence an operator can act on.
	Detail string `json:"detail"`
}

Verdict is what Classify concluded and the evidence it rests on.

func Classify

func Classify(p Evidence, ref Reference) Verdict

Classify decides what served the probe, from its evidence and the host's.

The order is the argument:

  1. The same boot ID as the host is the same kernel instance. Nothing else produces it, so this wins over every marker — a host kernel someone built and named "gvisor" is still the host's.
  2. Without boot IDs on both sides, the host's exact build (release, build string, /proc/version) is read as the host's kernel. That under-describes a VM booting the host's own kernel image — the safe direction: a refusal an operator can investigate, never isolation that is not there.
  3. gVisor's markers. The Sentry's boot log and build string are not something a Linux kernel writes, so they need no reference.
  4. A kernel other than the host's, with a hypervisor's mark on it — the CPU's hypervisor flag, a hypervisor's DMI vendor, /sys/hypervisor — and younger than the host's kernel is a VM. Only here, once the kernel is known to differ: on its own the flag says only that *some* hypervisor is underneath, and on a cloud VM a runc container sees the cloud's. And only younger: a VM serving a sandbox booted after the machine it runs on, so a "different" kernel that has been up longer than the reference is the reference that is wrong — a cluster whose default runtime is itself a VM hands its reference Pod one, and an agent inside a VM that drives an outer host's engine reads its own guest kernel. Either would otherwise make the outer host kernel look like isolation.
  5. Anything else is unknown.

Nothing here asks how long the reference has been up in absolute terms: a node an autoscaler — or a CI runner — booted minutes ago is still the host, and the boot ID still says so.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL