#!/usr/bin/env bash # Apply AXP CVE allowlist onto msm-4.14 (after Stage1+combo4). # Source: AXP kernel_patches + cve_checker strict list / bisect slices. # # Safety (2026-09-29 review): # - Default: ABORT on first patch failure (no continue-with-partial-tree). # - Keep *.rej / *.orig under CVE_REJECT_DIR (do not silently delete). # - Treat large line-offsets / fuzz as WARN or FAIL (mis-apply risk). # - Set CVE_STRICT_CONTINUE=1 only for forensic runs (explicit). set -euo pipefail KP="${AXP_KERNEL_PATCHES:-/srv/rom/references/axp/kernel_patches}" KERNEL_DIR="${1:-/srv/rom/src/lineage/kernel/google/msm-4.14}" HERE="$(cd "$(dirname "$0")" && pwd)" LIST="${CVE_STRICT_LIST:-$HERE/cve-strict.list}" DENY="${CVE_DENYLIST:-$HERE/cve-denylist.txt}" LOG_DIR="${CVE_APPLY_LOG_DIR:-/srv/rom/artifacts/009-axp-los23-port/cve-stage}" CONTINUE="${CVE_STRICT_CONTINUE:-0}" # Reject apply if GNU patch reports fuzz >= this (0 = disable fuzz fail) MAX_FUZZ="${CVE_MAX_FUZZ:-1}" # Reject if any hunk offset abs(lines) >= this (0 = disable) MAX_ABS_OFFSET="${CVE_MAX_ABS_OFFSET:-200}" DRY_RUN=0 [[ "${2:-}" == "--dry-run" ]] && DRY_RUN=1 if [[ ! -d "$KERNEL_DIR/include/linux" ]]; then echo "error: kernel dir missing: $KERNEL_DIR" >&2 exit 1 fi if [[ ! -d "$KP" ]]; then echo "error: AXP kernel_patches missing: $KP" >&2 exit 1 fi if [[ ! -f "$LIST" ]]; then echo "error: list missing: $LIST" >&2 exit 1 fi mkdir -p "$LOG_DIR" STAMP=$(date -u +%Y%m%dT%H%M%SZ) LOG="$LOG_DIR/apply-cve-strict-${STAMP}.log" FAIL_LIST="$LOG_DIR/apply-cve-strict-${STAMP}.fail.txt" REJECT_DIR="${CVE_REJECT_DIR:-$LOG_DIR/rejects-${STAMP}}" mkdir -p "$REJECT_DIR" : >"$FAIL_LIST" declare -A DENY_MAP=() if [[ -f "$DENY" ]]; then while IFS= read -r d || [[ -n "$d" ]]; do [[ -z "$d" || "$d" =~ ^# ]] && continue if [[ "$d" == *"/kernel_patches/"* ]]; then DENY_MAP["${d##*/kernel_patches/}"]=1 else DENY_MAP["$d"]=1 fi done <"$DENY" fi ok=0 fail=0 skip=0 miss=0 quality_fail=0 echo "=== CVE strict apply kernel=$KERNEL_DIR dry_run=$DRY_RUN continue=$CONTINUE max_fuzz=$MAX_FUZZ max_abs_offset=$MAX_ABS_OFFSET ===" | tee "$LOG" while IFS= read -r rel || [[ -n "$rel" ]]; do [[ -z "$rel" || "$rel" =~ ^# ]] && continue if [[ -n "${DENY_MAP[$rel]:-}" ]]; then echo "SKIP_DENY $rel" | tee -a "$LOG" skip=$((skip + 1)) continue fi p="$KP/$rel" if [[ ! -f "$p" ]]; then echo "MISS $rel" | tee -a "$LOG" miss=$((miss + 1)) echo "$rel" >>"$FAIL_LIST" if [[ "$CONTINUE" != "1" ]]; then echo "error: missing patch (abort)" >&2 exit 1 fi continue fi args=(-p1 --batch -d "$KERNEL_DIR" --forward --no-backup-if-mismatch) if [[ "$DRY_RUN" -eq 1 ]]; then args+=(--dry-run) fi patch_log=$(mktemp) set +e patch "${args[@]}" <"$p" >"$patch_log" 2>&1 prc=$? set -e cat "$patch_log" >>"$LOG" # Quality: fuzz / large offsets even when exit 0 fuzz_max=0 abs_off_max=0 eval "$(python3 -c ' import re, sys text = open(sys.argv[1], errors="replace").read() fuzz = off = 0 for m in re.finditer(r"with fuzz (\d+)", text): fuzz = max(fuzz, int(m.group(1))) for m in re.finditer(r"\(offset ([+-]?\d+) lines\)", text): off = max(off, abs(int(m.group(1)))) print(f"fuzz_max={fuzz}") print(f"abs_off_max={off}") ' "$patch_log")" rm -f "$patch_log" qbad=0 if [[ "$MAX_FUZZ" -gt 0 && "$fuzz_max" -ge "$MAX_FUZZ" ]]; then echo "QUALITY_FAIL fuzz=$fuzz_max>=$MAX_FUZZ $rel" | tee -a "$LOG" qbad=1 fi if [[ "$MAX_ABS_OFFSET" -gt 0 && "$abs_off_max" -ge "$MAX_ABS_OFFSET" ]]; then echo "QUALITY_FAIL abs_offset=$abs_off_max>=$MAX_ABS_OFFSET $rel" | tee -a "$LOG" qbad=1 fi if [[ "$prc" -eq 0 && "$qbad" -eq 0 ]]; then echo "OK $rel (fuzz_max=$fuzz_max abs_off_max=$abs_off_max)" | tee -a "$LOG" ok=$((ok + 1)) continue fi # Failure path: preserve rejects echo "FAIL $rel patch_rc=$prc fuzz_max=$fuzz_max abs_off_max=$abs_off_max" | tee -a "$LOG" echo "$rel" >>"$FAIL_LIST" fail=$((fail + 1)) [[ "$qbad" -eq 1 ]] && quality_fail=$((quality_fail + 1)) safe=$(echo "$rel" | tr '/' '_') dest="$REJECT_DIR/$safe" mkdir -p "$dest" # move any rej/orig created under kernel tree while IFS= read -r -d '' rej; do mv "$rej" "$dest/" 2>/dev/null || cp -a "$rej" "$dest/" || true done < <(find "$KERNEL_DIR" -name '*.rej' -print0 2>/dev/null) while IFS= read -r -d '' orig; do mv "$orig" "$dest/" 2>/dev/null || cp -a "$orig" "$dest/" || true done < <(find "$KERNEL_DIR" -name '*.orig' -print0 2>/dev/null) cp -a "$p" "$dest/patch" 2>/dev/null || true echo "rejects_kept=$dest" | tee -a "$LOG" if [[ "$CONTINUE" != "1" ]]; then echo "error: abort on FAIL (set CVE_STRICT_CONTINUE=1 to keep going)" >&2 echo "CVE strict ABORT OK=$ok FAIL=$fail QUALITY_FAIL=$quality_fail SKIP_DENY=$skip MISS=$miss log=$LOG" | tee -a "$LOG" exit 1 fi done <"$LIST" echo "CVE strict done OK=$ok FAIL=$fail QUALITY_FAIL=$quality_fail SKIP_DENY=$skip MISS=$miss dry_run=$DRY_RUN continue=$CONTINUE log=$LOG rejects=$REJECT_DIR" | tee -a "$LOG" if [[ "$fail" -gt 0 && "$CONTINUE" != "1" ]]; then exit 1 fi if [[ "$ok" -eq 0 && "$DRY_RUN" -eq 0 ]]; then echo "error: zero CVE patches applied" >&2 exit 1 fi