The keystore binder service ("android.security.IKeystoreService") allows users to issue several commands related to key management, including adding, removing, exporting and generating cryptographic keys. The service is accessible to many SELinux contexts, including application contexts, but also unprivileged daemons such as "media.codec".
Binder calls to this service are unpacked by IKeyStoreService (http://androidxref.com/8.0.0_r4/xref/system/security/keystore/IKeystoreService.cpp), and are then passed on to be processed by KeyStoreService. The "generateKey" command is handled by "KeyStoreService::generateKey" (http://androidxref.com/8.0.0_r4/xref/system/security/keystore/key_store_service.cpp#691). Here is a snippet from this function:
1. KeyStoreServiceReturnCode KeyStoreService::generateKey(const String16& name,
2. const hidl_vec<KeyParameter>& params,
3. const hidl_vec<uint8_t>& entropy, int uid,
4. int flags,
5. KeyCharacteristics* outCharacteristics) {
6. uid = getEffectiveUid(uid);
7. KeyStoreServiceReturnCode rc =
8. checkBinderPermissionAndKeystoreState(P_INSERT, uid, flags & KEYSTORE_FLAG_ENCRYPTED);
9. if (!rc.isOk()) {
10. return rc;
11. }
12. if ((flags & KEYSTORE_FLAG_CRITICAL_TO_DEVICE_ENCRYPTION) && get_app_id(uid) != AID_SYSTEM) {
13. ALOGE("Non-system uid %d cannot set FLAG_CRITICAL_TO_DEVICE_ENCRYPTION", uid);
14. return ResponseCode::PERMISSION_DENIED;
15. }
16.
17. if (containsTag(params, Tag::INCLUDE_UNIQUE_ID)) {
18. if (!checkBinderPermission(P_GEN_UNIQUE_ID)) return ResponseCode::PERMISSION_DENIED;
19. }
20. ...
21. }
Like most KeyStore calls, this method uses "KeyStoreService::checkBinderPermission" in order to validate the calling process's permissions. This function uses a twofold approach to verify the caller (http://androidxref.com/8.0.0_r4/xref/system/security/keystore/key_store_service.cpp#checkBinderPermission):
1. The caller's UID is retrieved using IPCThreadState::self()->getCallingUid() and compared against an array of pre-populated UIDs and permissions ("user_perms")
1.1 If the UID matches any in the array, its permission set is retrieved from the array
1.2 If the UID isn't in the array, the default permission set is used ("DEFAULT_PERMS")
2. The caller's SELinux context is retrieved using getpidcon(...) using the PID from the binder transaction (IPCThreadState::self()->getCallingPid())
2.1 An SELinux access check is performed for the given context and operation
Specifically to our case, if a "generateKey" command is called with a "INCLUDE_UNIQUE_ID" tag, the KeyStore will use an attestation certificate for the generated key with an application-scoped and time-bounded device-unique ID. Since creating attestation keys is a privileged operation, it should not be carried out by any user.
This restriction is enforced using the SELinux context enforcement alone -- the "default" permission set ("DEFAULT_PERMS") contains the aforementioned permission:
static const perm_t DEFAULT_PERMS = static_cast<perm_t>(
P_GET_STATE | P_GET | P_INSERT | P_DELETE | P_EXIST | P_LIST | P_SIGN | P_VERIFY |
P_GEN_UNIQUE_ID /* Only privileged apps can do this, but enforcement is done by SELinux */);
As noted in the comment above, this API is restricted to "priv_app" SELinux contexts, which is enforced using validation #2 above.
However, using the calling PID in order to enforce access controls in binder calls is an invalid approach. This is since the calling PID can transition from zombie to dead, allowing other PIDs to take its place. Therefore, the following attack flow is possible:
1. Process A forks and creates process B
2. Process A cycles pids until it reaches the pid before its own
3. Process B issues a binder transaction for the KeyStore service, containing an INCLUDE_UNIQUE_ID tag
4. Process A kills process B, allowing it to transition to dead
5. Process A spawns a new "priv_app" instance, occupying process B's PID
If points 4-5 are completed before the KeyStore service performs the "getpidcon" call, the permission check will use the new app's SELinux context, allowing the access control checks to pass. Otherwise, since no ill effects happen if the race fails, an attacker can continue issuing calls until the race succeeds.
As for spawning a new "priv_app" instance, this can be achieved by issuing a query request to a content provider published by a "priv_app". Many such providers exist (the contacts provider, telephony provider, settings provider, etc.). In this case, I chose to use the "calendar" provider, as it was not running on the device to begin with (and is therefore had to be spawned in order to handle the query request).
In order to expand the timing window for the PoC, I've added a "sleep" call to the KeyStore service's "generateKey" call. You can find the patch under "keystore.diff".
After applying the patch, the attached PoC should be built as part of the Android source tree, by extracting the source files into "frameworks/native/cmds/keystorerace", and running a build (e.g., "mmm keystorerace"). The resulting binary ("keystorerace") contains the PoC code. Running it should result in a new device-unique key being generated, despite not being executed from a "priv_app".
Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43996.zip