blob: b5bdd9821e2052c0c39c6ad3d97b623a5a9b7aa0 [file] [log] [blame]
Janis Danisevskis93927dd2020-12-23 12:23:08 -08001// Copyright 2020, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! This module implements the key garbage collector.
16//! The key garbage collector has one public function `notify_gc()`. This will create
17//! a thread on demand which will query the database for unreferenced key entries,
18//! optionally dispose of sensitive key material appropriately, and then delete
19//! the key entry from the database.
20
21use crate::globals::{get_keymint_device, DB};
22use crate::{error::map_km_error, globals::ASYNC_TASK};
23use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
24use anyhow::Result;
25
26#[derive(Clone, Copy)]
27pub struct Gc {
28 remaining_tries: u32,
29}
30
31impl Gc {
32 const MAX_ERROR_RETRIES: u32 = 3u32;
33
34 /// Attempts to process one unreferenced key from the database.
35 /// Returns Ok(true) if a key was deleted and Ok(false) if there were no more keys to process.
36 /// We process one key at a time, because deleting a key is a time consuming process which
37 /// may involve calling into the KeyMint backend and we don't want to hog neither the backend
38 /// nor the database for extended periods of time.
39 fn process_one_key() -> Result<bool> {
40 DB.with(|db| {
41 let mut db = db.borrow_mut();
42 if let Some((key_id, mut key_entry)) = db.get_unreferenced_key()? {
43 if let Some(blob) = key_entry.take_km_blob() {
44 let km_dev: Box<dyn IKeyMintDevice> =
45 get_keymint_device(key_entry.sec_level())?.get_interface()?;
46 if let Err(e) = map_km_error(km_dev.deleteKey(&blob)) {
47 // Log but ignore error.
48 log::error!("Error trying to delete key. {:?}", e);
49 }
50 }
51 db.purge_key_entry(key_id)?;
52 return Ok(true);
53 }
54 Ok(false)
55 })
56 }
57
58 /// Processes one key and then schedules another attempt until it runs out of tries or keys
59 /// to delete.
60 fn process_all(mut self) {
61 match Self::process_one_key() {
62 // We successfully removed a key.
63 Ok(true) => self.remaining_tries = Self::MAX_ERROR_RETRIES,
64 // There were no more keys to remove. We may exit.
65 Ok(false) => self.remaining_tries = 0,
66 // An error occurred. We retry in case the error was transient, but
67 // we also count down the number of tries so that we don't spin
68 // indefinitely.
69 Err(e) => {
70 self.remaining_tries -= 1;
71 log::error!(
72 concat!(
73 "Failed to delete key. Retrying in case this error was transient. ",
74 "(Tries remaining {}) {:?}"
75 ),
76 self.remaining_tries,
77 e
78 )
79 }
80 }
81 if self.remaining_tries != 0 {
82 ASYNC_TASK.queue_lo(move || {
83 self.process_all();
84 })
85 }
86 }
87
88 /// Notifies the key garbage collector to iterate through unreferenced keys and attempt
89 /// their deletion. We only process one key at a time and then schedule another
90 /// attempt by queueing it in the async_task (low priority) queue.
91 pub fn notify_gc() {
92 ASYNC_TASK.queue_lo(|| Self { remaining_tries: Self::MAX_ERROR_RETRIES }.process_all())
93 }
94}