Merge "Include security level in per-operation watchdogs" into main
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9b96f36..7ba873b 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -5,6 +5,3 @@
[Builtin Hooks Options]
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
rustfmt = --config-path=rustfmt.toml
-
-[Hook Scripts]
-aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
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 34e0c59..8f5617f 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -2429,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
@@ -2444,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(
@@ -2515,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 = ?;",
@@ -2573,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
@@ -2618,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/maintenance.rs b/keystore2/src/maintenance.rs
index 7b6ea68..3517286 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -251,7 +251,7 @@
if keystore2_flags::attest_modules() {
std::thread::spawn(move || {
Self::watch_apex_info()
- .unwrap_or_else(|e| log::error!("watch_apex_info failed: {e:?}"));
+ .unwrap_or_else(|e| log::error!("watch_apex_info failed, preventing keystore.module_hash.sent from being set to true; this may therefore block boot: {e:?}"));
});
} else {
rustutils::system_properties::write("keystore.module_hash.sent", "true")
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/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]