Evict CE keys on request or when a user is removed.

A work around for a kernel bug is needed to avoid the phone locking up
and turning into a hand warmer.

Test: com.android.cts.devicepolicy.ManagedProfileTest#testLockNowWithKeyEviction*
Bug: 31000719
Change-Id: Ia2121b3e3c22b10351296fa998892a91e601bb2c
diff --git a/Ext4Crypt.cpp b/Ext4Crypt.cpp
index 0063bef..682b34c 100644
--- a/Ext4Crypt.cpp
+++ b/Ext4Crypt.cpp
@@ -20,11 +20,14 @@
 #include "Utils.h"
 
 #include <algorithm>
+#include <chrono>
 #include <iomanip>
 #include <map>
+#include <mutex>
 #include <set>
 #include <sstream>
 #include <string>
+#include <thread>
 
 #include <dirent.h>
 #include <errno.h>
@@ -60,6 +63,8 @@
 static constexpr int FLAG_STORAGE_CE = 1 << 1;
 
 namespace {
+const std::chrono::seconds s_key_eviction_sleep_time(20);
+
 const std::string device_key_dir = std::string() + DATA_MNT_POINT + e4crypt_unencrypted_folder;
 const std::string device_key_path = device_key_dir + "/key";
 const std::string device_key_temp = device_key_dir + "/temp";
@@ -72,6 +77,10 @@
 // Some users are ephemeral, don't try to wipe their keys from disk
 std::set<userid_t> s_ephemeral_users;
 
+// Allow evictions to be cancelled.
+std::map<std::string, std::thread::id> s_desired_eviction_threads;
+std::mutex s_desired_eviction_threads_mutex;
+
 // Map user ids to key references
 std::map<userid_t, std::string> s_de_key_raw_refs;
 std::map<userid_t, std::string> s_ce_key_raw_refs;
@@ -158,6 +167,9 @@
     ext4_encryption_key ext4_key;
     if (!fill_key(key, &ext4_key)) return false;
     *raw_ref = generate_key_ref(ext4_key.raw, ext4_key.size);
+    // Ensure that no thread is waiting to evict this ref
+    std::lock_guard<std::mutex> lock(s_desired_eviction_threads_mutex);
+    s_desired_eviction_threads.erase(*raw_ref);
     auto ref = keyname(*raw_ref);
     key_serial_t device_keyring;
     if (!e4crypt_keyring(&device_keyring)) return false;
@@ -516,15 +528,66 @@
     return true;
 }
 
+static void evict_key_after_delay(const std::string raw_ref) {
+    LOG(DEBUG) << "Waiting to evict key in thread " << std::this_thread::get_id();
+    std::this_thread::sleep_for(s_key_eviction_sleep_time);
+    LOG(DEBUG) << "Done waiting to evict key in thread " << std::this_thread::get_id();
+
+    std::lock_guard<std::mutex> lock(s_desired_eviction_threads_mutex);
+    // Check the key should be evicted by this thread
+    auto search = s_desired_eviction_threads.find(raw_ref);
+    if (search == s_desired_eviction_threads.end()) {
+        LOG(DEBUG) << "Not evicting renewed-desirability key";
+        return;
+    }
+    if (search->second != std::this_thread::get_id()) {
+        LOG(DEBUG) << "We are not the thread to evict this key";
+        return;
+    }
+
+    // Remove the key from the keyring
+    auto ref = keyname(raw_ref);
+    key_serial_t device_keyring;
+    if (!e4crypt_keyring(&device_keyring)) return;
+    auto key_serial = keyctl_search(device_keyring, "logon", ref.c_str(), 0);
+    if (keyctl_revoke(key_serial) != 0) {
+        PLOG(ERROR) << "Failed to revoke key with serial " << key_serial;
+        return;
+    }
+    LOG(DEBUG) << "Revoked key with serial " << key_serial;
+}
+
+static bool evict_key(const std::string &raw_ref) {
+    // FIXME the use of a thread with delay is a work around for a kernel bug
+    std::lock_guard<std::mutex> lock(s_desired_eviction_threads_mutex);
+    std::thread t(evict_key_after_delay, raw_ref);
+    s_desired_eviction_threads[raw_ref] = t.get_id();
+    LOG(DEBUG) << "Scheduled key eviction in thread " << t.get_id();
+    t.detach();
+    return true; // Sadly no way to know if we were successful :(
+}
+
+static bool evict_ce_key(userid_t user_id) {
+    s_ce_keys.erase(user_id);
+    bool success = true;
+    std::string raw_ref;
+    // If we haven't loaded the CE key, no need to evict it.
+    if (lookup_key_ref(s_ce_key_raw_refs, user_id, &raw_ref)) {
+        success &= evict_key(raw_ref);
+    }
+    s_ce_key_raw_refs.erase(user_id);
+    return success;
+}
+
 bool e4crypt_destroy_user_key(userid_t user_id) {
     LOG(DEBUG) << "e4crypt_destroy_user_key(" << user_id << ")";
     if (!e4crypt_is_native()) {
         return true;
     }
     bool success = true;
-    s_ce_keys.erase(user_id);
     std::string raw_ref;
-    s_ce_key_raw_refs.erase(user_id);
+    success &= evict_ce_key(user_id);
+    success &= lookup_key_ref(s_de_key_raw_refs, user_id, &raw_ref) && evict_key(raw_ref);
     s_de_key_raw_refs.erase(user_id);
     auto it = s_ephemeral_users.find(user_id);
     if (it != s_ephemeral_users.end()) {
@@ -659,8 +722,9 @@
 
 // TODO: rename to 'evict' for consistency
 bool e4crypt_lock_user_key(userid_t user_id) {
+    LOG(DEBUG) << "e4crypt_lock_user_key " << user_id;
     if (e4crypt_is_native()) {
-        // TODO: remove from kernel keyring
+        return evict_ce_key(user_id);
     } else if (e4crypt_is_emulated()) {
         // When in emulation mode, we just use chmod
         if (!emulated_lock(android::vold::BuildDataSystemCePath(user_id)) ||