testsAndMisc/linux_configuration/scripts/mtk_root/README.md
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

97 lines
4.6 KiB
Markdown

# 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
```bash
./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
```bash
./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`.
## Related
`../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.