Samsung OEM-unlock gate anatomy (One UI 8.x era)
Everything between "flip the toggle" and "the bootloader unlocks" — from decompiling
services.jar on a 2026 Samsung flagship. Device-agnostic; applies to any recent
Samsung Android build.
The three layers
1. The service backend
OemLockService picks its backend at boot:
- a vendor IOemLock HAL exists → HAL backend (older devices / some vendors),
- otherwise → PersistentDataBlockLock — the flag lives in the persistent
partition (ro.frp.pst), managed by PersistentDataBlockService.
Modern Samsungs (Snapdragon flagships) use the PDB backend: the "OEM unlock" toggle state is software storage on a raw partition, not a hardware switch.
2. The gates (all software)
java
// PersistentDataBlockLock
isOemUnlockAllowedByCarrier() =
!UserManager.hasUserRestriction("no_oem_unlock", USER_SYSTEM);
setOemUnlockAllowedByCarrier(z, sig) { // "Signature provided but is not being used" <-- carrier sig is IGNORED setUserRestriction("no_oem_unlock", !z, USER_SYSTEM); if (!z && flashIsLocked) pdb.setOemUnlockEnabled(false); // side effect! }
// OemLockService.setOemUnlockAllowedByUser(z)
// gate 1: hasUserRestriction("no_factory_reset") -> "Admin does not allow OEM unlock"
// gate 2: !isOemUnlockAllowedByCarrier() -> "Carrier does not allow OEM unlock"
// then: setOemUnlockAllowedByDevice(z) -> writes the PDB flag
`````
Key insights:
- **"Carrier does not allow OEM unlock" is a user restriction** (no_oem_unlock), not a
SIM/carrier/signature check. The carrier signature parameter is ignored entirely.
- **The PDB setter's own gate isno_factory_reset** ("OEM unlock is disallowed by
user restriction: no_factory_reset") plus FRP-inactive.
- **The flag is the LAST BYTE of the persistent partition**, with a SHA-256 digest at
offset 0 (doSetOemUnlockEnabledLocked+computeAndWriteDigestLocked`).
3. The consumer (where it usually dies)
On older devices the abl reads the flag and performs the unlock. On newer bit-rev
firmware (Samsung's 2025-2026 "no more bootloader unlock" policy) the abl does not
read it at all — the toggle disappears from Settings and even a root-written flag is
ignored. Verified empirically on bit-4 SM-F956B: flag written as uid 0 with valid
digest, all service getters TRUE → reboot → ro.boot.other.locked=1,
vbmeta.device_state=locked. The bootloader was stripped, not the UI.
Transaction map (service call oem_lock N)
| tx | method | notes |
|---|---|---|
| 1 | getLockName | |
| 2 | setOemUnlockAllowedByCarrier(bool, byte[]) | signature ignored |
| 3 | isOemUnlockAllowedByCarrier | = !no_oem_unlock |
| 4 | setOemUnlockAllowedByUser(bool) | both gates + writes flag |
| 5 | isOemUnlockAllowedByUser | = PDB flag |
| 6 | isOemUnlockAllowed | carrier && device |
| 7 | isDeviceOemUnlocked |
= ro.boot.flash.locked == "0"
|
The classic pitfall
A no-arg probe like service call oem_lock 2 (meant as a "getter") actually calls
setCarrier(false, null) → sets no_oem_unlock=true and clears the PDB flag
(the flash-locked side effect). This self-inflicts the exact "Carrier does not allow
OEM unlock" refusal everyone then tries to debug. Always pass the boolean:
service call oem_lock 2 i32 1 to clear the restriction.
2026-10-28 — the OEM-unlocking row comes back (no flash, no firmware edit)
On our own SM-F956B (F956BXXS4DZI1, update bit 4, locked bootloader,
Knox warranty bit already tripped) the OEM unlocking switch in Developer
options is normally absent. It is not missing from the firmware — the bit-4 abl still
contains setOEMFlags / IsUnlocked — it is hidden because
LinuxLoader appends androidboot.other.locked=1 on every boot. With
temporary KernelSU root we rewrote that live property and the row reappeared.
# plain resetprop is refused for ro.* — use ksud's direct-area mode
su -c 'resetprop -n ro.boot.other.locked 0'
su -c 'getprop ro.boot.other.locked' # 0
# Settings → Developer options → OEM unlocking (row present)
First appearance was disabled with Samsung's “Connect to the internet or contact
your service provider to use OEM unlock.” — the KnoxGuard gate — with
ro.boot.kg=0x1 and knox.kg.state=Checking. Spoofing
ro.boot.kg=0x4 / knox.kg.state=Completed made the row
disappear; reverting to 0x1 / Checking brought it
back enabled. On this build, Completed hides the row and Checking
shows it — the opposite of the assumption we had been working from.
Tapping the switch raises Samsung's “Allow OEM unlocking?” dialog (warning:
device-protection features stop working while it is on). After confirming, the switch reads
checked=true and the persistent-data-block flag flips:
# before
Result: Parcel( 00000000 00000000 '........')
# after pressing Enable
Result: Parcel( 00000000 00000001 '........') # service call persistent_data_block 7
What this is, and what it is not. The flag is an unlock request
stored in the persistent data block. It is not an unlocked bootloader: the actual unlock is
the Download-Mode long-press-Volume-Up path → setOEMFlags → SCM → TZ → QFPROM
fuse 0xc1, and that confirmation wipes userdata. Whether the TZ/fuse write
accepts the request on a bit-4 build is untested. The property is RAM-only —
a reboot restores other.locked=1 and hides the row again — while the PDB flag
persists until it is switched off or the device is wiped.
Reversibility: switching it off clears the request; rebooting clears the property spoof. No partition was written and nothing was flashed for this result. (This also corrects the older note that the bit-4 abl has “no unlock code” — see the LZMA write-up: the code is present, the flag merely hides the UI.)
Same day — Download Mode still refuses (where it stands now)
With the toggle on and the request persisted, a physical power-off → Volume Up + Volume
Down → Download Mode shows only the normal “press up to continue”. The
“hold up to unlock bootloader” line never appears. Entering Download Mode through
adb reboot download skips the prompt path entirely, so use the key combo.
Why — measured, not guessed:
persistent 9487386438914219b7ce862ca7ec7593fa8a46718435b719fea28bb95722a82b # unchanged
devinfo ae8e0b0112822c89ce2ea9dbae977a55bbf4efcab7171083f2d9dcec0f668220 # unchanged
settings global oem_unlock_allowed = 1 # this is the toggle
oem_lock getLockName -> null # no OEM-lock HAL
Both partitions are byte-identical to the captures taken before the toggle
was ever enabled. The switch writes a framework setting only; with no oem_lock
HAL there is nothing to translate it into the state the bootloader reads. So the visible
toggle was real, but it is not by itself a bootloader-visible request.
Where the gate is now. The abl unlock code is present
(setOEMFlags, IsUnlocked, IsUnlockCritical,
GetUnlockCount, and it draws device_unlock.jpg). Download Mode’s app
(OdinApp_8650) also carries KNOXGUARD,
PRENORMAL, BROKEN, CHECKING, COMPLETED,
GetKGStatus, GetKGFuseFromBL/FromTA and
tz_kg. Our device reports ro.boot.kg=0x1 /
knox.kg.state=Checking — an unsettled state, and KG is fuse/TZ-backed, so a
property spoof cannot move it. The unlock screen is drawn only when a state dword
([x19+8]) equals 1; we are tracing that state now.
Status: unlock request set · bootloader still locked · gate narrowed to KnoxGuard. — live status page
Next: prove the KG branch in the Download Mode code, then (if it holds) the standard route of
a factory reset to move KG from Checking to Prenormal. Nothing was
flashed and no data was lost.
Practical facts
-
cmd user set-user-restrictiondidn't exist as expected on the tested build — the oem_lock transactions are the reliable way to flip these restrictions with uid 0. -
Kernel-context root shells can't reach servicemanager: use
runcon u:r:shell:s0 sh -c 'service call …'(uid stays 0, lookups work). - The PDB flag can be TRUE from the factory on some firmwares — software "allowed" while the bootloader refuses. Don't assume a TRUE getter means anything about the bootloader.