#!/overlayer/syshub/bin/env bash
# Zainium OS security audit. Run as root:  elevate bash zainium_security_audit.sh
# Every result is measured on this machine. PASS and FAIL count toward the score; WARN and INFO do not.
# The report is written to zainium-audit-<date>.txt in the current directory.

if [ "$(id -u)" != 0 ]; then
    echo "Run as root: elevate bash $0"
    exit 1
fi

REPORT="zainium-audit-$(date '+%Y%m%d-%H%M%S').txt"
exec python3 -I - "$REPORT" <<'PY'
import ctypes, errno, fcntl, glob, os, socket, struct, subprocess, sys, time

REPORT = sys.argv[1]
out = open(REPORT, "w")
counts = {"PASS": 0, "FAIL": 0, "WARN": 0}
failed = []

def log(s=""):
    print(s); out.write(s + "\n"); out.flush()

def res(kind, name, detail=""):
    if kind in counts:
        counts[kind] += 1
    if kind == "FAIL":
        failed.append(name)
    log(f"[{kind}] {name}" + (f" -- {detail}" if detail else ""))

def check(name, ok, detail="", warn=False):
    res("PASS" if ok else ("WARN" if warn else "FAIL"), name, detail)

def section(t):
    log(""); log("=" * 72); log(t); log("=" * 72)

def read(p, default=""):
    try:
        return open(p).read().strip()
    except OSError:
        return default

def run(cmd):
    r = subprocess.run(cmd, shell=True, capture_output=True, text=True)
    return (r.stdout + r.stderr).strip()

libc = ctypes.CDLL(None, use_errno=True)
MS_RDONLY, MS_REMOUNT = 1, 32
FS_IOC_GETFLAGS, FS_VERITY_FL = 0x80086601, 0x00100000

def verity_on(path):
    try:
        fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK)
    except OSError:
        return None
    try:
        return bool(struct.unpack("l", fcntl.ioctl(fd, FS_IOC_GETFLAGS, struct.pack("l", 0)))[0] & FS_VERITY_FL)
    except OSError:
        return None
    finally:
        os.close(fd)

started = time.time()
section("ZAINIUM OS - SECURITY AUDIT")
log("Organization : Zainium Dynamics")
osr = dict(l.split("=", 1) for l in read("/overlayer/syshub/etc/os-release").splitlines() if "=" in l)
log(f"System       : {osr.get('PRETTY_NAME', 'Zainium OS').strip(chr(34))} ({osr.get('BUILD_ID', '?')})")
log(f"Audit date   : {time.strftime('%Y-%m-%d %H:%M:%S %z')}")
log(f"Hostname     : {socket.gethostname()}")
log(f"Kernel       : {os.uname().release} {os.uname().version}")
log(f"Architecture : {os.uname().machine}")

# ── 1. boot chain ────────────────────────────────────────────────────────────
section("1. Boot chain: Secure Boot, TPM2, lockdown")
sb = glob.glob("/sys/firmware/efi/efivars/SecureBoot-*")
if sb:
    val = open(sb[0], "rb").read()[-1]
    check("UEFI Secure Boot is on", val == 1, f"SecureBoot={val}", warn=True)
else:
    res("WARN", "UEFI Secure Boot state", "no EFI variables (BIOS boot?)")
check("TPM2 device present", os.path.exists("/dev/tpmrm0"), "/dev/tpmrm0", warn=True)
lock = read("/sys/kernel/security/lockdown")
check("kernel lockdown is integrity or stricter", "[integrity]" in lock or "[confidentiality]" in lock, lock)
lsm = read("/sys/kernel/security/lsm").split(",")
log(f"LSMs: {','.join(lsm)}")
for m in ["lockdown", "yama", "apparmor", "landlock", "ipe", "ima", "evm"]:
    check(f"LSM {m} is active", m in lsm)

# ── 2. kernel modules ────────────────────────────────────────────────────────
section("2. Kernel module signatures")
MOD = f"/overlayer/syshub/drivers/modules/{os.uname().release}/kernel/drivers/net/dummy.ko"
MAGIC = b"~Module signature appended~\n"
def finit(data):
    p = "/run/zainium-audit-module.ko"
    open(p, "wb").write(data)
    fd = os.open(p, os.O_RDONLY)
    r = libc.syscall(313, fd, b"", 0); e = ctypes.get_errno()
    os.close(fd); os.unlink(p)
    if r == 0:
        libc.syscall(176, b"dummy", os.O_NONBLOCK)
    return r, e
if not os.path.exists(MOD) or any(l.startswith("dummy ") for l in open("/proc/modules")):
    res("WARN", "module signature test skipped", "dummy.ko missing or already loaded")
else:
    data = open(MOD, "rb").read()
    check("shipped modules carry a signature", data.endswith(MAGIC))
    sig_len = struct.unpack(">I", data[-len(MAGIC) - 4:-len(MAGIC)])[0]
    body = data[:len(data) - len(MAGIC) - 12 - sig_len]
    r, e = finit(body)
    check("an unsigned module is refused", r != 0, "LOADED" if r == 0 else errno.errorcode.get(e, e))
    t = bytearray(data); t[len(body) // 2] ^= 0xFF
    r, e = finit(bytes(t))
    check("a module changed after signing is refused", r != 0, "LOADED" if r == 0 else errno.errorcode.get(e, e))
    r, e = finit(data)
    check("a correctly signed module loads", r == 0, "loaded" if r == 0 else errno.errorcode.get(e, e))

# ── 3. IMA and IPE ───────────────────────────────────────────────────────────
section("3. Integrity: IMA and IPE")
pol = read("/sys/kernel/security/ima/policy")
check("IMA policy is loaded", "func=BPRM_CHECK" in pol and "func=MODULE_CHECK" in pol)
meas = read("/sys/kernel/security/ima/ascii_runtime_measurements").splitlines()
check("IMA measures executed programs", len(meas) > 10, f"{len(meas)} measurements")
check("IPE policy 'zainium' is active", read("/sys/kernel/security/ipe/policies/zainium/active") == "1")
check("IPE is enforcing", read("/sys/kernel/security/ipe/enforce") == "1")
fd = os.open("/overlayer/syshub/bin/bash", os.O_RDONLY)
r = libc.syscall(320, fd, -1, 0, None, 4); e = ctypes.get_errno(); os.close(fd)
check("IPE refuses loading a new kernel at run time (kexec)", r != 0 and e == errno.EACCES, errno.errorcode.get(e, e))

# ── 4. read-only system ──────────────────────────────────────────────────────
section("4. Read-only system (sealed OverlayFS)")
mi = {}
for l in read("/proc/self/mountinfo").splitlines():
    f = l.split(" ")
    mi[f[4]] = (f[5].split(","), f[-1])
for t in ["/overlayer/syshub", "/overlayer/syshub/etc", "/overlayer/syshub/var", "/overlayer/zaisys", "/overlayer/zexlib"]:
    o = mi.get(t, ([], "<not mounted>"))
    log(f"  {t:24} {','.join(o[0]):22} {o[1][:110]}")
sh = mi.get("/overlayer/syshub", ([], ""))
check("/overlayer/syshub is read-only and sealed", "ro" in sh[0] and "sealed" in sh[1].split(","))
zs = mi.get("/overlayer/zaisys", ([], ""))
check("/overlayer/zaisys is read-only and separate", "ro" in zs[0] and "upperdir" not in zs[1])
for t in ["/overlayer/syshub", "/overlayer/zaisys"]:
    r = libc.mount(None, t.encode(), None, MS_REMOUNT, None); e = ctypes.get_errno()
    if r == 0:
        libc.mount(None, t.encode(), None, MS_REMOUNT | MS_RDONLY, None)
    check(f"root cannot remount {t} writable", r != 0 and e == errno.EROFS, "REMOUNTED" if r == 0 else errno.errorcode.get(e, e))
for p in ["/overlayer/syshub/bin/zz-audit", "/overlayer/zaisys/zz-audit"]:
    try:
        open(p, "w").close(); os.unlink(p); ok, d = False, "write allowed"
    except OSError as ex:
        ok, d = ex.errno == errno.EROFS, os.strerror(ex.errno)
    check(f"root cannot write in {os.path.dirname(p)}", ok, d)
for d in ["etc", "var"]:
    p = f"/overlayer/syshub/{d}/zz-audit"
    open(p, "w").close()
    up = f"/overlayer/zexlib/union/{d}/zz-audit"
    check(f"changes to /overlayer/syshub/{d} go to the user layer", os.path.exists(up))
    if os.path.exists(up):
        os.unlink(up)
name = f"zz-audit-{os.getpid()}"
cached = os.path.exists(f"/overlayer/syshub/bin/{name}")
open(f"/overlayer/zexlib/union/bin/{name}", "w").close()
seen = os.path.exists(f"/overlayer/syshub/bin/{name}")
os.unlink(f"/overlayer/zexlib/union/bin/{name}")
check("installed software is visible at once, without a reboot", not cached and seen)
check("the raw system disk is not reachable (/zairoot)", not os.path.exists("/zairoot"))

# ── 5. fs-verity ─────────────────────────────────────────────────────────────
section("5. fs-verity on every system file")
for root, skip in [("/overlayer/syshub", ("etc", "var")), ("/overlayer/zaisys", ())]:
    on = off = 0; bad = []
    for d, dirs, files in os.walk(root):
        if d == root:
            dirs[:] = [x for x in dirs if x not in skip]
        for f in files:
            p = os.path.join(d, f)
            if os.path.islink(p) or not os.path.isfile(p):
                continue
            v = verity_on(p)
            if v: on += 1
            elif v is False: off += 1; bad.append(p)
    total = on + off
    for b in bad[:10]:
        log(f"  not sealed: {b}")
    check(f"all files in {root} are sealed" + (f" (except {', '.join(skip)})" if skip else ""),
          total > 0 and off == 0, f"{on}/{total} ({100.0 * on / max(total, 1):.2f}%)")
target = next((os.path.join(d, f) for d, _, fs in os.walk("/overlayer/zexlib/union/lib") for f in fs
               if not os.path.islink(os.path.join(d, f)) and verity_on(os.path.join(d, f))), None)
if target:
    try:
        os.close(os.open(target, os.O_WRONLY)); ok, d = False, "opened for writing"
    except OSError as ex:
        ok, d = ex.errno in (errno.EPERM, errno.ETXTBSY), os.strerror(ex.errno)
    check("a sealed file cannot be changed, even by root", ok, d)
for p in ["/overlayer/syshub/bin/elevate", "/overlayer/syshub/bin/zex", "/overlayer/syshub/bin/sudo",
          "/overlayer/syshub/lib/systemd/systemd"]:
    log("  " + run(f"/home/alizain/.cargo/bin/fs-verity digest {p} 2>&1 || fs-verity digest {p}"))

# ── 6. AppArmor ──────────────────────────────────────────────────────────────
section("6. AppArmor")
profiles = read("/sys/kernel/security/apparmor/profiles").splitlines()
modes = {}
for l in profiles:
    m = l.rsplit("(", 1)[-1].rstrip(")")
    modes[m] = modes.get(m, 0) + 1
log(f"  {len(profiles)} profiles: " + ", ".join(f"{v} {k}" for k, v in sorted(modes.items())))
check("AppArmor profiles are loaded and enforced", modes.get("enforce", 0) > 0, f"{modes.get('enforce', 0)} enforce")

# ── 7. kernel hardening ──────────────────────────────────────────────────────
section("7. Kernel hardening (sysctl)")
for key, want, warn in [
    ("kernel/kptr_restrict", lambda v: v >= 1, False),
    ("kernel/dmesg_restrict", lambda v: v == 1, False),
    ("kernel/yama/ptrace_scope", lambda v: v >= 1, False),
    ("kernel/unprivileged_bpf_disabled", lambda v: v >= 1, False),
    ("kernel/randomize_va_space", lambda v: v == 2, False),
    ("kernel/perf_event_paranoid", lambda v: v >= 2, True),
    ("fs/protected_symlinks", lambda v: v == 1, False),
    ("fs/protected_hardlinks", lambda v: v == 1, False),
    ("fs/protected_fifos", lambda v: v >= 1, True),
    ("fs/protected_regular", lambda v: v >= 1, True),
    ("fs/suid_dumpable", lambda v: v == 0, True),
]:
    v = read(f"/proc/sys/{key}", "missing")
    try:
        ok = want(int(v.split()[0]))
    except ValueError:
        ok = False
    check(key.replace("/", "."), ok, f"= {v}", warn=warn)

# ── 8. network ───────────────────────────────────────────────────────────────
section("8. Network")
for key, good in [("net/ipv4/tcp_syncookies", "1"), ("net/ipv4/conf/all/accept_redirects", "0"),
                  ("net/ipv4/conf/all/send_redirects", "0"), ("net/ipv4/conf/all/accept_source_route", "0"),
                  ("net/ipv6/conf/all/accept_redirects", "0")]:
    v = read(f"/proc/sys/{key}", "missing")
    check(key.replace("/", "."), v == good, f"= {v}", warn=True)
# systemd sets rp_filter on default and every interface, and leaves "all" alone on purpose
v = read("/proc/sys/net/ipv4/conf/default/rp_filter", "missing")
check("net.ipv4.conf.default.rp_filter (reverse path filter)", v in ("1", "2"), f"= {v}", warn=True)
listen = []
for proto, path in [("tcp", "/proc/net/tcp"), ("tcp6", "/proc/net/tcp6")]:
    for l in read(path).splitlines()[1:]:
        f = l.split()
        if f[3] == "0A":
            addr, port = f[1].rsplit(":", 1)
            local = addr in ("0100007F", "00000000000000000000000001000000")
            listen.append((proto, int(port, 16), "local" if local else "ALL"))
for p in sorted(set(listen)):
    log(f"  listening: {p[0]} port {p[1]} ({p[2]})")
public = [p for p in listen if p[2] == "ALL"]
check("no TCP port open to the network", not public, f"{len(public)} open" if public else "", warn=True)
fw = run("nft list ruleset 2>/dev/null | wc -l")
check("a firewall ruleset is loaded (nftables)", fw.isdigit() and int(fw) > 0, f"{fw} lines", warn=True)

# ── 9. privileges ────────────────────────────────────────────────────────────
section("9. Privileged programs")
suid = []
for d, _, files in os.walk("/overlayer/syshub"):
    for f in files:
        p = os.path.join(d, f)
        try:
            st = os.lstat(p)
        except OSError:
            continue
        if st.st_mode & 0o6000 and not os.path.islink(p):
            suid.append(p)
for p in sorted(suid):
    log(f"  setuid/setgid: {p}")
expected = {"bin/elevate", "bin/elev", "bin/pkexec", "bin/fusermount3", "lib/security/unix_chkpwd",
            "lib/polkit-1/polkit-agent-helper-1"}
extra = [p for p in suid if p.replace("/overlayer/syshub/", "") not in expected]
check("only the known setuid programs exist", not extra, ", ".join(extra) if extra else f"{len(suid)} known")

# ── 10. services ─────────────────────────────────────────────────────────────
section("10. Services")
f = run("systemctl --no-pager --failed --no-legend")
log(f or "  no failed units")
check("no failed services", f == "")

# ── summary ─────────────────────────────────────────────────────────────────
section("SUMMARY")
scored = counts["PASS"] + counts["FAIL"]
log(f"PASS {counts['PASS']}   FAIL {counts['FAIL']}   WARN {counts['WARN']}")
log(f"Score: {counts['PASS']}/{scored} = {100.0 * counts['PASS'] / max(scored, 1):.1f}% of scored checks")
for n in failed:
    log(f"  FAILED: {n}")
log(f"Audit took {time.time() - started:.0f} s. Report: {os.path.abspath(REPORT)}")
out.close()
os.chmod(REPORT, 0o644)
PY
