Merge "Log the consequences of watch_apex_info failing" into main
diff --git a/fsverity/OWNERS b/fsverity/OWNERS
index f9e7b25..1f2485a 100644
--- a/fsverity/OWNERS
+++ b/fsverity/OWNERS
@@ -1,4 +1,3 @@
-alanstokes@google.com
 ebiggers@google.com
 jeffv@google.com
 jiyong@google.com
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 4878eb3..92a4bed 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -192,6 +192,11 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+cc_aconfig_library {
+    name: "libkeystore2_flags_cc",
+    aconfig_declarations: "keystore2_flags",
+}
+
 rust_aconfig_library {
     name: "libkeystore2_flags_rust",
     crate_name: "keystore2_flags",
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 626a1c0..8f5617f 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -1163,14 +1163,6 @@
         let mut persistent_path_str = "file:".to_owned();
         persistent_path_str.push_str(&persistent_path.to_string_lossy());
 
-        // Connect to database in specific mode
-        let persistent_path_mode = if keystore2_flags::wal_db_journalmode_v3() {
-            "?journal_mode=WAL".to_owned()
-        } else {
-            "?journal_mode=DELETE".to_owned()
-        };
-        persistent_path_str.push_str(&persistent_path_mode);
-
         Ok(persistent_path_str)
     }
 
@@ -2437,7 +2429,7 @@
         tx.execute("DELETE FROM persistent.keyparameter WHERE keyentryid = ?;", params![key_id])
             .context("Trying to delete keyparameters.")?;
         tx.execute("DELETE FROM persistent.grant WHERE keyentryid = ?;", params![key_id])
-            .context("Trying to delete grants.")?;
+            .context("Trying to delete grants to other apps.")?;
         // The associated blobentry rows are not immediately deleted when the owning keyentry is
         // removed, because a KeyMint `deleteKey()` invocation is needed (specifically for the
         // `KEY_BLOB`).  That should not be done from within the database transaction.  Also, calls
@@ -2452,6 +2444,19 @@
         Ok(updated != 0)
     }
 
+    fn delete_received_grants(tx: &Transaction, user_id: u32) -> Result<bool> {
+        let updated = tx
+            .execute(
+                &format!("DELETE FROM persistent.grant WHERE cast ( (grantee/{AID_USER_OFFSET}) as int) = ?;"),
+                params![user_id],
+            )
+            .context(format!(
+                "Trying to delete grants received by user ID {:?} from other apps.",
+                user_id
+            ))?;
+        Ok(updated != 0)
+    }
+
     /// Marks the given key as unreferenced and removes all of the grants to this key.
     /// Returns Ok(true) if a key was marked unreferenced as a hint for the garbage collector.
     pub fn unbind_key(
@@ -2523,7 +2528,19 @@
                 );",
                 params![domain.0, namespace, KeyType::Client],
             )
-            .context("Trying to delete grants.")?;
+            .context(format!(
+                "Trying to delete grants issued for keys in domain {:?} and namespace {:?}.",
+                domain.0, namespace
+            ))?;
+            if domain == Domain::APP {
+                // Keystore uses the UID instead of the namespace argument for Domain::APP, so we
+                // just need to delete rows where grantee == namespace.
+                tx.execute("DELETE FROM persistent.grant WHERE grantee = ?;", params![namespace])
+                    .context(format!(
+                    "Trying to delete received grants for domain {:?} and namespace {:?}.",
+                    domain.0, namespace
+                ))?;
+            }
             tx.execute(
                 "DELETE FROM persistent.keyentry
                  WHERE domain = ? AND namespace = ? AND key_type = ?;",
@@ -2581,6 +2598,11 @@
         let _wp = wd::watch("KeystoreDB::unbind_keys_for_user");
 
         self.with_transaction(Immediate("TX_unbind_keys_for_user"), |tx| {
+            Self::delete_received_grants(tx, user_id).context(format!(
+                "In unbind_keys_for_user. Failed to delete received grants for user ID {:?}.",
+                user_id
+            ))?;
+
             let mut stmt = tx
                 .prepare(&format!(
                     "SELECT id from persistent.keyentry
@@ -2626,7 +2648,7 @@
             let mut notify_gc = false;
             for key_id in key_ids {
                 notify_gc = Self::mark_unreferenced(tx, key_id)
-                    .context("In unbind_keys_for_user.")?
+                    .context("In unbind_keys_for_user. Failed to mark key id as unreferenced.")?
                     || notify_gc;
             }
             Ok(()).do_gc(notify_gc)
diff --git a/keystore2/src/database/tests.rs b/keystore2/src/database/tests.rs
index 4ada694..fdcf254 100644
--- a/keystore2/src/database/tests.rs
+++ b/keystore2/src/database/tests.rs
@@ -2090,6 +2090,108 @@
     Ok(())
 }
 
+#[test]
+fn test_unbind_keys_for_user_removes_received_grants() -> Result<()> {
+    let mut db = new_test_db()?;
+    const USER_ID_1: u32 = 1;
+    const USER_ID_2: u32 = 2;
+    const APPLICATION_ID_1: u32 = 11;
+    const APPLICATION_ID_2: u32 = 22;
+    const UID_1_FOR_USER_ID_1: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID_1;
+    const UID_2_FOR_USER_ID_1: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID_2;
+    const UID_1_FOR_USER_ID_2: u32 = USER_ID_2 * AID_USER_OFFSET + APPLICATION_ID_1;
+
+    // Pretend two application IDs for user ID 1 were granted access to 1 key each and one
+    // application ID for user ID 2 was granted access to 1 key.
+    db.conn.execute(
+        &format!(
+            "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
+                    VALUES (1, {UID_1_FOR_USER_ID_1}, 111, 222),
+                           (2, {UID_1_FOR_USER_ID_2}, 333, 444),
+                           (3, {UID_2_FOR_USER_ID_1}, 555, 666);"
+        ),
+        [],
+    )?;
+    db.unbind_keys_for_user(USER_ID_1)?;
+
+    let mut stmt = db.conn.prepare("SELECT id, grantee FROM persistent.grant")?;
+    let mut rows = stmt.query_map::<(i64, u32), _, _>([], |row| Ok((row.get(0)?, row.get(1)?)))?;
+
+    // The rows for the user ID 1 grantees (UID_1_FOR_USER_ID_1 and UID_2_FOR_USER_ID_1) should be
+    // deleted and the row for the user ID 2 grantee (UID_1_FOR_USER_ID_2) should be untouched.
+    let r = rows.next().unwrap().unwrap();
+    assert_eq!(r, (2, UID_1_FOR_USER_ID_2));
+    assert!(rows.next().is_none());
+
+    Ok(())
+}
+
+#[test]
+fn test_unbind_keys_for_namespace_removes_received_grants() -> Result<()> {
+    const USER_ID_1: u32 = 1;
+    const APPLICATION_ID_1: u32 = 11;
+    const APPLICATION_ID_2: u32 = 22;
+    const UID_1_FOR_USER_ID_1: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID_1;
+    const UID_2_FOR_USER_ID_1: u32 = USER_ID_1 * AID_USER_OFFSET + APPLICATION_ID_2;
+
+    // Check that grants are removed for Domain::APP.
+    {
+        let mut db = new_test_db()?;
+
+        // Pretend two application IDs for user ID 1 were granted access to 1 key each.
+        db.conn.execute(
+            &format!(
+                "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
+                VALUES (1, {UID_1_FOR_USER_ID_1}, 111, 222), (2, {UID_2_FOR_USER_ID_1}, 333, 444);"
+            ),
+            [],
+        )?;
+        // Keystore uses the UID as the namespace for Domain::APP keys.
+        db.unbind_keys_for_namespace(Domain::APP, UID_1_FOR_USER_ID_1.into())?;
+
+        let mut stmt = db.conn.prepare("SELECT id, grantee FROM persistent.grant")?;
+        let mut rows =
+            stmt.query_map::<(i64, u32), _, _>([], |row| Ok((row.get(0)?, row.get(1)?)))?;
+
+        // The row for the grant to the namespace that was cleared (UID_1_FOR_USER_ID_1) should be
+        // deleted. The other row should be untouched.
+        let r = rows.next().unwrap().unwrap();
+        assert_eq!(r, (2, UID_2_FOR_USER_ID_1));
+        assert!(rows.next().is_none());
+    }
+
+    // Check that grants aren't removed for Domain::SELINUX.
+    {
+        let mut db = new_test_db()?;
+
+        // Pretend two application IDs for user ID 1 were granted access to 1 key each.
+        db.conn.execute(
+            &format!(
+                "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
+                VALUES (1, {UID_1_FOR_USER_ID_1}, 111, 222), (2, {UID_2_FOR_USER_ID_1}, 333, 444);"
+            ),
+            [],
+        )?;
+        // Keystore uses the UID as the namespace for Domain::APP keys. Here we're passing in
+        // Domain::SELINUX, but still pass the UID as the "namespace" argument to make sure the
+        // code's logic is correct.
+        db.unbind_keys_for_namespace(Domain::SELINUX, UID_1_FOR_USER_ID_1.into())?;
+
+        let mut stmt = db.conn.prepare("SELECT id, grantee FROM persistent.grant")?;
+        let mut rows =
+            stmt.query_map::<(i64, u32), _, _>([], |row| Ok((row.get(0)?, row.get(1)?)))?;
+
+        // Both rows should still be present.
+        let r = rows.next().unwrap().unwrap();
+        assert_eq!(r, (1, UID_1_FOR_USER_ID_1));
+        let r = rows.next().unwrap().unwrap();
+        assert_eq!(r, (2, UID_2_FOR_USER_ID_1));
+        assert!(rows.next().is_none());
+    }
+
+    Ok(())
+}
+
 fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> {
     db.key_exists(Domain::APP, nspace, alias, KeyType::Client)
 }
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index 2bdafd4..a1ce5f6 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -129,6 +129,6 @@
     // by the calling function and allow for natural fallback to the factory key.
     let rpc_name = get_remotely_provisioned_component_name(security_level)
         .context(ks_err!("Trying to get IRPC name."))?;
-    let _wd = wd::watch("Calling get_rkpd_attestation_key()");
+    let _wd = wd::watch_millis("Calling get_rkpd_attestation_key()", 1000);
     rkpd_client::get_rkpd_attestation_key(&rpc_name, caller_uid)
 }
diff --git a/keystore2/test_utils/attestation/Android.bp b/keystore2/test_utils/attestation/Android.bp
index dbf02d0..0ac5630 100644
--- a/keystore2/test_utils/attestation/Android.bp
+++ b/keystore2/test_utils/attestation/Android.bp
@@ -40,6 +40,7 @@
 rust_library {
     name: "libkeystore_attestation",
     defaults: ["libkeystore_attestation_defaults"],
+    vendor_available: true,
 }
 
 rust_test {
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
index 5e823c2..98b227b 100644
--- a/keystore2/test_utils/key_generations.rs
+++ b/keystore2/test_utils/key_generations.rs
@@ -536,13 +536,27 @@
             value: KeyParameterValue::Integer(get_os_version().try_into().unwrap())
         }
     ));
-    assert!(check_key_param(
-        authorizations,
-        &KeyParameter {
-            tag: Tag::OS_PATCHLEVEL,
-            value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())
-        }
-    ));
+    if is_gsi() && sl.is_keymaster() {
+        // The expected value of TAG::OS_PATCHLEVEL should match the system's reported
+        // OS patch level (obtained via get_os_patchlevel()). However, booting a Generic System
+        // Image (GSI) with a newer patch level is permitted. Therefore, the generated key's
+        // TAG::OS_PATCHLEVEL may be less than or equal to the current system's OS patch level.
+        assert!(authorizations.iter().map(|auth| &auth.keyParameter).any(|key_param| key_param
+            .tag
+            == Tag::OS_PATCHLEVEL
+            && key_param.value
+                <= KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())));
+    } else {
+        // The KeyMint spec required that the patch-levels match that of the running system, even
+        // under GSI.
+        assert!(check_key_param(
+            authorizations,
+            &KeyParameter {
+                tag: Tag::OS_PATCHLEVEL,
+                value: KeyParameterValue::Integer(get_os_patchlevel().try_into().unwrap())
+            }
+        ));
+    }
 
     assert!(check_key_param(
         authorizations,
diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs
index 02dfd3f..553add0 100644
--- a/keystore2/tests/keystore2_client_attest_key_tests.rs
+++ b/keystore2/tests/keystore2_client_attest_key_tests.rs
@@ -13,7 +13,8 @@
 // limitations under the License.
 
 use crate::keystore2_client_test_utils::{
-    app_attest_key_feature_exists, device_id_attestation_feature_exists, get_attest_id_value,
+    app_attest_key_feature_exists, device_id_attestation_check_acceptable_error,
+    device_id_attestation_feature_exists, get_attest_id_value,
     is_second_imei_id_attestation_required, skip_device_id_attest_tests,
 };
 use crate::{
@@ -558,7 +559,7 @@
 }
 
 /// Try to generate an attested key with attestation of invalid device's identifiers. Test should
-/// fail with error response code `CANNOT_ATTEST_IDS`.
+/// fail to generate a key with proper error code.
 #[test]
 fn keystore2_attest_key_fails_with_invalid_attestation_id() {
     skip_test_if_no_device_id_attestation_feature!();
@@ -602,7 +603,7 @@
         ));
 
         assert!(result.is_err());
-        assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
+        device_id_attestation_check_acceptable_error(attest_id, result.unwrap_err());
     }
 }
 
diff --git a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
index 91370c7..fb84808 100644
--- a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
+++ b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs
@@ -13,8 +13,9 @@
 // limitations under the License.
 
 use crate::keystore2_client_test_utils::{
-    delete_app_key, get_attest_id_value, is_second_imei_id_attestation_required,
-    perform_sample_asym_sign_verify_op, skip_device_unique_attestation_tests,
+    delete_app_key, device_id_attestation_check_acceptable_error, get_attest_id_value,
+    is_second_imei_id_attestation_required, perform_sample_asym_sign_verify_op,
+    skip_device_unique_attestation_tests,
 };
 use crate::require_keymint;
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
@@ -254,7 +255,7 @@
 }
 
 /// Try to generate a device unique attested key with attestation of invalid device's identifiers.
-/// Test should fail with error response code `CANNOT_ATTEST_IDS`.
+/// Test should fail to generate a key with proper error code.
 #[test]
 fn keystore2_device_unique_attest_key_fails_with_invalid_attestation_id() {
     let Some(sl) = SecLevel::strongbox() else { return };
@@ -288,7 +289,7 @@
         let result =
             key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias));
         assert!(result.is_err());
-        assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS));
+        device_id_attestation_check_acceptable_error(attest_id, result.unwrap_err());
     }
 }
 
diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs
index c171ab1..87d5f4d 100644
--- a/keystore2/tests/keystore2_client_grant_key_tests.rs
+++ b/keystore2/tests/keystore2_client_grant_key_tests.rs
@@ -18,6 +18,7 @@
 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
     Digest::Digest, KeyPurpose::KeyPurpose,
 };
+use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::IKeystoreMaintenance;
 use android_system_keystore2::aidl::android::system::keystore2::{
     Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
     KeyEntryResponse::KeyEntryResponse, KeyPermission::KeyPermission, ResponseCode::ResponseCode,
@@ -30,6 +31,8 @@
 use nix::unistd::getuid;
 use rustutils::users::AID_USER_OFFSET;
 
+static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance";
+
 /// Produce a [`KeyDescriptor`] for a granted key.
 fn granted_key_descriptor(nspace: i64) -> KeyDescriptor {
     KeyDescriptor { domain: Domain::GRANT, nspace, alias: None, blob: None }
@@ -88,6 +91,10 @@
     Ok(())
 }
 
+fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> {
+    binder::get_interface(USER_MANAGER_SERVICE_NAME).unwrap()
+}
+
 /// Try to grant an SELINUX key with permission that does not map to any of the `KeyPermission`
 /// values.  An error is expected with values that does not map to set of permissions listed in
 /// `KeyPermission`.
@@ -600,6 +607,223 @@
     assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
 }
 
+/// Grant a key to a UID (user ID A + app B) then uninstall user ID A. Initialize a new user with
+/// the same user ID as the now-removed user. Check that app B for the new user can't load the key.
+#[test]
+fn grant_removed_when_grantee_user_id_removed() {
+    const GRANTOR_USER_ID: u32 = 97;
+    const GRANTOR_APPLICATION_ID: u32 = 10003;
+    static GRANTOR_UID: u32 = GRANTOR_USER_ID * AID_USER_OFFSET + GRANTOR_APPLICATION_ID;
+    static GRANTOR_GID: u32 = GRANTOR_UID;
+
+    const GRANTEE_USER_ID: u32 = 99;
+    const GRANTEE_APPLICATION_ID: u32 = 10001;
+    static GRANTEE_UID: u32 = GRANTEE_USER_ID * AID_USER_OFFSET + GRANTEE_APPLICATION_ID;
+    static GRANTEE_GID: u32 = GRANTEE_UID;
+
+    // Add a new user.
+    let create_grantee_user_id_fn = || {
+        let maint = get_maintenance();
+        maint.onUserAdded(GRANTEE_USER_ID.try_into().unwrap()).expect("failed to add user");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_root(create_grantee_user_id_fn) };
+
+    // Child function to generate a key and grant it to GRANTEE_UID with `GET_INFO` permission.
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let access_vector = KeyPermission::GET_INFO.0;
+        let alias = format!("ks_grant_single_{}", getuid());
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
+
+        grant_keys.remove(0)
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let grant_key_nspace = unsafe { run_as::run_as_app(GRANTOR_UID, GRANTOR_GID, grantor_fn) };
+
+    // Child function for the grantee context: can load the granted key.
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let rsp = get_granted_key(&keystore2, grant_key_nspace).expect("failed to get granted key");
+
+        // Return the underlying key ID to simulate an ID leak.
+        assert_eq!(rsp.metadata.key.domain, Domain::KEY_ID);
+        rsp.metadata.key.nspace
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let key_id = unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
+    // Remove the grantee user and create a new user with the same user ID.
+    let overwrite_grantee_user_id_fn = || {
+        let maint = get_maintenance();
+        maint.onUserRemoved(GRANTEE_USER_ID.try_into().unwrap()).expect("failed to remove user");
+        maint.onUserAdded(GRANTEE_USER_ID.try_into().unwrap()).expect("failed to add user");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_root(overwrite_grantee_user_id_fn) };
+
+    // Second context identified by <uid, grant_nspace> (where uid is the same because the
+    // now-deleted and newly-created grantee users have the same user ID) does not have access to
+    // the above granted key.
+    let new_grantee_fn = move || {
+        // Check that the key cannot be accessed via grant (i.e. KeyDescriptor with Domain::GRANT).
+        let keystore2 = get_keystore_service();
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+
+        // Check that the key also cannot be accessed via key ID (i.e. KeyDescriptor with
+        // Domain::KEY_ID) if the second context somehow gets a hold of it.
+        let result = map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
+            domain: Domain::KEY_ID,
+            nspace: key_id,
+            alias: None,
+            blob: None,
+        }));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, new_grantee_fn) };
+
+    // Clean up: remove grantee user.
+    let remove_grantee_user_id_fn = || {
+        let maint = get_maintenance();
+        maint.onUserRemoved(GRANTEE_USER_ID.try_into().unwrap()).expect("failed to remove user");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_root(remove_grantee_user_id_fn) };
+}
+
+/// Grant a key to a UID (user ID A + app B) then clear the namespace for user ID A + app B. Check
+/// that the key can't be loaded by that UID (which would be the UID used if another app were to be
+/// installed for user ID A with the same application ID B).
+#[test]
+fn grant_removed_when_grantee_app_uninstalled() {
+    const GRANTOR_USER_ID: u32 = 97;
+    const GRANTOR_APPLICATION_ID: u32 = 10003;
+    static GRANTOR_UID: u32 = GRANTOR_USER_ID * AID_USER_OFFSET + GRANTOR_APPLICATION_ID;
+    static GRANTOR_GID: u32 = GRANTOR_UID;
+
+    const GRANTEE_USER_ID: u32 = 99;
+    const GRANTEE_APPLICATION_ID: u32 = 10001;
+    static GRANTEE_UID: u32 = GRANTEE_USER_ID * AID_USER_OFFSET + GRANTEE_APPLICATION_ID;
+    static GRANTEE_GID: u32 = GRANTEE_UID;
+
+    // Add a new user.
+    let create_grantee_user_id_fn = || {
+        let maint = get_maintenance();
+        maint.onUserAdded(GRANTEE_USER_ID.try_into().unwrap()).expect("failed to add user");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_root(create_grantee_user_id_fn) };
+
+    // Child function to generate a key and grant it to GRANTEE_UID with `GET_INFO` permission.
+    let grantor_fn = || {
+        let sl = SecLevel::tee();
+        let access_vector = KeyPermission::GET_INFO.0;
+        let alias = format!("ks_grant_single_{}", getuid());
+        let mut grant_keys = generate_ec_key_and_grant_to_users(
+            &sl,
+            Some(alias),
+            vec![GRANTEE_UID.try_into().unwrap()],
+            access_vector,
+        )
+        .unwrap();
+
+        grant_keys.remove(0)
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let grant_key_nspace = unsafe { run_as::run_as_app(GRANTOR_UID, GRANTOR_GID, grantor_fn) };
+
+    // Child function for the grantee context: can load the granted key.
+    let grantee_fn = move || {
+        let keystore2 = get_keystore_service();
+        let rsp = get_granted_key(&keystore2, grant_key_nspace).expect("failed to get granted key");
+
+        // Return the underlying key ID to simulate an ID leak.
+        assert_eq!(rsp.metadata.key.domain, Domain::KEY_ID);
+        rsp.metadata.key.nspace
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    let key_id = unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, grantee_fn) };
+
+    // Clear the app's namespace, which is what happens when an app is uninstalled. Exercising the
+    // full app uninstallation "flow" isn't possible from a Keystore VTS test since we'd need to
+    // exercise the Java code that calls into the Keystore service. So, we can only test the
+    // entrypoint that we know gets triggered during app uninstallation based on the code's control
+    // flow.
+    let clear_grantee_uid_namespace_fn = || {
+        let maint = get_maintenance();
+        maint
+            .clearNamespace(Domain::APP, GRANTEE_UID.try_into().unwrap())
+            .expect("failed to clear namespace");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_root(clear_grantee_uid_namespace_fn) };
+
+    // The same context identified by <uid, grant_nspace> not longer has access to the above granted
+    // key. This would be the context if a new app were installed and assigned the same app ID.
+    let new_grantee_fn = move || {
+        // Check that the key cannot be accessed via grant (i.e. KeyDescriptor with Domain::GRANT).
+        let keystore2 = get_keystore_service();
+        let result = get_granted_key(&keystore2, grant_key_nspace);
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err());
+
+        // Check that the key also cannot be accessed via key ID (i.e. KeyDescriptor with
+        // Domain::KEY_ID) if the second context somehow gets a hold of it.
+        let result = map_ks_error(keystore2.getKeyEntry(&KeyDescriptor {
+            domain: Domain::KEY_ID,
+            nspace: key_id,
+            alias: None,
+            blob: None,
+        }));
+        assert!(result.is_err());
+        assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_app(GRANTEE_UID, GRANTEE_GID, new_grantee_fn) };
+
+    // Clean up: remove grantee user.
+    let remove_grantee_user_id_fn = || {
+        let maint = get_maintenance();
+        maint.onUserRemoved(GRANTEE_USER_ID.try_into().unwrap()).expect("failed to remove user");
+    };
+
+    // Safety: only one thread at this point (enforced by `AndroidTest.xml` setting
+    // `--test-threads=1`), and nothing yet done with binder on the main thread.
+    unsafe { run_as::run_as_root(remove_grantee_user_id_fn) };
+}
+
 /// Grant an APP key to the user and immediately ungrant the granted key. In grantee context try to load
 /// the key. Grantee should fail to load the ungranted key with `KEY_NOT_FOUND` error response.
 #[test]
diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs
index 1bbdc91..8d70866 100644
--- a/keystore2/tests/keystore2_client_test_utils.rs
+++ b/keystore2/tests/keystore2_client_test_utils.rs
@@ -618,3 +618,25 @@
     let serial_num = cert.serial_number();
     assert_eq!(serial_num.to_bn().as_ref().unwrap(), expected_serial_num);
 }
+
+/// Check the error code from an attempt to perform device ID attestation with an invalid value.
+pub fn device_id_attestation_check_acceptable_error(attest_id_tag: Tag, e: Error) {
+    match e {
+        // Standard/default error code for ID mismatch.
+        Error::Km(ErrorCode::CANNOT_ATTEST_IDS) => {}
+        Error::Km(ErrorCode::INVALID_TAG) if get_vsr_api_level() < 34 => {
+            // Allow older implementations to (incorrectly) use INVALID_TAG.
+        }
+        Error::Km(ErrorCode::ATTESTATION_IDS_NOT_PROVISIONED)
+            if matches!(
+                attest_id_tag,
+                Tag::ATTESTATION_ID_IMEI
+                    | Tag::ATTESTATION_ID_MEID
+                    | Tag::ATTESTATION_ID_SECOND_IMEI
+            ) =>
+        {
+            // Non-phone devices will not have IMEI/MEID provisioned.
+        }
+        _ => panic!("Unexpected error {e:?} on ID mismatch for {attest_id_tag:?}"),
+    }
+}
diff --git a/prng_seeder/src/main.rs b/prng_seeder/src/main.rs
index d112d61..c6adfd4 100644
--- a/prng_seeder/src/main.rs
+++ b/prng_seeder/src/main.rs
@@ -69,11 +69,11 @@
 }
 
 fn setup() -> Result<(ConditionerBuilder, UnixListener)> {
+    configure_logging()?;
+    let cli = Cli::try_parse()?;
     // SAFETY: nobody has taken ownership of the inherited FDs yet.
     unsafe { rustutils::inherited_fd::init_once() }
         .context("In setup, failed to own inherited FDs")?;
-    configure_logging()?;
-    let cli = Cli::try_parse()?;
     // SAFETY: Nothing else sets the signal handler, so either it was set here or it is the default.
     unsafe { signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigIgn) }
         .context("In setup, setting SIGPIPE to SIG_IGN")?;