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 21 use crate::ks_err; 22 use crate::{ 23 async_task, 24 database::{KeystoreDB, SupersededBlob, Uuid}, 25 globals, 26 super_key::SuperKeyManager, 27 }; 28 use anyhow::{Context, Result}; 29 use async_task::AsyncTask; 30 use std::sync::{ 31 atomic::{AtomicU8, Ordering}, 32 Arc, RwLock, 33 }; 34 35 pub struct Gc { 36 async_task: Arc<AsyncTask>, 37 notified: Arc<AtomicU8>, 38 } 39 40 impl Gc { 41 /// Creates a garbage collector using the given async_task. 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`. new_init_with<F>(async_task: Arc<AsyncTask>, init: F) -> Self where F: FnOnce() -> ( Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, KeystoreDB, Arc<RwLock<SuperKeyManager>>, ) + Send + 'static,47 pub fn new_init_with<F>(async_task: Arc<AsyncTask>, init: F) -> Self 48 where 49 F: FnOnce() -> ( 50 Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, 51 KeystoreDB, 52 Arc<RwLock<SuperKeyManager>>, 53 ) + Send 54 + 'static, 55 { 56 let weak_at = Arc::downgrade(&async_task); 57 let notified = Arc::new(AtomicU8::new(0)); 58 let notified_clone = notified.clone(); 59 // Initialize the task's shelf. 60 async_task.queue_hi(move |shelf| { 61 let (invalidate_key, db, super_key) = init(); 62 let notified = notified_clone; 63 shelf.get_or_put_with(|| GcInternal { 64 deleted_blob_ids: vec![], 65 superseded_blobs: vec![], 66 invalidate_key, 67 db, 68 async_task: weak_at, 69 super_key, 70 notified, 71 }); 72 }); 73 Self { async_task, notified } 74 } 75 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. notify_gc(&self)79 pub fn notify_gc(&self) { 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 } 83 } 84 } 85 86 struct GcInternal { 87 deleted_blob_ids: Vec<i64>, 88 superseded_blobs: Vec<SupersededBlob>, 89 invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, 90 db: KeystoreDB, 91 async_task: std::sync::Weak<AsyncTask>, 92 super_key: Arc<RwLock<SuperKeyManager>>, 93 notified: Arc<AtomicU8>, 94 } 95 96 impl GcInternal { 97 /// Attempts to process one blob from the database. 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. 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. process_one_key(&mut self) -> Result<()>103 fn process_one_key(&mut self) -> Result<()> { 104 if self.superseded_blobs.is_empty() { 105 let blobs = self 106 .db 107 .handle_next_superseded_blobs(&self.deleted_blob_ids, 20) 108 .context(ks_err!("Trying to handle superseded blob."))?; 109 self.deleted_blob_ids = vec![]; 110 self.superseded_blobs = blobs; 111 } 112 113 if let Some(SupersededBlob { blob_id, blob, metadata }) = self.superseded_blobs.pop() { 114 // Add the next blob_id to the deleted blob ids list. So it will be 115 // removed from the database regardless of whether the following 116 // succeeds or not. 117 self.deleted_blob_ids.push(blob_id); 118 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.) 123 if let Some(uuid) = metadata.km_uuid() { 124 let blob = self 125 .super_key 126 .read() 127 .unwrap() 128 .unwrap_key_if_required(&metadata, &blob) 129 .context(ks_err!("Trying to unwrap to-be-deleted blob.",))?; 130 (self.invalidate_key)(uuid, &blob).context(ks_err!("Trying to invalidate key."))?; 131 } 132 } 133 Ok(()) 134 } 135 136 /// Processes one key and then schedules another attempt until it runs out of blobs to delete. step(&mut self)137 fn step(&mut self) { 138 self.notified.store(0, Ordering::Relaxed); 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 } 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. 154 if !self.deleted_blob_ids.is_empty() { 155 if let Some(at) = self.async_task.upgrade() { 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 } 163 } 164 } 165 } 166 } 167