fivenines
6/40

Guided Problem

MiniDock Build 04: Contain an Untrusted Workload

Time
25m
Level
intermediate
Artifacts
not specified
Progress0%
Lesson 6 · Foundations

The Security Sandwich

Design claim: Shared-kernel isolation needs independent authorization layers because no single filter covers identity, system calls, and object access.

Starting model

  • You can protect latency-sensitive workloads from noisy neighbors within one host budget.
  • The useful vocabulary at this point is deliberately small: capability, seccomp, LSM, AppArmor.

A shared kernel is a shared attack surface

A third-party workload must perform ordinary application work without gaining the host powers normally associated with root. Begin with the guarantee visible to a workload or operator. Kernel machinery matters only after a simpler arrangement can no longer supply that guarantee.

The tempting shortcut is straightforward: trust namespace isolation or the container's root identity as the security boundary. The shortcut is plausible on a quiet development host, where its missing boundary has not yet been tested. The process still invokes the host kernel, and namespace isolation alone does not decide which privileged operations, calls, paths, or labels are permitted. The constraint reveals which responsibility must move before the design can survive isolation or process failure.

Decompose authority before filtering it

Modern kernels split root's power into ~40 capabilities (bind low ports, load kernel modules, change file ownership…). A container engine drops most by default and keeps a small safe set — a containerized "root" that can chown its own files but cannot load a kernel module. Below that sits seccomp, a per-process allowlist over the ~350 syscalls the kernel exposes; the default profile blocks the dangerous tail (kernel keyring, raw clock manipulation, obscure module calls). Around everything, an LSM (AppArmor/SELinux) enforces path- and label-based rules. Three independent layers: an attacker must get through all of them.

Reduced capabilities, seccomp filtering, and an LSM policy independently narrow privilege, syscall surface, and object access. The named mechanism is the consequence of that separation: one guard owns a policy decision, another enforces a separate decision, and callers receive a stable promise.

Read the topology as a map of authority. Durable knowledge, transient setup, and kernel enforcement should not blur into one box.

Architecture — filters between the container and the kernel
  flowchart TB
    P["container process
('root' inside)"] CAP["capability check
is this power in the kept set?"] SEC["seccomp filter
is this syscall on the allowlist?"] LSM["LSM policy (AppArmor/SELinux)
may this process touch this path?"] K["kernel executes the call"] P --> SEC --> CAP --> LSM --> K ENG["engine
writes all three policies at create time"] -. configures .-> SEC ENG -. configures .-> CAP ENG -. configures .-> LSM

It stacks identity, syscall, and object-policy guards around the process. Its central claim is that compromise of one guard does not silently grant the authority controlled by another guard; the labels therefore describe authority rather than decorative grouping.

Reject dangerous work at the earliest responsible gate

The layers are boring by design — each just answers allow/deny. Here a compromised container tries to load a kernel module, the classic escape route:

A system call first encounters syscall policy, then kernel privilege checks, then object policy; an early denial prevents later work. Trace the order carefully; each step establishes a fact the next step is entitled to use.

The second figure tests the same model in motion: it contrasts an allowed file operation with a module-loading attempt denied before kernel work.

Sequence — an escape attempt dies at two gates
  sequenceDiagram
    participant A as Attacker (root in container)
    participant S as seccomp
    participant C as Capability check
    participant K as Kernel
    A->>S: syscall: init_module(evil.ko)
    alt syscall not in allowlist
      S-->>A: EPERM — blocked before the kernel logic runs
    else allowlisted (custom profile)
      S->>C: pass through
      C-->>A: EPERM — CAP_SYS_MODULE was dropped
    end
    note over A,K: two independent layers must both fail
for the call to reach kernel logic

It contrasts an allowed file operation with a module-loading attempt denied before kernel work. The ordering is valid only when it continues to preserve the stated invariant under retries and interruption.

Defense in depth must remain independent

The workload attempts to load a kernel module after compromising its application process, so a cheap independent guard must refuse it. A sound boundary either preserves its promise or refuses the operation where the promise becomes impossible.

Compromise of one guard does not silently grant the authority controlled by another guard. This rule survives changes in implementation names because it describes ownership rather than a particular process tree.

Wrap MiniDock's execution boundary

MiniDock wraps each workload in multiple kernel-enforced decisions instead of treating container root as host root. MiniDock needs the property now, but its learner must still decide where the responsibility belongs.

What carries forward

  • Compromise of one guard does not silently grant the authority controlled by another guard.
  • Trace allowed and prohibited operations through independent runtime security guards.
  • The rejected shortcut remains a diagnostic: if the design starts depending on it again, the original constraint has probably been lost.
Next step

See what actually stuck.

Take the practice scenarios now.