Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 1 | // 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 | |
Shaquille Johnson | 9da2e1c | 2022-09-19 12:39:01 +0000 | [diff] [blame] | 21 | use crate::ks_err; |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 22 | use crate::{ |
| 23 | async_task, |
David Drysdale | f1ba381 | 2024-05-08 17:50:13 +0100 | [diff] [blame] | 24 | database::{KeystoreDB, SupersededBlob, Uuid}, |
David Drysdale | 940a0ff | 2025-01-13 14:19:16 +0000 | [diff] [blame] | 25 | globals, |
Hasini Gunasinghe | deab85d | 2021-02-01 21:10:02 +0000 | [diff] [blame] | 26 | super_key::SuperKeyManager, |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 27 | }; |
| 28 | use anyhow::{Context, Result}; |
| 29 | use async_task::AsyncTask; |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 30 | use std::sync::{ |
| 31 | atomic::{AtomicU8, Ordering}, |
Janis Danisevskis | 0fd25a6 | 2022-01-04 19:53:37 -0800 | [diff] [blame] | 32 | Arc, RwLock, |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 33 | }; |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 34 | |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 35 | pub struct Gc { |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 36 | async_task: Arc<AsyncTask>, |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 37 | notified: Arc<AtomicU8>, |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 38 | } |
| 39 | |
| 40 | impl Gc { |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 41 | /// Creates a garbage collector using the given async_task. |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 42 | /// The garbage collector needs a function to invalidate key blobs, a database connection, |
| 43 | /// and a reference to the `SuperKeyManager`. They are obtained from the init function. |
| 44 | /// The function is only called if this is first time a garbage collector was initialized |
| 45 | /// with the given AsyncTask instance. |
| 46 | /// Note: It is a logical error to initialize different Gc instances with the same `AsyncTask`. |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 47 | pub fn new_init_with<F>(async_task: Arc<AsyncTask>, init: F) -> Self |
| 48 | where |
Hasini Gunasinghe | deab85d | 2021-02-01 21:10:02 +0000 | [diff] [blame] | 49 | F: FnOnce() -> ( |
| 50 | Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, |
| 51 | KeystoreDB, |
Janis Danisevskis | 0fd25a6 | 2022-01-04 19:53:37 -0800 | [diff] [blame] | 52 | Arc<RwLock<SuperKeyManager>>, |
Hasini Gunasinghe | deab85d | 2021-02-01 21:10:02 +0000 | [diff] [blame] | 53 | ) + Send |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 54 | + 'static, |
| 55 | { |
| 56 | let weak_at = Arc::downgrade(&async_task); |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 57 | let notified = Arc::new(AtomicU8::new(0)); |
| 58 | let notified_clone = notified.clone(); |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 59 | // Initialize the task's shelf. |
| 60 | async_task.queue_hi(move |shelf| { |
Hasini Gunasinghe | deab85d | 2021-02-01 21:10:02 +0000 | [diff] [blame] | 61 | let (invalidate_key, db, super_key) = init(); |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 62 | let notified = notified_clone; |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 63 | shelf.get_or_put_with(|| GcInternal { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 64 | deleted_blob_ids: vec![], |
| 65 | superseded_blobs: vec![], |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 66 | invalidate_key, |
| 67 | db, |
| 68 | async_task: weak_at, |
Hasini Gunasinghe | deab85d | 2021-02-01 21:10:02 +0000 | [diff] [blame] | 69 | super_key, |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 70 | notified, |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 71 | }); |
| 72 | }); |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 73 | Self { async_task, notified } |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 74 | } |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 75 | |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 76 | /// Notifies the key garbage collector to iterate through orphaned and superseded blobs and |
| 77 | /// attempts their deletion. We only process one key at a time and then schedule another |
| 78 | /// attempt by queueing it in the async_task (low priority) queue. |
| 79 | pub fn notify_gc(&self) { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 80 | if let Ok(0) = self.notified.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) { |
| 81 | self.async_task.queue_lo(|shelf| shelf.get_downcast_mut::<GcInternal>().unwrap().step()) |
| 82 | } |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 83 | } |
| 84 | } |
| 85 | |
| 86 | struct GcInternal { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 87 | deleted_blob_ids: Vec<i64>, |
David Drysdale | f1ba381 | 2024-05-08 17:50:13 +0100 | [diff] [blame] | 88 | superseded_blobs: Vec<SupersededBlob>, |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 89 | invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, |
| 90 | db: KeystoreDB, |
| 91 | async_task: std::sync::Weak<AsyncTask>, |
Janis Danisevskis | 0fd25a6 | 2022-01-04 19:53:37 -0800 | [diff] [blame] | 92 | super_key: Arc<RwLock<SuperKeyManager>>, |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 93 | notified: Arc<AtomicU8>, |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 94 | } |
| 95 | |
| 96 | impl GcInternal { |
| 97 | /// Attempts to process one blob from the database. |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 98 | /// We process one key at a time, because deleting a key is a time consuming process which |
| 99 | /// may involve calling into the KeyMint backend and we don't want to hog neither the backend |
| 100 | /// nor the database for extended periods of time. |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 101 | /// To limit the number of database transactions, which are also expensive and competing |
| 102 | /// with threads on the critical path, deleted blobs are loaded in batches. |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 103 | fn process_one_key(&mut self) -> Result<()> { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 104 | if self.superseded_blobs.is_empty() { |
| 105 | let blobs = self |
| 106 | .db |
| 107 | .handle_next_superseded_blobs(&self.deleted_blob_ids, 20) |
Shaquille Johnson | 9da2e1c | 2022-09-19 12:39:01 +0000 | [diff] [blame] | 108 | .context(ks_err!("Trying to handle superseded blob."))?; |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 109 | self.deleted_blob_ids = vec![]; |
| 110 | self.superseded_blobs = blobs; |
| 111 | } |
| 112 | |
David Drysdale | f1ba381 | 2024-05-08 17:50:13 +0100 | [diff] [blame] | 113 | if let Some(SupersededBlob { blob_id, blob, metadata }) = self.superseded_blobs.pop() { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 114 | // Add the next blob_id to the deleted blob ids list. So it will be |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 115 | // removed from the database regardless of whether the following |
| 116 | // succeeds or not. |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 117 | self.deleted_blob_ids.push(blob_id); |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 118 | |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 119 | // If the key has a km_uuid we try to get the corresponding device |
| 120 | // and delete the key, unwrapping if necessary and possible. |
| 121 | // (At this time keys may get deleted without having the super encryption |
| 122 | // key in this case we can only delete the key from the database.) |
David Drysdale | f1ba381 | 2024-05-08 17:50:13 +0100 | [diff] [blame] | 123 | if let Some(uuid) = metadata.km_uuid() { |
Hasini Gunasinghe | deab85d | 2021-02-01 21:10:02 +0000 | [diff] [blame] | 124 | let blob = self |
| 125 | .super_key |
Janis Danisevskis | 0fd25a6 | 2022-01-04 19:53:37 -0800 | [diff] [blame] | 126 | .read() |
| 127 | .unwrap() |
David Drysdale | f1ba381 | 2024-05-08 17:50:13 +0100 | [diff] [blame] | 128 | .unwrap_key_if_required(&metadata, &blob) |
Shaquille Johnson | 9da2e1c | 2022-09-19 12:39:01 +0000 | [diff] [blame] | 129 | .context(ks_err!("Trying to unwrap to-be-deleted blob.",))?; |
Chris Wailes | dabb6fe | 2022-11-16 15:56:19 -0800 | [diff] [blame] | 130 | (self.invalidate_key)(uuid, &blob).context(ks_err!("Trying to invalidate key."))?; |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 131 | } |
| 132 | } |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 133 | Ok(()) |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 134 | } |
| 135 | |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 136 | /// Processes one key and then schedules another attempt until it runs out of blobs to delete. |
| 137 | fn step(&mut self) { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 138 | self.notified.store(0, Ordering::Relaxed); |
David Drysdale | 940a0ff | 2025-01-13 14:19:16 +0000 | [diff] [blame] | 139 | if !globals::boot_completed() { |
| 140 | // Garbage collection involves a operation (`IKeyMintDevice::deleteKey()`) that cannot |
| 141 | // be rolled back in some cases (specifically, when the key is rollback-resistant), even |
| 142 | // if the Keystore database is restored to the version of an earlier userdata filesystem |
| 143 | // checkpoint. |
| 144 | // |
| 145 | // This means that we should not perform GC until boot has fully completed, and any |
| 146 | // in-progress OTA is definitely not going to be rolled back. |
| 147 | log::info!("skip GC as boot not completed"); |
| 148 | return; |
| 149 | } |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 150 | if let Err(e) = self.process_one_key() { |
| 151 | log::error!("Error trying to delete blob entry. {:?}", e); |
| 152 | } |
| 153 | // Schedule the next step. This gives high priority requests a chance to interleave. |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 154 | if !self.deleted_blob_ids.is_empty() { |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 155 | if let Some(at) = self.async_task.upgrade() { |
Janis Danisevskis | 3395f86 | 2021-05-06 10:54:17 -0700 | [diff] [blame] | 156 | if let Ok(0) = |
| 157 | self.notified.compare_exchange(0, 1, Ordering::Relaxed, Ordering::Relaxed) |
| 158 | { |
| 159 | at.queue_lo(move |shelf| { |
| 160 | shelf.get_downcast_mut::<GcInternal>().unwrap().step() |
| 161 | }); |
| 162 | } |
Janis Danisevskis | 7e8b462 | 2021-02-13 10:01:59 -0800 | [diff] [blame] | 163 | } |
| 164 | } |
Janis Danisevskis | 93927dd | 2020-12-23 12:23:08 -0800 | [diff] [blame] | 165 | } |
| 166 | } |