testsAndMisc/linux_configuration/scripts/mtk_root
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
..
tests mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
udev mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
00-preflight.sh mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
10-recon.sh mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
20-dump-stock.sh mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
90-verify-root.sh mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
install.sh mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
NOTES.md mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
README.md mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00
READY.md mtk_root: build the rooting toolkit before the phone lands 2026-08-04 20:38:44 +02:00

mtk_root

Host tooling for rooting a MediaTek Android device: preflight, read-only recon, stock partition dump, and post-flash verification.

Built ahead of a Ulefone Armor X12 Pro (MediaTek Helio G36 / MT6765) arriving, and validated read-only against a Pixel 6a.

There is no flashing script, deliberately. Automating irreversible partition writes would defeat the confirmation steps that catch a wrong partition name or a missing slot suffix. Flashing stays manual.

Scripts

Script Device needed What it does
install.sh no Installs deps, clones mtkclient + builds its venv, installs narrowed udev rules
00-preflight.sh no Verifies the host is intact. Run this first when the phone arrives
10-recon.sh yes Read-only. Reads every fact needed to pick a path, writes a facts file
20-dump-stock.sh yes MediaTek only. Dumps the stock ramdisk + vbmeta via mtkclient
90-verify-root.sh yes Post-flash checks, reported one by one

Usage

./install.sh                 # once, or after a Python upgrade
./00-preflight.sh            # confirm the host is ready
./10-recon.sh                # read the device, decide the path
./20-dump-stock.sh           # Ulefone only, after recon
# ... patch with Magisk and flash MANUALLY ...
./90-verify-root.sh          # check what actually worked

Add --serial <serial> to any device-facing script when more than one phone is attached; they refuse to guess rather than picking one.

Design notes

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 → boot over what is actually present. Per AOSP, devices launching on Android 13+ carry the ramdisk in init_boot; earlier devices keep it in boot even after upgrading. That rule explains why the preference order is right, but the code consults the filesystem, not the API level — so one code path serves both a Pixel (no init_boot, falls through to boot_a) and a 13-launch MediaTek device (init_boot_a taken). Patching the wrong one produces a phone that will not boot.

Classification refuses rather than guesses. ULEFONE requires a positive MediaTek platform signal and a vendor/model match. There is no "not a Pixel, so presumably the Ulefone" fallback: a third phone attached by accident lands in UNKNOWN and every caller stops.

Carrier lock is reported as UNDETERMINED, always. No readable property distinguishes carrier-locked from "the OEM unlocking toggle is simply off": sys.oem_unlock_allowed mirrors the toggle, ro.oem_unlock_supported is not populated on Pixels, and settings get global oem_unlock_allowed is null until the toggle is used. Asserting a negative here would wrongly rule out a path, so the script reports the raw signals and refuses to conclude. Settle it out of band via an IMEI check.

10-recon.sh is read-only and the tests enforce it. tests/run_tests.sh statically scans it for fastboot, magiskboot, adb reboot/push/install, settings put, dd, mkfs and erase, and separately asserts the scan fails when such a command is injected into a copy — a guard never observed failing is not known to work.

Artifacts

Images, APKs and facts files go to ~/.cache/mtk-root/, never into the repo: the pre-commit hook rejects all binaries, and a factory image runs to several GB. Only text manifests belong in git.

Ulefone firmware is deliberately not pre-fetched — the stock ramdisk is dumped off the device itself, so it matches that exact unit and build.

Tests

./tests/run_tests.sh

58 assertions, fully offline. Fixtures are props.txt + by-name.txt directories fed in via MTK_ROOT_FIXTURE; MTK_DUMP_CMD stubs the mtkclient invocation so the dump/manifest logic is exercised without hardware.

tests/fixtures/pixel-boot-ab/ is harvested from a real Pixel 6a. The ulefone-* fixtures are synthetic and encode assumed property values — see NOTES.md.

../single_use/utils/root_bl9000.sh is an older, single-device rooting script for a Blackview MT6893. It is untouched by this toolkit and still works; the two share no code by design, so changes here cannot break it.