Skip to content

Delete the RKP-only properties instead of flipping them to false - #270

Merged
JingMatrix merged 1 commit into
devfrom
fix/persist-rkp-only-knobs
Oct 19, 2026
Merged

JingMatrix merged 1 commit into
devfrom
fix/persist-rkp-only-knobs

Conversation

@JingMatrix

@JingMatrix JingMatrix commented Aug 21, 2026 •

Copy link
Copy Markdown
Owner

The two remote_provisioning.*.rkp_only properties gate whether keystore2 provisions attestation keys only through RKP or may fall back to a batch key; letting the batch key through is what routes a target app's key generation onto our keybox. They are plain system properties, not persist.*, and a vendor .prop that init re-applies each boot can reassert the on default (#236), so a live resetprop reverts on the next boot (#244) — and on some devices a delayed config sync reasserts it hours later with no reboot at all (#262). Flipping the value to false also leaves an anomalous reading behind: a device that ships true now reports false.

So instead of flipping the property, the Keyboxes screen now deletes it. resetprop --delete removes it entirely; keystore2 and our hook both read an unset property as false, so the keybox path is reached exactly as before, but the device looks like one that never declared RKP-only enforcement rather than one with a suspiciously flipped value. The choice is recorded as a delete intent in /data/adb/teesim/rkp.json ({ property: value-at-deletion }) and re-enforced at daemon start and on every push: a property the vendor .prop brought back is simply deleted again. Only the two known rkp_only names are ever deleted or persisted, so a stale or hand-edited rkp.json can never steer resetprop at an arbitrary property.

A deleted property reads back as unset — indistinguishable from one the device never shipped — so the daemon exposes its delete-intent list (GET /rkp) and the WebUI tells the two apart: a present property shows a trash button that deletes it, a deleted one shows a Restore control carrying the value remembered at deletion, and a property that is merely absent is hidden. Delete and restore run in the daemon under the same lock as the boot/push re-enforce (POST /rkp/delete, POST /rkp/restore), so a live change can never interleave with it.

Each of them also re-pushes the config, because the push is what carries the delete-intent set to the hook: the data-dir watcher only fires for config.json and keyboxes, so without it a fresh intent would not reach the hook until some unrelated push happened, and until then a reasserted property would read as a genuine rkp_only=true and never be reported for a re-delete. Restoring a property that was already unset when it was deleted only drops the intent — writing a plausible-looking true onto a device that never declared the knob would leave exactly the anomalous reading deletion exists to avoid.

enable_rkpd is persist.device_config and already survives on its own, so it stays a plain toggle and is not persisted here.

Some devices reassert the property hours after boot, between pushes, with no reboot at all (#262), which the boot/push pass alone would miss. The daemon pushes the delete-intent set down to the hook; when the hook reads an rkp_only property live despite that intent, it still allows that one lookup — denying an rkp-only level fails key generation outright, worse than the single check it races with — but notes the reassertion, which rides back on the getUsage reply. The daemon then re-deletes the property live, so the next provisioning reads it unset and takes the keybox path. keystore1 never resolves a remotely-provisioned key, so it no-ops both ends.

Also folded in a CI fix the clippy step now trips over: rust/build.sh resolved the newest NDK under $ANDROID_HOME/ndk, and on a runner that is a preinstalled one rather than the version the workflow installs. NDK 30 rejects the unversioned target triple cargo-ndk passes, so openssl-sys's bindgen dies on its cdefs.h (Unversioned target triples are not supported!). It now honours ANDROID_NDK_HOME/ANDROID_NDK_ROOT first, which is how the workflow already passes the pin; CMake always passed NDK_HOME explicitly, so only a direct build.sh clippy call was affected.

Closes #244.

@JingMatrix
JingMatrix force-pushed the fix/persist-rkp-only-knobs branch from d023eaa to 4322dc9 Compare October 2, 2026 10:23
@JingMatrix JingMatrix changed the title Persist the RKP-only knobs so a toggle survives a reboot Delete the RKP-only properties instead of flipping them to false Oct 2, 2026
@JingMatrix

Copy link
Copy Markdown
Owner Author

@waichanrx @Retro2512 @FroggMaster @bocho8 @KoljaFrahm @rossiscool123 — this is my final design here: instead of flipping the rkp_only properties to false (which leaves an anomalous value behind), it deletes them to reduce the detection surface, keeps them deleted across reboots, and re-deletes them if a device reasserts them later (#262).

Test build: https://github.com/JingMatrix/TEESimulator/actions/runs/36995223456 (sign in, grab the Debug or Release artifact at the bottom).

Please confirm: on the Keyboxes screen, delete the two RKP-only knobs, reboot, and check they stay gone — and that Restore puts them back. Thanks!

The two remote_provisioning.*.rkp_only properties gate whether keystore2 provisions
attestation keys only through RKP or may fall back to a batch key; letting the batch key
through is what routes a target app's key generation onto our keybox. They are plain system
properties, not persist.*, and a vendor .prop that init re-applies each boot can reassert the
on default (#236), so a live resetprop reverts on the next boot (#244) -- and on some devices
a delayed config sync reasserts it hours later with no reboot at all (#262). Writing false
also leaves an anomalous value behind: a device that ships true now reads false, which is
itself a signal.

So instead of flipping the property, the user now deletes it. resetprop --delete removes it
entirely; keystore2 and our hook both read an unset property as false, so the keybox path is
reached exactly as before, but the device now looks like one that never declared RKP-only
enforcement rather than one with a suspiciously flipped value. The choice is recorded as a
delete intent in /data/adb/teesim/rkp.json ({ property: value-at-deletion }) and re-enforced
at daemon start and on every push (applyRkpDeletions): a property the vendor .prop brought
back is simply deleted again. Only the two known rkp_only names are ever deleted or persisted,
so a stale or hand-edited rkp.json can never steer resetprop at an arbitrary property.

Because a deleted property reads back as unset -- indistinguishable from one the device never
shipped -- the daemon exposes its delete-intent list (GET /rkp) so the WebUI can tell the two
apart: a present property shows a trash control that deletes it, a deleted one shows a Restore
control (carrying the value remembered at deletion) that sets it back and clears the intent,
and a property that is merely absent is hidden. Delete and restore run in the daemon under the
same lock as the boot/push re-enforce (POST /rkp/delete, POST /rkp/restore), so a live change
can never interleave with it.

Each of them also re-pushes the config, because the push is what carries the delete-intent set
to the hook: the data-dir watcher only fires for config.json and keyboxes, so without it a
fresh intent would not reach the hook until some unrelated push happened, and until then a
reasserted property would read as a genuine rkp_only=true. Restoring a property that was
already unset when it was deleted only drops the intent -- writing a plausible-looking true
onto a device that never declared the knob would leave exactly the anomalous reading deletion
exists to avoid.

enable_rkpd is persist.device_config and already survives on its own, so it is no longer
routed through the daemon or persisted here -- it stays a plain WebUI toggle.

Some devices reassert the property hours after boot, between pushes, with no reboot at all
(#262), which the eager pass alone would miss. The daemon pushes the delete-intent set down to
the hook; when the hook reads an rkp_only prop live despite that intent, it still allows that
one lookup -- denying an rkp-only level fails key generation outright, worse than the single
check it races with -- but notes the reassertion, which rides back on the getUsage reply. The
daemon then re-deletes the property live, so the next provisioning reads it unset and takes the
keybox path. keystore1 never resolves a remotely-provisioned key, so it no-ops both ends.

Also fixes the NDK that rust/build.sh resolves, which the clippy step now trips over. It took
the newest NDK under $ANDROID_HOME/ndk, and on a CI runner that is a preinstalled one rather
than the version the workflow installs; NDK 30 rejects the unversioned target triple cargo-ndk
passes, so openssl-sys's bindgen dies on its cdefs.h. It now honours ANDROID_NDK_HOME and
ANDROID_NDK_ROOT first, which is how the workflow already passes the pin. CMake always passed
NDK_HOME explicitly, so only a direct build.sh call was ever affected.

Closes #244.
@JingMatrix
JingMatrix force-pushed the fix/persist-rkp-only-knobs branch from 8d0a4ac to e0fd8df Compare October 19, 2026 11:01
@JingMatrix
JingMatrix merged commit f3fbd12 into dev Oct 19, 2026
1 check passed
Zhanfg added a commit to Zhanfg/TEESimulator that referenced this pull request Oct 22, 2026
…ingMatrix#270)

Stage safe patch hunks from f3fbd12 onto isolated OnePlus integration candidate.
Vendor-overlapping paths require separate semantic reconciliation before CI.
Zhanfg added a commit to Zhanfg/TEESimulator that referenced this pull request Oct 22, 2026
… architecture

Port App, KeyAdmin and C ABI changes from upstream JingMatrix#270 while preserving OnePlus conformance routes, startup protections and documented endpoints.
Zhanfg added a commit to Zhanfg/TEESimulator that referenced this pull request Oct 22, 2026
…dware-aware fork

Integrate upstream JingMatrix#270 into compatibility RKP policy without altering strict hardware routing. Preserve OEM read-only RKP indicator in the WebUI.
@JingMatrix
JingMatrix deleted the fix/persist-rkp-only-knobs branch October 24, 2026 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Remote Key Provision Turns Back On After Reboot

1 participant