testsAndMisc/linux_configuration/scripts/mtk_root/90-verify-root.sh
Krzysztof kuhy Rudnicki 1b43ba093c
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
mtk_root: build the rooting toolkit before the phone lands
Prep for a Ulefone Armor X12 Pro (MediaTek MT6765) that has not arrived,
validated read-only against the Pixel 6a that has.

Partition discovery never hardcodes boot vs init_boot. It reads
/dev/block/by-name/, derives the slot scheme from observed _a suffixes, and
picks the ramdisk carrier by preference order init_boot then boot over what is
actually present. The AOSP launch-version rule explains why that ordering is
right; the code consults the filesystem, not the API level. One path therefore
serves both a Pixel (no init_boot, falls to boot_a) and a 13-launch MediaTek
device (init_boot_a taken). Patching the wrong one yields a phone that will
not boot.

Classification refuses rather than guesses: ULEFONE needs a positive MediaTek
signal AND a vendor match, with no "not a Pixel therefore Ulefone" fallback.
Carrier lock is always reported UNDETERMINED, because sys.oem_unlock_allowed,
ro.oem_unlock_supported and settings global oem_unlock_allowed read identically
whether a device is carrier-locked or merely has the toggle off; asserting a
negative would wrongly rule out a path.

No flashing script, deliberately. Automating irreversible partition writes
defeats the confirmation steps that catch a wrong partition or missing slot
suffix.

Three bugs found by running the code rather than only linting it:
- SIGPIPE: `adb version | head -1` under pipefail exited 141 and killed
  00-preflight.sh mid-run
- 20-dump-stock.sh exited silently when no recon had run, because find on a
  nonexistent cache dir fails under set -e
- the read-only static scan had a hole: stripping all quoted strings to avoid
  false positives on printf text also hid `adb shell "reboot bootloader"`.
  Now only printf/echo/cat arguments are stripped, and a negative self-test
  asserts the scan fails on injected commands

Also narrowed a world-writable udev catch-all (idVendor=="*", MODE="0666")
that granted every local process read/write access to every USB device;
commented with a backup, adb connectivity re-verified afterwards.

Validated: 58/58 offline fixture tests; 00-preflight.sh HOST READY (12/12);
10-recon.sh read-only against the Pixel reports PIXEL, boot_a, A/B, no
init_boot, path C-or-D indeterminate, with the device unchanged afterwards.
Untested and labelled as such in READY.md: the init_boot branch on real
hardware, mtkclient over BROM, and the whole flash/verify sequence.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ReVsw7qovYkkGbqcU9tn7
2026-08-04 20:38:44 +02:00

193 lines
5.4 KiB
Bash
Executable File

#!/bin/bash
# ============================================================================
# 90-verify-root.sh - Post-flash checks, reported one by one.
#
# There is deliberately NO aggregate "success" verdict. Root can be partially
# working in ways that matter: su present but Magisk's daemon absent, or the
# ramdisk flashed while vbmeta verification still rejects it. A single
# pass/fail hides exactly the case worth knowing about.
# ============================================================================
set -euo pipefail
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
readonly SCRIPT_DIR
readonly SCRIPT_NAME="${0##*/}"
# shellcheck source=../lib/mtk_common.sh
source "$SCRIPT_DIR/../lib/mtk_common.sh"
# Known-current Magisk at the time of writing. Checked so a stale APK is
# noticed; a mismatch is informational, not a failure.
readonly MAGISK_KNOWN_VERSION="30.7"
REQUESTED_SERIAL=""
PASS=0
FAIL=0
INFO=0
check_pass() {
PASS=$((PASS + 1))
printf ' \033[0;32m[PASS]\033[0m %-28s %s\n' "$1" "${2:-}"
}
check_fail() {
FAIL=$((FAIL + 1))
printf ' \033[0;31m[FAIL]\033[0m %-28s %s\n' "$1" "${2:-}"
}
check_info() {
INFO=$((INFO + 1))
printf ' \033[0;34m[INFO]\033[0m %-28s %s\n' "$1" "${2:-}"
}
usage() {
cat <<EOF
Usage: $SCRIPT_NAME [--serial <serial>] [--help]
Runs post-flash root checks and reports each independently.
Read-only: queries the device, changes nothing.
EOF
exit 0
}
check_su() {
local result=""
result="$(mtk_adb shell 'su -c id' 2>/dev/null | tr -d '\r' || true)"
if [[ $result == *"uid=0"* ]]; then
check_pass "su grants uid=0" "$result"
return 0
fi
# An unanswered Superuser prompt looks identical to no root at all.
check_fail "su grants uid=0" "got: ${result:-<no output>}"
printf ' If a Superuser prompt is waiting on the phone, grant it and re-run.\n'
return 1
}
check_magisk() {
local version="" version_code=""
version="$(mtk_adb shell 'su -c "magisk -c"' 2>/dev/null | tr -d '\r' || true)"
version_code="$(mtk_adb shell 'su -c "magisk -V"' 2>/dev/null | tr -d '\r' || true)"
if [[ -z $version && -z $version_code ]]; then
check_fail "magisk binary responds" "no output from 'magisk -c'"
return 1
fi
check_pass "magisk binary responds" "${version} (code ${version_code})"
if [[ $version == *"$MAGISK_KNOWN_VERSION"* ]]; then
check_info "magisk version" "matches expected v${MAGISK_KNOWN_VERSION}"
else
check_info "magisk version" "device has '${version}', toolkit expected v${MAGISK_KNOWN_VERSION}"
fi
return 0
}
check_daemon() {
local result=""
result="$(mtk_adb shell 'su -c "magisk --ping"' 2>/dev/null | tr -d '\r' || true)"
if [[ $result == "Pong" || $result == "pong" ]]; then
check_pass "magisk daemon running" "$result"
return 0
fi
check_fail "magisk daemon running" "ping returned: ${result:-<nothing>}"
return 1
}
check_boot_state() {
local vbstate="" locked=""
vbstate="$(dev_getprop ro.boot.verifiedbootstate)"
locked="$(dev_getprop ro.boot.flash.locked)"
# After unlocking, verified boot should no longer report green. Still-green
# alongside a claim of successful flashing means the flash did not take.
case "$vbstate" in
orange | yellow)
check_pass "verified boot state" "${vbstate} (expected after unlock)"
;;
green)
check_fail "verified boot state" "green - bootloader still locked, flash did not take"
;;
*)
check_info "verified boot state" "${vbstate:-<unset>}"
;;
esac
if [[ $locked == "0" ]]; then
check_pass "bootloader unlocked" "ro.boot.flash.locked=0"
elif [[ $locked == "1" ]]; then
check_fail "bootloader unlocked" "ro.boot.flash.locked=1 - still locked"
else
check_info "bootloader unlocked" "ro.boot.flash.locked=${locked:-<unset>}"
fi
}
check_ramdisk_changed() {
local manifest="" carrier=""
manifest="$(mtk_stock_dir)/${MTK_SERIAL}/manifest.txt"
if [[ ! -f $manifest ]]; then
check_info "stock manifest" "none at ${manifest} - cannot compare against stock"
return 0
fi
carrier="$(grep -m1 '^ramdisk_carrier=' "$manifest" 2>/dev/null || true)"
check_info "stock manifest" "present (${carrier#*=}) - compare hashes manually before relocking"
}
main() {
printf '\033[1m%s - post-flash verification\033[0m\n' "$SCRIPT_NAME"
if ! mtk_select_device "$REQUESTED_SERIAL"; then
exit 1
fi
if ! mtk_require_authorized; then
exit 1
fi
classify_device
mtk_info "Target: ${MTK_SERIAL} (${MTK_DEVICE_CLASS})"
mtk_heading "Checks"
# Each runs regardless of earlier failures - a partial result is the point.
check_su || true
check_magisk || true
check_daemon || true
check_boot_state
check_ramdisk_changed
mtk_heading "Result"
printf ' %d passed, %d failed, %d informational\n\n' "$PASS" "$FAIL" "$INFO"
printf ' Read the checks individually. There is no combined verdict here on\n'
printf ' purpose: partial root is a real state and it matters which part failed.\n\n'
printf ' Play Integrity / SafetyNet is deliberately NOT checked - it needs\n'
printf ' Zygisk plus a hiding config, and is a separate exercise.\n'
[[ $FAIL -eq 0 ]]
}
while [[ $# -gt 0 ]]; do
case "$1" in
--serial)
[[ $# -ge 2 ]] || {
mtk_error "--serial needs a value"
exit 1
}
REQUESTED_SERIAL="$2"
shift 2
;;
-h | --help) usage ;;
*)
mtk_error "Unknown option: $1"
exit 1
;;
esac
done
main