Skip to content

perf(payload): shrink x86_64 payload from 1704 to 720 bytes #42

perf(payload): shrink x86_64 payload from 1704 to 720 bytes

perf(payload): shrink x86_64 payload from 1704 to 720 bytes #42

Workflow file for this run

# SPDX-License-Identifier: LGPL-2.1-or-later OR MIT
name: build
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
permissions:
contents: read
jobs:
# GNU toolchain + glibc-static. Compile-check across architectures using
# the same toolchain most distros ship. Artifacts are large (~800 KB) but
# confirm the source builds cleanly under GCC for each target.
build-glibc:
name: glibc / ${{ matrix.target.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- { arch: x86_64, prefix: '', apt: '' }
- { arch: aarch64, prefix: aarch64-linux-gnu, apt: 'gcc-aarch64-linux-gnu linux-libc-dev-arm64-cross libc6-dev-arm64-cross' }
- { arch: armv7, prefix: arm-linux-gnueabihf, apt: 'gcc-arm-linux-gnueabihf linux-libc-dev-armhf-cross libc6-dev-armhf-cross' }
- { arch: riscv64, prefix: riscv64-linux-gnu, apt: 'gcc-riscv64-linux-gnu linux-libc-dev-riscv64-cross libc6-dev-riscv64-cross' }
- { arch: ppc64le, prefix: powerpc64le-linux-gnu, apt: 'gcc-powerpc64le-linux-gnu linux-libc-dev-ppc64el-cross libc6-dev-ppc64el-cross' }
- { arch: s390x, prefix: s390x-linux-gnu, apt: 'gcc-s390x-linux-gnu linux-libc-dev-s390x-cross libc6-dev-s390x-cross' }
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: install cross-toolchain
if: matrix.target.apt != ''
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends ${{ matrix.target.apt }}
- name: build
run: |
if [ -z "${{ matrix.target.prefix }}" ]; then
make
else
make CC=${{ matrix.target.prefix }}-gcc LD=${{ matrix.target.prefix }}-ld
fi
- name: verify
run: |
file payload exploit exploit-passwd vulnerable
echo "payload: $(stat -c%s payload) bytes"
echo "exploit: $(stat -c%s exploit) bytes"
echo "exploit-passwd: $(stat -c%s exploit-passwd) bytes"
echo "vulnerable: $(stat -c%s vulnerable) bytes"
- name: upload binaries
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: copy-fail-c-${{ matrix.target.arch }}-glibc
path: |
exploit
exploit-passwd
vulnerable
retention-days: 90
# zig cc + musl-static. Produces deploy-anywhere binaries with a fully
# static link and no runtime libc version dependency. zig ships musl libc
# for every target; we still need GNU binutils-<arch>-cross for the
# `ld -r -b binary` step that wraps the payload as a relocatable .o,
# plus kernel UAPI headers for nolibc and linux/if_alg.h.
build-musl:
name: musl / ${{ matrix.target.arch }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- { arch: x86_64, zig_target: x86_64-linux-musl, apt: '', ld_prefix: '', uapi_base: /usr/include, uapi_asm: /usr/include/x86_64-linux-gnu/asm }
- { arch: aarch64, zig_target: aarch64-linux-musl, apt: 'binutils-aarch64-linux-gnu linux-libc-dev-arm64-cross', ld_prefix: aarch64-linux-gnu, uapi_base: /usr/aarch64-linux-gnu/include, uapi_asm: /usr/aarch64-linux-gnu/include/asm }
- { arch: armv7, zig_target: arm-linux-musleabihf, apt: 'binutils-arm-linux-gnueabihf linux-libc-dev-armhf-cross', ld_prefix: arm-linux-gnueabihf, uapi_base: /usr/arm-linux-gnueabihf/include, uapi_asm: /usr/arm-linux-gnueabihf/include/asm }
- { arch: riscv64, zig_target: riscv64-linux-musl, apt: 'binutils-riscv64-linux-gnu linux-libc-dev-riscv64-cross', ld_prefix: riscv64-linux-gnu, uapi_base: /usr/riscv64-linux-gnu/include, uapi_asm: /usr/riscv64-linux-gnu/include/asm }
- { arch: ppc64le, zig_target: powerpc64le-linux-musl, apt: 'binutils-powerpc64le-linux-gnu linux-libc-dev-ppc64el-cross', ld_prefix: powerpc64le-linux-gnu, uapi_base: /usr/powerpc64le-linux-gnu/include, uapi_asm: /usr/powerpc64le-linux-gnu/include/asm }
- { arch: s390x, zig_target: s390x-linux-musl, apt: 'binutils-s390x-linux-gnu linux-libc-dev-s390x-cross', ld_prefix: s390x-linux-gnu, uapi_base: /usr/s390x-linux-gnu/include, uapi_asm: /usr/s390x-linux-gnu/include/asm }
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1
with:
version: 0.14.0
use-cache: false
- name: install cross-binutils and UAPI headers
if: matrix.target.apt != ''
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends ${{ matrix.target.apt }}
- name: build
run: |
LD_BIN=ld
if [ -n "${{ matrix.target.ld_prefix }}" ]; then
LD_BIN=${{ matrix.target.ld_prefix }}-ld
fi
make zig-musl-static \
ZIG_TARGET=${{ matrix.target.zig_target }} \
LD="$LD_BIN" \
UAPI_LINUX_DIR=${{ matrix.target.uapi_base }}/linux \
UAPI_ASM_GENERIC_DIR=${{ matrix.target.uapi_base }}/asm-generic \
UAPI_ASM_DIR=${{ matrix.target.uapi_asm }}
- name: strip binaries
env:
LD_PREFIX: ${{ matrix.target.ld_prefix }}
run: |
STRIP=strip
if [ -n "$LD_PREFIX" ]; then
STRIP="${LD_PREFIX}-strip"
fi
for bin in exploit exploit-passwd vulnerable; do
BEFORE=$(stat -c%s "$bin")
"$STRIP" --strip-all "$bin"
AFTER=$(stat -c%s "$bin")
echo "$bin stripped: $BEFORE -> $AFTER bytes"
done
- name: verify
run: |
file payload exploit exploit-passwd vulnerable
echo "payload: $(stat -c%s payload) bytes"
echo "exploit: $(stat -c%s exploit) bytes"
echo "exploit-passwd: $(stat -c%s exploit-passwd) bytes"
echo "vulnerable: $(stat -c%s vulnerable) bytes"
- name: upload binaries
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: copy-fail-c-${{ matrix.target.arch }}-musl
path: |
exploit
exploit-passwd
vulnerable
retention-days: 90
release:
if: startsWith(github.ref, 'refs/tags/v')
needs: [build-glibc, build-musl]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: download all artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts
- name: package release tarballs
run: |
mkdir -p release
stagebase="$(mktemp -d)"
for d in artifacts/copy-fail-c-*; do
base=$(basename "$d")
stage="$stagebase/$base"
mkdir -p "$stage"
cp "$d/exploit" "$d/exploit-passwd" "$d/vulnerable" "$stage/"
cp README.md LICENSE-MIT LICENSE-LGPL "$stage/"
chmod +x "$stage/exploit" "$stage/exploit-passwd" "$stage/vulnerable"
tar -czf "release/${base}.tar.gz" -C "$stagebase" "$base"
done
( cd release && sha256sum *.tar.gz > SHA256SUMS )
ls -la release/
- name: create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ github.ref_name }}
run: |
gh release create "$TAG" \
--title "$TAG" \
--notes-file - release/* <<'EOF'
Cross-platform C port of CVE-2026-31431 (Copy Fail).
Discovery and disclosure: Theori / Xint, https://copy.fail/
## Downloads
Each archive is named `copy-fail-c-<arch>-<libc>.tar.gz` and
contains three statically-linked binaries plus README and LICENSE:
* `exploit`: binary-mutation variant. Mutates a setuid binary's
page cache, then execs it.
* `exploit-passwd`: /etc/passwd UID-flip variant. Mutates four
bytes of /etc/passwd's page cache, then execs `su`. Works where
the binary-mutation route is blocked but has a narrower cashout
surface; see README for details.
* `vulnerable`: non-destructive vulnerability checker. Creates a
local testfile and runs the AF_ALG/splice primitive against
its own page cache to detect kernel susceptibility, without
touching any system file. Runs unprivileged. Exits 100 if
vulnerable, 0 otherwise.
## Build modes
* `-glibc`: GNU cross-toolchain, glibc-static. Larger (~800 KB)
but functionally identical to source-built binaries on a
glibc system.
* `-musl`: zig cc + musl-static. Smaller (~30-60 KB) and
forward-compatible with any glibc version on the target.
Architectures: x86_64, aarch64, armv7, riscv64, ppc64le, s390x.
## Verification
`SHA256SUMS` lists the SHA-256 of every tarball:
sha256sum -c SHA256SUMS
See the [README](https://github.com/tgies/copy-fail-c/blob/main/README.md)
for the kernel-version window of applicability and a description
of the exploit mechanism.
EOF