• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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