From e5741f37237bd9edf29e59b95d1c13882ab64d65 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:30:23 +0800 Subject: [PATCH 01/14] vold: Don't persist KeyMint-upgraded key blobs Restore the AOSP guard that returns early when KeyMint did not upgrade the blob; without it *opHandle.getUpgradedBlob() dereferenced an empty std::optional on every retrieveKey() call. Also drop the write to /tmp/keymaster_key_blob/, which nothing reads. A blob rebound to a newer OS version or patch level can no longer be opened by the installed system, so keep the upgrade in memory only. --- KeyStorage.cpp | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/KeyStorage.cpp b/KeyStorage.cpp index a186c04..be66f88 100644 --- a/KeyStorage.cpp +++ b/KeyStorage.cpp @@ -335,12 +335,8 @@ static KeystoreOperation BeginKeystoreOp(Keystore& keystore, const std::string& auto blob_file = dir + "/" + kFn_keymaster_key_blob; LOG(INFO) << "reading blob_file: " << blob_file; - std::string blob_dir(kFn_keymaster_key_blob); - std::string temp_dir = "/tmp/" + blob_dir + "/"; - if (TEMP_FAILURE_RETRY(mkdir(temp_dir.c_str(), 0700)) == -1) { - PLOG(ERROR) << "key mkdir " << temp_dir; - } - auto upgraded_blob_file = temp_dir + kFn_keymaster_key_blob; + // Never persist an upgraded blob: the installed system cannot open a blob + // rebound to a newer OS version or patch level. // auto upgraded_blob_file = dir + "/" + kFn_keymaster_key_blob_upgraded; std::lock_guard lock(key_upgrade_lock); @@ -360,17 +356,15 @@ static KeystoreOperation BeginKeystoreOp(Keystore& keystore, const std::string& if (!opHandle) return opHandle; // If key blob wasn't upgraded, nothing left to do. - // if (!opHandle.getUpgradedBlob()) return opHandle; + if (!opHandle.getUpgradedBlob()) return opHandle; // if (already_upgraded) { // LOG(ERROR) << "Unexpected case; already-upgraded key " << upgraded_blob_file // << " still requires upgrade"; // return KeystoreOperation(); // } - LOG(INFO) << "Upgrading key: " << blob_file; - - if (!writeStringToFile(*opHandle.getUpgradedBlob(), upgraded_blob_file)) - return KeystoreOperation(); + LOG(WARNING) << "KeyMint upgraded " << blob_file + << " for this operation only; the on-disk blob is left unchanged"; // if (cp_needsCheckpoint()) { // LOG(INFO) << "Wrote upgraded key to " << upgraded_blob_file // << "; delaying commit due to checkpoint"; From 10594141bfc6790b8048cbdfafc965de503a2313 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:31:50 +0800 Subject: [PATCH 02/14] vold: Don't wipe Keystore when the metadata key is missing AOSP calls deleteAllKeys() here because a missing metadata key means a factory reset is in progress. Recovery has no such guarantee: the key also looks missing when /metadata failed to mount, and wiping Keystore destroys every key on the device, not just the FBE ones. read_key() is reached with neverGen() from TWRP, so a genuinely missing key still fails safely in retrieveOrGenerateKey(). --- MetadataCrypt.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/MetadataCrypt.cpp b/MetadataCrypt.cpp index 3842e8f..bc2e7ea 100644 --- a/MetadataCrypt.cpp +++ b/MetadataCrypt.cpp @@ -130,15 +130,9 @@ static bool read_key(const std::string& metadata_key_dir, const KeyGeneration& g auto in_dsu = android::base::GetBoolProperty("ro.gsid.image_running", false); // !pathExists(dir) does not imply there's a factory reset when in DSU mode. if (!pathExists(dir) && !in_dsu && first_key) { - auto delete_all = android::base::GetBoolProperty( - "ro.crypto.metadata_init_delete_all_keys.enabled", false); - if (delete_all) { - LOG(INFO) << "Metadata key does not exist, calling deleteAllKeys"; - Keystore::deleteAllKeys(); - } else { - LOG(INFO) << "Metadata key does not exist but " - "ro.crypto.metadata_init_delete_all_keys.enabled is false"; - } + // AOSP wipes Keystore here, assuming a missing key means a factory + // reset. In recovery it usually means /metadata failed to mount. + LOG(WARNING) << "Metadata key does not exist at " << dir << ", not wiping Keystore"; } auto temp = metadata_key_dir + "/tmp"; return retrieveOrGenerateKey(dir, temp, kEmptyAuthentication, gen, key); From 687d9bd7424889f5f92407fdacd96c5da5c9dcdf Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:32:51 +0800 Subject: [PATCH 03/14] vold: Never create FBE keys in recovery fscrypt_initialize_systemwide_keys() and fscrypt_init_user0() are AOSP first-boot paths: they generate the device key and user 0's DE/CE keys whenever those are missing. In recovery a key reads as missing whenever we simply failed to open it, and generating a replacement makes the existing /data unrecoverable. Pass neverGen() for the device key and fail outright when user 0's keys are not found. --- FsCrypt.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/FsCrypt.cpp b/FsCrypt.cpp index fa952a6..799c65a 100644 --- a/FsCrypt.cpp +++ b/FsCrypt.cpp @@ -551,8 +551,10 @@ bool fscrypt_initialize_systemwide_keys() { KeyBuffer device_key; install: + // Never generate the device key here: a new one makes the existing /data + // permanently unreadable. if (!retrieveOrGenerateKey(device_key_path, device_key_temp, kEmptyAuthentication, - makeGen(s_data_options), &device_key)) + android::vold::neverGen(), &device_key)) return false; // This initializes s_device_policy, which is a global variable so that @@ -659,11 +661,17 @@ bool fscrypt_init_user0() { if (!prepare_dir(user_key_dir + "/ce", 0700, AID_ROOT, AID_ROOT)) return false; if (!prepare_dir(user_key_dir + "/de", 0700, AID_ROOT, AID_ROOT)) return false; - // Create user 0's DE and CE keys if they don't already exist. Check - // each key independently, since if the first boot was interrupted it is - // possible that the DE key exists but the CE key does not. - if (!de_key_exists(0) && !create_de_key(0, false)) return false; - if (!ce_key_exists(0) && !create_ce_key(0, false)) return false; + // AOSP creates user 0's keys here when they are missing. That is right + // on a first boot, but in recovery a missing key means we failed to + // read it, and creating a new one throws the user's data away. + if (!de_key_exists(0)) { + LOG(ERROR) << "DE key for user 0 not found, refusing to create one"; + return false; + } + if (!ce_key_exists(0)) { + LOG(ERROR) << "CE key for user 0 not found, refusing to create one"; + return false; + } // TODO: switch to loading only DE_0 here once framework makes // explicit calls to install DE keys for secondary users From dfe2eb14c8c0ae66bafeaf02eeb9b1178fa83809 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:33:36 +0800 Subject: [PATCH 04/14] vold: Don't rewrite /data/unencrypted key metadata These files exist so that init can pick up the device DE policy on the next boot, and the installed system rewrites them itself. Recovery only needs s_device_policy in memory, so writing them here just puts three more writes on /data during decryption; per_boot_ref in particular was overwritten with a key that is discarded on reboot. --- FsCrypt.cpp | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/FsCrypt.cpp b/FsCrypt.cpp index 799c65a..6e3b416 100644 --- a/FsCrypt.cpp +++ b/FsCrypt.cpp @@ -570,28 +570,16 @@ bool fscrypt_initialize_systemwide_keys() { return false; } - std::string options_string; - if (!OptionsToString(s_device_policy.options, &options_string)) { - LOG(ERROR) << "Unable to serialize options"; - return false; - } - std::string options_filename = std::string(DATA_MNT_POINT) + fscrypt_key_mode; - if (!android::vold::writeStringToFile(options_string, options_filename)) return false; - - std::string ref_filename = std::string(DATA_MNT_POINT) + fscrypt_key_ref; + // AOSP writes /data/unencrypted/{mode,ref,per_boot_ref} here for init to + // pick up on the next boot. The installed system owns those files and + // rewrites them itself, so recovery only keeps the policy in memory. de_key_raw_ref = s_device_policy.key_raw_ref; - if (!android::vold::writeStringToFile(s_device_policy.key_raw_ref, ref_filename)) return false; - LOG(INFO) << "Wrote system DE key reference to:" << ref_filename; KeyBuffer per_boot_key; if (!generateStorageKey(makeGen(s_data_options), &per_boot_key)) return false; EncryptionPolicy per_boot_policy; if (!install_storage_key(DATA_MNT_POINT, s_data_options, per_boot_key, &per_boot_policy)) return false; - std::string per_boot_ref_filename = std::string("/data") + fscrypt_key_per_boot_ref; - if (!android::vold::writeStringToFile(per_boot_policy.key_raw_ref, per_boot_ref_filename)) - return false; - LOG(INFO) << "Wrote per boot key reference to:" << per_boot_ref_filename; return true; } From e61cad4e3857100d147973ec47338f9dfc8ed246 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:34:35 +0800 Subject: [PATCH 05/14] vold: Don't fixate CE keys when reading them AOSP fixates on a successful read: every sibling key directory is passed to destroyKey(), which secdiscards the files and deletes the KeyMint key from Keystore. Normally only "current" exists, but a user midway through a credential change also has a cx* binding, and losing it in recovery is not recoverable. Rename to read_user_ce_key() since it no longer fixates. --- FsCrypt.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/FsCrypt.cpp b/FsCrypt.cpp index 6e3b416..54dd91c 100644 --- a/FsCrypt.cpp +++ b/FsCrypt.cpp @@ -209,17 +209,17 @@ static bool fixate_user_ce_key(const std::string& directory_path, const std::str return true; } -static bool read_and_fixate_user_ce_key(userid_t user_id, - const android::vold::KeyAuthentication& auth, - KeyBuffer* ce_key) { +// Unlike AOSP this does not fixate the key it found: dropping the sibling +// bindings is a Keystore deletion we cannot undo if the user was midway +// through a credential change. +static bool read_user_ce_key(userid_t user_id, const android::vold::KeyAuthentication& auth, + KeyBuffer* ce_key) { auto const directory_path = get_ce_key_directory_path(user_id); auto const paths = get_ce_key_paths(directory_path); for (auto const ce_key_path : paths) { LOG(INFO) << "Trying user CE key " << ce_key_path; if (retrieveKey(ce_key_path, auth, ce_key)) { LOG(INFO) << "Successfully retrieved key"; - s_deferred_fixations.erase(directory_path); - fixate_user_ce_key(directory_path, ce_key_path, paths); return true; } } @@ -842,10 +842,10 @@ bool fscrypt_set_ce_key_protection(userid_t user_id, const std::string& secret_h // at upgrade time, when CE keys that were previously protected by // kEmptyAuthentication are encrypted by the user's synthetic password. LOG(INFO) << "CE key already exists on-disk; re-protecting it with the given secret"; - if (!read_and_fixate_user_ce_key(user_id, kEmptyAuthentication, &ce_key)) { + if (!read_user_ce_key(user_id, kEmptyAuthentication, &ce_key)) { // Before failing, also check whether the key is already protected // with the given secret. - if (read_and_fixate_user_ce_key(user_id, *auth, &ce_key)) { + if (read_user_ce_key(user_id, *auth, &ce_key)) { LOG(INFO) << "CE key is already protected by given secret. Nothing to do."; LOG(INFO) << "Errors above are for the attempt with empty auth and can be ignored."; return true; @@ -926,7 +926,7 @@ bool fscrypt_unlock_ce_storage(userid_t user_id, const std::string& secret_hex) auto auth = authentication_from_hex(secret_hex); if (!auth) return false; KeyBuffer ce_key; - if (!read_and_fixate_user_ce_key(user_id, *auth, &ce_key)) return false; + if (!read_user_ce_key(user_id, *auth, &ce_key)) return false; EncryptionPolicy ce_policy; if (!install_storage_key(DATA_MNT_POINT, s_data_options, ce_key, &ce_policy)) return false; s_ce_policies[user_id].internal = ce_policy; From 94bef10257a13c768ef1d3d8c51c83667063c609 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:35:17 +0800 Subject: [PATCH 06/14] vold: Fix undefined behaviour in the legacy Decrypt_User path The GateKeeper callback only assigns auth_token and auth_token_len on success, but the caller used them unconditionally: hwRet.isOk() is still true when GateKeeper rejects the password, so a rejected unlock sized a stack VLA from an uninitialized length. The hex buffer it built was never read, and the token itself was leaked, so drop the buffer, free the token and check ret before continuing. --- Decrypt.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/Decrypt.cpp b/Decrypt.cpp index a02bad8..a1f35bb 100755 --- a/Decrypt.cpp +++ b/Decrypt.cpp @@ -1018,9 +1018,9 @@ extern "C" int Get_Password_Type(const userid_t user_id, std::string& filename) extern "C" bool Decrypt_User(const userid_t user_id, const std::string& Password) { printf("Attempting to decrypt user\n"); - uint8_t *auth_token; - uint32_t auth_token_len; - int ret; + uint8_t *auth_token = NULL; + uint32_t auth_token_len = 0; + int ret = -1; struct stat st; if (user_id > 9999) { @@ -1089,16 +1089,12 @@ extern "C" bool Decrypt_User(const userid_t user_id, const std::string& Password } } ); - if (!hwRet.isOk()) { + delete[] auth_token; + if (!hwRet.isOk() || ret != 0) { + printf("gatekeeper verification failed\n"); return false; } - char token_hex[(auth_token_len*2)+1]; - token_hex[(auth_token_len*2)] = 0; - uint32_t i; - for (i=0;i Date: Sun, 2 Aug 2026 11:36:03 +0800 Subject: [PATCH 07/14] vold: KeystoreInfo: Close locksettings.db on the remaining error paths The step-failure path returned without finalizing the statement or closing the database, and sqlite3_open() can allocate a handle even when it reports failure. Either one leaves /data/system/locksettings.db open and /data busy on unmount. --- KeystoreInfo.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/KeystoreInfo.cpp b/KeystoreInfo.cpp index a74a713..3a9d69b 100755 --- a/KeystoreInfo.cpp +++ b/KeystoreInfo.cpp @@ -53,6 +53,7 @@ std::string KeystoreInfo::getHandle(const userid_t user_id) { rc = sqlite3_open("/data/system/locksettings.db", &db); if (rc) { fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db)); + sqlite3_close(db); return ""; } std::string sql = "SELECT * FROM locksettings WHERE name = 'sp-handle' AND user = " + std::to_string(user_id); @@ -67,6 +68,8 @@ std::string KeystoreInfo::getHandle(const userid_t user_id) { } if (rc != SQLITE_DONE) { fprintf(stderr, "error: %s\n", sqlite3_errmsg(db)); + sqlite3_finalize(stmt); + sqlite3_close(db); return ""; } sqlite3_finalize(stmt); From 7889a41817e7a598f0815d2103cb31c4e3a8e3e2 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:36:47 +0800 Subject: [PATCH 08/14] vold: Zero-pad the spblob file name to 16 digits uint2hex() renders the sp-handle unpadded, which is what the Keystore alias needs, but the spblob files are named after the handle padded to 16 digits. The fallback only tried one and two leading zeros, so a handle with more than two high zero digits failed to open. --- Decrypt.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/Decrypt.cpp b/Decrypt.cpp index a1f35bb..87f23e3 100755 --- a/Decrypt.cpp +++ b/Decrypt.cpp @@ -296,18 +296,17 @@ bool Get_Spblob_Data(const std::string& spblob_path, const std::string& handle_s } else found_file = true; } else { + // The Keystore alias uses the handle as-is, but the spblob files are + // named after it zero-padded to 16 digits. printf("trying to read %s_file data with leading 0\n", tag.c_str()); - std::vector file_paths = { - spblob_path + "0" + handle_str + suffix, - spblob_path + "00" + handle_str + suffix - }; - for (auto& file : file_paths) { - if (!android::base::ReadFileToString(file, data)) { - printf("Failed to read '%s'\n", file.c_str()); - } else { - found_file = true; - break; - } + std::string padded = handle_str.size() < 16 + ? std::string(16 - handle_str.size(), '0') + handle_str + : handle_str; + file = spblob_path + padded + suffix; + if (!android::base::ReadFileToString(file, data)) { + printf("Failed to read '%s'\n", file.c_str()); + } else { + found_file = true; } } return found_file; From 13fdb4cb0179b8f19c1629afb17bc4d5e6d3e8d1 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:37:34 +0800 Subject: [PATCH 09/14] vold: Add a timeout to the keystore2 service lookups AServiceManager_waitForService() never returns if the service does not register, so a keystore2 that fails to start leaves the decrypt screen hung with no error. Poll checkService() instead; the callers already handle a null binder. Allow thirty seconds rather than a few. Devices whose KeyMint HAL is started from a script after the version properties have been fixed up keep keystore2 blocked until that happens, and it is on the far side of mounting a partition and reading its build.prop. --- Keystore.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Keystore.cpp b/Keystore.cpp index d7b1a0f..24b78ef 100644 --- a/Keystore.cpp +++ b/Keystore.cpp @@ -16,6 +16,8 @@ #include "Keystore.h" +#include + #include #include @@ -103,8 +105,22 @@ bool KeystoreOperation::finish(std::string* output) { return true; } +// AServiceManager_waitForService() waits forever, which hangs the decrypt UI +// when a service cannot come up at all. Poll so the caller can report failure, +// but leave enough room for a device that starts keystore2 or the KeyMint HAL +// from a script partway through decryption. +static ::ndk::SpAIBinder waitForService(const char* name) { + for (int i = 0; i < 300; i++) { + ::ndk::SpAIBinder binder(AServiceManager_checkService(name)); + if (binder.get() != nullptr) return binder; + usleep(100000); + } + LOG(ERROR) << "Timed out waiting for " << name; + return ::ndk::SpAIBinder(); +} + Keystore::Keystore() { - ::ndk::SpAIBinder binder(AServiceManager_waitForService(keystore2_service_name)); + ::ndk::SpAIBinder binder(waitForService(keystore2_service_name)); auto keystore2Service = ks2::IKeystoreService::fromBinder(binder); if (!keystore2Service) { @@ -224,7 +240,7 @@ KeystoreOperation Keystore::begin(const std::string& key, const km::Authorizatio } void Keystore::earlyBootEnded() { - ::ndk::SpAIBinder binder(AServiceManager_waitForService(maintenance_service_name)); + ::ndk::SpAIBinder binder(waitForService(maintenance_service_name)); auto maint_service = ks2_maint::IKeystoreMaintenance::fromBinder(binder); if (!maint_service) { @@ -237,7 +253,7 @@ void Keystore::earlyBootEnded() { } void Keystore::deleteAllKeys() { - ::ndk::SpAIBinder binder(AServiceManager_waitForService(maintenance_service_name)); + ::ndk::SpAIBinder binder(waitForService(maintenance_service_name)); auto maint_service = ks2_maint::IKeystoreMaintenance::fromBinder(binder); if (!maint_service) { From 8ee4a151777f1194c9881478e0e8b4a564048914 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:38:46 +0800 Subject: [PATCH 10/14] vold: Give libvold the file_contexts handle AOSP expects sehandle.h declares sehandle at global scope and AOSP has every executable that links libvold define and initialize it: main.cpp for vold, VoldFuzzer.cpp for the fuzzer. libvold itself only reads it. A second definition was later added inside namespace android::vold in Utils.cpp so that recovery, which links libvold without vold's main(), would still link. That definition shadows the global one for every unqualified use in the namespace, so PrepareDir() and CreateDeviceNode() read a pointer nothing ever assigns. It was harmless while AOSP still guarded the lookup with if (sehandle), but once that guard went away selabel_lookup() started faulting on it, and the lookup was commented out of PrepareDir() in response. Every directory created under /data since then inherits its parent's label. Keep a single definition at global scope and open the handle on first use, so main() and the fuzzer keep their eager initialization while recovery gets one too, then restore the AOSP labeling. --- Utils.cpp | 54 +++++++++++++++++++++++++++++++------------- main.cpp | 8 +------ sehandle.h | 12 ++++++++++ tests/VoldFuzzer.cpp | 6 +---- 4 files changed, 52 insertions(+), 28 deletions(-) diff --git a/Utils.cpp b/Utils.cpp index 4e2d221..4eff4d7 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -66,6 +66,11 @@ using android::base::StartsWith; using android::base::StringPrintf; using android::base::unique_fd; +// Declared by sehandle.h at global scope. AOSP has each executable define and +// initialize this in its own main(); it lives here instead so that the ones +// that link libvold without a main() of their own still get a handle. +struct selabel_handle* sehandle; + namespace android { namespace vold { @@ -89,12 +94,24 @@ static const char* kAppObbDir = "/Android/obb/"; static const char* kMediaProviderCtx = "u:r:mediaprovider:"; static const char* kMediaProviderAppCtx = "u:r:mediaprovider_app:"; -struct selabel_handle* sehandle; // Lock used to protect process-level SELinux changes from racing with each // other between multiple threads. static std::mutex kSecurityLock; +struct selabel_handle* GetSehandle() { + static std::once_flag once; + std::call_once(once, [] { + sehandle = selinux_android_file_context_handle(); + if (!sehandle) { + LOG(ERROR) << "Failed to get SELinux file contexts handle"; + return; + } + selinux_android_set_sehandle(sehandle); + }); + return sehandle; +} + std::string GetFuseMountPathForUser(userid_t user_id, const std::string& relative_upper_path) { return StringPrintf("/mnt/user/%d/%s", user_id, relative_upper_path.c_str()); } @@ -106,7 +123,10 @@ status_t CreateDeviceNode(const std::string& path, dev_t dev) { auto secontext = std::unique_ptr(nullptr, freecon); char* tmp_secontext; - if (selabel_lookup(sehandle, &tmp_secontext, cpath, S_IFBLK) == 0) { + auto* handle = GetSehandle(); + if (!handle) { + LOG(WARNING) << "No file_contexts, " << path << " will inherit its parent's label"; + } else if (selabel_lookup(handle, &tmp_secontext, cpath, S_IFBLK) == 0) { secontext.reset(tmp_secontext); if (setfscreatecon(secontext.get()) != 0) { LOG(ERROR) << "Failed to setfscreatecon for device node " << path; @@ -456,20 +476,22 @@ status_t PrepareDir(const std::string& path, mode_t mode, uid_t uid, gid_t gid, auto clearfscreatecon = android::base::make_scope_guard([] { setfscreatecon(nullptr); }); auto secontext = std::unique_ptr(nullptr, freecon); char* tmp_secontext; -// if (selabel_lookup(sehandle, &tmp_secontext, cpath, S_IFDIR) == 0) { -// LOG(INFO) << "PrepareDir selabel_lookup"; -// secontext.reset(tmp_secontext); -// LOG(INFO) << "PrepareDir secontext reset"; -// if (setfscreatecon(secontext.get()) != 0) { -// LOG(ERROR) << "Failed to setfscreatecon for directory " << path; -// return -EINVAL; -// } -// } else if (errno == ENOENT) { -// LOG(INFO) << "No selabel defined for directory " << path; -// } else { -// LOG(ERROR) << "Failed to look up selabel for directory " << path; -// return -errno; -// } + + auto* handle = GetSehandle(); + if (!handle) { + LOG(WARNING) << "No file_contexts, " << path << " will inherit its parent's label"; + } else if (selabel_lookup(handle, &tmp_secontext, cpath, S_IFDIR) == 0) { + secontext.reset(tmp_secontext); + if (setfscreatecon(secontext.get()) != 0) { + LOG(ERROR) << "Failed to setfscreatecon for directory " << path; + return -EINVAL; + } + } else if (errno == ENOENT) { + LOG(DEBUG) << "No selabel defined for directory " << path; + } else { + PLOG(ERROR) << "Failed to look up selabel for directory " << path; + return -errno; + } if (fs_prepare_dir(cpath, mode, uid, gid) != 0) return -errno; if (attrs && SetAttrs(path, attrs) != 0) return -errno; diff --git a/main.cpp b/main.cpp index bdce76e..ebb8754 100644 --- a/main.cpp +++ b/main.cpp @@ -57,7 +57,6 @@ static void parse_args(int argc, char** argv); static void VoldLogger(android::base::LogId log_buffer_id, android::base::LogSeverity severity, const char* tag, const char* file, unsigned int line, const char* message); -struct selabel_handle* sehandle; android::base::LogdLogger logd_logger(android::base::SYSTEM); using android::base::StringPrintf; @@ -82,12 +81,7 @@ int main(int argc, char** argv) { parse_args(argc, argv); - sehandle = selinux_android_file_context_handle(); - if (!sehandle) { - LOG(ERROR) << "Failed to get SELinux file contexts handle"; - exit(1); - } - selinux_android_set_sehandle(sehandle); + if (!android::vold::GetSehandle()) exit(1); mkdir("/dev/block/vold", 0755); diff --git a/sehandle.h b/sehandle.h index 8921db5..7574843 100644 --- a/sehandle.h +++ b/sehandle.h @@ -21,4 +21,16 @@ extern struct selabel_handle* sehandle; +namespace android { +namespace vold { + +/* Returns the file_contexts handle, opening it on first use. vold's main() + * calls this at startup; executables that link libvold without it, such as + * recovery, get the handle on their first PrepareDir(). Returns null when + * file_contexts could not be opened. */ +struct selabel_handle* GetSehandle(); + +} // namespace vold +} // namespace android + #endif diff --git a/tests/VoldFuzzer.cpp b/tests/VoldFuzzer.cpp index 173c765..b0b93e9 100644 --- a/tests/VoldFuzzer.cpp +++ b/tests/VoldFuzzer.cpp @@ -24,15 +24,11 @@ using ::android::fuzzService; using ::android::sp; -struct selabel_handle* sehandle; - extern "C" int LLVMFuzzerInitialize(int argc, char argv) { - sehandle = selinux_android_file_context_handle(); - if (!sehandle) { + if (!android::vold::GetSehandle()) { LOG(ERROR) << "Failed to get SELinux file contexts handle in voldFuzzer!"; exit(1); } - selinux_android_set_sehandle(sehandle); return 0; } From 17aa7d87d35bb162000c45e8b2502d7c694aeb77 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:39:29 +0800 Subject: [PATCH 11/14] vold: Relabel directly in RestoreconRecursive Setting selinux.restorecon_recursive only works because AOSP's init.rc has a trigger for it. Recovery's init does not, and WaitForProperty() returns immediately since we just set the value ourselves, so the three calls at the end of fscrypt_prepare_user_storage() did nothing at all. --- Utils.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Utils.cpp b/Utils.cpp index 4eff4d7..d55ebaf 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -1266,12 +1266,12 @@ bool IsSameFile(const std::string& path1, const std::string& path2) { status_t RestoreconRecursive(const std::string& path) { LOG(DEBUG) << "Starting restorecon of " << path; - static constexpr const char* kRestoreconString = "selinux.restorecon_recursive"; - - android::base::SetProperty(kRestoreconString, ""); - android::base::SetProperty(kRestoreconString, path); - - android::base::WaitForProperty(kRestoreconString, path); + // AOSP hands this to init through selinux.restorecon_recursive. Recovery's + // init has no trigger for that property, so relabel here instead. + if (selinux_android_restorecon(path.c_str(), SELINUX_ANDROID_RESTORECON_RECURSE) != 0) { + PLOG(ERROR) << "Failed to restorecon " << path; + return -errno; + } LOG(DEBUG) << "Finished restorecon of " << path; return OK; From 4a44b71232f75566f55659f8491dc4b3d71a9304 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:42:16 +0800 Subject: [PATCH 12/14] vold: Sync the keystore2 database while keystore2 is stopped copySqliteDb() streamed the file over the database keystore2 already had open, and left the stale -wal and -shm next to it. Stop keystore2 first, clear those, copy through the SQLite backup API so pending WAL content comes across, then start keystore2 again and wait for it to register. Do it from Decrypt_User_Synth_Pass() as well, since the synthetic password key is looked up by alias and needs the database in place whatever the credential type is. --- Decrypt.cpp | 69 ++++++++++++++++++++++++++++++++++++++++-------- Decrypt.h | 2 +- KeystoreInfo.cpp | 22 +++++++++++++++ KeystoreInfo.hpp | 1 + 4 files changed, 82 insertions(+), 12 deletions(-) diff --git a/Decrypt.cpp b/Decrypt.cpp index 87f23e3..40077d4 100755 --- a/Decrypt.cpp +++ b/Decrypt.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "cutils/properties.h" @@ -490,16 +491,57 @@ namespace keystore { } } - void copySqliteDb() { - std::string keystore_path = "/tmp/misc/keystore/"; - std::string dst = keystore_path + "persistent.sqlite"; - std::string src = "/data/misc/keystore/persistent.sqlite"; - std::ifstream srcif(src.c_str(), std::ios::binary); - std::ofstream dstof(dst.c_str(), std::ios::binary); - printf("copying '%s' to '%s'\n", src.c_str(), dst.c_str()); - dstof << srcif.rdbuf(); - srcif.close(); - dstof.close(); + static bool waitForServiceState(const char* service, const char* state) { + char current[PROPERTY_VALUE_MAX] = {}; + std::string prop = std::string("init.svc.") + service; + for (int i = 0; i < 100; i++) { + property_get(prop.c_str(), current, ""); + if (!strcmp(current, state)) return true; + usleep(100000); + } + printf("'%s' did not reach state '%s'\n", service, state); + return false; + } + + /* keystore2 runs with its database on tmpfs so that nothing done here can + * reach the real one. It still has to see the installed system's keys, so + * seed it from /data before the first keystore2 call. */ + bool syncKeystoreDb() { + static bool synced = false; + if (synced) return true; + + const std::string src = "/data/misc/keystore/persistent.sqlite"; + const std::string dst = "/tmp/misc/keystore/persistent.sqlite"; + if (!android::vold::pathExists(src)) { + printf("no keystore database at '%s'\n", src.c_str()); + return false; + } + + // keystore2 holds the destination open, so stop it for a clean copy. + printf("stopping keystore2 to sync '%s'\n", src.c_str()); + property_set("ctl.stop", "keystore2"); + waitForServiceState("keystore2", "stopped"); + + unlink("/tmp/misc/keystore/persistent.sqlite-wal"); + unlink("/tmp/misc/keystore/persistent.sqlite-shm"); + unlink(dst.c_str()); + + KeystoreInfo keystore_info; + bool copied = keystore_info.backupDatabase(src, dst); + if (copied) chmod(dst.c_str(), 0600); + + property_set("ctl.start", "keystore2"); + if (!waitForServiceState("keystore2", "running")) return false; + for (int i = 0; i < 100; i++) { + if (AServiceManager_checkService( + "android.system.keystore2.IKeystoreService/default") != nullptr) { + synced = copied; + return copied; + } + usleep(100000); + } + printf("keystore2 did not register after restart\n"); + return false; } /* C++ replacement for function of the same name @@ -733,6 +775,12 @@ bool Decrypt_User_Synth_Pass(const userid_t user_id, const std::string& Password // Get the handle: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/LockSettingsService.java#2017 KeystoreInfo keystore_info; std::string handle_str = keystore_info.getHandle(user_id); + // The synthetic password key is looked up by alias, so keystore2 needs the + // installed system's database before any of the calls below. + if (!android::keystore::syncKeystoreDb()) { + printf("Failed to sync the keystore database\n"); + return Free_Return(retval, weaver_key, &pwd); + } // Now we begin driving unwrapPasswordBasedSyntheticPassword from: https://android.googlesource.com/platform/frameworks/base/+/android-8.0.0_r23/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java#758 // First we read the password data which contains scrypt parameters // printf("pwd N %i R %i P %i salt ", pwd.scryptN, pwd.scryptR, pwd.scryptP); output_hex((char*)pwd.salt, pwd.salt_len); printf("\n"); @@ -750,7 +798,6 @@ bool Decrypt_User_Synth_Pass(const userid_t user_id, const std::string& Password return Free_Return(retval, weaver_key, &pwd); } } else { - android::keystore::copySqliteDb(); // early copy db for keystore std::string defpassword = "default-password"; memcpy(password_token, defpassword.data(), defpassword.length()); } diff --git a/Decrypt.h b/Decrypt.h index ff926dc..5afda85 100755 --- a/Decrypt.h +++ b/Decrypt.h @@ -34,7 +34,7 @@ static constexpr int NAMESPACE_LOCKSETTINGS = 103; namespace android { namespace keystore { - void copySqliteDb(); + bool syncKeystoreDb(); int Get_Password_Type(const userid_t user_id, std::string& filename); bool Decrypt_DE(); bool Decrypt_User(const userid_t user_id, const std::string& Password); diff --git a/KeystoreInfo.cpp b/KeystoreInfo.cpp index 3a9d69b..04a6811 100755 --- a/KeystoreInfo.cpp +++ b/KeystoreInfo.cpp @@ -77,6 +77,28 @@ std::string KeystoreInfo::getHandle(const userid_t user_id) { return uint2hex(value); } +// Uses the SQLite backup API rather than a plain file copy so that a database +// with pending WAL content is reproduced correctly. +bool KeystoreInfo::backupDatabase(const std::string& src, const std::string& dst) { + sqlite3 *src_db = NULL; + sqlite3 *dst_db = NULL; + bool ok = false; + + if (sqlite3_open_v2(src.c_str(), &src_db, SQLITE_OPEN_READONLY, NULL) == SQLITE_OK && + sqlite3_open(dst.c_str(), &dst_db) == SQLITE_OK) { + sqlite3_backup *backup = sqlite3_backup_init(dst_db, "main", src_db, "main"); + if (backup) { + sqlite3_backup_step(backup, -1); + ok = sqlite3_backup_finish(backup) == SQLITE_OK; + } + } + if (!ok) + fprintf(stderr, "Failed to back up '%s' to '%s'\n", src.c_str(), dst.c_str()); + sqlite3_close(dst_db); + sqlite3_close(src_db); + return ok; +} + std::string KeystoreInfo::getAlias(std::string handle) { std::string alias(SYNTHETIC_PASSWORD_KEY_PREFIX); alias = alias + handle; diff --git a/KeystoreInfo.hpp b/KeystoreInfo.hpp index a6a5850..9c40b4a 100755 --- a/KeystoreInfo.hpp +++ b/KeystoreInfo.hpp @@ -24,6 +24,7 @@ class KeystoreInfo { public: std::string getHandle(const userid_t user_id); std::string getAlias(std::string handle); +bool backupDatabase(const std::string& src, const std::string& dst); private: std::string uint2hex(int64_t num); From 08472478c8cbae8ff368c0978b3c2bd73c1623a1 Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 11:47:54 +0800 Subject: [PATCH 13/14] vold: Support AIDL GateKeeper in the legacy Decrypt_User path Decrypt_User_Synth_Pass() already tries the AIDL service before falling back to HIDL, but the gatekeeper.password.key path still called IGatekeeper::getService() only, so it returned null and failed outright on a device that declares just the AIDL GateKeeper. --- Decrypt.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/Decrypt.cpp b/Decrypt.cpp index 40077d4..774a696 100755 --- a/Decrypt.cpp +++ b/Decrypt.cpp @@ -1102,15 +1102,35 @@ extern "C" bool Decrypt_User(const userid_t user_id, const std::string& Password } bool should_reenroll; bool request_reenroll = false; - android::sp gk_device; - gk_device = ::android::hardware::gatekeeper::V1_0::IGatekeeper::getService(); - if (gk_device == nullptr) - return false; android::hardware::hidl_vec curPwdHandle; curPwdHandle.setToExternal(const_cast((const uint8_t *)handle.c_str()), st.st_size); android::hardware::hidl_vec enteredPwd; enteredPwd.setToExternal(const_cast((const uint8_t *)Password.c_str()), Password.size()); + // Devices that only declare the AIDL GateKeeper have no HIDL service. + constexpr const char gatekeeperServiceName[] = "android.hardware.gatekeeper.IGatekeeper/default"; + if (AServiceManager_isDeclared(gatekeeperServiceName)) { + ::ndk::SpAIBinder gkBinder(AServiceManager_waitForService(gatekeeperServiceName)); + auto aidl_gk_device = AidlIGatekeeper::fromBinder(gkBinder); + if (!aidl_gk_device) { + printf("failed to get gatekeeper service\n"); + return false; + } + AidlGatekeeperVerifyResp rsp; + auto result = aidl_gk_device->verify(user_id, 0 /* challenge */, curPwdHandle, enteredPwd, &rsp); + if (!result.isOk() || rsp.statusCode < AidlIGatekeeper::STATUS_OK) { + printf("gatekeeper verification failed\n"); + return false; + } + printf("GateKeeper status ok\n"); + std::string secret = HashPassword(Password); + return Decrypt_CE_storage(user_id, secret); + } + + android::sp gk_device; + gk_device = ::android::hardware::gatekeeper::V1_0::IGatekeeper::getService(); + if (gk_device == nullptr) + return false; android::hardware::Return hwRet = From 5fbbbd63af14c030cff9cc3b81ba93a8b8a3dcfd Mon Sep 17 00:00:00 2001 From: YuKongA <70465933+YuKongA@users.noreply.github.com> Date: Sun, 2 Aug 2026 12:17:57 +0800 Subject: [PATCH 14/14] vold: Report when KeyMint upgrades the synthetic password key createOperation() returns upgradedBlob whenever KeyMint rebinds the key because our OS version or patch levels do not match the installed system, and we were dropping it on the floor. It is the only direct evidence that the environment is wrong, so log it. The upgrade itself is contained: it only reaches the tmpfs Keystore database, and keystore2 gates its garbage collector, which is what would delete the superseded blob from KeyMint, on sys.boot_completed. Call that out too, since a device tree that sets the property to start its own services also removes that protection. --- Decrypt.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Decrypt.cpp b/Decrypt.cpp index 774a696..5029e0d 100755 --- a/Decrypt.cpp +++ b/Decrypt.cpp @@ -632,6 +632,25 @@ namespace keystore { printf("Begin Operation failed\n"); return disk_decryption_secret_key; } + if (encOperationResponse.upgradedBlob) { + /* KeyMint rebound the synthetic password key because our OS + * version or patch levels do not match the installed system. + * The upgrade only lives in the tmpfs Keystore database, so the + * blob on /data stays usable, but keystore2 has marked the old + * one superseded and its garbage collector deletes superseded + * blobs from KeyMint itself. That collector is gated on + * sys.boot_completed, so it stays idle here unless something + * sets that property. */ + printf("WARNING: KeyMint upgraded the synthetic password key\n"); + printf("WARNING: the recovery environment does not match the installed system\n"); + char boot_completed[PROPERTY_VALUE_MAX] = {}; + property_get("sys.boot_completed", boot_completed, ""); + if (!strcmp(boot_completed, "1")) { + printf("ERROR: sys.boot_completed is set, so keystore2 will garbage\n"); + printf("ERROR: collect the original key blob. Do not set that property\n"); + printf("ERROR: in recovery.\n"); + } + } std::optional> optPlaintext; begin_rc = encOperationResponse.iOperation->finish(cipher_text_hidlvec, {}, &optPlaintext);