#!/usr/bin/env python3
"""
Safe CVE-2026-31431 mitigation verification script
仅检测，不做任何写入、不触发利用

检测目标：
1. install algif_aead /bin/false 是否存在
2. algif_aead 当前是否已加载
3. modprobe 是否还能手动加载 algif_aead
4. AF_ALG socket 是否可创建（基础能力）
5. /usr/bin/su 基础状态检查
"""

import os
import socket
import subprocess


TARGET_CONF = "/etc/modprobe.d/disable-algif.conf"


# =====================
# Color Helpers
# =====================

def red(msg):
    return f"\033[31m{msg}\033[0m"


def green(msg):
    return f"\033[32m{msg}\033[0m"


def yellow(msg):
    return f"\033[33m{msg}\033[0m"


def blue(msg):
    return f"\033[34m{msg}\033[0m"


def cyan(msg):
    return f"\033[36m{msg}\033[0m"


# =====================
# Command Runner
# =====================

def run(cmd):
    try:
        r = subprocess.run(
            cmd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True
        )
        return r.returncode, r.stdout.strip(), r.stderr.strip()
    except Exception as e:
        return -1, "", str(e)


# =====================
# Check 1
# =====================

def check_modprobe_config():
    print(blue("[*] Checking modprobe disable rule..."))

    if not os.path.exists(TARGET_CONF):
        print(red(f"[-] Config not found: {TARGET_CONF}"))
        return False

    try:
        with open(TARGET_CONF, "r") as f:
            content = f.read()

        expected = "install algif_aead /bin/false"

        if expected in content:
            print(green("[+] Found expected rule:"))
            print(f"    {expected}")
            return True
        else:
            print(red("[!] Config exists but expected rule not found"))
            print(content)
            return False

    except Exception as e:
        print(red(f"[!] Failed reading config: {e}"))
        return False


# =====================
# Check 2
# =====================

def check_loaded_modules():
    print(blue("\n[*] Checking loaded kernel modules..."))

    try:
        with open("/proc/modules", "r") as f:
            modules = f.read()

        targets = [
            "af_alg",
            "algif_hash",
            "algif_skcipher",
            "algif_aead",
            "algif_rng",
        ]

        loaded_aead = False

        for mod in targets:
            if mod in modules:
                if mod == "algif_aead":
                    print(red(f"[!] Loaded (RISK): {mod}"))
                    loaded_aead = True
                else:
                    print(green(f"[+] Loaded: {mod}"))
            else:
                if mod == "algif_aead":
                    print(green(f"[+] Not loaded (GOOD): {mod}"))
                else:
                    print(yellow(f"[-] Not loaded: {mod}"))

        return loaded_aead

    except Exception as e:
        print(red(f"[!] Failed reading /proc/modules: {e}"))
        return None


# =====================
# Check 3
# =====================

def check_modprobe_behavior():
    print(blue("\n[*] Verifying modprobe behavior for algif_aead..."))

    code, out, err = run(["modprobe", "-n", "-v", "algif_aead"])

    print(f"[+] Return code: {code}")

    if out:
        print(cyan("[stdout]"))
        print(out)

    if err:
        print(cyan("[stderr]"))
        print(err)

    if "/bin/false" in out or "/bin/false" in err:
        print(green(
            "[+] Mitigation looks EFFECTIVE "
            "(modprobe redirected to /bin/false)"
        ))
        return True

    print(red("[!] Mitigation may NOT be effective"))
    return False


# =====================
# Check 4
# =====================

def check_af_alg():
    print(blue("\n[*] Checking AF_ALG + algif_aead availability..."))

    try:
        s = socket.socket(38, socket.SOCK_SEQPACKET, 0)
        print(green("[+] AF_ALG socket can be created"))

        try:
            s.bind((
                "aead",
                "authencesn(hmac(sha256),cbc(aes))"
            ))

            print(red(
                "[!] AEAD bind SUCCESS "
                "(algif_aead is still callable)"
            ))
            print(red(
                "    This means mitigation may NOT be sufficient"
            ))

            s.close()
            return True

        except Exception as e:
            print(green(
                "[+] AEAD bind blocked "
                "(algif_aead likely unavailable)"
            ))
            print(green(f"    Detail: {e}"))

            s.close()
            return False

    except Exception as e:
        print(red(f"[-] AF_ALG unavailable: {e}"))
        return False


# =====================
# Check 5
# =====================

def check_su_metadata():
    print(blue("\n[*] Checking /usr/bin/su metadata..."))

    path = "/usr/bin/su"

    if not os.path.exists(path):
        print(red("[-] /usr/bin/su does not exist"))
        return

    st = os.stat(path)

    print(f"[+] Size: {st.st_size} bytes")
    print(f"[+] UID : {st.st_uid}")
    print(f"[+] GID : {st.st_gid}")
    print(f"[+] Mode: {oct(st.st_mode)}")

    if st.st_mode & 0o4000:
        print(green("[+] SUID bit is set (normal)"))
    else:
        print(red("[!] SUID bit missing (suspicious)"))


# =====================
# Final Assessment
# =====================

def final_judgement(conf_ok, loaded_aead, modprobe_blocked):
    print(cyan("\n=============================="))
    print(cyan("Final Assessment"))
    print(cyan("=============================="))

    if conf_ok and (loaded_aead is False) and modprobe_blocked:
        print(green("[SAFE] mitigation is likely effective"))
        print(green("       algif_aead blocked + unloaded"))

    elif conf_ok and modprobe_blocked:
        print(yellow("[PARTIAL]"))
        print(yellow(
            "       future loading blocked, "
            "but algif_aead may still be loaded"
        ))
        print(yellow(
            "       reboot or manual unload may still be needed"
        ))

    else:
        print(red("[RISK]"))
        print(red("       mitigation not fully effective"))


# =====================
# Main
# =====================

if __name__ == "__main__":
    print(cyan("=== CVE-2026-31431 Safe Mitigation Verification ===\n"))

    conf_ok = check_modprobe_config()
    loaded_aead = check_loaded_modules()
    modprobe_blocked = check_modprobe_behavior()

    check_af_alg()
    check_su_metadata()

    final_judgement(
        conf_ok,
        loaded_aead,
        modprobe_blocked
    )
