From d5d19d698898eb303e594d43c821d339a5515441 Mon Sep 17 00:00:00 2001 From: Shih-Kun Huang Date: Sun, 31 May 2026 17:29:43 +0800 Subject: [PATCH 1/3] ci: run SHS runtime smoke test natively on x86_64 ubuntu-22.04 build-linux only compile-checks; nothing exercised the runtime path end-to-end. GitHub's ubuntu-22.04 runner is native x86_64 + clang-12 -- exactly what docker/run-smoke.sh's container emulates -- so run the script directly there (no Docker) and assert the SHS serve co-process fires (a "shs_cand" line in the KOFTA_DEBUG log). Make run-smoke.sh reusable outside the container via SMOKE_REPO/BUILD/ WORK env overrides: when BUILD == REPO it builds in place and skips the read-only-mount copy. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/smoke-linux.yml | 55 +++++++++++++++++++++++++++++++ docker/run-smoke.sh | 41 +++++++++++++++-------- 2 files changed, 83 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/smoke-linux.yml diff --git a/.github/workflows/smoke-linux.yml b/.github/workflows/smoke-linux.yml new file mode 100644 index 0000000..1cd7938 --- /dev/null +++ b/.github/workflows/smoke-linux.yml @@ -0,0 +1,55 @@ +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). +# +# GitHub's ubuntu-22.04 runner is native x86_64 + ships clang-12, which is +# exactly what docker/run-smoke.sh's container emulates -- so we run the script +# directly here (no Docker), pointing it at the checkout via SMOKE_BUILD. This +# is the only place the runtime path is exercised end-to-end; the local +# docker/smoke.sh wrapper is for developers on an x86_64 host. + +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-22.04 + steps: + - uses: actions/checkout@v4 + + - name: Install clang/llvm 12 + run: | + sudo apt-get update + sudo apt-get install -y clang-12 llvm-12-dev + + # 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 diff --git a/docker/run-smoke.sh b/docker/run-smoke.sh index 68fb532..842e68d 100755 --- a/docker/run-smoke.sh +++ b/docker/run-smoke.sh @@ -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 From f455e69d2d598444ec588d27d39e4c5476c1ab4d Mon Sep 17 00:00:00 2001 From: Shih-Kun Huang Date: Sun, 31 May 2026 17:49:28 +0800 Subject: [PATCH 2/3] ci(smoke): run in ubuntu:20.04 container for glibc <=2.33 startup layout The native ubuntu-22.04 run aborted on the dry run: KOFTA's __args_leak reads argv/argc at a hardcoded stack offset (lea 0x50(%rsp), +0xc) tuned for glibc <=2.33's __libc_csu_init. glibc 2.34 (22.04) refactored startup, so the offset is wrong and the forkserver dies before any SHS code runs. Run inside an ubuntu:20.04 container (glibc 2.31) -- the authors' toolchain -- which also ships clang-12 for the legacy LLVM pass. Co-Authored-By: Claude Opus 4.7 --- .github/workflows/smoke-linux.yml | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/.github/workflows/smoke-linux.yml b/.github/workflows/smoke-linux.yml index 1cd7938..5534210 100644 --- a/.github/workflows/smoke-linux.yml +++ b/.github/workflows/smoke-linux.yml @@ -5,11 +5,12 @@ name: smoke-linux # 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). # -# GitHub's ubuntu-22.04 runner is native x86_64 + ships clang-12, which is -# exactly what docker/run-smoke.sh's container emulates -- so we run the script -# directly here (no Docker), pointing it at the checkout via SMOKE_BUILD. This -# is the only place the runtime path is exercised end-to-end; the local -# docker/smoke.sh wrapper is for developers on an x86_64 host. +# 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: @@ -35,14 +36,24 @@ on: jobs: smoke: - runs-on: ubuntu-22.04 + 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 + - name: Install clang/llvm 12 + tooling + env: + DEBIAN_FRONTEND: noninteractive run: | - sudo apt-get update - sudo apt-get install -y clang-12 llvm-12-dev + 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 From fabec828494745dfe553a440210f3a7305e991f4 Mon Sep 17 00:00:00 2001 From: Shih-Kun Huang Date: Sun, 31 May 2026 17:56:11 +0800 Subject: [PATCH 3/3] fix(docker): pin verification image to ubuntu:20.04 (glibc 2.31) The local smoke image had the same latent bug the CI job hit on 22.04: KOFTA's __args_leak ("lea 0x50(%rsp)", +0xc) is tuned for glibc <=2.33's __libc_csu_init startup. glibc 2.34 (22.04) refactored startup, so the offset points at garbage and the forkserver dies on the dry run. 20.04 ships glibc 2.31 (offset valid) and clang-12, matching the now-CI-proven working environment. Co-Authored-By: Claude Opus 4.7 --- docker/Dockerfile | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 0d99346..5480e83 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -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 \