Repository navigation
firehose: add OPPO/OnePlus VIP (verified image protection) support - #829
Open
2loch-ness6 wants to merge 1 commit into
Open
2loch-ness6 wants to merge 1 commit into
2loch-ness6 wants to merge 1 commit into
Conversation
Newer OPPO/OnePlus/Realme targets (SM8650/OxygenOS 15 and up) enable
OPPO's VIP extension: firehose refuses every storage command until the
host has handed it the signed digest table and signature. With stock
edl.py such a target just fails, e.g. "Failed to open the UFS Device" or
"label cannot be null".
Implement the host side of the handshake:
- --vip, --vip-digest, --vip-sign, --vip-label: the digest table
(Digest.elf / Digest.mbn) and signature (Sign.bin) files used by the
official OPLUS tool. Because they apply to every command, they are
stripped from argv before docopt sees them instead of being added to
each usage line.
- firehose.connect(): when the target announces VIP with its "receiving
the partition info of size N" prompt, send the digest table, then
<verify value="ping" EnableVip="1"/>, then the signature. If any step is
rejected, mark VIP as failed and refuse to configure the target.
- firehose.parse_vip_table(): parse the SVIP digest table (magic
0x7CEF0312) into a per-partition map of permissions, start sector and
size.
- cmd_read(), cmd_read_buffer(), cmd_program(), cmd_program_buffer(),
cmd_erase(): emit the label= attribute that VIP targets require in order
to authorise access. It defaults to --vip-label ("PrimaryGPT" for GPT
reads) and is only emitted on VIP targets, so the XML sent to every
other device is unchanged.
- --skipinitialread: skip configure()'s initial storage-sector probe.
configure() itself still has to run, it is what initialises the storage.
- cmd_read_buffer(): use the parsed response dict in the error path
instead of the legacy tuple indexes. Indexing rsp[2] raised KeyError and
hid the real target error message. Also stop retrying forever when the
target stops sending data mid read.
- firehose.connect(): parse every log line of a USB read instead of only
the first, the remaining lines were dropped.
Verified on a OnePlus 12 (CPH2581, SM8650, UFS, 187 signed digest
entries): target authentication, GPT parsing on all LUNs, reads of
locked and unlocked partitions, and writes to unlocked partitions.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds support for OPPO/OnePlus "VIP" (verified image protection): the digest-table handshake
that newer OPPO/OnePlus/Realme programmers (SM8650 and up) demand before they accept any
read, write or erase.
Why
I wrote this because I had no other way out. My OnePlus 12 (CPH2581) was left unusable
after a bad OxygenOS 15.0.0.863 update, fastboot was gone too, and every thread I found
said the only remaining option was a paid unlock/flash tool - SOL otherwise. The public
loaders simply refused to talk to the device:
That is the same wall half of this issue tracker has been hitting for years (#439, #481,
#492, #605, #698, #730, #751, ...). This patch is what got the phone back: full EDL
access again, GPTs on every LUN, partition reads, and the writes that restored it. It
seemed a waste to keep that to myself, so here it is, cleaned up, for everyone who was
told they needed a paid tool for the same fix.
What the target expects
The programmer wants, in this order:
Digest.elf/Digest.mbn) as a raw, non-XML packet<?xml version="1.0" ?><data><verify value="ping" EnableVip="1"/></data>Sign.bin, 256 bytes padded to 4096) as a raw packetAfter that, every read/write/erase has to carry
label="<partition>"naming a partitionof the digest table, otherwise the target answers
ERROR: label cannot be null.Implementation
--vip,--vip-digest=file,--vip-sign=file,--vip-label=labelplus--skipinitialread. Since they apply to every command, they are pulled out ofargvbeforedocoptand merged into the parsed arguments, rather than being repeatedin every one of the ~46 usage lines.
firehose.connect()detects the announcement (receiving the partition info/receiving the signed table/receiving the sign table) and runs the handshake. Thetarget's handshake log is fed through the normal log parser, so
supported_functionsstill works afterwards. On failure,
vip_okis set toFalseandconfigure()refusesto continue instead of producing misleading timeouts.
firehose.parse_vip_table()parses the SVIP digest table (magic0x7CEF0312) into aper-partition map of permissions, start sector and size. Informational only; the target
is the one that enforces access. Entries are per LUN but names repeat across LUNs, so the
map is keyed by name. Parsing is best effort: a bare
.mbnwithout the SVIP header justwarns and the handshake still runs.
cmd_read(),cmd_read_buffer(),cmd_program(),cmd_program_buffer(),cmd_erase()emit thelabel=attribute.printgptusesPrimaryGPT;r,rl,w,wlandederive it from the partition they are handed; raw sector access(
rs,rf,ws,wf,es) uses--vip-labelbecause there is no partition name tofall back on. The attribute is only emitted on VIP targets, so the XML sent to every
other device is unchanged.
Also fixed on the way, because they got in the way of the handshake:
cmd_read_buffer()raisedKeyError: 2when the target replied without the expectedresponse fields, which hid the real error text (e.g. the permission error below).
connect()only parsed the first line of each USB read as a log line; the rest wasdropped.
cmd_read_buffer()retried forever when the target stopped sending data mid read.Usage
--vip-signis optional: without it the handshake stops after the digest table, which isall that some programmers ask for.
configure()still has to run, it is what initialisesthe storage;
--skipinitialreadonly skips its doomed probe read. The README sectiondocuments all of this.
Testing
Done on a OnePlus 12 (CPH2581, SM8650, UFS 512 GB,
CPH2581_15.0.0.863(EX01)), which iswhere the tool was needed in the first place:
parsed into 173 unique partition names
printgpton all LUNs, including the LUN 0superextent and thePrimaryGPT/BackupGPTentries that repeat per LUN groupones included)
for byte against the code as it behaved on hardware, to make sure nothing else changes
CI:
flake8 . --count --select=E9,F63,F7,F82is clean and the advisory run reports 140items vs. 141 before the patch, i.e. no new warnings.
Related issues
issue asks for. The variant in the first comments hashes whole firehose commands instead
and is not covered by this patch.
receiving the signed table of size 8192.smc_status = 0xfffffffe-vip img authentication failedis one of the recognised failure strings.--signeddigests/--testvipimpactflow offh_loader, now available in edl without the OPPO tool.is the reason, and what to pass to make it work.
receiving the sign tableprompt is handled.Related OPPO/oplus reports that are not addressed here: #794 and #799 (OPD2415/SM8750
fail in Sahara with
AUTH_FAILUREbefore firehose even starts, so they are a differentstage of the boot chain) and the older OPPO devices in #207/#390/#458, which predate VIP.
Known limits
cmd_erase()carries labels for completeness, but erasing was not exercised on VIPhardware (reads and writes were).
r:0givesERROR: not get permission,line=141/not allowed on external network), while theSHA256 footer check of a write to a pinned partition appeared to be advisory - a write to
a digest-pinned partition landed and only reported a
sha256finalmismatch. That wastested on a single partition, so I would not rely on it; treat the digest table as
enforced.