avatar🌌
TangentTangent Blog

技术博客 | Go & Kubernetes & Linux

技术博客 | Go & Kubernetes & Linux

Reading runc Source Code (Part 8): Core OCI Spec Data Structures (Part 2) — Linux Platform Configuration

Introduction ​

In the previous article, we analyzed the container’s top-level architecture Spec and the Process environment within the OCI specification. However, what truly gives Linux containers their isolation magic is the Linux field encapsulated within the Spec.

This Linux struct is arguably the core and the most technically profound part of the entire config.go. It perfectly maps the low-level features provided by the Linux kernel (Namespaces, Cgroups, Seccomp, Sysctls, etc.) into structured JSON data. This article focuses on dismantling this part to see how containers interface with Linux kernel features.

The Core Structure: Linux ​

In vendor/github.com/opencontainers/runtime-spec/specs-go/config.go, the Linux struct is defined as follows (categorized and slightly condensed for readability):

go
type Linux struct {
	// --- 1. Namespaces and Identity Mapping ---
	Namespaces  []LinuxNamespace            `json:"namespaces,omitempty"`
	UIDMappings []LinuxIDMapping            `json:"uidMappings,omitempty"`
	GIDMappings []LinuxIDMapping            `json:"gidMappings,omitempty"`
	TimeOffsets map[string]LinuxTimeOffset  `json:"timeOffsets,omitempty"`

	// --- 2. Resource Control ---
	CgroupsPath  string              `json:"cgroupsPath,omitempty"`
	Resources    *LinuxResources     `json:"resources,omitempty"`
	IntelRdt     *LinuxIntelRdt      `json:"intelRdt,omitempty"`
	MemoryPolicy *LinuxMemoryPolicy  `json:"memoryPolicy,omitempty"`

	// --- 3. Security and Syscalls ---
	Seccomp     *LinuxSeccomp     `json:"seccomp,omitempty"`
	Personality *LinuxPersonality `json:"personality,omitempty"`

	// --- 4. Filesystem and Devices ---
	RootfsPropagation string                      `json:"rootfsPropagation,omitempty"`
	MaskedPaths       []string                    `json:"maskedPaths,omitempty"`
	ReadonlyPaths     []string                    `json:"readonlyPaths,omitempty"`
	Devices           []LinuxDevice               `json:"devices,omitempty"`
	NetDevices        map[string]LinuxNetDevice   `json:"netDevices,omitempty"`

	// --- 5. Kernel Parameter Tuning ---
	Sysctl map[string]string `json:"sysctl,omitempty"`
}

Next, we will conduct an in-depth analysis of the key fields within these categories.

1. Namespaces and Identity Mapping ​

This is the foundation of a container’s "view isolation".

Namespaces ​

go
type LinuxNamespace struct {
	Type LinuxNamespaceType `json:"type"`
	Path string             `json:"path,omitempty"`
}

Namespaces is an array defining the namespaces (like pid, network, mount, uts, ipc, cgroup) the container needs to create or join. Pay special attention to the Path field. If left empty, runc will call unshare to create a brand-new Namespace. If a specific path is provided (e.g., /proc/1234/ns/net), runc will call setns to make the container join an already existing Namespace. This is the exact underlying implementation principle of how all containers within a Kubernetes Pod share the same network stack!

UIDMappings and GIDMappings ​

go
type LinuxIDMapping struct {
	ContainerID uint32 `json:"containerID"`
	HostID      uint32 `json:"hostID"`
	Size        uint32 `json:"size"`
}

This is only effective when the User Namespace is enabled. It defines the mapping relationship between User IDs inside and outside the container. For example: ContainerID=0, HostID=100000, Size=65536 indicates mapping the root (0) inside the container to a normal user (100000) on the host, thereby completely eliminating the privilege escalation risk caused by container escapes (the core of Rootless containers).

TimeOffsets (Newer Feature) ​

Supports the Time Namespace, allowing the container to have a different system time from the host (for instance, to simulate a past point in time to test an application) without affecting the host’s actual clock.

2. Kernel Parameter Tuning (Sysctl) ​

go
Sysctl map[string]string `json:"sysctl,omitempty"`

This is a key-value dictionary corresponding to the kernel parameters under Linux’s /proc/sys. In a containerized environment, certain kernel parameters (especially those related to the network stack, like net.ipv4.ip_forward or net.core.somaxconn) are isolated per Network Namespace. This means modifying them inside the container is safe and will not affect the host. Through this field, users can independently tune kernel parameters for each container without needing to execute sysctl -w in application startup scripts.

3. Security Config: Filesystem Masking (MaskedPaths / ReadonlyPaths) ​

By default, since the container shares the host kernel, the host’s /proc and /sys directories can leak a massive amount of low-level hardware, kernel-state information, and even cryptographic keys to processes.

  • MaskedPaths []string: The specified paths will be "masked" inside the container. runc typically achieves this by mounting an empty read-only file or directory (like /dev/null) over these paths, preventing processes inside the container from reading their actual contents. Common masked paths include /proc/kcore (kernel physical memory image) and /proc/timer_list.
  • ReadonlyPaths []string: The specified paths will be remounted in read-only (ro) mode (via Bind Mount) to prevent processes inside the container from modifying system-level configurations. Typical examples are /proc/sys and /proc/sysrq-trigger.

We already witnessed these two arrays in the default config.json generated via runc spec in the previous article. They are vital defense lines for containers against kernel-state attacks.

4. Execution Domain (Personality) ​

go
type LinuxPersonality struct {
	Domain LinuxPersonalityDomain `json:"domain"`
	Flags  []string               `json:"flags,omitempty"`
}

This is a very low-level feature corresponding to the Linux personality(2) system call. It allows a process to alter its "execution domain". The most common use case is providing a 32-bit (i386) execution environment view for a container running on a 64-bit (x86_64) kernel, which is incredibly useful for maintaining compatibility with legacy statically compiled programs.

Conclusion ​

Within the Linux struct of config.go, we observe how the OCI specification meticulously manages a container’s "kernel vision". From how it joins the network stack to independent kernel parameter configuration, and the masking of sensitive kernel filesystems, every configuration maps to a classic Linux system programming concept.

Attentive readers may have noticed that we intentionally skipped the extremely massive Resources (Cgroups resource groups) and Seccomp (system call filtering) fields within the Linux struct. Due to the complexity and importance of the data structures they contain, we will dedicate the next article (Part 3) to deeply deconstructing them!

Reading runc Source Code (Part 9): Core OCI Spec Data Structures (Part 3) — LinuxResources and LinuxSeccomp
Reading runc Source Code (Part 6): Runtime, Init Process and Actions in SPEC.md
由 Valaxy v1.2.2 驱动|主题-Yunv1.2.2