Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions labs/lab12/scripts/run-lab12-evidence.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"
142 changes: 142 additions & 0 deletions submissions/lab12.md
Original file line number Diff line number Diff line change
@@ -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.