Merge "fs_mgr: Harden adb-remount-test.sh on read-only filesystem"
diff --git a/bootstat/OWNERS b/bootstat/OWNERS
index 50b2097..f66b309 100644
--- a/bootstat/OWNERS
+++ b/bootstat/OWNERS
@@ -1,2 +1,2 @@
 jhawkins@google.com
-salyzyn@google.com
+dvander@google.com
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index 2390d79..53f85bf 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -206,6 +206,7 @@
     ],
 
     whole_static_libs: [
+        "libasync_safe",
         "gwp_asan_crash_handler",
         "libscudo",
         "libtombstone_proto",
diff --git a/debuggerd/crash_dump.cpp b/debuggerd/crash_dump.cpp
index 51afcc2..68a43cf 100644
--- a/debuggerd/crash_dump.cpp
+++ b/debuggerd/crash_dump.cpp
@@ -153,14 +153,14 @@
   }
 
   struct timeval tv = {
-    .tv_sec = 1,
-    .tv_usec = 0,
+      .tv_sec = 1 * android::base::TimeoutMultiplier(),
+      .tv_usec = 0,
   };
   if (setsockopt(amfd.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) {
     PLOG(ERROR) << "failed to set send timeout on activity manager socket";
     return false;
   }
-  tv.tv_sec = 3;  // 3 seconds on handshake read
+  tv.tv_sec = 3 * android::base::TimeoutMultiplier();  // 3 seconds on handshake read
   if (setsockopt(amfd.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) {
     PLOG(ERROR) << "failed to set receive timeout on activity manager socket";
     return false;
@@ -447,7 +447,7 @@
   //
   // Note: processes with many threads and minidebug-info can take a bit to
   //       unwind, do not make this too small. b/62828735
-  alarm(30);
+  alarm(30 * android::base::TimeoutMultiplier());
 
   // Get the process name (aka cmdline).
   std::string process_name = get_process_name(g_target_thread);
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 822b74a..185bd6e 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -36,12 +36,12 @@
 #include <string>
 
 #include <android-base/file.h>
-#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/log.h>
+#include <async_safe/log.h>
 #include <log/log.h>
 #include <log/log_read.h>
 #include <log/logprint.h>
@@ -588,7 +588,7 @@
 
   unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch());
   if (!unwinder.Init()) {
-    LOG(FATAL) << "Failed to init unwinder object.";
+    async_safe_fatal("failed to init unwinder object");
   }
 
   ProcessInfo process_info;
@@ -606,8 +606,11 @@
   Tombstone tombstone;
   engrave_tombstone_proto(&tombstone, unwinder, threads, target_thread, process_info, open_files);
 
-  if (!tombstone.SerializeToFileDescriptor(proto_fd.get())) {
-    PLOG(ERROR) << "Failed to write proto tombstone";
+  if (proto_fd != -1) {
+    if (!tombstone.SerializeToFileDescriptor(proto_fd.get())) {
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to write proto tombstone: %s",
+                            strerror(errno));
+    }
   }
 
   log_t log;
@@ -631,7 +634,7 @@
 
     auto it = threads.find(target_thread);
     if (it == threads.end()) {
-      LOG(FATAL) << "failed to find target thread";
+      async_safe_fatal("failed to find target thread");
     }
 
     dump_thread(&log, unwinder, it->second, process_info, true);
diff --git a/debuggerd/libdebuggerd/tombstone_proto.cpp b/debuggerd/libdebuggerd/tombstone_proto.cpp
index 801b112..bb3c7ea 100644
--- a/debuggerd/libdebuggerd/tombstone_proto.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto.cpp
@@ -31,7 +31,8 @@
 #include <memory>
 #include <string>
 
-#include <android-base/logging.h>
+#include <async_safe/log.h>
+
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
@@ -151,13 +152,15 @@
 
   size_t length;
   if (!process_memory->ReadFully(address, &length, sizeof(length))) {
-    PLOG(ERROR) << "Failed to read abort message header";
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read abort message header: %s",
+                          strerror(errno));
     return;
   }
 
   // The length field includes the length of the length field itself.
   if (length < sizeof(size_t)) {
-    LOG(ERROR) << "Abort message header malformed: claimed length = " << length;
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG,
+                          "abort message header malformed: claimed length = %zu", length);
     return;
   }
 
@@ -168,7 +171,8 @@
   msg.resize(length);
 
   if (!process_memory->ReadFully(address + sizeof(length), &msg[0], length)) {
-    PLOG(ERROR) << "Failed to read abort message header";
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to read abort message header: %s",
+                          strerror(errno));
     return;
   }
 
@@ -236,7 +240,11 @@
 
           dump.set_begin_address(value);
 
-          CHECK(start_offset + bytes <= sizeof(buf));
+          if (start_offset + bytes > sizeof(buf)) {
+            async_safe_fatal("dump_memory overflowed? start offset = %zu, bytes read = %zd",
+                             start_offset, bytes);
+          }
+
           dump.set_memory(buf, start_offset + bytes);
 
           *thread.add_memory_dump() = std::move(dump);
@@ -247,10 +255,12 @@
   unwinder->SetRegs(regs_copy.get());
   unwinder->Unwind();
   if (unwinder->NumFrames() == 0) {
-    LOG(ERROR) << "Failed to unwind";
+    async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to unwind");
     if (unwinder->LastErrorCode() != unwindstack::ERROR_NONE) {
-      LOG(ERROR) << "  Error code: " << unwinder->LastErrorCodeString();
-      LOG(ERROR) << "  Error address: " << StringPrintf("0x%" PRIx64, unwinder->LastErrorAddress());
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error code: %s",
+                            unwinder->LastErrorCodeString());
+      async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "  error address: 0x%" PRIx64,
+                            unwinder->LastErrorAddress());
     }
   } else {
     unwinder->SetDisplayBuildID(true);
@@ -351,11 +361,9 @@
         // non-blocking EOF; we're done
         break;
       } else {
-        ALOGE("Error while reading log: %s\n", strerror(-actual));
         break;
       }
     } else if (actual == 0) {
-      ALOGE("Got zero bytes while reading log: %s\n", strerror(errno));
       break;
     }
 
@@ -431,7 +439,9 @@
   result.set_selinux_label(main_thread.selinux_label);
 
   result.set_process_name(main_thread.process_name);
-  CHECK(main_thread.siginfo != nullptr);
+  if (!main_thread.siginfo) {
+    async_safe_fatal("siginfo missing");
+  }
 
   Signal sig;
   sig.set_number(main_thread.signo);
diff --git a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
index 5ba0ad6..187379d 100644
--- a/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
+++ b/debuggerd/libdebuggerd/tombstone_proto_to_text.cpp
@@ -25,9 +25,9 @@
 #include <utility>
 #include <vector>
 
-#include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 #include <android-base/unique_fd.h>
+#include <async_safe/log.h>
 
 #include "tombstone.pb.h"
 
@@ -113,7 +113,7 @@
       break;
 
     default:
-      LOG(FATAL) << "unknown architecture";
+      async_safe_fatal("unknown architecture");
   }
 
   for (const auto& reg : thread.registers()) {
@@ -217,7 +217,6 @@
   }
 
   if (!tombstone.has_signal_info()) {
-    LOG(ERROR) << "signal info missing in tombstone";
     CBL("signal information missing");
   } else {
     std::string fault_addr_desc;
@@ -319,7 +318,7 @@
   const auto& threads = tombstone.threads();
   auto main_thread_it = threads.find(tombstone.tid());
   if (main_thread_it == threads.end()) {
-    LOG(ERROR) << "failed to find entry for main thread in tombstone";
+    CBL("failed to find entry for main thread in tombstone");
     return false;
   }
 
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index f406ad4..f941990 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -30,11 +30,11 @@
 
 #include <string>
 
-#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android-base/unique_fd.h>
+#include <async_safe/log.h>
 #include <bionic/reserved_signals.h>
 #include <debuggerd/handler.h>
 #include <log/log.h>
@@ -259,11 +259,11 @@
   memset(&capdata, 0, sizeof(capdata));
 
   if (capset(&capheader, &capdata[0]) == -1) {
-    PLOG(FATAL) << "failed to drop capabilities";
+    async_safe_fatal("failed to drop capabilities: %s", strerror(errno));
   }
 
   if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0) {
-    PLOG(FATAL) << "failed to set PR_SET_NO_NEW_PRIVS";
+    async_safe_fatal("failed to set PR_SET_NO_NEW_PRIVS: %s", strerror(errno));
   }
 }
 
diff --git a/debuggerd/tombstoned/intercept_manager.cpp b/debuggerd/tombstoned/intercept_manager.cpp
index 437639e..4d4646a 100644
--- a/debuggerd/tombstoned/intercept_manager.cpp
+++ b/debuggerd/tombstoned/intercept_manager.cpp
@@ -26,6 +26,7 @@
 
 #include <android-base/cmsg.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
 
 #include "protocol.h"
@@ -162,7 +163,7 @@
     event_assign(intercept->intercept_event, intercept_manager->base, sockfd, EV_READ | EV_TIMEOUT,
                  intercept_close_cb, arg);
 
-    struct timeval timeout = { .tv_sec = 10, .tv_usec = 0 };
+    struct timeval timeout = {.tv_sec = 10 * android::base::TimeoutMultiplier(), .tv_usec = 0};
     event_add(intercept->intercept_event, &timeout);
   }
 
@@ -178,7 +179,7 @@
   intercept->intercept_manager = static_cast<InterceptManager*>(arg);
   intercept->sockfd.reset(sockfd);
 
-  struct timeval timeout = { 1, 0 };
+  struct timeval timeout = {1 * android::base::TimeoutMultiplier(), 0};
   event_base* base = evconnlistener_get_base(listener);
   event* intercept_event =
     event_new(base, sockfd, EV_TIMEOUT | EV_READ, intercept_request_cb, intercept);
diff --git a/debuggerd/tombstoned/tombstoned.cpp b/debuggerd/tombstoned/tombstoned.cpp
index f057260..3e0c47c 100644
--- a/debuggerd/tombstoned/tombstoned.cpp
+++ b/debuggerd/tombstoned/tombstoned.cpp
@@ -320,7 +320,7 @@
   }
 
   // TODO: Make this configurable by the interceptor?
-  struct timeval timeout = {10, 0};
+  struct timeval timeout = {10 * android::base::TimeoutMultiplier(), 0};
 
   event_base* base = event_get_base(crash->crash_event);
 
@@ -340,7 +340,7 @@
 
   // TODO: Make sure that only java crashes come in on the java socket
   // and only native crashes on the native socket.
-  struct timeval timeout = { 1, 0 };
+  struct timeval timeout = {1 * android::base::TimeoutMultiplier(), 0};
   event* crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash);
   crash->crash_socket_fd.reset(sockfd);
   crash->crash_event = crash_event;
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 2876094..1efe793 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -2143,6 +2143,41 @@
     return false;
 }
 
+std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry) {
+    if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
+        return "";
+    }
+    DeviceMapper& dm = DeviceMapper::Instance();
+    std::string device = GetVerityDeviceName(entry);
+
+    std::vector<DeviceMapper::TargetInfo> table;
+    if (dm.GetState(device) == DmDeviceState::INVALID || !dm.GetTableInfo(device, &table)) {
+        return "";
+    }
+    for (const auto& target : table) {
+        if (strcmp(target.spec.target_type, "verity") != 0) {
+            continue;
+        }
+
+        // The format is stable for dm-verity version 0 & 1. And the data is expected to have
+        // the fixed format:
+        // <version> <dev> <hash_dev> <data_block_size> <hash_block_size> <num_data_blocks>
+        // <hash_start_block> <algorithm> <digest> <salt>
+        // Details in https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/verity.html
+
+        std::vector<std::string> tokens = android::base::Split(target.data, " \t\r\n");
+        if (tokens[0] != "0" && tokens[0] != "1") {
+            LOG(WARNING) << "Unrecognized device mapper version in " << target.data;
+            return "";
+        }
+
+        // Hashtree algorithm is the 8th token in the output
+        return android::base::Trim(tokens[7]);
+    }
+
+    return "";
+}
+
 bool fs_mgr_verity_is_check_at_most_once(const android::fs_mgr::FstabEntry& entry) {
     if (!entry.fs_mgr_flags.verify && !entry.fs_mgr_flags.avb) {
         return false;
diff --git a/fs_mgr/include/fs_mgr.h b/fs_mgr/include/fs_mgr.h
index 11e3664..22c02cc 100644
--- a/fs_mgr/include/fs_mgr.h
+++ b/fs_mgr/include/fs_mgr.h
@@ -88,6 +88,10 @@
 bool fs_mgr_load_verity_state(int* mode);
 // Returns true if verity is enabled on this particular FstabEntry.
 bool fs_mgr_is_verity_enabled(const android::fs_mgr::FstabEntry& entry);
+// Returns the hash algorithm used to build the hashtree of this particular FstabEntry. Returns an
+// empty string if the input isn't a dm-verity entry, or if there is an error.
+std::string fs_mgr_get_hashtree_algorithm(const android::fs_mgr::FstabEntry& entry);
+
 bool fs_mgr_swapon_all(const android::fs_mgr::Fstab& fstab);
 bool fs_mgr_update_logical_partition(android::fs_mgr::FstabEntry* entry);
 
diff --git a/fs_mgr/libfs_avb/Android.bp b/fs_mgr/libfs_avb/Android.bp
index 2f1ca70..45e7a30 100644
--- a/fs_mgr/libfs_avb/Android.bp
+++ b/fs_mgr/libfs_avb/Android.bp
@@ -100,6 +100,7 @@
     shared_libs: [
         "libcrypto",
     ],
+    compile_multilib: "first",
     data: [
         ":avbtool",
         ":fec",
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index d2ffaa7..ff7a727 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -379,6 +379,7 @@
     FRIEND_TEST(SnapshotUpdateTest, MergeCannotRemoveCow);
     FRIEND_TEST(SnapshotUpdateTest, MergeInRecovery);
     FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
+    FRIEND_TEST(SnapshotUpdateTest, SpaceSwapUpdate);
     friend class SnapshotTest;
     friend class SnapshotUpdateTest;
     friend class FlashAfterUpdateTest;
@@ -717,6 +718,8 @@
     bool PerformInitTransition(InitTransition transition,
                                std::vector<std::string>* snapuserd_argv = nullptr);
 
+    SnapuserdClient* snapuserd_client() const { return snapuserd_client_.get(); }
+
     std::string gsid_dir_;
     std::string metadata_dir_;
     std::unique_ptr<IDeviceInfo> device_;
diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
index ff0047e..b038527 100644
--- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
+++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h
@@ -146,6 +146,7 @@
 bool WriteRandomData(const std::string& path, std::optional<size_t> expect_size = std::nullopt,
                      std::string* hash = nullptr);
 bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr);
+std::string HashSnapshot(ISnapshotWriter* writer);
 
 std::optional<std::string> GetHash(const std::string& path);
 
diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp
index 0b8a03a..95e7d89 100644
--- a/fs_mgr/libsnapshot/snapshot_test.cpp
+++ b/fs_mgr/libsnapshot/snapshot_test.cpp
@@ -225,7 +225,7 @@
     }
 
     AssertionResult MapUpdateSnapshot(const std::string& name,
-                                      std::unique_ptr<ICowWriter>* writer) {
+                                      std::unique_ptr<ISnapshotWriter>* writer) {
         TestPartitionOpener opener(fake_super);
         CreateLogicalPartitionParams params{
                 .block_device = fake_super,
@@ -279,6 +279,7 @@
             return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
         }
         if (!(res = DeleteDevice(snapshot + "-base"))) return res;
+        if (!(res = DeleteDevice(snapshot + "-src"))) return res;
         return AssertionSuccess();
     }
 
@@ -319,7 +320,7 @@
 
     // Prepare A/B slot for a partition named "test_partition".
     AssertionResult PrepareOneSnapshot(uint64_t device_size,
-                                       std::unique_ptr<ICowWriter>* writer = nullptr) {
+                                       std::unique_ptr<ISnapshotWriter>* writer = nullptr) {
         lock_ = nullptr;
 
         DeltaArchiveManifest manifest;
@@ -511,7 +512,7 @@
 
     static const uint64_t kDeviceSize = 1024 * 1024;
 
-    std::unique_ptr<ICowWriter> writer;
+    std::unique_ptr<ISnapshotWriter> writer;
     ASSERT_TRUE(PrepareOneSnapshot(kDeviceSize, &writer));
 
     // Release the lock.
@@ -827,11 +828,14 @@
 
         opener_ = std::make_unique<TestPartitionOpener>(fake_super);
 
+        auto dynamic_partition_metadata = manifest_.mutable_dynamic_partition_metadata();
+        dynamic_partition_metadata->set_vabc_enabled(IsCompressionEnabled());
+
         // Create a fake update package metadata.
         // Not using full name "system", "vendor", "product" because these names collide with the
         // mapped partitions on the running device.
         // Each test modifies manifest_ slightly to indicate changes to the partition layout.
-        group_ = manifest_.mutable_dynamic_partition_metadata()->add_groups();
+        group_ = dynamic_partition_metadata->add_groups();
         group_->set_name("group");
         group_->set_size(kGroupSize);
         group_->add_partition_names("sys");
@@ -945,7 +949,7 @@
 
     AssertionResult MapOneUpdateSnapshot(const std::string& name) {
         if (IsCompressionEnabled()) {
-            std::unique_ptr<ICowWriter> writer;
+            std::unique_ptr<ISnapshotWriter> writer;
             return MapUpdateSnapshot(name, &writer);
         } else {
             std::string path;
@@ -955,7 +959,7 @@
 
     AssertionResult WriteSnapshotAndHash(const std::string& name) {
         if (IsCompressionEnabled()) {
-            std::unique_ptr<ICowWriter> writer;
+            std::unique_ptr<ISnapshotWriter> writer;
             auto res = MapUpdateSnapshot(name, &writer);
             if (!res) {
                 return res;
@@ -984,6 +988,42 @@
                                   << ", hash: " << hashes_[name];
     }
 
+    // Generate a snapshot that moves all the upper blocks down to the start.
+    // It doesn't really matter the order, we just want copies that reference
+    // blocks that won't exist if the partition shrinks.
+    AssertionResult ShiftAllSnapshotBlocks(const std::string& name, uint64_t old_size) {
+        std::unique_ptr<ISnapshotWriter> writer;
+        if (auto res = MapUpdateSnapshot(name, &writer); !res) {
+            return res;
+        }
+        if (!writer->options().max_blocks || !*writer->options().max_blocks) {
+            return AssertionFailure() << "No max blocks set for " << name << " writer";
+        }
+
+        uint64_t src_block = (old_size / writer->options().block_size) - 1;
+        uint64_t dst_block = 0;
+        uint64_t max_blocks = *writer->options().max_blocks;
+        while (dst_block < max_blocks && dst_block < src_block) {
+            if (!writer->AddCopy(dst_block, src_block)) {
+                return AssertionFailure() << "Unable to add copy for " << name << " for blocks "
+                                          << src_block << ", " << dst_block;
+            }
+            dst_block++;
+            src_block--;
+        }
+        if (!writer->Finalize()) {
+            return AssertionFailure() << "Unable to finalize writer for " << name;
+        }
+
+        auto hash = HashSnapshot(writer.get());
+        if (hash.empty()) {
+            return AssertionFailure() << "Unable to hash snapshot writer for " << name;
+        }
+        hashes_[name] = hash;
+
+        return AssertionSuccess();
+    }
+
     AssertionResult MapUpdateSnapshots(const std::vector<std::string>& names = {"sys_b", "vnd_b",
                                                                                 "prd_b"}) {
         for (const auto& name : names) {
@@ -1092,8 +1132,132 @@
     // Initiate the merge and wait for it to be completed.
     ASSERT_TRUE(init->InitiateMerge());
     ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    {
+        // We should have started in SECOND_PHASE since nothing shrinks.
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
+        ASSERT_EQ(status.merge_phase(), MergePhase::SECOND_PHASE);
+    }
     ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
 
+    // Make sure the second phase ran and deleted snapshots.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        std::vector<std::string> snapshots;
+        ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots));
+        ASSERT_TRUE(snapshots.empty());
+    }
+
+    // Check that the target partitions have the same content after the merge.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name))
+                << "Content of " << name << " changes after the merge";
+    }
+}
+
+// Test that shrinking and growing partitions at the same time is handled
+// correctly in VABC.
+TEST_F(SnapshotUpdateTest, SpaceSwapUpdate) {
+    // OTA client blindly unmaps all partitions that are possibly mapped.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
+    }
+
+    auto old_sys_size = GetSize(sys_);
+    auto old_prd_size = GetSize(prd_);
+
+    // Grow |sys| but shrink |prd|.
+    SetSize(sys_, old_sys_size * 2);
+    sys_->set_estimate_cow_size(8_MiB);
+    SetSize(prd_, old_prd_size / 2);
+    prd_->set_estimate_cow_size(1_MiB);
+
+    AddOperationForPartitions();
+
+    ASSERT_TRUE(sm->BeginUpdate());
+    ASSERT_TRUE(sm->CreateUpdateSnapshots(manifest_));
+
+    // Check that the old partition sizes were saved correctly.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+
+        SnapshotStatus status;
+        ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "prd_b", &status));
+        ASSERT_EQ(status.old_partition_size(), 3145728);
+        ASSERT_TRUE(sm->ReadSnapshotStatus(local_lock.get(), "sys_b", &status));
+        ASSERT_EQ(status.old_partition_size(), 3145728);
+    }
+
+    ASSERT_TRUE(WriteSnapshotAndHash("sys_b"));
+    ASSERT_TRUE(WriteSnapshotAndHash("vnd_b"));
+    ASSERT_TRUE(ShiftAllSnapshotBlocks("prd_b", old_prd_size));
+
+    sync();
+
+    // Assert that source partitions aren't affected.
+    for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    ASSERT_TRUE(sm->FinishedSnapshotWrites(false));
+
+    // Simulate shutting down the device.
+    ASSERT_TRUE(UnmapAll());
+
+    // After reboot, init does first stage mount.
+    auto init = NewManagerForFirstStageMount("_b");
+    ASSERT_NE(init, nullptr);
+    ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    auto indicator = sm->GetRollbackIndicatorPath();
+    ASSERT_NE(access(indicator.c_str(), R_OK), 0);
+
+    // Check that the target partitions have the same content.
+    for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
+        ASSERT_TRUE(IsPartitionUnchanged(name));
+    }
+
+    // Initiate the merge and wait for it to be completed.
+    ASSERT_TRUE(init->InitiateMerge());
+    ASSERT_EQ(init->IsSnapuserdRequired(), IsCompressionEnabled());
+    {
+        // Check that the merge phase is FIRST_PHASE until at least one call
+        // to ProcessUpdateState() occurs.
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        auto status = init->ReadSnapshotUpdateStatus(local_lock.get());
+        ASSERT_EQ(status.merge_phase(), MergePhase::FIRST_PHASE);
+    }
+
+    // Simulate shutting down the device and creating partitions again.
+    ASSERT_TRUE(UnmapAll());
+    ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super", snapshot_timeout_));
+
+    // Check that we used the correct types after rebooting mid-merge.
+    DeviceMapper::TargetInfo target;
+    ASSERT_TRUE(init->IsSnapshotDevice("prd_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot-merge");
+    ASSERT_TRUE(init->IsSnapshotDevice("sys_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+    ASSERT_TRUE(init->IsSnapshotDevice("vnd_b", &target));
+    ASSERT_EQ(DeviceMapper::GetTargetType(target.spec), "snapshot");
+
+    // Complete the merge.
+    ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
+
+    // Make sure the second phase ran and deleted snapshots.
+    {
+        ASSERT_TRUE(AcquireLock());
+        auto local_lock = std::move(lock_);
+        std::vector<std::string> snapshots;
+        ASSERT_TRUE(init->ListSnapshots(local_lock.get(), &snapshots));
+        ASSERT_TRUE(snapshots.empty());
+    }
+
     // Check that the target partitions have the same content after the merge.
     for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
         ASSERT_TRUE(IsPartitionUnchanged(name))
@@ -1814,6 +1978,13 @@
 
     ASSERT_TRUE(init->PerformInitTransition(SnapshotManager::InitTransition::SECOND_STAGE));
 
+    // :TODO: this is a workaround to ensure the handler list stays empty. We
+    // should make this test more like actual init, and spawn two copies of
+    // snapuserd, given how many other tests we now have for normal snapuserd.
+    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("sys_b-user-cow-init"));
+    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("vnd_b-user-cow-init"));
+    ASSERT_TRUE(init->snapuserd_client()->WaitForDeviceDelete("prd_b-user-cow-init"));
+
     // The control device should have been renamed.
     ASSERT_TRUE(android::fs_mgr::WaitForFileDeleted("/dev/dm-user/sys_b-user-cow-init", 10s));
     ASSERT_EQ(access("/dev/dm-user/sys_b-user-cow", F_OK), 0);
diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp
index 6104c82..e3e3af8 100644
--- a/fs_mgr/libsnapshot/test_helpers.cpp
+++ b/fs_mgr/libsnapshot/test_helpers.cpp
@@ -23,6 +23,7 @@
 #include <android-base/unique_fd.h>
 #include <gtest/gtest.h>
 #include <openssl/sha.h>
+#include <payload_consumer/file_descriptor.h>
 
 namespace android {
 namespace snapshot {
@@ -169,6 +170,37 @@
     return true;
 }
 
+std::string HashSnapshot(ISnapshotWriter* writer) {
+    auto reader = writer->OpenReader();
+    if (!reader) {
+        return {};
+    }
+
+    SHA256_CTX ctx;
+    SHA256_Init(&ctx);
+
+    uint64_t remaining = reader->BlockDevSize();
+    char buffer[4096];
+    while (remaining) {
+        size_t to_read =
+                static_cast<size_t>(std::min(remaining, static_cast<uint64_t>(sizeof(buffer))));
+        ssize_t read = reader->Read(&buffer, to_read);
+        if (read <= 0) {
+            if (read < 0) {
+                LOG(ERROR) << "Failed to read from snapshot writer";
+                return {};
+            }
+            break;
+        }
+        SHA256_Update(&ctx, buffer, to_read);
+        remaining -= static_cast<size_t>(read);
+    }
+
+    uint8_t out[32];
+    SHA256_Final(out, &ctx);
+    return ToHexString(out, sizeof(out));
+}
+
 std::optional<std::string> GetHash(const std::string& path) {
     std::string content;
     if (!android::base::ReadFileToString(path, &content, true)) {
diff --git a/gatekeeperd/Android.bp b/gatekeeperd/Android.bp
index 2d9a820..d1046df 100644
--- a/gatekeeperd/Android.bp
+++ b/gatekeeperd/Android.bp
@@ -41,7 +41,7 @@
         "libhidlbase",
         "android.hardware.gatekeeper@1.0",
         "libgatekeeper_aidl",
-        "android.hardware.security.keymint-unstable-ndk_platform",
+        "android.hardware.security.keymint-V1-ndk_platform",
         "android.security.authorization-ndk_platform",
     ],
 
diff --git a/gatekeeperd/gatekeeperd.cpp b/gatekeeperd/gatekeeperd.cpp
index 941f8c2..781b4af 100644
--- a/gatekeeperd/gatekeeperd.cpp
+++ b/gatekeeperd/gatekeeperd.cpp
@@ -327,7 +327,6 @@
                         LOG(ERROR) << "Failure in sending AuthToken to AuthorizationService.";
                         return GK_ERROR;
                     }
-                    AIBinder_decStrong(authzAIBinder);
                 }
                 sp<IServiceManager> sm = defaultServiceManager();
 
diff --git a/init/Android.bp b/init/Android.bp
index cd295cf..06ecc59 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -233,6 +233,7 @@
             ],
         },
     },
+    visibility: ["//packages/modules/Virtualization/microdroid"],
 }
 
 // Tests
diff --git a/init/builtins.cpp b/init/builtins.cpp
index c44e03e..6b7d1e9 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -861,6 +861,11 @@
         // for system as root, so it has property [partition.system.verified].
         std::string partition = entry.mount_point == "/" ? "system" : Basename(entry.mount_point);
         SetProperty("partition." + partition + ".verified", std::to_string(mode));
+
+        std::string hash_alg = fs_mgr_get_hashtree_algorithm(entry);
+        if (!hash_alg.empty()) {
+            SetProperty("partition." + partition + ".verified.hash_alg", hash_alg);
+        }
     }
 
     return {};
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index f01a6c0..6954c03 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -286,11 +286,7 @@
         }
     }
 
-
     if (want_console == FirstStageConsoleParam::CONSOLE_ON_FAILURE) {
-        if (!DoCreateDevices()) {
-            LOG(ERROR) << "Failed to create device nodes early";
-        }
         StartConsole(cmdline);
     }
 
@@ -331,7 +327,7 @@
         }
     }
 
-    if (!DoFirstStageMount(want_console != FirstStageConsoleParam::CONSOLE_ON_FAILURE)) {
+    if (!DoFirstStageMount()) {
         LOG(FATAL) << "Failed to mount required partitions early ...";
     }
 
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index de72f23..7307237 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -82,7 +82,6 @@
     // The factory method to create either FirstStageMountVBootV1 or FirstStageMountVBootV2
     // based on device tree configurations.
     static std::unique_ptr<FirstStageMount> Create();
-    bool DoCreateDevices();    // Creates devices and logical partitions from storage devices
     bool DoFirstStageMount();  // Mounts fstab entries read from device tree.
     bool InitDevices();
 
@@ -245,7 +244,13 @@
     }
 }
 
-bool FirstStageMount::DoCreateDevices() {
+bool FirstStageMount::DoFirstStageMount() {
+    if (!IsDmLinearEnabled() && fstab_.empty()) {
+        // Nothing to mount.
+        LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
+        return true;
+    }
+
     if (!InitDevices()) return false;
 
     // Mount /metadata before creating logical partitions, since we need to
@@ -264,16 +269,6 @@
 
     if (!CreateLogicalPartitions()) return false;
 
-    return true;
-}
-
-bool FirstStageMount::DoFirstStageMount() {
-    if (!IsDmLinearEnabled() && fstab_.empty()) {
-        // Nothing to mount.
-        LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
-        return true;
-    }
-
     if (!MountPartitions()) return false;
 
     return true;
@@ -834,18 +829,8 @@
 
 // Public functions
 // ----------------
-// Creates devices and logical partitions from storage devices
-bool DoCreateDevices() {
-    std::unique_ptr<FirstStageMount> handle = FirstStageMount::Create();
-    if (!handle) {
-        LOG(ERROR) << "Failed to create FirstStageMount";
-        return false;
-    }
-    return handle->DoCreateDevices();
-}
-
 // Mounts partitions specified by fstab in device tree.
-bool DoFirstStageMount(bool create_devices) {
+bool DoFirstStageMount() {
     // Skips first stage mount if we're in recovery mode.
     if (IsRecoveryMode()) {
         LOG(INFO) << "First stage mount skipped (recovery mode)";
@@ -857,11 +842,6 @@
         LOG(ERROR) << "Failed to create FirstStageMount";
         return false;
     }
-
-    if (create_devices) {
-        if (!handle->DoCreateDevices()) return false;
-    }
-
     return handle->DoFirstStageMount();
 }
 
diff --git a/init/first_stage_mount.h b/init/first_stage_mount.h
index 2f4e663..21d87fd 100644
--- a/init/first_stage_mount.h
+++ b/init/first_stage_mount.h
@@ -19,8 +19,7 @@
 namespace android {
 namespace init {
 
-bool DoCreateDevices();
-bool DoFirstStageMount(bool create_devices);
+bool DoFirstStageMount();
 void SetInitAvbVersionInRecovery();
 
 }  // namespace init
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index 923d769..331255b 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -16,6 +16,7 @@
 
 #include "ueventd.h"
 
+#include <android/api-level.h>
 #include <ctype.h>
 #include <dirent.h>
 #include <fcntl.h>
@@ -266,6 +267,17 @@
     LOG(INFO) << "Coldboot took " << cold_boot_timer.duration().count() / 1000.0f << " seconds";
 }
 
+static UeventdConfiguration GetConfiguration() {
+    // TODO: Remove these legacy paths once Android S is no longer supported.
+    if (android::base::GetIntProperty("ro.product.first_api_level", 10000) <= __ANDROID_API_S__) {
+        auto hardware = android::base::GetProperty("ro.hardware", "");
+        return ParseConfig({"/system/etc/ueventd.rc", "/vendor/ueventd.rc", "/odm/ueventd.rc",
+                            "/ueventd." + hardware + ".rc"});
+    }
+
+    return ParseConfig({"/system/etc/ueventd.rc"});
+}
+
 int ueventd_main(int argc, char** argv) {
     /*
      * init sets the umask to 077 for forked processes. We need to
@@ -283,7 +295,7 @@
 
     std::vector<std::unique_ptr<UeventHandler>> uevent_handlers;
 
-    auto ueventd_configuration = ParseConfig("/system/etc/ueventd.rc");
+    auto ueventd_configuration = GetConfiguration();
 
     uevent_handlers.emplace_back(std::make_unique<DeviceHandler>(
             std::move(ueventd_configuration.dev_permissions),
diff --git a/init/ueventd_parser.cpp b/init/ueventd_parser.cpp
index 2605158..cab988b 100644
--- a/init/ueventd_parser.cpp
+++ b/init/ueventd_parser.cpp
@@ -230,7 +230,7 @@
     return {};
 }
 
-UeventdConfiguration ParseConfig(const std::string& config) {
+UeventdConfiguration ParseConfig(const std::vector<std::string>& configs) {
     Parser parser;
     UeventdConfiguration ueventd_configuration;
 
@@ -260,7 +260,9 @@
                                std::bind(ParseEnabledDisabledLine, _1,
                                          &ueventd_configuration.enable_parallel_restorecon));
 
-    parser.ParseConfig(config);
+    for (const auto& config : configs) {
+        parser.ParseConfig(config);
+    }
 
     return ueventd_configuration;
 }
diff --git a/init/ueventd_parser.h b/init/ueventd_parser.h
index 2672626..eaafa5a 100644
--- a/init/ueventd_parser.h
+++ b/init/ueventd_parser.h
@@ -36,7 +36,7 @@
     bool enable_parallel_restorecon = false;
 };
 
-UeventdConfiguration ParseConfig(const std::string& config);
+UeventdConfiguration ParseConfig(const std::vector<std::string>& configs);
 
 }  // namespace init
 }  // namespace android
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index d46aeab..c75e538 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -167,7 +167,6 @@
         "canned_fs_config.cpp",
         "iosched_policy.cpp",
         "load_file.cpp",
-        "memory.cpp",
         "native_handle.cpp",
         "properties.cpp",
         "record_stream.cpp",
diff --git a/libcutils/include/cutils/memory.h b/libcutils/include/cutils/memory.h
index 0fba53c..c6476c1 100644
--- a/libcutils/include/cutils/memory.h
+++ b/libcutils/include/cutils/memory.h
@@ -28,9 +28,6 @@
 size_t strlcpy(char *dst, const char *src, size_t size);
 #endif
 
-// Disables memory mitigations for the entire process, and logs appropriately.
-void process_disable_memory_mitigations();
-
 #ifdef __cplusplus
 } // extern "C"
 #endif
diff --git a/libcutils/memory.cpp b/libcutils/memory.cpp
deleted file mode 100644
index 5a410c2..0000000
--- a/libcutils/memory.cpp
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <cutils/memory.h>
-
-#include <log/log.h>
-
-#if !defined(__APPLE__)
-#include <malloc.h>
-#endif
-
-void process_disable_memory_mitigations() {
-    bool success = false;
-#ifdef __BIONIC__
-    success = mallopt(M_BIONIC_DISABLE_MEMORY_MITIGATIONS, 0);
-#endif
-
-    // TODO: if b/158870657 is fixed and scudo is used globally,
-    // we can assert on failure rather than just log.
-    if (success) {
-        ALOGI("Disabled memory mitigations for process.");
-    } else {
-        ALOGE("Could not disable memory mitigations for process.");
-    }
-}
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index d669ebe..2bf48fc 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -131,13 +131,25 @@
     return StringPrintf("%s/uid_%d/pid_%d", cgroup, uid, pid);
 }
 
-static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid) {
-    int ret;
-
+static int RemoveProcessGroup(const char* cgroup, uid_t uid, int pid, unsigned int retries) {
+    int ret = 0;
     auto uid_pid_path = ConvertUidPidToPath(cgroup, uid, pid);
-    ret = rmdir(uid_pid_path.c_str());
-
     auto uid_path = ConvertUidToPath(cgroup, uid);
+
+    if (retries == 0) {
+        retries = 1;
+    }
+
+    while (retries--) {
+        ret = rmdir(uid_pid_path.c_str());
+        if (!ret || errno != EBUSY) break;
+        std::this_thread::sleep_for(5ms);
+    }
+
+    // With the exception of boot or shutdown, system uid_ folders are always populated. Spinning
+    // here would needlessly delay most pid removals. Additionally, once empty a uid_ cgroup won't
+    // have processes hanging on it (we've already spun for all its pid_), so there's no need to
+    // spin anyway.
     rmdir(uid_path.c_str());
 
     return ret;
@@ -176,7 +188,7 @@
     std::vector<std::string> cgroups;
     std::string path;
 
-    if (CgroupGetControllerPath("cpuacct", &path)) {
+    if (CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &path)) {
         cgroups.push_back(path);
     }
     if (CgroupGetControllerPath("memory", &path)) {
@@ -212,19 +224,49 @@
     }
 }
 
+/**
+ * Process groups are primarily created by the Zygote, meaning that uid/pid groups are created by
+ * the user root. Ownership for the newly created cgroup and all of its files must thus be
+ * transferred for the user/group passed as uid/gid before system_server can properly access them.
+ */
 static bool MkdirAndChown(const std::string& path, mode_t mode, uid_t uid, gid_t gid) {
     if (mkdir(path.c_str(), mode) == -1 && errno != EEXIST) {
         return false;
     }
 
-    if (chown(path.c_str(), uid, gid) == -1) {
-        int saved_errno = errno;
-        rmdir(path.c_str());
-        errno = saved_errno;
-        return false;
+    auto dir = std::unique_ptr<DIR, decltype(&closedir)>(opendir(path.c_str()), closedir);
+
+    if (dir == NULL) {
+        PLOG(ERROR) << "opendir failed for " << path;
+        goto err;
+    }
+
+    struct dirent* dir_entry;
+    while ((dir_entry = readdir(dir.get()))) {
+        if (!strcmp("..", dir_entry->d_name)) {
+            continue;
+        }
+
+        std::string file_path = path + "/" + dir_entry->d_name;
+
+        if (lchown(file_path.c_str(), uid, gid) < 0) {
+            PLOG(ERROR) << "lchown failed for " << file_path;
+            goto err;
+        }
+
+        if (fchmodat(AT_FDCWD, file_path.c_str(), mode, AT_SYMLINK_NOFOLLOW) != 0) {
+            PLOG(ERROR) << "fchmodat failed for " << file_path;
+            goto err;
+        }
     }
 
     return true;
+err:
+    int saved_errno = errno;
+    rmdir(path.c_str());
+    errno = saved_errno;
+
+    return false;
 }
 
 // Returns number of processes killed on success
@@ -302,17 +344,9 @@
 
 static int KillProcessGroup(uid_t uid, int initialPid, int signal, int retries,
                             int* max_processes) {
-    std::string cpuacct_path;
-    std::string memory_path;
-
-    CgroupGetControllerPath("cpuacct", &cpuacct_path);
-    CgroupGetControllerPath("memory", &memory_path);
-    memory_path += "/apps";
-
-    const char* cgroup =
-            (!access(ConvertUidPidToPath(cpuacct_path.c_str(), uid, initialPid).c_str(), F_OK))
-                    ? cpuacct_path.c_str()
-                    : memory_path.c_str();
+    std::string hierarchy_root_path;
+    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &hierarchy_root_path);
+    const char* cgroup = hierarchy_root_path.c_str();
 
     std::chrono::steady_clock::time_point start = std::chrono::steady_clock::now();
 
@@ -355,7 +389,17 @@
             LOG(INFO) << "Successfully killed process cgroup uid " << uid << " pid " << initialPid
                       << " in " << static_cast<int>(ms) << "ms";
         }
-        return RemoveProcessGroup(cgroup, uid, initialPid);
+
+        int err = RemoveProcessGroup(cgroup, uid, initialPid, retries);
+
+        if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+            std::string memory_path;
+            CgroupGetControllerPath("memory", &memory_path);
+            memory_path += "/apps";
+            if (RemoveProcessGroup(memory_path.c_str(), uid, initialPid, retries)) return -1;
+        }
+
+        return err;
     } else {
         if (retries > 0) {
             LOG(ERROR) << "Failed to kill process cgroup uid " << uid << " pid " << initialPid
@@ -374,15 +418,7 @@
     return KillProcessGroup(uid, initialPid, signal, 0 /*retries*/, max_processes);
 }
 
-int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
-    std::string cgroup;
-    if (isMemoryCgroupSupported() && (memControl || UsePerAppMemcg())) {
-        CgroupGetControllerPath("memory", &cgroup);
-        cgroup += "/apps";
-    } else {
-        CgroupGetControllerPath("cpuacct", &cgroup);
-    }
-
+static int createProcessGroupInternal(uid_t uid, int initialPid, std::string cgroup) {
     auto uid_path = ConvertUidToPath(cgroup.c_str(), uid);
 
     if (!MkdirAndChown(uid_path, 0750, AID_SYSTEM, AID_SYSTEM)) {
@@ -408,6 +444,27 @@
     return ret;
 }
 
+int createProcessGroup(uid_t uid, int initialPid, bool memControl) {
+    std::string cgroup;
+
+    if (memControl && !UsePerAppMemcg()) {
+        PLOG(ERROR) << "service memory controls are used without per-process memory cgroup support";
+        return -EINVAL;
+    }
+
+    if (isMemoryCgroupSupported() && UsePerAppMemcg()) {
+        CgroupGetControllerPath("memory", &cgroup);
+        cgroup += "/apps";
+        int ret = createProcessGroupInternal(uid, initialPid, cgroup);
+        if (ret != 0) {
+            return ret;
+        }
+    }
+
+    CgroupGetControllerPath(CGROUPV2_CONTROLLER_NAME, &cgroup);
+    return createProcessGroupInternal(uid, initialPid, cgroup);
+}
+
 static bool SetProcessGroupValue(int tid, const std::string& attr_name, int64_t value) {
     if (!isMemoryCgroupSupported()) {
         PLOG(ERROR) << "Memcg is not mounted.";
diff --git a/libprocessgroup/profiles/cgroups.json b/libprocessgroup/profiles/cgroups.json
index 5b7a28a..962d2ba 100644
--- a/libprocessgroup/profiles/cgroups.json
+++ b/libprocessgroup/profiles/cgroups.json
@@ -15,11 +15,6 @@
       "GID": "system"
     },
     {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    },
-    {
       "Controller": "cpuset",
       "Path": "/dev/cpuset",
       "Mode": "0755",
@@ -42,7 +37,7 @@
     "Controllers": [
       {
         "Controller": "freezer",
-        "Path": "freezer",
+        "Path": ".",
         "Mode": "0755",
         "UID": "system",
         "GID": "system"
diff --git a/libprocessgroup/profiles/cgroups.recovery.json b/libprocessgroup/profiles/cgroups.recovery.json
index f0bf5fd..2c63c08 100644
--- a/libprocessgroup/profiles/cgroups.recovery.json
+++ b/libprocessgroup/profiles/cgroups.recovery.json
@@ -1,9 +1,2 @@
 {
-  "Cgroups": [
-    {
-      "Controller": "cpuacct",
-      "Path": "/acct",
-      "Mode": "0555"
-    }
-  ]
 }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 5b57bdd..628098b 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -46,7 +46,7 @@
       "File": "cpu.uclamp.latency_sensitive"
     },
     {
-      "Name": "FreezerState",
+      "Name": "Freezer",
       "Controller": "freezer",
       "File": "cgroup.freeze"
     }
@@ -70,11 +70,11 @@
       "Name": "Frozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": ""
+            "Name": "Freezer",
+            "Value": "1"
           }
         }
       ]
@@ -83,11 +83,11 @@
       "Name": "Unfrozen",
       "Actions": [
         {
-          "Name": "JoinCgroup",
+          "Name": "SetAttribute",
           "Params":
           {
-            "Controller": "freezer",
-            "Path": "../"
+            "Name": "Freezer",
+            "Value": "0"
           }
         }
       ]
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 1311306..8d4ce25 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -273,7 +273,7 @@
     value = StringReplace(value, "<pid>", std::to_string(pid), true);
 
     if (!WriteStringToFile(value, filepath)) {
-        PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
         return false;
     }
 
@@ -290,7 +290,7 @@
     value = StringReplace(value, "<pid>", std::to_string(tid), true);
 
     if (!WriteStringToFile(value, filepath)) {
-        PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
+        if (logfailures_) PLOG(ERROR) << "Failed to write '" << value << "' to " << filepath;
         return false;
     }
 
@@ -516,7 +516,10 @@
                 std::string attr_filepath = params_val["FilePath"].asString();
                 std::string attr_value = params_val["Value"].asString();
                 if (!attr_filepath.empty() && !attr_value.empty()) {
-                    profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value));
+                    const Json::Value& logfailures = params_val["LogFailures"];
+                    bool attr_logfailures = logfailures.isNull() || logfailures.asBool();
+                    profile->Add(std::make_unique<WriteFileAction>(attr_filepath, attr_value,
+                                                                   attr_logfailures));
                 } else if (attr_filepath.empty()) {
                     LOG(WARNING) << "WriteFile: invalid parameter: "
                                  << "empty filepath";
diff --git a/libprocessgroup/task_profiles.h b/libprocessgroup/task_profiles.h
index 98bcb0e..25a84b0 100644
--- a/libprocessgroup/task_profiles.h
+++ b/libprocessgroup/task_profiles.h
@@ -142,14 +142,16 @@
 // Write to file action
 class WriteFileAction : public ProfileAction {
   public:
-    WriteFileAction(const std::string& filepath, const std::string& value) noexcept
-        : filepath_(filepath), value_(value) {}
+    WriteFileAction(const std::string& filepath, const std::string& value,
+                    bool logfailures) noexcept
+        : filepath_(filepath), value_(value), logfailures_(logfailures) {}
 
     virtual bool ExecuteForProcess(uid_t uid, pid_t pid) const;
     virtual bool ExecuteForTask(int tid) const;
 
   private:
     std::string filepath_, value_;
+    bool logfailures_;
 };
 
 class TaskProfile {
diff --git a/llkd/libllkd.cpp b/llkd/libllkd.cpp
index a24d900..9f3e218 100644
--- a/llkd/libllkd.cpp
+++ b/llkd/libllkd.cpp
@@ -962,7 +962,7 @@
     //
     // This alarm is effectively the live lock detection of llkd, as
     // we understandably can not monitor ourselves otherwise.
-    ::alarm(duration_cast<seconds>(llkTimeoutMs * 2).count());
+    ::alarm(duration_cast<seconds>(llkTimeoutMs * 2 * android::base::TimeoutMultiplier()).count());
 
     // kernel jiffy precision fastest acquisition
     static timespec last;
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 21a78c1..03af4f3 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -731,6 +731,7 @@
     mkdir /data/misc/installd 0700 root root
     mkdir /data/misc/apexdata 0711 root root
     mkdir /data/misc/apexrollback 0700 root root
+    mkdir /data/misc/appcompat/ 0700 system system
     mkdir /data/misc/snapshotctl_log 0755 root root
     # create location to store pre-reboot information
     mkdir /data/misc/prereboot 0700 system system
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index aebcd5f..e9293b5 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -1,6 +1,5 @@
-import /vendor/ueventd.rc
-import /odm/ueventd.rc
-import /ueventd.${ro.hardware}.rc
+import /vendor/etc/ueventd.rc
+import /odm/etc/ueventd.rc
 
 firmware_directories /etc/firmware/ /odm/firmware/ /vendor/firmware/ /firmware/image/
 uevent_socket_rcvbuf_size 16M
diff --git a/trusty/apploader/Android.bp b/trusty/apploader/Android.bp
new file mode 100644
index 0000000..7e97cb8
--- /dev/null
+++ b/trusty/apploader/Android.bp
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2020 The Android Open-Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_binary {
+    name: "trusty_apploader",
+    vendor: true,
+
+    srcs: [
+        "apploader.cpp",
+    ],
+
+    shared_libs: [
+        "libbase",
+        "libc",
+        "liblog",
+        "libtrusty",
+        "libdmabufheap",
+    ],
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+}
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
new file mode 100644
index 0000000..5d5d882
--- /dev/null
+++ b/trusty/apploader/apploader.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "TrustyAppLoader"
+
+#include <BufferAllocator/BufferAllocator.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/sendfile.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <trusty/tipc.h>
+#include <unistd.h>
+#include <algorithm>
+#include <string>
+
+#include "apploader_ipc.h"
+
+using android::base::unique_fd;
+using std::string;
+
+constexpr const char kTrustyDefaultDeviceName[] = "/dev/trusty-ipc-dev0";
+
+static const char* dev_name = kTrustyDefaultDeviceName;
+
+static const char* _sopts = "hs";
+static const struct option _lopts[] = {
+        {"help", no_argument, 0, 'h'},
+        {"dev", required_argument, 0, 'D'},
+        {0, 0, 0, 0},
+};
+
+static const char* usage =
+        "Usage: %s [options] package-file\n"
+        "\n"
+        "options:\n"
+        "  -h, --help            prints this message and exit\n"
+        "  -D, --dev name        Trusty device name\n"
+        "\n";
+
+static void print_usage_and_exit(const char* prog, int code) {
+    fprintf(stderr, usage, prog);
+    exit(code);
+}
+
+static void parse_options(int argc, char** argv) {
+    int c;
+    int oidx = 0;
+
+    while (1) {
+        c = getopt_long(argc, argv, _sopts, _lopts, &oidx);
+        if (c == -1) {
+            break; /* done */
+        }
+
+        switch (c) {
+            case 'h':
+                print_usage_and_exit(argv[0], EXIT_SUCCESS);
+                break;
+
+            case 'D':
+                dev_name = strdup(optarg);
+                break;
+
+            default:
+                print_usage_and_exit(argv[0], EXIT_FAILURE);
+        }
+    }
+}
+
+static unique_fd read_file(const char* file_name, off64_t* out_file_size) {
+    int rc;
+    long page_size = sysconf(_SC_PAGESIZE);
+    off64_t file_size, file_page_offset, file_page_size;
+    struct stat64 st;
+
+    unique_fd file_fd(TEMP_FAILURE_RETRY(open(file_name, O_RDONLY)));
+    if (!file_fd.ok()) {
+        fprintf(stderr, "Error opening file '%s': %s\n", file_name, strerror(errno));
+        return {};
+    }
+
+    rc = fstat64(file_fd, &st);
+    if (rc < 0) {
+        fprintf(stderr, "Error calling stat on file '%s': %s\n", file_name, strerror(errno));
+        return {};
+    }
+
+    assert(st.st_size >= 0);
+    file_size = st.st_size;
+
+    /* The dmabuf size needs to be a multiple of the page size */
+    file_page_offset = file_size & (page_size - 1);
+    if (file_page_offset) {
+        file_page_offset = page_size - file_page_offset;
+    }
+    if (__builtin_add_overflow(file_size, file_page_offset, &file_page_size)) {
+        fprintf(stderr, "Failed to page-align file size\n");
+        return {};
+    }
+
+    BufferAllocator alloc;
+    unique_fd dmabuf_fd(alloc.Alloc(kDmabufSystemHeapName, file_page_size));
+    if (!dmabuf_fd.ok()) {
+        fprintf(stderr, "Error creating dmabuf: %d\n", dmabuf_fd.get());
+        return dmabuf_fd;
+    }
+
+    void* shm = mmap(0, file_page_size, PROT_READ | PROT_WRITE, MAP_SHARED, dmabuf_fd, 0);
+    if (shm == MAP_FAILED) {
+        return {};
+    }
+
+    off64_t file_offset = 0;
+    while (file_offset < file_size) {
+        ssize_t num_read = TEMP_FAILURE_RETRY(
+                pread(file_fd, (char*)shm + file_offset, file_size - file_offset, file_offset));
+
+        if (num_read < 0) {
+            fprintf(stderr, "Error reading package file '%s': %s\n", file_name, strerror(errno));
+            break;
+        }
+
+        if (num_read == 0) {
+            fprintf(stderr, "Unexpected end of file '%s'\n", file_name);
+            break;
+        }
+
+        file_offset += (off64_t)num_read;
+    }
+
+    munmap(shm, file_page_size);
+
+    if (file_offset < file_size) {
+        return {};
+    }
+
+    assert(file_offset == file_size);
+    if (out_file_size) {
+        *out_file_size = file_size;
+    }
+
+    return dmabuf_fd;
+}
+
+static ssize_t send_load_message(int tipc_fd, int package_fd, off64_t package_size) {
+    struct apploader_header hdr = {
+            .cmd = APPLOADER_CMD_LOAD_APPLICATION,
+    };
+    struct apploader_load_app_req req = {
+            .package_size = static_cast<uint64_t>(package_size),
+    };
+    struct iovec tx[2] = {{&hdr, sizeof(hdr)}, {&req, sizeof(req)}};
+    struct trusty_shm shm = {
+            .fd = package_fd,
+            .transfer = TRUSTY_SHARE,
+    };
+    return tipc_send(tipc_fd, tx, 2, &shm, 1);
+}
+
+static ssize_t read_response(int tipc_fd) {
+    struct apploader_resp resp;
+    ssize_t rc = read(tipc_fd, &resp, sizeof(resp));
+    if (rc < 0) {
+        fprintf(stderr, "Failed to read response: %zd\n", rc);
+        return rc;
+    }
+
+    if (rc < sizeof(resp)) {
+        fprintf(stderr, "Not enough data in response: %zd\n", rc);
+        return -EIO;
+    }
+
+    if (resp.hdr.cmd != (APPLOADER_CMD_LOAD_APPLICATION | APPLOADER_RESP_BIT)) {
+        fprintf(stderr, "Invalid command in response: %u\n", resp.hdr.cmd);
+        return -EINVAL;
+    }
+
+    switch (resp.error) {
+        case APPLOADER_NO_ERROR:
+            break;
+        case APPLOADER_ERR_UNKNOWN_CMD:
+            fprintf(stderr, "Error: unknown command\n");
+            break;
+        case APPLOADER_ERR_INVALID_CMD:
+            fprintf(stderr, "Error: invalid command arguments\n");
+            break;
+        case APPLOADER_ERR_NO_MEMORY:
+            fprintf(stderr, "Error: out of Trusty memory\n");
+            break;
+        case APPLOADER_ERR_VERIFICATION_FAILED:
+            fprintf(stderr, "Error: failed to verify the package\n");
+            break;
+        case APPLOADER_ERR_LOADING_FAILED:
+            fprintf(stderr, "Error: failed to load the package\n");
+            break;
+        case APPLOADER_ERR_ALREADY_EXISTS:
+            fprintf(stderr, "Error: application already exists\n");
+            break;
+        case APPLOADER_ERR_INTERNAL:
+            fprintf(stderr, "Error: internal apploader error\n");
+            break;
+        default:
+            fprintf(stderr, "Unrecognized error: %u\n", resp.error);
+            break;
+    }
+
+    return static_cast<ssize_t>(resp.error);
+}
+
+static ssize_t send_app_package(const char* package_file_name) {
+    ssize_t rc = 0;
+    int tipc_fd = -1;
+    off64_t package_size;
+
+    unique_fd package_fd = read_file(package_file_name, &package_size);
+    if (!package_fd.ok()) {
+        rc = -1;
+        goto err_read_file;
+    }
+
+    tipc_fd = tipc_connect(dev_name, APPLOADER_PORT);
+    if (tipc_fd < 0) {
+        fprintf(stderr, "Failed to connect to Trusty app loader: %s\n", strerror(-tipc_fd));
+        rc = tipc_fd;
+        goto err_tipc_connect;
+    }
+
+    rc = send_load_message(tipc_fd, package_fd, package_size);
+    if (rc < 0) {
+        fprintf(stderr, "Failed to send package: %zd\n", rc);
+        goto err_send;
+    }
+
+    rc = read_response(tipc_fd);
+
+err_send:
+    tipc_close(tipc_fd);
+err_tipc_connect:
+err_read_file:
+    return rc;
+}
+
+int main(int argc, char** argv) {
+    parse_options(argc, argv);
+    if (optind + 1 != argc) {
+        print_usage_and_exit(argv[0], EXIT_FAILURE);
+    }
+
+    int rc = send_app_package(argv[optind]);
+    return rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/trusty/apploader/apploader_ipc.h b/trusty/apploader/apploader_ipc.h
new file mode 100644
index 0000000..d8c915e
--- /dev/null
+++ b/trusty/apploader/apploader_ipc.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#define APPLOADER_PORT "com.android.trusty.apploader"
+
+enum apploader_command : uint32_t {
+    APPLOADER_REQ_SHIFT = 1,
+    APPLOADER_RESP_BIT = 1,
+
+    APPLOADER_CMD_LOAD_APPLICATION = (0 << APPLOADER_REQ_SHIFT),
+    APPLOADER_CMD_GET_VERSION = (1 << APPLOADER_REQ_SHIFT),
+    APPLOADER_CMD_UNLOAD_APPLICATION = (2 << APPLOADER_REQ_SHIFT),
+};
+
+/**
+ * enum apploader_error - error codes for apploader
+ * @APPLOADER_NO_ERROR:                 no error
+ * @APPLOADER_ERR_UNKNOWN_CMD:          unknown or not implemented command
+ * @APPLOADER_ERR_INVALID_CMD:          invalid arguments or inputs passed to
+ *                                      command
+ * @APPLOADER_ERR_NO_MEMORY:            failed to allocate memory
+ * @APPLOADER_ERR_VERIFICATION_FAILED:  failed to verify input application
+ *                                      package for any reason, e.g., signature
+ *                                      verification failed
+ * @APPLOADER_ERR_LOADING_FAILED:       Trusty kernel or apploader service
+ *                                      failed to load application
+ * @APPLOADER_ERR_ALREADY_EXISTS:       application has already been loaded
+ * @APPLOADER_ERR_INTERNAL:             miscellaneous or internal apploader
+ *                                      error not covered by the above
+ */
+enum apploader_error : uint32_t {
+    APPLOADER_NO_ERROR = 0,
+    APPLOADER_ERR_UNKNOWN_CMD,
+    APPLOADER_ERR_INVALID_CMD,
+    APPLOADER_ERR_NO_MEMORY,
+    APPLOADER_ERR_VERIFICATION_FAILED,
+    APPLOADER_ERR_LOADING_FAILED,
+    APPLOADER_ERR_ALREADY_EXISTS,
+    APPLOADER_ERR_INTERNAL,
+};
+
+/**
+ * apploader_header - Serial header for communicating with apploader
+ * @cmd: the command; one of &enum apploader_command values.
+ */
+struct apploader_header {
+    uint32_t cmd;
+} __packed;
+
+/**
+ * apploader_load_app_req - Serial arguments for LOAD_APPLICATION command
+ * @package_size: size of the application package.
+ *
+ * Load an application from a given memory region. The request message also
+ * contains a handle for a memfd that contains the application package.
+ *
+ * The response is a &struct apploader_resp with the error code or
+ * %APPLOADER_NO_ERROR on success.
+ */
+struct apploader_load_app_req {
+    uint64_t package_size;
+} __packed;
+
+/**
+ * apploader_resp - Common header for all apploader responses
+ * @hdr - header with command value.
+ * @error - error code returned by peer; one of &enum apploader_error values.
+ *
+ * This structure is followed by the response-specific payload, if the command
+ * has one.
+ */
+struct apploader_resp {
+    struct apploader_header hdr;
+    uint32_t error;
+} __packed;
diff --git a/trusty/gatekeeper/gatekeeper_ipc.h b/trusty/gatekeeper/gatekeeper_ipc.h
index b05dcd8..8709d1a 100644
--- a/trusty/gatekeeper/gatekeeper_ipc.h
+++ b/trusty/gatekeeper/gatekeeper_ipc.h
@@ -20,11 +20,13 @@
 #define GATEKEEPER_MAX_BUFFER_LENGTH 1024
 
 enum gatekeeper_command {
-	GK_REQ_SHIFT = 1,
-	GK_RESP_BIT  = 1,
+    GK_REQ_SHIFT = 1,
+    GK_RESP_BIT = 1,
 
-	GK_ENROLL       = (0 << GK_REQ_SHIFT),
-	GK_VERIFY       = (1 << GK_REQ_SHIFT),
+    GK_ENROLL = (0 << GK_REQ_SHIFT),
+    GK_VERIFY = (1 << GK_REQ_SHIFT),
+    GK_DELETE_USER = (2 << GK_REQ_SHIFT),
+    GK_DELETE_ALL_USERS = (3 << GK_REQ_SHIFT),
 };
 
 /**
diff --git a/trusty/gatekeeper/trusty_gatekeeper.cpp b/trusty/gatekeeper/trusty_gatekeeper.cpp
index e416fb2..ec4f81b 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.cpp
+++ b/trusty/gatekeeper/trusty_gatekeeper.cpp
@@ -133,13 +133,48 @@
     return {};
 }
 
-Return<void> TrustyGateKeeperDevice::deleteUser(uint32_t /*uid*/, deleteUser_cb _hidl_cb) {
-    _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+Return<void> TrustyGateKeeperDevice::deleteUser(uint32_t uid, deleteUser_cb _hidl_cb) {
+    if (error_ != 0) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return {};
+    }
+
+    DeleteUserRequest request(uid);
+    DeleteUserResponse response;
+    auto error = Send(request, &response);
+
+    if (error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else if (response.error == ERROR_NOT_IMPLEMENTED) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+    } else if (response.error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else {
+        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+    }
     return {};
 }
 
 Return<void> TrustyGateKeeperDevice::deleteAllUsers(deleteAllUsers_cb _hidl_cb) {
-    _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+    if (error_ != 0) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+        return {};
+    }
+
+    DeleteAllUsersRequest request;
+    DeleteAllUsersResponse response;
+    auto error = Send(request, &response);
+
+    if (error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else if (response.error == ERROR_NOT_IMPLEMENTED) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_NOT_IMPLEMENTED, 0, {}});
+    } else if (response.error != ERROR_NONE) {
+        _hidl_cb({GatekeeperStatusCode::ERROR_GENERAL_FAILURE, 0, {}});
+    } else {
+        _hidl_cb({GatekeeperStatusCode::STATUS_OK, response.retry_timeout, {}});
+    }
+
     return {};
 }
 
diff --git a/trusty/gatekeeper/trusty_gatekeeper.h b/trusty/gatekeeper/trusty_gatekeeper.h
index c0713f4..420dd7a 100644
--- a/trusty/gatekeeper/trusty_gatekeeper.h
+++ b/trusty/gatekeeper/trusty_gatekeeper.h
@@ -81,6 +81,15 @@
         return Send(GK_VERIFY, request, response);
     }
 
+    gatekeeper_error_t Send(const DeleteUserRequest& request, DeleteUserResponse* response) {
+        return Send(GK_DELETE_USER, request, response);
+    }
+
+    gatekeeper_error_t Send(const DeleteAllUsersRequest& request,
+                            DeleteAllUsersResponse* response) {
+        return Send(GK_DELETE_ALL_USERS, request, response);
+    }
+
     int error_;
 };
 
diff --git a/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp b/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
index 7184e4d..d787f7a 100644
--- a/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
+++ b/trusty/keymaster/3.0/TrustyKeymaster3Device.cpp
@@ -276,7 +276,7 @@
     ImportKeyRequest request(impl_->message_version());
     request.key_description.Reinitialize(KmParamSet(params));
     request.key_format = legacy_enum_conversion(keyFormat);
-    request.SetKeyMaterial(keyData.data(), keyData.size());
+    request.key_data = KeymasterKeyBlob(keyData.data(), keyData.size());
 
     ImportKeyResponse response(impl_->message_version());
     impl_->ImportKey(request, &response);
diff --git a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
index 73ad6ae..e68ba82 100644
--- a/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
+++ b/trusty/keymaster/4.0/TrustyKeymaster4Device.cpp
@@ -391,7 +391,7 @@
     ImportKeyRequest request(impl_->message_version());
     request.key_description.Reinitialize(KmParamSet(params));
     request.key_format = legacy_enum_conversion(keyFormat);
-    request.SetKeyMaterial(keyData.data(), keyData.size());
+    request.key_data = KeymasterKeyBlob(keyData.data(), keyData.size());
 
     ImportKeyResponse response(impl_->message_version());
     impl_->ImportKey(request, &response);
diff --git a/trusty/trusty-base.mk b/trusty/trusty-base.mk
index fd8daa8..12521b0 100644
--- a/trusty/trusty-base.mk
+++ b/trusty/trusty-base.mk
@@ -24,7 +24,8 @@
 
 PRODUCT_PACKAGES += \
 	android.hardware.keymaster@4.0-service.trusty \
-	android.hardware.gatekeeper@1.0-service.trusty
+	android.hardware.gatekeeper@1.0-service.trusty \
+	trusty_apploader
 
 PRODUCT_PROPERTY_OVERRIDES += \
 	ro.hardware.keystore=trusty \