nestedvirt

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

README

nestedvirt

Host-local detection for KVM guests that have used nested virtualization.

The library reads KVM nested_run counters from debugfs, correlates non-zero counters with /proc, and identifies QEMU guests when their command line contains common libvirt/QEMU fields such as -name guest=... and -uuid. For QEMU processes, it also discovers likely monitor/QMP Unix sockets by joining /proc/<pid>/fd socket inodes with /proc/net/unix.

The normal build includes pure-Go libvirt RPC support. The scanner connects to qemu:///system by default and enriches QEMU findings with libvirt domain identity and OpenStack Nova metadata when the metadata is present. It does not link against libvirt.so.

The command line tool is intended for compute-host triage:

nestedvirt scan
nestedvirt scan --json

To download the latest release, verify the checksum, extract it, and run a scan on a Linux compute host:

curl -fsSL https://raw.githubusercontent.com/vexxhost/nestedvirt/main/scripts/run-latest.sh | bash

Pass scanner flags after bash -s --; a leading flag implies scan:

curl -fsSL https://raw.githubusercontent.com/vexxhost/nestedvirt/main/scripts/run-latest.sh | bash -s -- --json

Set NESTEDVIRT_TAG=v0.1.0 to pin a specific release.

Exit codes:

  • 0: scan completed and no nested virtualization usage was observed
  • 1: scan completed and nested virtualization usage was observed
  • 2: scan failed

API documentation lives in the Go package docs:

https://pkg.go.dev/github.com/vexxhost/nestedvirt

Documentation

Overview

Package nestedvirt detects KVM guests that have used nested virtualization.

The package is intentionally centered on host-local evidence. It reads KVM nested_run counters from debugfs through github.com/vexxhost/debugfs, then correlates non-zero counters with process metadata from /proc through github.com/prometheus/procfs. When the process looks like QEMU, the scanner extracts common libvirt/QEMU identity fields such as the guest name and UUID, and discovers likely QMP monitor sockets by joining the process fd table with /proc/net/unix. It also connects to libvirt through qemu:///system by default using the pure-Go libvirt RPC protocol and enriches findings with domain identity and Nova metadata when available.

A scan reports observed nested virtualization use. It does not prove that a guest permanently requires nested virtualization, but a non-zero counter is a strong signal that disabling nested virtualization may break that workload.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Finding

type Finding struct {
	Process           Process            `json:"process"`
	VM                *VMIdentity        `json:"vm,omitempty"`
	MonitorSockets    []MonitorSocket    `json:"monitor_sockets,omitempty"`
	LibvirtDomain     *LibvirtDomain     `json:"libvirt_domain,omitempty"`
	NestedRunCount    uint64             `json:"nested_run_count"`
	NestedRunCounters []NestedRunCounter `json:"nested_run_counters"`
	Requirement       Requirement        `json:"requirement"`
	Errors            []FindingError     `json:"errors,omitempty"`
}

Finding describes one process whose KVM VM has a non-zero nested_run counter.

type FindingError

type FindingError struct {
	PID       int    `json:"pid"`
	Operation string `json:"operation"`
	Error     string `json:"error"`
}

FindingError records process-inspection errors that did not prevent the scan from reporting the nested_run evidence.

type LibvirtDomain

type LibvirtDomain struct {
	Name         string        `json:"name,omitempty"`
	UUID         string        `json:"uuid,omitempty"`
	NovaMetadata *NovaMetadata `json:"nova_metadata,omitempty"`
}

LibvirtDomain contains identity and metadata read from libvirt for a QEMU domain.

type MonitorSocket

type MonitorSocket struct {
	FD     int    `json:"fd"`
	Inode  uint64 `json:"inode"`
	Path   string `json:"path"`
	Source string `json:"source"`
}

MonitorSocket describes a Unix socket that looks like a QEMU monitor or QMP endpoint for the process.

type NestedRunCounter

type NestedRunCounter struct {
	Path  string `json:"path"`
	Count uint64 `json:"count"`
}

NestedRunCounter identifies one debugfs nested_run counter that contributed to a finding.

type NovaFlavor

type NovaFlavor struct {
	Name         string `json:"name,omitempty"`
	MemoryMiB    string `json:"memory_mib,omitempty"`
	DiskGiB      string `json:"disk_gib,omitempty"`
	SwapMiB      string `json:"swap_mib,omitempty"`
	EphemeralGiB string `json:"ephemeral_gib,omitempty"`
	VCPUs        string `json:"vcpus,omitempty"`
}

NovaFlavor describes flavor fields Nova stores in libvirt metadata.

type NovaIdentity

type NovaIdentity struct {
	UUID string `json:"uuid,omitempty"`
	Name string `json:"name,omitempty"`
}

NovaIdentity contains a Nova user or project display value and UUID.

type NovaMetadata

type NovaMetadata struct {
	Namespace      string      `json:"namespace,omitempty"`
	Name           string      `json:"name,omitempty"`
	Hostname       string      `json:"hostname,omitempty"`
	CreationTime   string      `json:"creation_time,omitempty"`
	PackageVersion string      `json:"package_version,omitempty"`
	Flavor         *NovaFlavor `json:"flavor,omitempty"`
	Owner          *NovaOwner  `json:"owner,omitempty"`
	Root           *NovaRoot   `json:"root,omitempty"`
}

NovaMetadata contains common OpenStack Nova metadata from a libvirt domain.

type NovaOwner

type NovaOwner struct {
	User    NovaIdentity `json:"user,omitempty"`
	Project NovaIdentity `json:"project,omitempty"`
}

NovaOwner describes the user and project that own the server.

type NovaRoot

type NovaRoot struct {
	Type string `json:"type,omitempty"`
	UUID string `json:"uuid,omitempty"`
}

NovaRoot describes the root source recorded by Nova.

type Option

type Option func(*scannerConfig) error

Option configures a Scanner.

func WithClock

func WithClock(now func() time.Time) Option

WithClock configures the timestamp source used in reports.

func WithDebugFS

func WithDebugFS(fs debugfs.FS) Option

WithDebugFS configures the debugfs reader directly.

func WithDebugFSMount

func WithDebugFSMount(mountPoint string) Option

WithDebugFSMount configures the debugfs mount point.

func WithLibvirtURI

func WithLibvirtURI(uri string) Option

WithLibvirtURI configures the libvirt connection URI.

func WithProcFS

func WithProcFS(fs procfs.FS) Option

WithProcFS configures the procfs reader directly.

func WithProcFSMount

func WithProcFSMount(mountPoint string) Option

WithProcFSMount configures the procfs mount point.

type Process

type Process struct {
	PID        int         `json:"pid"`
	Command    string      `json:"command,omitempty"`
	Executable string      `json:"executable,omitempty"`
	Kind       ProcessKind `json:"kind"`
}

Process describes the userspace process attached to a KVM VM.

type ProcessKind

type ProcessKind string

ProcessKind classifies the userspace process attached to a KVM VM.

const (
	// ProcessKindUnknown means the scanner could not read enough process
	// metadata to classify the process.
	ProcessKindUnknown ProcessKind = "unknown"

	// ProcessKindQEMU means the process looks like QEMU or qemu-kvm.
	ProcessKindQEMU ProcessKind = "qemu"

	// ProcessKindOther means the process was readable but did not look like
	// QEMU.
	ProcessKindOther ProcessKind = "other"
)

type Report

type Report struct {
	ScannedAt time.Time `json:"scanned_at"`
	Summary   Summary   `json:"summary"`
	Findings  []Finding `json:"findings"`
}

Report is the result of a host scan.

func Scan

func Scan(ctx context.Context, opts ...Option) (Report, error)

Scan creates a default Scanner with opts and runs it.

func (Report) NestedVirtObserved

func (r Report) NestedVirtObserved() bool

NestedVirtObserved reports whether any process has a non-zero nested_run counter.

type Requirement

type Requirement string

Requirement describes whether a workload should be treated as requiring nested virtualization.

const (
	// RequirementUnknown is used because a nested_run counter only proves prior
	// use, not a durable contractual requirement.
	RequirementUnknown Requirement = "unknown"
)

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner correlates KVM debugfs counters with process metadata.

func NewScanner

func NewScanner(opts ...Option) (*Scanner, error)

NewScanner creates a Scanner. By default it reads /sys/kernel/debug and /proc.

func (*Scanner) Scan

func (s *Scanner) Scan(ctx context.Context) (Report, error)

Scan reads KVM nested_run counters and returns processes with observed nested virtualization use.

type Summary

type Summary struct {
	NestedRunCounters   int  `json:"nested_run_counters"`
	ObservedProcesses   int  `json:"observed_processes"`
	QEMUProcesses       int  `json:"qemu_processes"`
	UnknownProcesses    int  `json:"unknown_processes"`
	MonitorSockets      int  `json:"monitor_sockets"`
	LibvirtDomains      int  `json:"libvirt_domains"`
	LibvirtNovaMetadata int  `json:"libvirt_nova_metadata"`
	NestedVirtObserved  bool `json:"nested_virt_observed"`
}

Summary contains aggregate scan counts.

type VMIdentity

type VMIdentity struct {
	Name    string   `json:"name,omitempty"`
	UUID    string   `json:"uuid,omitempty"`
	Sources []string `json:"sources,omitempty"`
}

VMIdentity contains VM identity discovered from a QEMU command line.

Directories

Path Synopsis
cmd
nestedvirt command
internal
cli

Jump to

Keyboard shortcuts

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