#!/usr/bin/env bash
###############################################################################
# pwnescape-env-check.sh —— PwnEscape(CVE-2026-99999)复现环境一键检查
#
# 检查三类条件:
#   A. 宿主机内核是否未打补丁(不含 ip_check_csum_start,上游 commit 6785011f8b16)—— 唯一硬性门槛
#   B. 复现路径能力:泄露原语只需 /dev/net/tun(tap 注入 / QEMU TCG,无需 KVM);
#      KVM 嵌套是复刻完整 guest->host 逃逸链的保真度选项,不再作为门槛
#   C. 复现工具链与资源(qemu / tcpdump / 内存磁盘)
#
# 用法:
#   sudo bash pwnescape-env-check.sh          # 建议 root 运行,/proc/kallsyms 才可见
#   sudo bash pwnescape-env-check.sh -q       # 安静模式:只输出一行 PEC_RESULT,适合批量
#
# OSS 分发一键执行(先下载再跑,便于校验 sha256):
#   curl -fsSL <OSS_URL> -o /tmp/pec.sh && sudo bash /tmp/pec.sh
#
# 退出码: 0 = 环境就绪   1 = 环境不满足   2 = 补丁状态无法判定,需人工确认
# 安静模式固定输出一行(便于批量 grep):
#   PEC_RESULT: verdict=ready|blocked|unknown reason=... path=.. patched=.. cpuvirt=.. \
#               kvmdev=.. tun=.. qemu=.. kernel=..
# verdict=ready 判定 = 未修补 + /dev/net/tun 可用(与嵌套虚拟化无关);path=kvm 表示可复刻完整链
###############################################################################

set -u

# ---------- 输出辅助 ----------
if [ -t 1 ]; then
  C_G=$'\e[32m'; C_R=$'\e[31m'; C_Y=$'\e[33m'; C_B=$'\e[1m'; C_D=$'\e[2m'; C_0=$'\e[0m'
else
  C_G=''; C_R=''; C_Y=''; C_B=''; C_D=''; C_0=''
fi

PASS=0; FAIL=0; WARN=0
QUIET=0
case "${1:-}" in -q|--quiet) QUIET=1 ;; esac

ok()   { PASS=$((PASS+1)); [ "$QUIET" = 1 ] && return 0; printf '  [%sPASS%s] %s\n' "$C_G" "$C_0" "$*"; }
bad()  { FAIL=$((FAIL+1)); [ "$QUIET" = 1 ] && return 0; printf '  [%sFAIL%s] %s\n' "$C_R" "$C_0" "$*"; }
warn() { WARN=$((WARN+1)); [ "$QUIET" = 1 ] && return 0; printf '  [%sWARN%s] %s\n' "$C_Y" "$C_0" "$*"; }
note() { [ "$QUIET" = 1 ] && return 0; printf '  [ %s..%s ] %s\n' "$C_D" "$C_0" "$*"; }
section() { [ "$QUIET" = 1 ] && return 0; printf '\n%s== %s ==%s\n' "$C_B" "$*" "$C_0"; }

# $1 >= $2 时为真(版本号比较)
version_ge() { [ "$1" = "$(printf '%s\n' "$1" "$2" | sort -V | tail -n1)" ]; }

ROOT=0; [ "$(id -u 2>/dev/null)" = 0 ] && ROOT=1
ARCH=$(uname -m)
KERNEL_PATCHED=unknown   # yes / no / unknown
CPUVIRT=no
KVMDEV=0
TUNOK=no
QEMUOK=no

# ---------- 0. 基本信息 ----------
section "0. 基本信息"
if [ -r /etc/os-release ]; then
  . /etc/os-release
  note "发行版: ${PRETTY_NAME:-未知}"
else
  note "发行版: 未知(无 /etc/os-release)"
fi
KVER=$(uname -r)
note "内核: $KVER ($(uname -m))"
if command -v systemd-detect-virt >/dev/null 2>&1; then
  VIRT=$(systemd-detect-virt 2>/dev/null || true)
  [ -n "${VIRT:-}" ] && note "本机虚拟化身份: $VIRT(本身是 VM 时,guest 内再跑 KVM 依赖上层宿主开放嵌套)"
fi
if [ "$ROOT" = 1 ]; then
  ok "以 root 运行"
else
  warn "非 root 运行:/proc/kallsyms 可能被屏蔽,建议 sudo 重跑"
fi

# ---------- A. 内核补丁状态 ----------
section "A. 内核补丁状态(修复标志: ip_check_csum_start / commit 6785011f8b16)"

KALLSYMS_LINES=0
if [ -r /proc/kallsyms ]; then
  KALLSYMS_LINES=$(wc -l < /proc/kallsyms)
fi

if [ "$KALLSYMS_LINES" -ge 5 ]; then
  if grep -qw ip_check_csum_start /proc/kallsyms; then
    KERNEL_PATCHED=yes
    bad "内核含 ip_check_csum_start:已修补,本机无法复现 guest->host 泄露"
  else
    KERNEL_PATCHED=no
    ok "未见 ip_check_csum_start:内核大概率未修补(可复现)"
  fi
else
  warn "/proc/kallsyms 不可读或为空($KALLSYMS_LINES 行)"
fi

# kallsyms 不可用时退回 System.map
SYSMAP="/boot/System.map-$KVER"
if [ "$KERNEL_PATCHED" = unknown ] && [ -r "$SYSMAP" ]; then
  if grep -qw ip_check_csum_start "$SYSMAP"; then
    KERNEL_PATCHED=yes
    bad "System.map 含 ip_check_csum_start:已修补"
  else
    KERNEL_PATCHED=no
    ok "System.map 未见 ip_check_csum_start:大概率未修补"
  fi
fi

# 版本启发式:修复于 2026-10-14 进入 mainline(约 v6.22 合并窗口)
BASE_VER=$(printf '%s' "$KVER" | cut -d- -f1)
MAJMIN=$(printf '%s' "$BASE_VER" | cut -d. -f1,2)
if version_ge "$MAJMIN" 6.22; then
  note "主线版本 $MAJMIN >= 6.22:上游应已含修复;若符号检测相反,以符号为准"
else
  note "主线版本 $MAJMIN < 6.22:主线不含该修复(发行版回传除外,以符号检测为准)"
fi

# ---------- B. 复现路径能力 ----------
section "B. 复现路径能力(泄露原语只需 tap;KVM 仅决定完整链保真度)"

note "漏洞路径 virtio_net_hdr -> tap -> 宿主内核,不经过 /dev/kvm:泄露原语用 tap 注入或 QEMU TCG 即可复现"

if [ "$ARCH" = aarch64 ]; then
  # ARM 没有 vmx/svm 这类 cpuinfo 标志,嵌套能力以 modprobe kvm + /dev/kvm 实测为准
  note "aarch64 平台:无 vmx/svm 标志属正常,以 /dev/kvm 实测为准(注:AWS Graviton 虚拟化实例不支持嵌套,需 x86 C8i/M8i/R8i 或 .metal)"
  CPUVIRT=yes   # 最终以 /dev/kvm 实测裁决
else
  FLAGLINE=$(grep -m1 '^flags' /proc/cpuinfo 2>/dev/null | tr -d ' ' | sed 's/^flags://')
  case ",$FLAGLINE," in
    *,vmx,*|*,svm,*) CPUVIRT=yes ;;
  esac
  if [ "$CPUVIRT" = yes ]; then
    ok "CPU 透传虚拟化扩展(vmx/svm)"
  else
    warn "CPU 无 vmx/svm 标志(x86):无 KVM 加速(不影响 tap/TCG 复现泄露原语)"
  fi
fi

if [ -e /dev/kvm ]; then
  if [ -w /dev/kvm ] || [ "$ROOT" = 1 ]; then
    ok "/dev/kvm 存在且可写:可复刻完整 guest->host 链"
    KVMDEV=1
  else
    warn "/dev/kvm 存在但当前用户不可写(加入 kvm 组或用 root 运行)"
    KVMDEV=1
  fi
else
  if [ "$ROOT" = 1 ] && command -v modprobe >/dev/null 2>&1; then
    note "/dev/kvm 不存在,尝试 modprobe kvm / kvm_intel / kvm_amd ..."
    modprobe kvm 2>/dev/null || modprobe kvm_intel 2>/dev/null || modprobe kvm_amd 2>/dev/null || true
    if [ -e /dev/kvm ]; then
      ok "modprobe 后 /dev/kvm 已出现"
      KVMDEV=1
    elif [ -d /sys/module/kvm ] && ! grep -qw kvm /proc/misc 2>/dev/null; then
      warn "/dev/kvm 未创建:KVM 已编入内核(CONFIG_KVM=y)但初始化失败,上层未暴露虚拟化扩展 → 本机无 KVM,走 tap/TCG 路径"
    else
      warn "/dev/kvm 不存在且无法加载 KVM 模块 → 本机无 KVM,走 tap/TCG 路径"
    fi
  else
    warn "/dev/kvm 不存在(root 重跑可尝试自动 modprobe);不影响 tap/TCG 复现"
  fi
fi

# nested 参数:仅影响"guest 里再套 guest"的三层场景,信息性输出
NESTED="unknown"
if   [ -r /sys/module/kvm_intel/parameters/nested ]; then
  NESTED=$(cat /sys/module/kvm_intel/parameters/nested)
elif [ -r /sys/module/kvm_amd/parameters/nested ]; then
  NESTED=$(cat /sys/module/kvm_amd/parameters/nested)
fi
case "$NESTED" in
  Y|1) note "nested=$NESTED(仅三层嵌套场景相关)" ;;
  N|0) note "nested=$NESTED(仅三层嵌套场景相关)" ;;
  *)
    if [ "$ARCH" = aarch64 ]; then
      note "aarch64 KVM 无 nested 参数(嵌套能力由硬件/内核决定)"
    else
      note "KVM 模块未加载,nested 参数未知(启动 guest 后可复查)"
    fi ;;
esac

if [ -c /dev/net/tun ]; then
  ok "/dev/net/tun 可用:泄露原语的唯一硬条件(tap 注入 / TCG guest 走此路径)"
  TUNOK=yes
else
  bad "/dev/net/tun 不存在:泄露原语也无法注入(检查 CONFIG_TUN / udev / 是否在无特权容器内)"
fi

if [ -d /sys/module/vhost_net ]; then
  note "vhost_net 已加载(virtio-net 加速,可选)"
else
  note "vhost_net 未加载(可选,qemu 可回退用户态)"
fi

# ---------- C. 工具链与资源 ----------
section "C. 复现工具链与资源"

QEMU=""
case "$ARCH" in
  aarch64) QEMU_CANDS="qemu-system-aarch64 qemu-kvm qemu-system-x86_64" ;;
  *)       QEMU_CANDS="qemu-system-x86_64 qemu-kvm" ;;
esac
for c in $QEMU_CANDS; do
  if command -v "$c" >/dev/null 2>&1; then QEMU=$c; break; fi
done
if [ -n "$QEMU" ]; then
  ok "$QEMU: $($QEMU --version 2>/dev/null | head -1)"
  QEMUOK=yes
else
  bad "未安装 QEMU(Debian/Ubuntu: apt install qemu-kvm qemu-utils)"
fi

if command -v tcpdump >/dev/null 2>&1; then
  ok "tcpdump: $(tcpdump --version 2>&1 | head -1)"
else
  warn "未安装 tcpdump(宿主机出口抓分片需要)"
fi

if command -v ip >/dev/null 2>&1; then
  ok "iproute2 已安装"
else
  bad "缺少 ip 命令(apt install iproute2)"
fi

if command -v bridge >/dev/null 2>&1 || command -v brctl >/dev/null 2>&1; then
  ok "网桥工具可用(bridge/brctl)"
else
  warn "未找到 bridge/brctl(可用 ip link 手工建桥)"
fi

CPUS=$(nproc 2>/dev/null || echo 1)
if [ "$CPUS" -ge 2 ]; then
  ok "CPU 核数: $CPUS"
else
  warn "CPU 仅 $CPUS 核,guest 会很卡"
fi

MEMMB=$(free -m 2>/dev/null | awk '/^Mem:/{print $2}')
if [ -n "${MEMMB:-}" ]; then
  if [ "$MEMMB" -ge 4096 ]; then
    ok "内存: ${MEMMB}MB"
  else
    warn "内存仅 ${MEMMB}MB,建议 >=4GB(宿主 + 2G guest)"
  fi
fi

AVAIL_GB=$(df -BG . 2>/dev/null | awk 'NR==2{print $4}' | tr -dc '0-9')
if [ -n "${AVAIL_GB:-}" ]; then
  if [ "$AVAIL_GB" -ge 20 ]; then
    ok "当前目录磁盘可用: ${AVAIL_GB}GB"
  else
    warn "当前目录磁盘可用仅 ${AVAIL_GB}GB(guest 镜像 + 抓包,建议 >=20GB)"
  fi
fi

# ---------- D. 网络出口与安全提示 ----------
section "D. 网络出口与实验安全提示"
EGRESS=$(ip route show default 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="dev") print $(i+1)}' | head -1)
if [ -n "$EGRESS" ]; then
  MTU=$(cat "/sys/class/net/$EGRESS/mtu" 2>/dev/null || echo '?')
  note "默认出口网卡: $EGRESS (MTU $MTU)"
  note "观察畸形分片(IHL>5): tcpdump -i $EGRESS -nn -vv -X '(ip[0] & 0xf) > 5'"
else
  note "未发现默认路由(纯内部 bridge 实验可忽略)"
fi
warn "泄露分片会把宿主机内核内存发往网络:务必限制在内部 bridge/本机内,抓到即丢,严禁发往公网"

# ---------- 结论 ----------
section "结论"
VERDICT=2; VSTR=unknown; REASON=undetermined; PATHLVL=none
if [ "$KVMDEV" = 1 ] && [ "$CPUVIRT" = yes ]; then KVM_READY=1; else KVM_READY=0; fi

case "$KERNEL_PATCHED" in
  yes)
    if [ "$QUIET" = 0 ]; then printf '%s[x] 内核已包含 ip_check_csum_start 修复,本机无法复现。%s\n' "$C_R" "$C_0"; fi
    note "方案:换内核未回传补丁的机器,或在实验机上自行编译不含该 commit 的内核"
    VERDICT=1; VSTR=blocked; REASON=kernel_patched
    ;;
  no)
    if [ "$TUNOK" = yes ]; then
      if [ "$KVM_READY" = 1 ]; then
        PATHLVL=kvm
        if [ "$QUIET" = 0 ]; then printf '%s[o] 环境就绪:内核未修补 + tap 可用 + KVM 可用(最高保真度)。%s\n' "$C_G" "$C_0"; fi
        note "可复刻完整 guest->host 链:qemu -enable-kvm + virtio-net guest,调小出口 MTU 触发宿主分片"
      else
        PATHLVL=tap
        if [ "$QUIET" = 0 ]; then printf '%s[o] 环境就绪:内核未修补 + tap 可用(tap 注入 / QEMU TCG 即可复现泄露原语)。%s\n' "$C_G" "$C_0"; fi
        note "无 KVM 不影响泄露原语;要复刻完整逃逸链再换 C8i/M8i/R8i、.metal 或 GCP 等支持嵌套的机型"
      fi
      VERDICT=0; VSTR=ready; REASON=none
    else
      if [ "$QUIET" = 0 ]; then printf '%s[x] 内核未修补,但 /dev/net/tun 不可用,连泄露原语都无法注入。%s\n' "$C_R" "$C_0"; fi
      note "方案:换宿主环境(容器内无 tap 权限时需特权或宿主本身)"
      VERDICT=1; VSTR=blocked; REASON=no_tun
    fi
    ;;
  *)
    if [ "$QUIET" = 0 ]; then printf '%s[?] 内核补丁状态无法判定(kallsyms/System.map 均不可用)。%s\n' "$C_Y" "$C_0"; fi
    note "请 sudo 重跑;仍不行时检查 /proc/sys/kernel/kptr_restrict 与 host 是否屏蔽了符号表"
    VERDICT=2; VSTR=unknown; REASON=undetermined
    ;;
esac

if [ "$QUIET" = 0 ]; then printf '\n统计: PASS=%d FAIL=%d WARN=%d\n' "$PASS" "$FAIL" "$WARN"; fi
printf 'PEC_RESULT: verdict=%s reason=%s path=%s patched=%s cpuvirt=%s kvmdev=%s tun=%s qemu=%s kernel=%s\n' \
  "$VSTR" "$REASON" "$PATHLVL" "$KERNEL_PATCHED" "$CPUVIRT" "$KVMDEV" "$TUNOK" "$QEMUOK" "$KVER"
exit "$VERDICT"
