Merge "keystore2: Statically link Rust libs to save RAM"
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 6386201..0ba49ed 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -62,6 +62,9 @@
shared_libs: [
"libcutils",
],
+ features: [
+ "watchdog",
+ ],
}
rust_library {
@@ -91,6 +94,10 @@
"libkeystore2_test_utils",
"libnix",
],
+ // The test should always include watchdog.
+ features: [
+ "watchdog",
+ ],
}
rust_binary {
diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs
index 848b770..0096686 100644
--- a/keystore2/src/apc.rs
+++ b/keystore2/src/apc.rs
@@ -21,7 +21,7 @@
sync::{mpsc::Sender, Arc, Mutex},
};
-use crate::utils::{compat_2_response_code, ui_opts_2_compat};
+use crate::utils::{compat_2_response_code, ui_opts_2_compat, watchdog as wd};
use android_security_apc::aidl::android::security::apc::{
IConfirmationCallback::IConfirmationCallback,
IProtectedConfirmation::{BnProtectedConfirmation, IProtectedConfirmation},
@@ -363,6 +363,8 @@
locale: &str,
ui_option_flags: i32,
) -> BinderResult<()> {
+ // presentPrompt can take more time than other operations.
+ let _wp = wd::watch_millis("IProtectedConfirmation::presentPrompt", 3000);
map_or_log_err(
self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags),
Ok,
@@ -372,9 +374,11 @@
&self,
listener: &binder::Strong<dyn IConfirmationCallback>,
) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IProtectedConfirmation::cancelPrompt", 500);
map_or_log_err(self.cancel_prompt(listener), Ok)
}
fn isSupported(&self) -> BinderResult<bool> {
+ let _wp = wd::watch_millis("IProtectedConfirmation::isSupported", 500);
map_or_log_err(Self::is_supported(), Ok)
}
}
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index cac75c0..d07dab5 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -18,7 +18,7 @@
use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR};
use crate::permission::KeystorePerm;
use crate::super_key::UserState;
-use crate::utils::check_keystore_permission;
+use crate::utils::{check_keystore_permission, watchdog as wd};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
};
@@ -234,6 +234,7 @@
impl IKeystoreAuthorization for AuthorizationManager {
fn addAuthToken(&self, auth_token: &HardwareAuthToken) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreAuthorization::addAuthToken", 500);
map_or_log_err(self.add_auth_token(auth_token), Ok)
}
@@ -244,6 +245,10 @@
password: Option<&[u8]>,
unlocking_sids: Option<&[i64]>,
) -> BinderResult<()> {
+ let _wp =
+ wd::watch_millis_with("IKeystoreAuthorization::onLockScreenEvent", 500, move || {
+ format!("lock event: {}", lock_screen_event.0)
+ });
map_or_log_err(
self.on_lock_screen_event(
lock_screen_event,
@@ -261,6 +266,7 @@
secure_user_id: i64,
auth_token_max_age_millis: i64,
) -> binder::public_api::Result<AuthorizationTokens> {
+ let _wp = wd::watch_millis("IKeystoreAuthorization::getAuthTokensForCredStore", 500);
map_or_log_err(
self.get_auth_tokens_for_credstore(
challenge,
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 32e2c98..e62ec4e 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -46,7 +46,7 @@
use crate::impl_metadata; // This is in db_utils.rs
use crate::key_parameter::{KeyParameter, Tag};
use crate::permission::KeyPermSet;
-use crate::utils::{get_current_time_in_seconds, AID_USER_OFFSET};
+use crate::utils::{get_current_time_in_seconds, watchdog as wd, AID_USER_OFFSET};
use crate::{
db_utils::{self, SqlField},
gc::Gc,
@@ -851,6 +851,8 @@
/// KeystoreDB cannot be used by multiple threads.
/// Each thread should open their own connection using `thread_local!`.
pub fn new(db_root: &Path, gc: Option<Gc>) -> Result<Self> {
+ let _wp = wd::watch_millis("KeystoreDB::new", 500);
+
// Build the path to the sqlite file.
let mut persistent_path = db_root.to_path_buf();
persistent_path.push(Self::PERSISTENT_DB_FILENAME);
@@ -1096,6 +1098,8 @@
&mut self,
storage_type: StatsdStorageType,
) -> Result<Keystore2StorageStats> {
+ let _wp = wd::watch_millis("KeystoreDB::get_storage_stat", 500);
+
match storage_type {
StatsdStorageType::Database => self.get_total_size(),
StatsdStorageType::KeyEntry => {
@@ -1151,6 +1155,7 @@
&mut self,
blob_id_to_delete: Option<i64>,
) -> Result<Option<(i64, Vec<u8>, BlobMetaData)>> {
+ let _wp = wd::watch_millis("KeystoreDB::handle_next_superseded_blob", 500);
self.with_transaction(TransactionBehavior::Immediate, |tx| {
// Delete the given blob if one was given.
if let Some(blob_id_to_delete) = blob_id_to_delete {
@@ -1220,6 +1225,8 @@
/// Unlike with `mark_unreferenced`, we don't need to purge grants, because only keys that made
/// it to `KeyLifeCycle::Live` may have grants.
pub fn cleanup_leftovers(&mut self) -> Result<usize> {
+ let _wp = wd::watch_millis("KeystoreDB::cleanup_leftovers", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
tx.execute(
"UPDATE persistent.keyentry SET state = ? WHERE state = ?;",
@@ -1239,6 +1246,8 @@
alias: &str,
key_type: KeyType,
) -> Result<bool> {
+ let _wp = wd::watch_millis("KeystoreDB::key_exists", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_descriptor =
KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None };
@@ -1264,6 +1273,8 @@
blob_metadata: &BlobMetaData,
key_metadata: &KeyMetaData,
) -> Result<KeyEntry> {
+ let _wp = wd::watch_millis("KeystoreDB::store_super_key", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_id = Self::insert_with_retry(|id| {
tx.execute(
@@ -1307,6 +1318,8 @@
key_type: &SuperKeyType,
user_id: u32,
) -> Result<Option<(KeyIdGuard, KeyEntry)>> {
+ let _wp = wd::watch_millis("KeystoreDB::load_super_key", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_descriptor = KeyDescriptor {
domain: Domain::APP,
@@ -1346,6 +1359,8 @@
where
F: Fn() -> Result<(Vec<u8>, BlobMetaData)>,
{
+ let _wp = wd::watch_millis("KeystoreDB::get_or_create_key_with", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let id = {
let mut stmt = tx
@@ -1494,6 +1509,8 @@
namespace: &i64,
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
+ let _wp = wd::watch_millis("KeystoreDB::create_key_entry", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::create_key_entry_internal(tx, domain, namespace, km_uuid).no_gc()
})
@@ -1545,6 +1562,8 @@
private_key: &[u8],
km_uuid: &Uuid,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::create_attestation_key_entry", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_id = KEY_ID_LOCK.get(
Self::insert_with_retry(|id| {
@@ -1587,6 +1606,8 @@
blob: Option<&[u8]>,
blob_metadata: Option<&BlobMetaData>,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::set_blob", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::set_blob_internal(&tx, key_id.0, sc_type, blob, blob_metadata).need_gc()
})
@@ -1598,6 +1619,8 @@
/// We use this to insert key blobs into the database which can then be garbage collected
/// lazily by the key garbage collector.
pub fn set_deleted_blob(&mut self, blob: &[u8], blob_metadata: &BlobMetaData) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::set_deleted_blob", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::set_blob_internal(
&tx,
@@ -1708,6 +1731,8 @@
expiration_date: i64,
km_uuid: &Uuid,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::store_signed_attestation_certificate_chain", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let mut stmt = tx
.prepare(
@@ -1777,6 +1802,8 @@
namespace: i64,
km_uuid: &Uuid,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::assign_attestation_key", 500);
+
match domain {
Domain::APP | Domain::SELINUX => {}
_ => {
@@ -1839,6 +1866,8 @@
num_keys: i32,
km_uuid: &Uuid,
) -> Result<Vec<Vec<u8>>> {
+ let _wp = wd::watch_millis("KeystoreDB::fetch_unsigned_attestation_keys", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let mut stmt = tx
.prepare(
@@ -1876,6 +1905,8 @@
/// Removes any keys that have expired as of the current time. Returns the number of keys
/// marked unreferenced that are bound to be garbage collected.
pub fn delete_expired_attestation_keys(&mut self) -> Result<i32> {
+ let _wp = wd::watch_millis("KeystoreDB::delete_expired_attestation_keys", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let mut stmt = tx
.prepare(
@@ -1911,6 +1942,8 @@
/// Deletes all remotely provisioned attestation keys in the system, regardless of the state
/// they are in. This is useful primarily as a testing mechanism.
pub fn delete_all_attestation_keys(&mut self) -> Result<i64> {
+ let _wp = wd::watch_millis("KeystoreDB::delete_all_attestation_keys", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let mut stmt = tx
.prepare(
@@ -1942,6 +1975,8 @@
date: i64,
km_uuid: &Uuid,
) -> Result<AttestationPoolStatus> {
+ let _wp = wd::watch_millis("KeystoreDB::get_attestation_pool_status", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let mut stmt = tx.prepare(
"SELECT data
@@ -2009,6 +2044,8 @@
namespace: i64,
km_uuid: &Uuid,
) -> Result<Option<CertificateChain>> {
+ let _wp = wd::watch_millis("KeystoreDB::retrieve_attestation_key_and_cert_chain", 500);
+
match domain {
Domain::APP | Domain::SELINUX => {}
_ => {
@@ -2143,6 +2180,8 @@
caller_uid: u32,
check_permission: impl Fn(&KeyDescriptor) -> Result<()>,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::migrate_key_namespace", 500);
+
let destination = match destination.domain {
Domain::APP => KeyDescriptor { nspace: caller_uid as i64, ..(*destination).clone() },
Domain::SELINUX => (*destination).clone(),
@@ -2211,6 +2250,8 @@
metadata: &KeyMetaData,
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
+ let _wp = wd::watch_millis("KeystoreDB::store_new_key", 500);
+
let (alias, domain, namespace) = match key {
KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None }
| KeyDescriptor { alias: Some(alias), domain: Domain::SELINUX, nspace, blob: None } => {
@@ -2266,6 +2307,8 @@
cert: &[u8],
km_uuid: &Uuid,
) -> Result<KeyIdGuard> {
+ let _wp = wd::watch_millis("KeystoreDB::store_new_certificate", 500);
+
let (alias, domain, namespace) = match key {
KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None }
| KeyDescriptor { alias: Some(alias), domain: Domain::SELINUX, nspace, blob: None } => {
@@ -2545,6 +2588,8 @@
/// zero, the key also gets marked unreferenced and scheduled for deletion.
/// Returns Ok(true) if the key was marked unreferenced as a hint to the garbage collector.
pub fn check_and_update_key_usage_count(&mut self, key_id: i64) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::check_and_update_key_usage_count", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let limit: Option<i32> = tx
.query_row(
@@ -2591,6 +2636,8 @@
caller_uid: u32,
check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
) -> Result<(KeyIdGuard, KeyEntry)> {
+ let _wp = wd::watch_millis("KeystoreDB::load_key_entry", 500);
+
loop {
match self.load_key_entry_internal(
key,
@@ -2718,6 +2765,8 @@
caller_uid: u32,
check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::unbind_key", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let (key_id, access_key_descriptor, access_vector) =
Self::load_access_tuple(tx, key, key_type, caller_uid)
@@ -2747,6 +2796,8 @@
/// Delete all artifacts belonging to the namespace given by the domain-namespace tuple.
/// This leaves all of the blob entries orphaned for subsequent garbage collection.
pub fn unbind_keys_for_namespace(&mut self, domain: Domain, namespace: i64) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::unbind_keys_for_namespace", 500);
+
if !(domain == Domain::APP || domain == Domain::SELINUX) {
return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
.context("In unbind_keys_for_namespace.");
@@ -2798,6 +2849,8 @@
user_id: u32,
keep_non_super_encrypted_keys: bool,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::unbind_keys_for_user", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let mut stmt = tx
.prepare(&format!(
@@ -2898,6 +2951,8 @@
/// The key descriptors will have the domain, nspace, and alias field set.
/// Domain must be APP or SELINUX, the caller must make sure of that.
pub fn list(&mut self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
+ let _wp = wd::watch_millis("KeystoreDB::list", 500);
+
self.with_transaction(TransactionBehavior::Deferred, |tx| {
let mut stmt = tx
.prepare(
@@ -2939,6 +2994,8 @@
access_vector: KeyPermSet,
check_permission: impl Fn(&KeyDescriptor, &KeyPermSet) -> Result<()>,
) -> Result<KeyDescriptor> {
+ let _wp = wd::watch_millis("KeystoreDB::grant", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
// Load the key_id and complete the access control tuple.
// We ignore the access vector here because grants cannot be granted.
@@ -3004,6 +3061,8 @@
grantee_uid: u32,
check_permission: impl Fn(&KeyDescriptor) -> Result<()>,
) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::ungrant", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
// Load the key_id and complete the access control tuple.
// We ignore the access vector here because grants cannot be granted.
@@ -3055,6 +3114,8 @@
/// Insert or replace the auth token based on the UNIQUE constraint of the auth token table
pub fn insert_auth_token(&mut self, auth_token: &HardwareAuthToken) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::insert_auth_token", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
tx.execute(
"INSERT OR REPLACE INTO perboot.authtoken (challenge, user_id, auth_id,
@@ -3082,6 +3143,8 @@
where
F: Fn(&AuthTokenEntry) -> bool,
{
+ let _wp = wd::watch_millis("KeystoreDB::find_auth_token_entry", 500);
+
self.with_transaction(TransactionBehavior::Deferred, |tx| {
let mut stmt = tx
.prepare("SELECT * from perboot.authtoken ORDER BY time_received DESC;")
@@ -3117,6 +3180,8 @@
/// Insert last_off_body into the metadata table at the initialization of auth token table
pub fn insert_last_off_body(&mut self, last_off_body: MonotonicRawTime) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::insert_last_off_body", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
tx.execute(
"INSERT OR REPLACE INTO perboot.metadata (key, value) VALUES (?, ?);",
@@ -3129,6 +3194,8 @@
/// Update last_off_body when on_device_off_body is called
pub fn update_last_off_body(&mut self, last_off_body: MonotonicRawTime) -> Result<()> {
+ let _wp = wd::watch_millis("KeystoreDB::update_last_off_body", 500);
+
self.with_transaction(TransactionBehavior::Immediate, |tx| {
tx.execute(
"UPDATE perboot.metadata SET value = ? WHERE key = ?;",
@@ -3141,6 +3208,8 @@
/// Get last_off_body time when finding auth tokens
fn get_last_off_body(tx: &Transaction) -> Result<MonotonicRawTime> {
+ let _wp = wd::watch_millis("KeystoreDB::get_last_off_body", 500);
+
tx.query_row(
"SELECT value from perboot.metadata WHERE key = ?;",
params!["last_off_body"],
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 378b72f..04d1f77 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -682,9 +682,10 @@
// So the HAT cannot be presented on create. So on update/finish we present both
// an per-op-bound auth token and a timestamp token.
(Some(_), true, true) => (None, DeferredAuthState::TimeStampedOpAuthRequired),
- (Some(hat), true, false) => {
- (None, DeferredAuthState::TimeStampRequired(hat.take_auth_token()))
- }
+ (Some(hat), true, false) => (
+ Some(hat.auth_token().clone()),
+ DeferredAuthState::TimeStampRequired(hat.take_auth_token()),
+ ),
(Some(hat), false, true) => {
(Some(hat.take_auth_token()), DeferredAuthState::OpAuthRequired)
}
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index d1b2ffb..f969cb6 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -30,16 +30,13 @@
//! Keystore functions should use `anyhow::Result` to return error conditions, and
//! context should be added every time an error is forwarded.
-use std::cmp::PartialEq;
-
pub use android_hardware_security_keymint::aidl::android::hardware::security::keymint::ErrorCode::ErrorCode;
pub use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
-
-use keystore2_selinux as selinux;
-
use android_system_keystore2::binder::{
ExceptionCode, Result as BinderResult, Status as BinderStatus, StatusCode,
};
+use keystore2_selinux as selinux;
+use std::cmp::PartialEq;
/// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated
/// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant.
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index bd28ca6..54fceab 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -20,6 +20,7 @@
use crate::legacy_blob::LegacyBlobLoader;
use crate::legacy_migrator::LegacyMigrator;
use crate::super_key::SuperKeyManager;
+use crate::utils::watchdog as wd;
use crate::utils::Asp;
use crate::{async_task::AsyncTask, database::MonotonicRawTime};
use crate::{
@@ -58,6 +59,7 @@
Box::new(|uuid, blob| {
let km_dev: Strong<dyn IKeyMintDevice> =
get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?.get_interface()?;
+ let _wp = wd::watch_millis("In create_thread_local_db: calling deleteKey", 500);
map_km_error(km_dev.deleteKey(&*blob))
.context("In invalidate key closure: Trying to invalidate key blob.")
}),
@@ -227,8 +229,10 @@
.context("In connect_keymint: Trying to get Legacy wrapper.")
}?;
+ let wp = wd::watch_millis("In connect_keymint: calling getHardwareInfo()", 500);
let hw_info = map_km_error(keymint.getHardwareInfo())
.context("In connect_keymint: Failed to get hardware info.")?;
+ drop(wp);
Ok((Asp::new(keymint.as_binder()), hw_info))
}
diff --git a/keystore2/src/legacy_migrator.rs b/keystore2/src/legacy_migrator.rs
index 5e0d573..d5647cd 100644
--- a/keystore2/src/legacy_migrator.rs
+++ b/keystore2/src/legacy_migrator.rs
@@ -17,7 +17,7 @@
use crate::error::Error;
use crate::key_parameter::KeyParameterValue;
use crate::legacy_blob::BlobValue;
-use crate::utils::uid_to_android_user;
+use crate::utils::{uid_to_android_user, watchdog as wd};
use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
use crate::{
database::{
@@ -196,6 +196,8 @@
/// List all aliases for uid in the legacy database.
pub fn list_uid(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
+ let _wp = wd::watch_millis("LegacyMigrator::list_uid", 500);
+
let uid = match (domain, namespace) {
(Domain::APP, namespace) => namespace as u32,
(Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
@@ -290,6 +292,8 @@
where
F: Fn() -> Result<T>,
{
+ let _wp = wd::watch_millis("LegacyMigrator::with_try_migrate", 500);
+
// Access the key and return on success.
match key_accessor() {
Ok(result) => return Ok(result),
@@ -342,6 +346,8 @@
where
F: FnMut() -> Result<Option<T>>,
{
+ let _wp = wd::watch_millis("LegacyMigrator::with_try_migrate_super_key", 500);
+
match key_accessor() {
Ok(Some(result)) => return Ok(Some(result)),
Ok(None) => {}
@@ -364,6 +370,8 @@
/// Deletes all keys belonging to the given namespace, migrating them into the database
/// for subsequent garbage collection if necessary.
pub fn bulk_delete_uid(&self, domain: Domain, nspace: i64) -> Result<()> {
+ let _wp = wd::watch_millis("LegacyMigrator::bulk_delete_uid", 500);
+
let uid = match (domain, nspace) {
(Domain::APP, nspace) => nspace as u32,
(Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
@@ -385,6 +393,8 @@
user_id: u32,
keep_non_super_encrypted_keys: bool,
) -> Result<()> {
+ let _wp = wd::watch_millis("LegacyMigrator::bulk_delete_user", 500);
+
let result = self.do_serialized(move |migrator_state| {
migrator_state
.bulk_delete(BulkDeleteRequest::User(user_id), keep_non_super_encrypted_keys)
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 3332b83..c04c4b0 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -47,3 +47,6 @@
mod db_utils;
mod gc;
mod super_key;
+
+#[cfg(feature = "watchdog")]
+mod watchdog;
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 9e7576e..a099d18 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -22,7 +22,7 @@
use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::UserState;
-use crate::utils::{check_key_permission, check_keystore_permission};
+use crate::utils::{check_key_permission, check_keystore_permission, watchdog as wd};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_security_maintenance::aidl::android::security::maintenance::{
@@ -133,11 +133,17 @@
}
}
- fn early_boot_ended_help(sec_level: &SecurityLevel) -> Result<()> {
- let (dev, _, _) =
- get_keymint_device(sec_level).context("In early_boot_ended: getting keymint device")?;
+ fn early_boot_ended_help(sec_level: SecurityLevel) -> Result<()> {
+ let (dev, _, _) = get_keymint_device(&sec_level)
+ .context("In early_boot_ended: getting keymint device")?;
let km_dev: Strong<dyn IKeyMintDevice> =
dev.get_interface().context("In early_boot_ended: getting keymint device interface")?;
+
+ let _wp = wd::watch_millis_with(
+ "In early_boot_ended_help: calling earlyBootEnded()",
+ 500,
+ move || format!("Seclevel: {:?}", sec_level),
+ );
map_km_error(km_dev.earlyBootEnded())
.context("In keymint device: calling earlyBootEnded")?;
Ok(())
@@ -157,7 +163,7 @@
(SecurityLevel::STRONGBOX, "STRONGBOX"),
];
sec_levels.iter().fold(Ok(()), |result, (sec_level, sec_level_string)| {
- let curr_result = Maintenance::early_boot_ended_help(sec_level);
+ let curr_result = Maintenance::early_boot_ended_help(*sec_level);
if curr_result.is_err() {
log::error!(
"Call to earlyBootEnded failed for security level {}.",
@@ -219,30 +225,37 @@
impl IKeystoreMaintenance for Maintenance {
fn onUserPasswordChanged(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::onUserPasswordChanged", 500);
map_or_log_err(Self::on_user_password_changed(user_id, password.map(|pw| pw.into())), Ok)
}
fn onUserAdded(&self, user_id: i32) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::onUserAdded", 500);
map_or_log_err(Self::add_or_remove_user(user_id), Ok)
}
fn onUserRemoved(&self, user_id: i32) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::onUserRemoved", 500);
map_or_log_err(Self::add_or_remove_user(user_id), Ok)
}
fn clearNamespace(&self, domain: Domain, nspace: i64) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::clearNamespace", 500);
map_or_log_err(Self::clear_namespace(domain, nspace), Ok)
}
fn getState(&self, user_id: i32) -> BinderResult<AidlUserState> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::getState", 500);
map_or_log_err(Self::get_state(user_id), Ok)
}
fn earlyBootEnded(&self) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::earlyBootEnded", 500);
map_or_log_err(Self::early_boot_ended(), Ok)
}
fn onDeviceOffBody(&self) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::onDeviceOffBody", 500);
map_or_log_err(Self::on_device_off_body(), Ok)
}
@@ -251,6 +264,7 @@
source: &KeyDescriptor,
destination: &KeyDescriptor,
) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreMaintenance::migrateKeyNamespace", 500);
map_or_log_err(Self::migrate_key_namespace(source, destination), Ok)
}
}
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 0b5c77a..8d7ad0a 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -128,7 +128,7 @@
use crate::enforcements::AuthInfo;
use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode};
use crate::metrics::log_key_operation_event_stats;
-use crate::utils::Asp;
+use crate::utils::{watchdog as wd, Asp};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
IKeyMintOperation::IKeyMintOperation, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose,
SecurityLevel::SecurityLevel,
@@ -291,6 +291,8 @@
}
};
+ let _wp = wd::watch_millis("In Operation::prune: calling abort()", 500);
+
// We abort the operation. If there was an error we log it but ignore it.
if let Err(e) = map_km_error(km_op.abort()) {
log::error!("In prune: KeyMint::abort failed with {:?}.", e);
@@ -370,10 +372,10 @@
.before_update()
.context("In update_aad: Trying to get auth tokens.")?;
- self.update_outcome(
- &mut *outcome,
- map_km_error(km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())),
- )
+ self.update_outcome(&mut *outcome, {
+ let _wp = wd::watch_millis("Operation::update_aad: calling updateAad", 500);
+ map_km_error(km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref()))
+ })
.context("In update_aad: KeyMint::update failed.")?;
Ok(())
@@ -397,10 +399,10 @@
.context("In update: Trying to get auth tokens.")?;
let output = self
- .update_outcome(
- &mut *outcome,
- map_km_error(km_op.update(input, hat.as_ref(), tst.as_ref())),
- )
+ .update_outcome(&mut *outcome, {
+ let _wp = wd::watch_millis("Operation::update: calling update", 500);
+ map_km_error(km_op.update(input, hat.as_ref(), tst.as_ref()))
+ })
.context("In update: KeyMint::update failed.")?;
if output.is_empty() {
@@ -430,16 +432,16 @@
.context("In finish: Trying to get auth tokens.")?;
let output = self
- .update_outcome(
- &mut *outcome,
+ .update_outcome(&mut *outcome, {
+ let _wp = wd::watch_millis("Operation::finish: calling finish", 500);
map_km_error(km_op.finish(
input,
signature,
hat.as_ref(),
tst.as_ref(),
confirmation_token.as_deref(),
- )),
- )
+ ))
+ })
.context("In finish: KeyMint::finish failed.")?;
self.auth_info.lock().unwrap().after_finish().context("In finish.")?;
@@ -463,7 +465,10 @@
let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
self.km_op.get_interface().context("In abort: Failed to get KeyMintOperation.")?;
- map_km_error(km_op.abort()).context("In abort: KeyMint::abort failed.")
+ {
+ let _wp = wd::watch_millis("Operation::abort: calling abort", 500);
+ map_km_error(km_op.abort()).context("In abort: KeyMint::abort failed.")
+ }
}
}
@@ -837,6 +842,7 @@
impl IKeystoreOperation for KeystoreOperation {
fn updateAad(&self, aad_input: &[u8]) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IKeystoreOperation::updateAad", 500);
map_or_log_err(
self.with_locked_operation(
|op| op.update_aad(aad_input).context("In KeystoreOperation::updateAad"),
@@ -847,6 +853,7 @@
}
fn update(&self, input: &[u8]) -> binder::public_api::Result<Option<Vec<u8>>> {
+ let _wp = wd::watch_millis("IKeystoreOperation::update", 500);
map_or_log_err(
self.with_locked_operation(
|op| op.update(input).context("In KeystoreOperation::update"),
@@ -860,6 +867,7 @@
input: Option<&[u8]>,
signature: Option<&[u8]>,
) -> binder::public_api::Result<Option<Vec<u8>>> {
+ let _wp = wd::watch_millis("IKeystoreOperation::finish", 500);
map_or_log_err(
self.with_locked_operation(
|op| op.finish(input, signature).context("In KeystoreOperation::finish"),
@@ -870,6 +878,7 @@
}
fn abort(&self) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IKeystoreOperation::abort", 500);
map_err_with(
self.with_locked_operation(
|op| op.abort(Outcome::Abort).context("In KeystoreOperation::abort"),
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index 06432fe..9e6ef41 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -22,7 +22,7 @@
error::{map_km_error, Error},
globals::get_keymint_device,
super_key::KeyBlob,
- utils::{key_characteristics_to_internal, Asp, AID_KEYSTORE},
+ utils::{key_characteristics_to_internal, watchdog as wd, Asp, AID_KEYSTORE},
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
BeginResult::BeginResult, ErrorCode::ErrorCode, HardwareAuthToken::HardwareAuthToken,
@@ -151,7 +151,7 @@
self.create_and_store_key(db, &key_desc, |km_dev| km_dev.generateKey(¶ms, None))
.context("In lookup_or_generate_key: generate_and_store_key failed")?;
Self::lookup_from_desc(db, key_desc)
- .context("In lookup_or_generate_key: secpnd lookup failed")
+ .context("In lookup_or_generate_key: second lookup failed")
}
}
@@ -170,8 +170,14 @@
{
match f(key_blob) {
Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
- let upgraded_blob = map_km_error(km_dev.upgradeKey(key_blob, &[]))
- .context("In upgrade_keyblob_if_required_with: Upgrade failed")?;
+ let upgraded_blob = map_km_error({
+ let _wp = wd::watch_millis(
+ "In KeyMintDevice::upgrade_keyblob_if_required_with: calling upgradeKey.",
+ 500,
+ );
+ km_dev.upgradeKey(key_blob, &[])
+ })
+ .context("In upgrade_keyblob_if_required_with: Upgrade failed")?;
let mut new_blob_metadata = BlobMetaData::new();
new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
@@ -223,14 +229,20 @@
let begin_result: BeginResult = self
.upgrade_keyblob_if_required_with(db, &km_dev, key_id_guard, &key_blob, |blob| {
- map_km_error(km_dev.begin(purpose, blob, operation_parameters, auth_token))
+ map_km_error({
+ let _wp = wd::watch_millis("In use_key_in_one_step: calling: begin", 500);
+ km_dev.begin(purpose, blob, operation_parameters, auth_token)
+ })
})
.context("In use_key_in_one_step: Failed to begin operation.")?;
let operation: Strong<dyn IKeyMintOperation> = begin_result
.operation
.ok_or_else(Error::sys)
.context("In use_key_in_one_step: Operation missing")?;
- map_km_error(operation.finish(Some(input), None, None, None, None))
- .context("In use_key_in_one_step: Failed to finish operation.")
+ map_km_error({
+ let _wp = wd::watch_millis("In use_key_in_one_step: calling: finish", 500);
+ operation.finish(Some(input), None, None, None, None)
+ })
+ .context("In use_key_in_one_step: Failed to finish operation.")
}
}
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index f8ee369..fc1a6ad 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -45,7 +45,7 @@
use crate::database::{CertificateChain, KeystoreDB, Uuid};
use crate::error::{self, map_or_log_err, map_rem_prov_error, Error};
use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB};
-use crate::utils::Asp;
+use crate::utils::{watchdog as wd, Asp};
/// Contains helper functions to check if remote provisioning is enabled on the system and, if so,
/// to assign and retrieve attestation keys and certificate chains.
@@ -392,6 +392,7 @@
expired_by: i64,
sec_level: SecurityLevel,
) -> binder::public_api::Result<AttestationPoolStatus> {
+ let _wp = wd::watch_millis("IRemoteProvisioning::getPoolStatus", 500);
map_or_log_err(self.get_pool_status(expired_by, sec_level), Ok)
}
@@ -405,6 +406,7 @@
protected_data: &mut ProtectedData,
device_info: &mut DeviceInfo,
) -> binder::public_api::Result<Vec<u8>> {
+ let _wp = wd::watch_millis("IRemoteProvisioning::generateCsr", 500);
map_or_log_err(
self.generate_csr(
test_mode,
@@ -427,6 +429,7 @@
expiration_date: i64,
sec_level: SecurityLevel,
) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IRemoteProvisioning::provisionCertChain", 500);
map_or_log_err(
self.provision_cert_chain(public_key, batch_cert, certs, expiration_date, sec_level),
Ok,
@@ -438,14 +441,17 @@
is_test_mode: bool,
sec_level: SecurityLevel,
) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IRemoteProvisioning::generateKeyPair", 500);
map_or_log_err(self.generate_key_pair(is_test_mode, sec_level), Ok)
}
fn getSecurityLevels(&self) -> binder::public_api::Result<Vec<SecurityLevel>> {
+ let _wp = wd::watch_millis("IRemoteProvisioning::getSecurityLevels", 500);
map_or_log_err(self.get_security_levels(), Ok)
}
fn deleteAllKeys(&self) -> binder::public_api::Result<i64> {
+ let _wp = wd::watch_millis("IRemoteProvisioning::deleteAllKeys", 500);
map_or_log_err(self.delete_all_keys(), Ok)
}
}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 53880a1..d10aba0 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -14,6 +14,30 @@
//! This crate implements the IKeystoreSecurityLevel interface.
+use crate::attestation_key_utils::{get_attest_key_info, AttestationKeyInfo};
+use crate::audit_log::{log_key_deleted, log_key_generated, log_key_imported};
+use crate::database::{CertificateInfo, KeyIdGuard};
+use crate::error::{self, map_km_error, map_or_log_err, Error, ErrorCode};
+use crate::globals::{DB, ENFORCEMENTS, LEGACY_MIGRATOR, SUPER_KEY};
+use crate::key_parameter::KeyParameter as KsKeyParam;
+use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
+use crate::metrics::log_key_creation_event_stats;
+use crate::remote_provisioning::RemProvState;
+use crate::super_key::{KeyBlob, SuperKeyManager};
+use crate::utils::{
+ check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
+ key_characteristics_to_internal, uid_to_android_user, watchdog as wd, Asp,
+};
+use crate::{
+ database::{
+ BlobMetaData, BlobMetaEntry, DateTime, KeyEntry, KeyEntryLoadBits, KeyMetaData,
+ KeyMetaEntry, KeyType, SubComponentType, Uuid,
+ },
+ operation::KeystoreOperation,
+ operation::LoggingInfo,
+ operation::OperationDb,
+ permission::KeyPerm,
+};
use crate::{globals::get_keymint_device, id_rotation::IdRotationState};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, AttestationKey::AttestationKey,
@@ -30,34 +54,6 @@
IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
KeyMetadata::KeyMetadata, KeyParameters::KeyParameters,
};
-
-use crate::attestation_key_utils::{get_attest_key_info, AttestationKeyInfo};
-use crate::audit_log::{log_key_deleted, log_key_generated, log_key_imported};
-use crate::database::{CertificateInfo, KeyIdGuard};
-use crate::globals::{DB, ENFORCEMENTS, LEGACY_MIGRATOR, SUPER_KEY};
-use crate::key_parameter::KeyParameter as KsKeyParam;
-use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
-use crate::metrics::log_key_creation_event_stats;
-use crate::remote_provisioning::RemProvState;
-use crate::super_key::{KeyBlob, SuperKeyManager};
-use crate::utils::{
- check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
- uid_to_android_user, Asp,
-};
-use crate::{
- database::{
- BlobMetaData, BlobMetaEntry, DateTime, KeyEntry, KeyEntryLoadBits, KeyMetaData,
- KeyMetaEntry, KeyType, SubComponentType, Uuid,
- },
- operation::KeystoreOperation,
- operation::LoggingInfo,
- operation::OperationDb,
- permission::KeyPerm,
-};
-use crate::{
- error::{self, map_km_error, map_or_log_err, Error, ErrorCode},
- utils::key_characteristics_to_internal,
-};
use anyhow::{anyhow, Context, Result};
/// Implementation of the IKeystoreSecurityLevel Interface.
@@ -104,6 +100,11 @@
Ok((result, km_uuid))
}
+ fn watch_millis(&self, id: &'static str, millis: u64) -> Option<wd::WatchPoint> {
+ let sec_level = self.security_level;
+ wd::watch_millis_with(id, millis, move || format!("SecurityLevel {:?}", sec_level))
+ }
+
fn store_new_key(
&self,
key: KeyDescriptor,
@@ -313,12 +314,13 @@
&blob_metadata,
&operation_parameters,
|blob| loop {
- match map_km_error(km_dev.begin(
- purpose,
- blob,
- &operation_parameters,
- immediate_hat.as_ref(),
- )) {
+ match map_km_error({
+ let _wp = self.watch_millis(
+ "In KeystoreSecurityLevel::create_operation: calling begin",
+ 500,
+ );
+ km_dev.begin(purpose, blob, &operation_parameters, immediate_hat.as_ref())
+ }) {
Err(Error::Km(ErrorCode::TOO_MANY_OPERATIONS)) => {
self.operation_db.prune(caller_uid, forced)?;
continue;
@@ -334,12 +336,19 @@
let op_params: Vec<KeyParameter> = operation_parameters.to_vec();
let operation = match begin_result.operation {
- Some(km_op) => {
- self.operation_db.create_operation(km_op, caller_uid, auth_info, forced,
- LoggingInfo::new(self.security_level, purpose, op_params,
- upgraded_blob.is_some()))
- },
- None => return Err(Error::sys()).context("In create_operation: Begin operation returned successfully, but did not return a valid operation."),
+ Some(km_op) => self.operation_db.create_operation(
+ km_op,
+ caller_uid,
+ auth_info,
+ forced,
+ LoggingInfo::new(self.security_level, purpose, op_params, upgraded_blob.is_some()),
+ ),
+ None => {
+ return Err(Error::sys()).context(concat!(
+ "In create_operation: Begin operation returned successfully, ",
+ "but did not return a valid operation."
+ ))
+ }
};
let op_binder: binder::public_api::Strong<dyn IKeystoreOperation> =
@@ -371,9 +380,19 @@
let mut result = params.to_vec();
// If there is an attestation challenge we need to get an application id.
if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) {
- let aaid = keystore2_aaid::get_aaid(uid).map_err(|e| {
- anyhow!(format!("In add_certificate_parameters: get_aaid returned status {}.", e))
- })?;
+ let aaid = {
+ let _wp = self.watch_millis(
+ "In KeystoreSecurityLevel::add_certificate_parameters calling: get_aaid",
+ 500,
+ );
+ keystore2_aaid::get_aaid(uid).map_err(|e| {
+ anyhow!(format!(
+ "In add_certificate_parameters: get_aaid returned status {}.",
+ e
+ ))
+ })
+ }?;
+
result.push(KeyParameter {
tag: Tag::ATTESTATION_APPLICATION_ID,
value: KeyParameterValue::Blob(aaid),
@@ -495,21 +514,48 @@
attestKeyParams: vec![],
issuerSubjectName: issuer_subject.clone(),
});
- map_km_error(km_dev.generateKey(¶ms, attest_key.as_ref()))
+ map_km_error({
+ let _wp = self.watch_millis(
+ concat!(
+ "In KeystoreSecurityLevel::generate_key (UserGenerated): ",
+ "calling generate_key."
+ ),
+ 5000, // Generate can take a little longer.
+ );
+ km_dev.generateKey(¶ms, attest_key.as_ref())
+ })
},
)
.context("In generate_key: Using user generated attestation key.")
.map(|(result, _)| result),
Some(AttestationKeyInfo::RemoteProvisioned { attestation_key, attestation_certs }) => {
- map_km_error(km_dev.generateKey(¶ms, Some(&attestation_key)))
- .context("While generating Key with remote provisioned attestation key.")
- .map(|mut creation_result| {
- creation_result.certificateChain.push(attestation_certs);
- creation_result
- })
+ map_km_error({
+ let _wp = self.watch_millis(
+ concat!(
+ "In KeystoreSecurityLevel::generate_key (RemoteProvisioned): ",
+ "calling generate_key.",
+ ),
+ 5000, // Generate can take a little longer.
+ );
+ km_dev.generateKey(¶ms, Some(&attestation_key))
+ })
+ .context("While generating Key with remote provisioned attestation key.")
+ .map(|mut creation_result| {
+ creation_result.certificateChain.push(attestation_certs);
+ creation_result
+ })
}
- None => map_km_error(km_dev.generateKey(¶ms, None))
- .context("While generating Key without explicit attestation key."),
+ None => map_km_error({
+ let _wp = self.watch_millis(
+ concat!(
+ "In KeystoreSecurityLevel::generate_key (No attestation): ",
+ "calling generate_key.",
+ ),
+ 5000, // Generate can take a little longer.
+ );
+ km_dev.generateKey(¶ms, None)
+ })
+ .context("While generating Key without explicit attestation key."),
}
.context("In generate_key.")?;
@@ -566,9 +612,12 @@
let km_dev: Strong<dyn IKeyMintDevice> =
self.keymint.get_interface().context("In import_key: Trying to get the KM device")?;
- let creation_result =
- map_km_error(km_dev.importKey(¶ms, format, key_data, None /* attestKey */))
- .context("In import_key: Trying to call importKey")?;
+ let creation_result = map_km_error({
+ let _wp =
+ self.watch_millis("In KeystoreSecurityLevel::import_key: calling importKey.", 500);
+ km_dev.importKey(¶ms, format, key_data, None /* attestKey */)
+ })
+ .context("In import_key: Trying to call importKey")?;
let user_id = uid_to_android_user(caller_uid);
self.store_new_key(key, creation_result, user_id, Some(flags)).context("In import_key.")
@@ -681,6 +730,10 @@
&wrapping_blob_metadata,
&[],
|wrapping_blob| {
+ let _wp = self.watch_millis(
+ "In KeystoreSecurityLevel::import_wrapped_key: calling importWrappedKey.",
+ 500,
+ );
let creation_result = map_km_error(km_dev.importWrappedKey(
wrapped_data,
wrapping_blob,
@@ -739,8 +792,17 @@
{
match f(key_blob) {
Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
- let upgraded_blob = map_km_error(km_dev.upgradeKey(key_blob, params))
- .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
+ let upgraded_blob = {
+ let _wp = self.watch_millis(
+ concat!(
+ "In KeystoreSecurityLevel::upgrade_keyblob_if_required_with: ",
+ "calling upgradeKey."
+ ),
+ 500,
+ );
+ map_km_error(km_dev.upgradeKey(key_blob, params))
+ }
+ .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
if let Some(kid) = key_id_guard {
Self::store_upgraded_keyblob(
@@ -810,14 +872,35 @@
"In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: ",
"Getting keymint device interface"
))?;
- match map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob)) {
+ match {
+ let _wp = self.watch_millis(
+ concat!(
+ "In IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ",
+ "calling convertStorageKeyToEphemeral (1)"
+ ),
+ 500,
+ );
+ map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob))
+ } {
Ok(result) => {
Ok(EphemeralStorageKeyResponse { ephemeralKey: result, upgradedBlob: None })
}
Err(error::Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
- let upgraded_blob = map_km_error(km_dev.upgradeKey(key_blob, &[]))
- .context("In convert_storage_key_to_ephemeral: Failed to upgrade key blob.")?;
- let ephemeral_key = map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob))
+ let upgraded_blob = {
+ let _wp = self.watch_millis(
+ "In convert_storage_key_to_ephemeral: calling upgradeKey",
+ 500,
+ );
+ map_km_error(km_dev.upgradeKey(key_blob, &[]))
+ }
+ .context("In convert_storage_key_to_ephemeral: Failed to upgrade key blob.")?;
+ let ephemeral_key = {
+ let _wp = self.watch_millis(
+ "In convert_storage_key_to_ephemeral: calling convertStorageKeyToEphemeral (2)",
+ 500,
+ );
+ map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob))
+ }
.context(concat!(
"In convert_storage_key_to_ephemeral: ",
"Failed to retrieve ephemeral key (after upgrade)."
@@ -851,7 +934,11 @@
.keymint
.get_interface()
.context("In IKeystoreSecurityLevel delete_key: Getting keymint device interface")?;
- map_km_error(km_dev.deleteKey(&key_blob)).context("In keymint device deleteKey")
+ {
+ let _wp =
+ self.watch_millis("In KeystoreSecuritylevel::delete_key: calling deleteKey", 500);
+ map_km_error(km_dev.deleteKey(&key_blob)).context("In keymint device deleteKey")
+ }
}
}
@@ -864,6 +951,7 @@
operation_parameters: &[KeyParameter],
forced: bool,
) -> binder::public_api::Result<CreateOperationResponse> {
+ let _wp = self.watch_millis("IKeystoreSecurityLevel::createOperation", 500);
map_or_log_err(self.create_operation(key, operation_parameters, forced), Ok)
}
fn generateKey(
@@ -874,6 +962,9 @@
flags: i32,
entropy: &[u8],
) -> binder::public_api::Result<KeyMetadata> {
+ // Duration is set to 5 seconds, because generateKey - especially for RSA keys, takes more
+ // time than other operations
+ let _wp = self.watch_millis("IKeystoreSecurityLevel::generateKey", 5000);
let result = self.generate_key(key, attestation_key, params, flags, entropy);
log_key_creation_event_stats(self.security_level, params, &result);
log_key_generated(key, ThreadState::get_calling_uid(), result.is_ok());
@@ -887,6 +978,7 @@
flags: i32,
key_data: &[u8],
) -> binder::public_api::Result<KeyMetadata> {
+ let _wp = self.watch_millis("IKeystoreSecurityLevel::importKey", 500);
let result = self.import_key(key, attestation_key, params, flags, key_data);
log_key_creation_event_stats(self.security_level, params, &result);
log_key_imported(key, ThreadState::get_calling_uid(), result.is_ok());
@@ -900,6 +992,7 @@
params: &[KeyParameter],
authenticators: &[AuthenticatorSpec],
) -> binder::public_api::Result<KeyMetadata> {
+ let _wp = self.watch_millis("IKeystoreSecurityLevel::importWrappedKey", 500);
let result =
self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators);
log_key_creation_event_stats(self.security_level, params, &result);
@@ -910,9 +1003,11 @@
&self,
storage_key: &KeyDescriptor,
) -> binder::public_api::Result<EphemeralStorageKeyResponse> {
+ let _wp = self.watch_millis("IKeystoreSecurityLevel::convertStorageKeyToEphemeral", 500);
map_or_log_err(self.convert_storage_key_to_ephemeral(storage_key), Ok)
}
fn deleteKey(&self, key: &KeyDescriptor) -> binder::public_api::Result<()> {
+ let _wp = self.watch_millis("IKeystoreSecurityLevel::deleteKey", 500);
let result = self.delete_key(key);
log_key_deleted(key, ThreadState::get_calling_uid(), result.is_ok());
map_or_log_err(result, Ok)
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index b8ea244..3ce0550 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -22,7 +22,7 @@
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
check_grant_permission, check_key_permission, check_keystore_permission,
- key_parameters_to_authorizations, Asp,
+ key_parameters_to_authorizations, watchdog as wd, Asp,
};
use crate::{
database::Uuid,
@@ -354,9 +354,13 @@
&self,
security_level: SecurityLevel,
) -> binder::public_api::Result<Strong<dyn IKeystoreSecurityLevel>> {
+ let _wp = wd::watch_millis_with("IKeystoreService::getSecurityLevel", 500, move || {
+ format!("security_level: {}", security_level.0)
+ });
map_or_log_err(self.get_security_level(security_level), Ok)
}
fn getKeyEntry(&self, key: &KeyDescriptor) -> binder::public_api::Result<KeyEntryResponse> {
+ let _wp = wd::watch_millis("IKeystoreService::get_key_entry", 500);
map_or_log_err(self.get_key_entry(key), Ok)
}
fn updateSubcomponent(
@@ -365,6 +369,7 @@
public_cert: Option<&[u8]>,
certificate_chain: Option<&[u8]>,
) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IKeystoreService::updateSubcomponent", 500);
map_or_log_err(self.update_subcomponent(key, public_cert, certificate_chain), Ok)
}
fn listEntries(
@@ -372,9 +377,11 @@
domain: Domain,
namespace: i64,
) -> binder::public_api::Result<Vec<KeyDescriptor>> {
+ let _wp = wd::watch_millis("IKeystoreService::listEntries", 500);
map_or_log_err(self.list_entries(domain, namespace), Ok)
}
fn deleteKey(&self, key: &KeyDescriptor) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IKeystoreService::deleteKey", 500);
let result = self.delete_key(key);
log_key_deleted(key, ThreadState::get_calling_uid(), result.is_ok());
map_or_log_err(result, Ok)
@@ -385,9 +392,11 @@
grantee_uid: i32,
access_vector: i32,
) -> binder::public_api::Result<KeyDescriptor> {
+ let _wp = wd::watch_millis("IKeystoreService::grant", 500);
map_or_log_err(self.grant(key, grantee_uid, access_vector.into()), Ok)
}
fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> binder::public_api::Result<()> {
+ let _wp = wd::watch_millis("IKeystoreService::ungrant", 500);
map_or_log_err(self.ungrant(key, grantee_uid), Ok)
}
}
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 50a5f31..848707c 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -29,6 +29,7 @@
legacy_migrator::LegacyMigrator,
raw_device::KeyMintDevice,
try_insert::TryInsert,
+ utils::watchdog as wd,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken,
@@ -949,6 +950,10 @@
let key_params: Vec<KmKeyParameter> =
key_params.into_iter().map(|x| x.into()).collect();
km_dev.create_and_store_key(db, &key_desc, |dev| {
+ let _wp = wd::watch_millis(
+ "In lock_screen_lock_bound_key: calling importKey.",
+ 500,
+ );
dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None)
})?;
entry.biometric_unlock = Some(BiometricUnlock {
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index bca27d1..9852aad 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -107,11 +107,17 @@
let permission_controller: binder::Strong<dyn IPermissionController::IPermissionController> =
binder::get_interface("permission")?;
- let binder_result = permission_controller.checkPermission(
- "android.permission.READ_PRIVILEGED_PHONE_STATE",
- ThreadState::get_calling_pid(),
- ThreadState::get_calling_uid() as i32,
- );
+ let binder_result = {
+ let _wp = watchdog::watch_millis(
+ "In check_device_attestation_permissions: calling checkPermission.",
+ 500,
+ );
+ permission_controller.checkPermission(
+ "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ ThreadState::get_calling_pid(),
+ ThreadState::get_calling_uid() as i32,
+ )
+ };
let has_permissions = map_binder_status(binder_result)
.context("In check_device_attestation_permissions: checkPermission failed")?;
match has_permissions {
@@ -233,6 +239,55 @@
unsafe { cutils_bindgen::multiuser_get_user_id(uid) }
}
+/// This module provides helpers for simplified use of the watchdog module.
+#[cfg(feature = "watchdog")]
+pub mod watchdog {
+ pub use crate::watchdog::WatchPoint;
+ use crate::watchdog::Watchdog;
+ use lazy_static::lazy_static;
+ use std::sync::Arc;
+ use std::time::Duration;
+
+ lazy_static! {
+ /// A Watchdog thread, that can be used to create watch points.
+ static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10));
+ }
+
+ /// Sets a watch point with `id` and a timeout of `millis` milliseconds.
+ pub fn watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint> {
+ Watchdog::watch(&WD, id, Duration::from_millis(millis))
+ }
+
+ /// Like `watch_millis` but with a callback that is called every time a report
+ /// is printed about this watch point.
+ pub fn watch_millis_with(
+ id: &'static str,
+ millis: u64,
+ callback: impl Fn() -> String + Send + 'static,
+ ) -> Option<WatchPoint> {
+ Watchdog::watch_with(&WD, id, Duration::from_millis(millis), callback)
+ }
+}
+
+/// This module provides empty/noop implementations of the watch dog utility functions.
+#[cfg(not(feature = "watchdog"))]
+pub mod watchdog {
+ /// Noop watch point.
+ pub struct WatchPoint();
+ /// Sets a Noop watch point.
+ fn watch_millis(_: &'static str, _: u64) -> Option<WatchPoint> {
+ None
+ }
+
+ pub fn watch_millis_with(
+ _: &'static str,
+ _: u64,
+ _: impl Fn() -> String + Send + 'static,
+ ) -> Option<WatchPoint> {
+ None
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/keystore2/src/watchdog.rs b/keystore2/src/watchdog.rs
new file mode 100644
index 0000000..9cca171
--- /dev/null
+++ b/keystore2/src/watchdog.rs
@@ -0,0 +1,326 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Can be removed when instrumentations are added to keystore.
+#![allow(dead_code)]
+
+//! This module implements a watchdog thread.
+
+use std::{
+ cmp::min,
+ collections::HashMap,
+ sync::Arc,
+ sync::{Condvar, Mutex, MutexGuard},
+ thread,
+};
+use std::{
+ marker::PhantomData,
+ time::{Duration, Instant},
+};
+
+/// Represents a Watchdog record. It can be created with `Watchdog::watch` or
+/// `Watchdog::watch_with`. It disarms the record when dropped.
+pub struct WatchPoint {
+ id: &'static str,
+ wd: Arc<Watchdog>,
+ not_send: PhantomData<*mut ()>, // WatchPoint must not be Send.
+}
+
+impl Drop for WatchPoint {
+ fn drop(&mut self) {
+ self.wd.disarm(self.id)
+ }
+}
+
+#[derive(Debug, PartialEq, Eq)]
+enum State {
+ NotRunning,
+ Running,
+}
+
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+struct Index {
+ tid: thread::ThreadId,
+ id: &'static str,
+}
+
+struct Record {
+ started: Instant,
+ deadline: Instant,
+ callback: Option<Box<dyn Fn() -> String + Send + 'static>>,
+}
+
+struct WatchdogState {
+ state: State,
+ thread: Option<thread::JoinHandle<()>>,
+ timeout: Duration,
+ records: HashMap<Index, Record>,
+ last_report: Instant,
+ has_overdue: bool,
+}
+
+impl WatchdogState {
+ fn update_overdue_and_find_next_timeout(&mut self) -> (bool, Option<Duration>) {
+ let now = Instant::now();
+ let mut next_timeout: Option<Duration> = None;
+ let mut has_overdue = false;
+ for (_, r) in self.records.iter() {
+ let timeout = r.deadline.saturating_duration_since(now);
+ if timeout == Duration::new(0, 0) {
+ has_overdue = true;
+ continue;
+ }
+ next_timeout = match next_timeout {
+ Some(nt) => {
+ if timeout < nt {
+ Some(timeout)
+ } else {
+ Some(nt)
+ }
+ }
+ None => Some(timeout),
+ };
+ }
+ (has_overdue, next_timeout)
+ }
+
+ fn log_report(&mut self, has_overdue: bool) -> bool {
+ match (self.has_overdue, has_overdue) {
+ (true, true) => {
+ if self.last_report.elapsed() < Watchdog::NOISY_REPORT_TIMEOUT {
+ self.has_overdue = false;
+ return false;
+ }
+ }
+ (_, false) => {
+ self.has_overdue = false;
+ return false;
+ }
+ (false, true) => {}
+ }
+ self.last_report = Instant::now();
+ self.has_overdue = has_overdue;
+ log::warn!("Keystore Watchdog report:");
+ log::warn!("Overdue records:");
+ let now = Instant::now();
+ for (i, r) in self.records.iter() {
+ if r.deadline.saturating_duration_since(now) == Duration::new(0, 0) {
+ match &r.callback {
+ Some(cb) => {
+ log::warn!(
+ "{:?} {} Pending: {:?} Overdue {:?}: {}",
+ i.tid,
+ i.id,
+ r.started.elapsed(),
+ r.deadline.elapsed(),
+ (cb)()
+ );
+ }
+ None => {
+ log::warn!(
+ "{:?} {} Pending: {:?} Overdue {:?}",
+ i.tid,
+ i.id,
+ r.started.elapsed(),
+ r.deadline.elapsed()
+ );
+ }
+ }
+ }
+ }
+ true
+ }
+
+ fn disarm(&mut self, index: Index) {
+ self.records.remove(&index);
+ }
+
+ fn arm(&mut self, index: Index, record: Record) {
+ if self.records.insert(index.clone(), record).is_some() {
+ log::warn!("Recursive watchdog record at \"{:?}\" replaces previous record.", index);
+ }
+ }
+}
+
+/// Watchdog spawns a thread that logs records of all overdue watch points when a deadline
+/// is missed and at least every second as long as overdue watch points exist.
+/// The thread terminates when idle for a given period of time.
+pub struct Watchdog {
+ state: Arc<(Condvar, Mutex<WatchdogState>)>,
+}
+
+impl Watchdog {
+ /// If we have overdue records, we want to be noisy about it and log a report
+ /// at least every `NOISY_REPORT_TIMEOUT` interval.
+ const NOISY_REPORT_TIMEOUT: Duration = Duration::from_secs(1);
+
+ /// Construct a [`Watchdog`]. When `timeout` has elapsed since the watchdog thread became
+ /// idle, i.e., there are no more active or overdue watch points, the watchdog thread
+ /// terminates.
+ pub fn new(timeout: Duration) -> Arc<Self> {
+ Arc::new(Self {
+ state: Arc::new((
+ Condvar::new(),
+ Mutex::new(WatchdogState {
+ state: State::NotRunning,
+ thread: None,
+ timeout,
+ records: HashMap::new(),
+ last_report: Instant::now(),
+ has_overdue: false,
+ }),
+ )),
+ })
+ }
+
+ fn watch_with_optional(
+ wd: &Arc<Self>,
+ callback: Option<Box<dyn Fn() -> String + Send + 'static>>,
+ id: &'static str,
+ timeout: Duration,
+ ) -> Option<WatchPoint> {
+ let deadline = Instant::now().checked_add(timeout);
+ if deadline.is_none() {
+ log::warn!("Deadline computation failed for WatchPoint \"{}\"", id);
+ log::warn!("WatchPoint not armed.");
+ return None;
+ }
+ wd.arm(callback, id, deadline.unwrap());
+ Some(WatchPoint { id, wd: wd.clone(), not_send: Default::default() })
+ }
+
+ /// Create a new watch point. If the WatchPoint is not dropped before the timeout
+ /// expires, a report is logged at least every second, which includes the id string
+ /// and whatever string the callback returns.
+ pub fn watch_with(
+ wd: &Arc<Self>,
+ id: &'static str,
+ timeout: Duration,
+ callback: impl Fn() -> String + Send + 'static,
+ ) -> Option<WatchPoint> {
+ Self::watch_with_optional(wd, Some(Box::new(callback)), id, timeout)
+ }
+
+ /// Like `watch_with`, but without a callback.
+ pub fn watch(wd: &Arc<Self>, id: &'static str, timeout: Duration) -> Option<WatchPoint> {
+ Self::watch_with_optional(wd, None, id, timeout)
+ }
+
+ fn arm(
+ &self,
+ callback: Option<Box<dyn Fn() -> String + Send + 'static>>,
+ id: &'static str,
+ deadline: Instant,
+ ) {
+ let tid = thread::current().id();
+ let index = Index { tid, id };
+ let record = Record { started: Instant::now(), deadline, callback };
+
+ let (ref condvar, ref state) = *self.state;
+
+ let mut state = state.lock().unwrap();
+ state.arm(index, record);
+
+ if state.state != State::Running {
+ self.spawn_thread(&mut state);
+ }
+ drop(state);
+ condvar.notify_all();
+ }
+
+ fn disarm(&self, id: &'static str) {
+ let tid = thread::current().id();
+ let index = Index { tid, id };
+ let (_, ref state) = *self.state;
+
+ let mut state = state.lock().unwrap();
+ state.disarm(index);
+ // There is no need to notify condvar. There is no action required for the
+ // watchdog thread before the next deadline.
+ }
+
+ fn spawn_thread(&self, state: &mut MutexGuard<WatchdogState>) {
+ if let Some(t) = state.thread.take() {
+ t.join().expect("Watchdog thread panicked.");
+ }
+
+ let cloned_state = self.state.clone();
+
+ state.thread = Some(thread::spawn(move || {
+ let (ref condvar, ref state) = *cloned_state;
+
+ let mut state = state.lock().unwrap();
+
+ loop {
+ let (has_overdue, next_timeout) = state.update_overdue_and_find_next_timeout();
+ state.log_report(has_overdue);
+ let (next_timeout, idle) = match (has_overdue, next_timeout) {
+ (true, Some(next_timeout)) => {
+ (min(next_timeout, Self::NOISY_REPORT_TIMEOUT), false)
+ }
+ (false, Some(next_timeout)) => (next_timeout, false),
+ (true, None) => (Self::NOISY_REPORT_TIMEOUT, false),
+ (false, None) => (state.timeout, true),
+ };
+
+ let (s, timeout) = condvar.wait_timeout(state, next_timeout).unwrap();
+ state = s;
+
+ if idle && timeout.timed_out() && state.records.is_empty() {
+ state.state = State::NotRunning;
+ break;
+ }
+ }
+ log::info!("Watchdog thread idle -> terminating. Have a great day.");
+ }));
+ state.state = State::Running;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+
+ use super::*;
+ use std::sync::atomic;
+ use std::thread;
+ use std::time::Duration;
+
+ #[test]
+ fn test_watchdog() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_watchdog_tests")
+ .with_min_level(log::Level::Debug),
+ );
+
+ let wd = Watchdog::new(Watchdog::NOISY_REPORT_TIMEOUT.checked_mul(3).unwrap());
+ let hit_count = Arc::new(atomic::AtomicU8::new(0));
+ let hit_count_clone = hit_count.clone();
+ let wp =
+ Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), move || {
+ format!("hit_count: {}", hit_count_clone.fetch_add(1, atomic::Ordering::Relaxed))
+ });
+ assert_eq!(0, hit_count.load(atomic::Ordering::Relaxed));
+ thread::sleep(Duration::from_millis(500));
+ assert_eq!(1, hit_count.load(atomic::Ordering::Relaxed));
+ thread::sleep(Watchdog::NOISY_REPORT_TIMEOUT);
+ assert_eq!(2, hit_count.load(atomic::Ordering::Relaxed));
+ drop(wp);
+ thread::sleep(Watchdog::NOISY_REPORT_TIMEOUT.checked_mul(4).unwrap());
+ assert_eq!(2, hit_count.load(atomic::Ordering::Relaxed));
+ let (_, ref state) = *wd.state;
+ let state = state.lock().unwrap();
+ assert_eq!(state.state, State::NotRunning);
+ }
+}
diff --git a/keystore2/vpnprofilestore/lib.rs b/keystore2/vpnprofilestore/lib.rs
index d92e045..8b3bc2b 100644
--- a/keystore2/vpnprofilestore/lib.rs
+++ b/keystore2/vpnprofilestore/lib.rs
@@ -23,7 +23,7 @@
ThreadState,
};
use anyhow::{Context, Result};
-use keystore2::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
+use keystore2::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader, utils::watchdog as wd};
use rusqlite::{
params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS,
};
@@ -366,15 +366,19 @@
impl IVpnProfileStore for VpnProfileStore {
fn get(&self, alias: &str) -> BinderResult<Vec<u8>> {
+ let _wp = wd::watch_millis("IVpnProfileStore::get", 500);
map_or_log_err(self.get(alias), Ok)
}
fn put(&self, alias: &str, profile: &[u8]) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IVpnProfileStore::put", 500);
map_or_log_err(self.put(alias, profile), Ok)
}
fn remove(&self, alias: &str) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IVpnProfileStore::remove", 500);
map_or_log_err(self.remove(alias), Ok)
}
fn list(&self, prefix: &str) -> BinderResult<Vec<String>> {
+ let _wp = wd::watch_millis("IVpnProfileStore::list", 500);
map_or_log_err(self.list(prefix), Ok)
}
}