Skip to content
Merged
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
66 changes: 66 additions & 0 deletions .github/workflows/smoke-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: smoke-linux

# Runtime smoke test for the SHS C seam. Unlike build-linux (compile-only), this
# actually runs a short kofta-fuzz campaign and asserts the seam fires at
# runtime: afl-fuzz launches the `kofta-shs serve` co-process, streams NDJSON,
# and gets candidates back (a "shs_cand,..." line in the KOFTA_DEBUG log).
#
# Runs in an Ubuntu 20.04 container (glibc 2.31) on the native x86_64 runner.
# The OS version is load-bearing: KOFTA's __args_leak relies on glibc <=2.33's
# startup stack layout (see the `container:` note below), so 22.04/glibc 2.35
# breaks the forkserver. 20.04 ships clang-12, satisfying both KOFTA's legacy
# LLVM-pass requirement and the old-glibc requirement. run-smoke.sh builds in
# place via SMOKE_BUILD and asserts the SHS serve co-process fires end-to-end.

on:
push:
paths:
- "**.c"
- "**.h"
- "llvm_mode/**"
- "Makefile"
- "docker/**"
- "kofta-shs"
- "shs/**"
- ".github/workflows/smoke-linux.yml"
pull_request:
paths:
- "**.c"
- "**.h"
- "llvm_mode/**"
- "Makefile"
- "docker/**"
- "kofta-shs"
- "shs/**"
- ".github/workflows/smoke-linux.yml"

jobs:
smoke:
runs-on: ubuntu-latest
# KOFTA's __args_leak (llvm_mode/kofta-llvm-rt.o.c) grabs argv/argc with a
# hardcoded stack offset ("lea 0x50(%rsp)", +0xc) tuned for glibc <=2.33's
# __libc_csu_init startup. glibc 2.34 (Ubuntu 22.04) refactored startup, so
# the offset points at garbage there and the forkserver dies on the dry run.
# Run inside an Ubuntu 20.04 container (glibc 2.31) -- the toolchain the
# KOFTA authors developed on -- where the offset is valid.
container: ubuntu:20.04
steps:
- uses: actions/checkout@v4

- name: Install clang/llvm 12 + tooling
env:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get update
apt-get install -y --no-install-recommends \
clang-12 llvm-12-dev make libc6-dev python3 ca-certificates

# Build in place (SMOKE_BUILD == checkout), scratch in the runner temp.
# The script asserts a "shs_cand" line and exits non-zero if the seam
# never fired, so a green job means the serve co-process really ran.
- name: Run SHS runtime smoke test
run: |
SMOKE_REPO="$GITHUB_WORKSPACE" \
SMOKE_BUILD="$GITHUB_WORKSPACE" \
SMOKE_WORK="$RUNNER_TEMP/smoke-work" \
bash docker/run-smoke.sh
19 changes: 12 additions & 7 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# Local runtime verification image for the SHS C path.
#
# clang/llvm 12 is mandatory: KOFTA's LLVM passes register via the legacy pass
# manager (RegisterStandardPasses) and use the single-arg CreateLoad overload.
# clang 13+ defaults to the new PM and silently skips our instrumentation;
# LLVM 14 dropped the single-arg CreateLoad. Ubuntu 22.04 ships clang-12.
# KOFTA's runtime (kofta-llvm-rt.o.c) leaks argv/argc via hardcoded x86_64
# inline asm ("lea 0x50(%rsp)"), so this image must run on an x86_64 Docker
# Ubuntu 20.04 is load-bearing (not just for clang-12). KOFTA's runtime
# (kofta-llvm-rt.o.c) leaks argv/argc via a hardcoded stack offset
# ("lea 0x50(%rsp)", +0xc) tuned for glibc <=2.33's __libc_csu_init startup.
# glibc 2.34 (Ubuntu 22.04) refactored startup, so on 22.04 the offset points
# at garbage and the forkserver dies on the dry run before any fuzzing happens.
# 20.04 ships glibc 2.31 (offset valid) AND clang-12, which is also mandatory:
# KOFTA's LLVM passes register via the legacy pass manager (RegisterStandard-
# Passes) and use the single-arg CreateLoad overload; clang 13+ defaults to the
# new PM and silently skips our instrumentation, and LLVM 14 dropped single-arg
# CreateLoad.
# The argv-leak asm is x86_64-only, so this image must run on an x86_64 Docker
# host. On Apple Silicon, start Colima as an x86_64 VM (see docker/smoke.sh):
# colima start --arch x86_64
# We deliberately do NOT use `FROM --platform=...` -- that needs BuildKit/buildx
# for cross-builds, which the brew `docker` CLI doesn't ship. A native x86_64 VM
# builds this natively with the classic builder instead.
FROM ubuntu:22.04
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
Expand Down
41 changes: 28 additions & 13 deletions docker/run-smoke.sh
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
#!/usr/bin/env bash
# Runs inside the container (see docker/Dockerfile CMD). Builds KOFTA from the
# read-only /repo bind mount, compiles the tiny instrumented target, runs a
# short kofta-fuzz campaign with the SHS C seam launching one long-lived
# `kofta-shs serve` co-process (NDJSON over a pipe) against the offline --mock
# client, and asserts that the seam actually fired.
# Builds KOFTA, compiles the tiny instrumented target, runs a short kofta-fuzz
# campaign with the SHS C seam launching one long-lived `kofta-shs serve`
# co-process (NDJSON over a pipe) against the offline --mock client, and asserts
# that the seam actually fired.
#
# Two ways to run, both on a native x86_64 Linux host (KOFTA's runtime has
# x86_64-only argv-leak asm; arm64 emulation breaks the forkserver):
# * In the verification container (docker/Dockerfile CMD): /repo is a
# read-only bind mount, so it is copied into a writable $BUILD first.
# * Directly on a CI runner (ubuntu-22.04 is native x86_64 + clang-12): point
# SMOKE_BUILD at the already-writable checkout and the copy is skipped.
#
# Overridable via env (defaults match the container layout):
# SMOKE_REPO source tree (default /repo)
# SMOKE_BUILD writable build dir (default /build; set == repo to build in place)
# SMOKE_WORK scratch dir for the run (default /work)
#
# PASS criterion: the KOFTA_DEBUG log contains at least one "shs_cand,..." line,
# proving afl-fuzz.c queried kofta-shs and got candidates back. Finding the
# planted crash is a bonus (printed but not required, since fork-timing varies).
set -euo pipefail

REPO=/repo
BUILD=/build
WORK=/work
REPO="${SMOKE_REPO:-/repo}"
BUILD="${SMOKE_BUILD:-/build}"
WORK="${SMOKE_WORK:-/work}"

echo "==> copying repo (read-only mount) into writable $BUILD"
rm -rf "$BUILD"
cp -r "$REPO" "$BUILD"
if [ "$BUILD" != "$REPO" ]; then
echo "==> copying repo ($REPO) into writable $BUILD"
rm -rf "$BUILD"
cp -r "$REPO" "$BUILD"
else
echo "==> building in place at $BUILD (no copy)"
fi
cd "$BUILD"

echo "==> building afl-fuzz (KOFTA_DEBUG=1)"
# AFL_NO_X86 skips the legacy GCC-mode x86 assembly self-test; the container is
# arm64 and we only use the arch-independent llvm_mode (afl-clang-fast) path.
# AFL_NO_X86 skips the legacy GCC-mode x86 assembly self-test; we only use the
# llvm_mode (afl-clang-fast) path, so the gcc-mode self-test is irrelevant here.
make clean >/dev/null
AFL_NO_X86=1 make CC=clang-12 KOFTA_DEBUG=1

Expand Down
Loading