mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
- hook_cred_label (sudo/setuid): resolve vfs_context_current generically; uthread offset drifted to 0x3F0 - proc_security_policy: anchor on PRIV_GLOBAL_PROC_INFO (0x3EA), not the drifted `cmp #0x21` switch literal - proc_pidinfo: match the unique guard+EINVAL+flavor-switch shape (now inlined into proc_info_internal) - task_conversion_eval: accept the kernel_task compare in either operand order - io_secure_bsd_root: anchor on the kIOReturnNotPrivileged deny-CSEL; drop the moved [x19,#0x11A] context - mac_mount: match the role-state ldrb + bit-6 test (Site 2 folded into one load on 26.5) - spawn_validate_persona: anchor on the dual sibling [#8]/[#0xc] cbz reject - vm_map_protect: handle both the 26.1/26.3 b.ne gate and the 26.5 W^X mask register (mov #5 -> #7) - kcall10: locate the sysent table structurally; drop the brittle _nosys tiny-stub heuristic - add tests/test_jb_kernel_patches.sh (make test_jb_patches[, QUICK=1]) over every supported cloudOS kernel Fixes #342. The 26.1-derived patchset silently skipped 9 hooks on 26.5 — the pipeline only fails a component when it emits zero patches total, so a half-patched kernel shipped as "success" and sudo broke with 'nosuid'. Verified across 26.1/26.3/26.4/26.5
228 lines
8.7 KiB
Bash
Executable File
228 lines
8.7 KiB
Bash
Executable File
#!/usr/bin/env zsh
|
|
# test_jb_kernel_patches.sh — the single test for the JB kernel patch layer.
|
|
#
|
|
# Runs the WHOLE `kernel-jb` patcher (every JB hook, including all the Sandbox
|
|
# MACF ops hooks) over each cloudOS kernel the README lists as supported, and
|
|
# requires that every version applies with 0 failures and emits every expected
|
|
# patch. This is both the correctness check and the backward-compatibility gate
|
|
# (we develop against the newest kernel but must not regress older ones).
|
|
#
|
|
# The kernel we patch (`kernelcache.research.vphone600`) ships inside the cloudOS
|
|
# (PCC) firmware, so "kernel versions" == the cloudOS builds in the README's
|
|
# "Tested Environments" table. For each build we resolve its PCC "OS" asset URL
|
|
# from `ipsw dl pcc --info`, pull ONLY `kernelcache.research.vphone600` via remote
|
|
# extraction (~14 MB, not the full IPSW), run `patch-component --component
|
|
# kernel-jb`, and validate. Anything cached under the work dir is reused.
|
|
#
|
|
# tests/test_jb_kernel_patches.sh # all supported cloudOS builds
|
|
# tests/test_jb_kernel_patches.sh --quick # only the local/newest kernel (fast dev loop)
|
|
# tests/test_jb_kernel_patches.sh 23B85 23D128 # only these builds
|
|
# tests/test_jb_kernel_patches.sh --no-build # skip rebuilding the patcher
|
|
# BUILDS="23B85 23D128 23E5207q" tests/test_jb_kernel_patches.sh
|
|
#
|
|
# Exit code: 0 iff EVERY tested kernel applies all expected patches with 0 failures.
|
|
set -euo pipefail
|
|
|
|
HERE=${0:a:h}
|
|
ROOT=${HERE:h} # tests/ lives at the repo root
|
|
cd "$ROOT"
|
|
|
|
WORK=${WORK:-/tmp/vphone_kjb_versions}
|
|
BIN=".build/debug/vphone-cli"
|
|
README="$ROOT/README.md"
|
|
PCC_INFO="$WORK/pcc_info.txt" # cached `ipsw dl pcc --info` dump
|
|
mkdir -p "$WORK"
|
|
|
|
# Patch IDs that MUST emit on every supported kernel: the P0 sudo hook plus the
|
|
# eight routines retargeted for 26.5. If any is missing, that hook silently
|
|
# skipped. (The 0-failure gate below additionally catches every OTHER routine —
|
|
# including all the Sandbox ops hooks — since the pipeline counts a routine that
|
|
# emits nothing as a failure.)
|
|
REQUIRED_IDS=(
|
|
jb.hook_cred_label.c23_cave
|
|
task_conversion_eval
|
|
jb.proc_security_policy.mov_x0_0
|
|
jb.proc_pidinfo.nop_guard_a
|
|
jb.io_secure_bsd_root.zero_return
|
|
kernelcache_jb.mac_mount.flag_gate
|
|
kernelcache_jb.spawn_validate_persona.cbz1
|
|
kernelcache_jb.vm_map_protect
|
|
jb.kcall10.sy_call
|
|
)
|
|
|
|
NO_BUILD=0
|
|
QUICK=0
|
|
ARG_BUILDS=()
|
|
for a in "$@"; do
|
|
case "$a" in
|
|
--no-build) NO_BUILD=1 ;;
|
|
--quick) QUICK=1 ;;
|
|
-*) echo "unknown option: $a" >&2; exit 2 ;;
|
|
*) ARG_BUILDS+=("$a") ;;
|
|
esac
|
|
done
|
|
|
|
command -v ipsw >/dev/null 2>&1 || { echo "[-] 'ipsw' CLI not found (brew install blacktop/tap/ipsw)"; exit 2; }
|
|
|
|
# --- 1. Determine which cloudOS builds to test -------------------------------
|
|
# Priority: --quick (local kernel) > CLI args > $BUILDS env > README cloudOS column.
|
|
typeset -a BUILD_LIST
|
|
if (( QUICK )); then
|
|
BUILD_LIST=(local)
|
|
elif (( ${#ARG_BUILDS} )); then
|
|
BUILD_LIST=("${ARG_BUILDS[@]}")
|
|
elif [[ -n "${BUILDS:-}" ]]; then
|
|
BUILD_LIST=(${=BUILDS})
|
|
else
|
|
# cloudOS column entries look like `26.4-23E5207q` (version-dash-build); the
|
|
# iPhone column uses `17,3_26.5_23F77` (commas/underscores), so filter those out.
|
|
BUILD_LIST=($(python3 - "$README" <<'PY'
|
|
import re, sys
|
|
seen=[]
|
|
insec=False
|
|
for line in open(sys.argv[1], encoding="utf-8"):
|
|
if line.startswith("## Tested Environments"): insec=True; continue
|
|
if insec and line.startswith("## "): break
|
|
if not insec: continue
|
|
for tok in re.findall(r"`([^`]+)`", line):
|
|
m=re.fullmatch(r"\d+\.\d[\d.]*-([0-9A-Za-z]+)", tok) # 26.4-23E5207q
|
|
if m and m.group(1) not in seen:
|
|
seen.append(m.group(1))
|
|
print("\n".join(seen))
|
|
PY
|
|
))
|
|
fi
|
|
[[ ${#BUILD_LIST} -gt 0 ]] || { echo "[-] no cloudOS builds resolved"; exit 2; }
|
|
echo "kernel builds to test: ${BUILD_LIST[*]}"
|
|
|
|
# --- 2. Build the patcher ----------------------------------------------------
|
|
if (( ! NO_BUILD )); then
|
|
echo "==> building patcher ..."
|
|
if ! make patcher_build > "$WORK/build.log" 2>&1; then
|
|
echo "[-] patcher build failed:"; tail -20 "$WORK/build.log"; exit 1
|
|
fi
|
|
fi
|
|
[[ -x "$BIN" ]] || { echo "[-] $BIN missing (run without --no-build)"; exit 1; }
|
|
|
|
# --- 3. Cache the PCC release index (build -> OS asset URL) ------------------
|
|
ensure_pcc_info() {
|
|
[[ -s "$PCC_INFO" ]] && return 0
|
|
echo "==> fetching PCC release index (ipsw dl pcc --info) ..."
|
|
if ! ipsw dl pcc --info > "$PCC_INFO" 2>&1; then
|
|
echo "[-] 'ipsw dl pcc --info' failed:"; tail -5 "$PCC_INFO"; return 1
|
|
fi
|
|
}
|
|
|
|
# Resolve one cloudOS build -> its PCC "OS" asset URL (only releases that still
|
|
# carry the vphone600 firmware). Empty output => not found.
|
|
resolve_url() {
|
|
local build="$1"
|
|
PCC_INFO="$PCC_INFO" python3 - "$build" <<'PY'
|
|
import os, re, sys
|
|
build = sys.argv[1]
|
|
raw = open(os.environ["PCC_INFO"], encoding="utf-8", errors="replace").read()
|
|
clean = re.sub(r"\x1b\[[0-9;]*m", "", raw)
|
|
|
|
blocks, cur = [], None
|
|
for line in clean.splitlines():
|
|
if re.match(r"^\d+\)\s+[0-9a-f]{16,}", line):
|
|
cur = {"vphone": False, "url": None, "builds": set()}
|
|
blocks.append(cur)
|
|
continue
|
|
if cur is None:
|
|
continue
|
|
if "\U0001F4F1 VPHONE" in line: # 📱 VPHONE = vphone firmware present
|
|
cur["vphone"] = True
|
|
u = re.search(r"(https://updates\.cdn-apple\.com/private-cloud-compute/[0-9a-f]+)", line)
|
|
if u and cur["url"] is None:
|
|
cur["url"] = u.group(1)
|
|
cur["builds"].update(re.findall(r"\b(2[0-9][A-Z][0-9A-Za-z]+)\b", line))
|
|
|
|
for b in blocks:
|
|
if b["vphone"] and b["url"] and build in b["builds"]:
|
|
print(b["url"])
|
|
break
|
|
PY
|
|
}
|
|
|
|
# --- 4. Obtain kernelcache.research.vphone600 for a build -------------------
|
|
# "local" => reuse the pristine kernelcache already extracted under ipsws/.
|
|
# Otherwise reuse an already-extracted copy in the work dir; else remote-extract.
|
|
get_kernelcache() {
|
|
local build="$1"
|
|
if [[ "$build" == local ]]; then
|
|
ls ipsws/*/kernelcache.research.vphone600 2>/dev/null | grep -v Restore | head -1
|
|
return
|
|
fi
|
|
local dir="$WORK/$build" kc
|
|
kc=$(find "$dir" -name 'kernelcache.research.vphone600' -type f 2>/dev/null | head -1)
|
|
if [[ -n "$kc" && -f "$kc" ]]; then echo "$kc"; return 0; fi
|
|
|
|
ensure_pcc_info || { echo ""; return 1; }
|
|
local url; url=$(resolve_url "$build")
|
|
[[ -n "$url" ]] || { echo ""; return 1; }
|
|
mkdir -p "$dir"
|
|
if ! ipsw extract --kernel --remote "$url" --output "$dir" >> "$WORK/$build.extract.log" 2>&1; then
|
|
echo "" ; return 1
|
|
fi
|
|
find "$dir" -name 'kernelcache.research.vphone600' -type f 2>/dev/null | head -1
|
|
}
|
|
|
|
# --- 5. Run the patcher + validate one kernelcache --------------------------
|
|
typeset -A RESULT
|
|
overall=0
|
|
for build in "${BUILD_LIST[@]}"; do
|
|
echo ""
|
|
echo "──────────── kernel $build ────────────"
|
|
kc=$(get_kernelcache "$build") || true
|
|
if [[ -z "$kc" || ! -f "$kc" ]]; then
|
|
echo " [-] could not obtain kernelcache (no vphone release / download failed)"
|
|
RESULT[$build]="NO-KERNEL"; overall=1; continue
|
|
fi
|
|
echo " kernel: $kc"
|
|
|
|
out="$WORK/$build"; mkdir -p "$out"
|
|
if ! "$BIN" patch-component --component kernel-jb \
|
|
--input "$kc" --output "$out/kc.patched.bin" --records-out "$out/records.json" \
|
|
> "$out/run.log" 2>&1; then
|
|
echo " [-] patcher crashed:"; tail -8 "$out/run.log" | sed 's/^/ /'
|
|
RESULT[$build]="CRASH"; overall=1; continue
|
|
fi
|
|
|
|
applied=$(grep -oE "applied [0-9]+ patches" "$out/run.log" | grep -oE "[0-9]+" | head -1 || echo "?")
|
|
fails=$(grep -cE "\[-\]" "$out/run.log" || true)
|
|
sandbox=$(grep -oE 'sandbox_ext_[0-9]+' "$out/records.json" 2>/dev/null | sort -u | wc -l | tr -d ' ')
|
|
|
|
# Every required patch ID must have emitted.
|
|
missing=()
|
|
for id in "${REQUIRED_IDS[@]}"; do
|
|
grep -q "\"$id\"" "$out/records.json" 2>/dev/null || missing+=("$id")
|
|
done
|
|
|
|
echo " applied: $applied failures: $fails sandbox hooks: $sandbox"
|
|
if (( fails == 0 )) && (( ${#missing} == 0 )) && (( sandbox >= 1 )); then
|
|
echo " ✅ PASS"
|
|
RESULT[$build]="PASS ($applied applied, $sandbox sandbox)"
|
|
else
|
|
echo " ❌ FAIL"
|
|
(( fails > 0 )) && { echo " failures:"; grep -E "\[-\]" "$out/run.log" | sed 's/^/ /'; }
|
|
(( ${#missing} )) && echo " missing patch IDs: ${missing[*]}"
|
|
(( sandbox < 1 )) && echo " no sandbox hooks emitted"
|
|
RESULT[$build]="FAIL"; overall=1
|
|
fi
|
|
done
|
|
|
|
# --- 6. Summary matrix -------------------------------------------------------
|
|
echo ""
|
|
echo "════════════════ summary ════════════════"
|
|
for build in "${BUILD_LIST[@]}"; do
|
|
printf " %-12s %s\n" "$build" "${RESULT[$build]:-?}"
|
|
done
|
|
echo ""
|
|
if (( overall == 0 )); then
|
|
echo "ALL KERNELS PASS — every JB hook (incl. Sandbox) applies, backward-compatible."
|
|
else
|
|
echo "ONE OR MORE KERNELS FAILED — see above."
|
|
fi
|
|
exit $overall
|