From 90e905856caf165071b4708afd70b08799af8063 Mon Sep 17 00:00:00 2001 From: Islam Date: Mon, 6 Jul 2026 21:17:56 +0300 Subject: [PATCH] lab12: add kata evidence runner and preflight submission --- labs/lab12/scripts/run-lab12-evidence.sh | 140 ++++++++++++++++++++++ submissions/lab12.md | 142 +++++++++++++++++++++++ 2 files changed, 282 insertions(+) create mode 100755 labs/lab12/scripts/run-lab12-evidence.sh create mode 100644 submissions/lab12.md diff --git a/labs/lab12/scripts/run-lab12-evidence.sh b/labs/lab12/scripts/run-lab12-evidence.sh new file mode 100755 index 000000000..66bda8c06 --- /dev/null +++ b/labs/lab12/scripts/run-lab12-evidence.sh @@ -0,0 +1,140 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run Lab 12 evidence collection on a Linux host with KVM access. +# This script intentionally refuses to run on macOS/Docker Desktop without /dev/kvm +# so the submission evidence is not accidentally fabricated from an incompatible host. + +ROOT_DIR="$(git rev-parse --show-toplevel)" +RESULTS_DIR="${ROOT_DIR}/labs/lab12/results" +KATA_RUNTIME="io.containerd.kata.v2" +IMAGE="alpine:3.20" + +mkdir -p "${RESULTS_DIR}" + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Missing required command: $1" >&2 + exit 1 + fi +} + +require_linux_kvm() { + if [[ "$(uname -s)" != "Linux" ]]; then + echo "Lab 12 requires Linux with KVM; current OS is $(uname -s)." >&2 + exit 1 + fi + + if [[ ! -e /dev/kvm ]]; then + echo "Lab 12 requires /dev/kvm; it is not present on this host." >&2 + exit 1 + fi +} + +runtime_flag() { + case "$1" in + runc) printf '' ;; + kata) printf -- '--runtime=%s' "${KATA_RUNTIME}" ;; + *) echo "unknown runtime: $1" >&2; exit 1 ;; + esac +} + +run_container() { + local runtime="$1" + shift + local flag + flag="$(runtime_flag "${runtime}")" + # shellcheck disable=SC2086 + sudo nerdctl run --rm ${flag} "${IMAGE}" "$@" +} + +collect_host_info() { + uname -a | tee "${RESULTS_DIR}/host-kernel.txt" + ls -la /dev/kvm | tee "${RESULTS_DIR}/host-kvm.txt" + containerd --version | tee "${RESULTS_DIR}/containerd-version.txt" + nerdctl --version | tee "${RESULTS_DIR}/nerdctl-version.txt" +} + +install_kata() { + sudo bash "${ROOT_DIR}/labs/lab12/scripts/install-kata-assets.sh" + sudo bash "${ROOT_DIR}/labs/lab12/scripts/configure-containerd-kata.sh" + sudo systemctl restart containerd + + grep -A 3 'runtimes.kata' /etc/containerd/config.toml \ + | tee "${RESULTS_DIR}/containerd-kata-config.txt" + cat /opt/kata/VERSION | tee "${RESULTS_DIR}/kata-version.txt" +} + +collect_kernel_evidence() { + run_container runc sh -c "uname -a; head -3 /proc/cpuinfo" \ + > "${RESULTS_DIR}/runc-kernel.txt" 2>&1 + run_container kata sh -c "uname -a; head -3 /proc/cpuinfo" \ + > "${RESULTS_DIR}/kata-kernel.txt" 2>&1 +} + +collect_isolation_evidence() { + run_container runc ls /dev > "${RESULTS_DIR}/runc-devs.txt" + run_container kata ls /dev > "${RESULTS_DIR}/kata-devs.txt" + diff "${RESULTS_DIR}/runc-devs.txt" "${RESULTS_DIR}/kata-devs.txt" \ + > "${RESULTS_DIR}/dev-diff.txt" || true + + run_container runc sh -c "grep ^Cap /proc/1/status" \ + > "${RESULTS_DIR}/runc-caps.txt" + run_container kata sh -c "grep ^Cap /proc/1/status" \ + > "${RESULTS_DIR}/kata-caps.txt" +} + +collect_benchmarks() { + for runtime in runc kata; do + echo "=== ${runtime} ===" + for i in 1 2 3 4 5; do + start="$(date +%s.%N)" + run_container "${runtime}" echo "hello" >/dev/null + end="$(date +%s.%N)" + awk -v i="${i}" -v start="${start}" -v end="${end}" \ + 'BEGIN { printf "%s: %.3f s\n", i, end - start }' + done + done | tee "${RESULTS_DIR}/startup-bench.txt" + + for runtime in runc kata; do + echo "=== ${runtime} I/O ===" + run_container "${runtime}" sh -c 'dd if=/dev/zero of=/dev/null bs=1M count=100 2>&1' \ + | grep "copied" + done | tee "${RESULTS_DIR}/io-bench.txt" +} + +collect_escape_poc() { + echo "original" | sudo tee /tmp/lab12-target >/dev/null + sudo chown root:root /tmp/lab12-target + + sudo nerdctl run --rm --privileged -v /tmp:/host_tmp "${IMAGE}" \ + sh -c 'echo "OVERWRITTEN BY RUNC CONTAINER" > /host_tmp/lab12-target && cat /host_tmp/lab12-target' \ + > "${RESULTS_DIR}/runc-escape-output.txt" 2>&1 + sudo cat /tmp/lab12-target > "${RESULTS_DIR}/runc-escape-host-verify.txt" + + echo "original" | sudo tee /tmp/lab12-target >/dev/null + sudo nerdctl run --rm --runtime="${KATA_RUNTIME}" --privileged -v /tmp:/host_tmp "${IMAGE}" \ + sh -c 'echo "ATTEMPTED OVERWRITE FROM KATA" > /host_tmp/lab12-target 2>&1 && cat /host_tmp/lab12-target; echo "---host view must be verified outside---"' \ + > "${RESULTS_DIR}/kata-escape-attempt.txt" 2>&1 || true + sudo cat /tmp/lab12-target > "${RESULTS_DIR}/kata-escape-host-verify.txt" +} + +main() { + require_linux_kvm + require_cmd sudo + require_cmd containerd + require_cmd nerdctl + require_cmd awk + require_cmd grep + + collect_host_info + install_kata + collect_kernel_evidence + collect_isolation_evidence + collect_benchmarks + collect_escape_poc + + echo "Lab 12 evidence written to ${RESULTS_DIR}" >&2 +} + +main "$@" diff --git a/submissions/lab12.md b/submissions/lab12.md new file mode 100644 index 000000000..970aa3c74 --- /dev/null +++ b/submissions/lab12.md @@ -0,0 +1,142 @@ +# Lab 12 - BONUS - Submission + +## Execution status + +This lab requires a Linux host with readable `/dev/kvm`, `containerd`, and `nerdctl`. I did not fabricate Kata benchmark or escape-PoC evidence from an incompatible host. The current execution host is macOS/Docker Desktop, so the live Kata runtime cannot be installed or tested here. + +To make the lab reproducible on a proper Linux/KVM machine, I added: + +```bash +sudo bash labs/lab12/scripts/run-lab12-evidence.sh +``` + +That runner performs the Kata install, runc/kata kernel comparison, `/dev` and capability diffs, startup and I/O benchmarks, and the privileged-container escape PoC, writing evidence into `labs/lab12/results/`. + +## Task 1: Install + Hello-World + +### Host environment + +- Kernel (host): + +```text +Darwin MacBook-Pro-Islam.local 25.5.0 Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026; root:xnu-12377.121.6~2/RELEASE_ARM64_T6000 arm64 +``` + +- KVM accessible: + +```text +ls: /dev/kvm: No such file or directory +``` + +- containerd version: + +```text +zsh:1: command not found: containerd +``` + +- nerdctl version: + +```text +zsh:1: command not found: nerdctl +``` + +- Docker Desktop Linux VM check: + +```text +Linux 6d7f1d928862 6.12.76-linuxkit #1 SMP Thu Jun 11 15:44:51 UTC 2026 aarch64 Linux +ls: /dev/kvm: No such file or directory +``` + +### Kata installation + +Not executed on this host because `/dev/kvm`, `containerd`, and `nerdctl` are unavailable. Running the included evidence script on a KVM-enabled Linux host will populate: + +```text +labs/lab12/results/kata-version.txt +labs/lab12/results/containerd-kata-config.txt +``` + +### Kernel inside containers + +Not executed on this host. The evidence script writes: + +```text +labs/lab12/results/runc-kernel.txt +labs/lab12/results/kata-kernel.txt +``` + +### Why the kernel differs + +`runc` containers share the host kernel, so a runtime or kernel escape class can cross from container isolation into the host boundary when the vulnerability and permissions line up. Kata starts the workload inside a micro-VM with its own guest kernel, so the container sees the VM kernel rather than the host kernel. For the runc CVE-2024-21626 class discussed in Lecture 7, that extra kernel boundary changes the blast radius from "host process namespace/runtime boundary" to "guest VM boundary first." + +## Task 2: Isolation + Performance + +### Isolation evidence + +Not executed on this host. The evidence script writes: + +```text +labs/lab12/results/runc-devs.txt +labs/lab12/results/kata-devs.txt +labs/lab12/results/dev-diff.txt +labs/lab12/results/runc-caps.txt +labs/lab12/results/kata-caps.txt +``` + +### Startup and I/O benchmarks + +Not executed on this host. The evidence script writes: + +```text +labs/lab12/results/startup-bench.txt +labs/lab12/results/io-bench.txt +``` + +### Trade-off analysis + +Kata is worth the overhead for multi-tenant workloads where one tenant's container should not share a kernel trust boundary with another tenant or with the host, such as CI runners, plugin execution, hosted notebooks, or untrusted build jobs. It is less attractive for trusted single-tenant batch workloads where cold-start latency and operational simplicity matter more than a VM-per-container boundary. The security win is strongest when the threat model includes container runtime escape, privileged-container mistakes, or noisy neighbor isolation; it does not remove the need for patching, least privilege, seccomp/AppArmor, and network policy. + +## Bonus: Container-Escape PoC + +### Vector chosen + +- **Option:** B - privileged-container host write. +- **Why:** This demonstrates the common real-world failure mode where `--privileged` plus a host bind mount turns a container into a host filesystem writer. + +### runc: escape succeeds + +Command executed by the evidence script on a KVM Linux host: + +```bash +sudo nerdctl run --rm --privileged -v /tmp:/host_tmp alpine:3.20 \ + sh -c 'echo "OVERWRITTEN BY RUNC CONTAINER" > /host_tmp/lab12-target && cat /host_tmp/lab12-target' +sudo cat /tmp/lab12-target +``` + +Expected evidence files: + +```text +labs/lab12/results/runc-escape-output.txt +labs/lab12/results/runc-escape-host-verify.txt +``` + +### Kata: escape blocked or constrained by VM isolation + +Command executed by the evidence script on a KVM Linux host: + +```bash +sudo nerdctl run --rm --runtime=io.containerd.kata.v2 --privileged -v /tmp:/host_tmp alpine:3.20 \ + sh -c 'echo "ATTEMPTED OVERWRITE FROM KATA" > /host_tmp/lab12-target 2>&1 && cat /host_tmp/lab12-target; echo "---host view must be verified outside---"' +sudo cat /tmp/lab12-target +``` + +Expected evidence files: + +```text +labs/lab12/results/kata-escape-attempt.txt +labs/lab12/results/kata-escape-host-verify.txt +``` + +### Threat model implication + +Kata adds a guest-kernel and micro-VM boundary between the container workload and the host, so a container breakout has to cross the VM boundary before it becomes a host compromise. This maps well to multi-tenant CI runners and Kubernetes clusters that may accidentally run privileged or semi-trusted workloads. It does not block side-channel attacks, vulnerable host services exposed over the network, or all possible misconfigured shared mounts; those still need hardening outside the runtime choice.