Merge "sparse_fuzzer: Bug fix"
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index dcf92be..9b96f36 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,8 +1,10 @@
 [Builtin Hooks]
 clang_format = true
+rustfmt = true
 
 [Builtin Hooks Options]
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+rustfmt = --config-path=rustfmt.toml
 
 [Hook Scripts]
 aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
diff --git a/debuggerd/Android.bp b/debuggerd/Android.bp
index e0c138b..ad0231d 100644
--- a/debuggerd/Android.bp
+++ b/debuggerd/Android.bp
@@ -24,6 +24,10 @@
     export_include_dirs: ["common/include"],
     recovery_available: true,
     vendor_ramdisk_available: true,
+    apex_available: [
+        "com.android.virt",
+        "//apex_available:platform",
+   ],
 }
 
 cc_library_shared {
@@ -44,6 +48,10 @@
         "libbase",
         "libcutils",
     ],
+    apex_available: [
+        "com.android.virt",
+        "//apex_available:platform",
+    ],
 
     export_header_lib_headers: ["libdebuggerd_common_headers"],
     export_include_dirs: ["tombstoned/include"],
diff --git a/debuggerd/TEST_MAPPING b/debuggerd/TEST_MAPPING
index d5327db..394447b 100644
--- a/debuggerd/TEST_MAPPING
+++ b/debuggerd/TEST_MAPPING
@@ -2,6 +2,14 @@
   "presubmit": [
     {
       "name": "debuggerd_test"
+    },
+    {
+      "name": "libtombstoned_client_rust_test"
+    }
+  ],
+  "hwasan-presubmit": [
+    {
+      "name": "debuggerd_test"
     }
   ]
 }
diff --git a/debuggerd/crasher/Android.bp b/debuggerd/crasher/Android.bp
index 23b106e..799163e 100644
--- a/debuggerd/crasher/Android.bp
+++ b/debuggerd/crasher/Android.bp
@@ -45,6 +45,8 @@
     shared_libs: [
         "libbase",
         "liblog",
+    ],
+    static_libs: [
         "libseccomp_policy",
     ],
     multilib: {
diff --git a/debuggerd/crasher/crasher.cpp b/debuggerd/crasher/crasher.cpp
index db30b8f..55490b5 100644
--- a/debuggerd/crasher/crasher.cpp
+++ b/debuggerd/crasher/crasher.cpp
@@ -39,6 +39,8 @@
 #include "debuggerd/handler.h"
 #endif
 
+extern "C" void android_set_abort_message(const char* msg);
+
 #if defined(__arm__)
 // See https://www.kernel.org/doc/Documentation/arm/kernel_user_helpers.txt for details.
 #define __kuser_helper_version (*(int32_t*) 0xffff0ffc)
@@ -182,6 +184,8 @@
     fprintf(stderr, "  leak                  leak memory until we get OOM-killed\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "  abort                 call abort()\n");
+    fprintf(stderr, "  abort_with_msg        call abort() setting an abort message\n");
+    fprintf(stderr, "  abort_with_null_msg   call abort() setting a null abort message\n");
     fprintf(stderr, "  assert                call assert() without a function\n");
     fprintf(stderr, "  assert2               call assert() with a function\n");
     fprintf(stderr, "  exit                  call exit(1)\n");
@@ -259,6 +263,12 @@
       return crash(42);
     } else if (!strcasecmp(arg, "abort")) {
       maybe_abort();
+    } else if (!strcasecmp(arg, "abort_with_msg")) {
+      android_set_abort_message("Aborting due to crasher");
+      maybe_abort();
+    } else if (!strcasecmp(arg, "abort_with_null")) {
+      android_set_abort_message(nullptr);
+      maybe_abort();
     } else if (!strcasecmp(arg, "assert")) {
       __assert("some_file.c", 123, "false");
     } else if (!strcasecmp(arg, "assert2")) {
diff --git a/debuggerd/debuggerd_test.cpp b/debuggerd/debuggerd_test.cpp
index a5e2413..f4ba347 100644
--- a/debuggerd/debuggerd_test.cpp
+++ b/debuggerd/debuggerd_test.cpp
@@ -113,8 +113,14 @@
 // Enable GWP-ASan at the start of this process. GWP-ASan is enabled using
 // process sampling, so we need to ensure we force GWP-ASan on.
 __attribute__((constructor)) static void enable_gwp_asan() {
-  bool force = true;
-  android_mallopt(M_INITIALIZE_GWP_ASAN, &force, sizeof(force));
+  android_mallopt_gwp_asan_options_t opts;
+  // No, we're not an app, but let's turn ourselves on without sampling.
+  // Technically, if someone's using the *.default_app sysprops, they'll adjust
+  // our settings, but I don't think this will be common on a device that's
+  // running debuggerd_tests.
+  opts.desire = android_mallopt_gwp_asan_options_t::Action::TURN_ON_FOR_APP;
+  opts.program_name = "";
+  android_mallopt(M_INITIALIZE_GWP_ASAN, &opts, sizeof(android_mallopt_gwp_asan_options_t));
 }
 
 static void tombstoned_intercept(pid_t target_pid, unique_fd* intercept_fd, unique_fd* output_fd,
@@ -377,6 +383,8 @@
 #if !defined(__aarch64__)
   GTEST_SKIP() << "Requires aarch64";
 #endif
+  // HWASan crashes with SIGABRT on tag mismatch.
+  SKIP_WITH_HWASAN;
   int intercept_result;
   unique_fd output_fd;
   StartProcess([]() {
@@ -408,6 +416,10 @@
 #if defined(__i386__)
   GTEST_SKIP() << "architecture does not pass arguments in registers";
 #endif
+  // The memory dump in HWASan crashes sadly shows the memory near the registers
+  // in the HWASan dump function, rather the faulting context. This is a known
+  // issue.
+  SKIP_WITH_HWASAN;
   int intercept_result;
   unique_fd output_fd;
   StartProcess([]() {
@@ -486,6 +498,8 @@
     // instead of GWP-ASan.
     GTEST_SKIP() << "Skipped on MTE.";
   }
+  // Skip this test on HWASan, which will reliably catch test errors as well.
+  SKIP_WITH_HWASAN;
 
   GwpAsanTestParameters params = GetParam();
   LogcatCollector logcat_collector;
@@ -2021,6 +2035,9 @@
 
 // Verify that a fault address after the last map is properly handled.
 TEST_F(CrasherTest, fault_address_after_last_map) {
+  // This makes assumptions about the memory layout that are not true in HWASan
+  // processes.
+  SKIP_WITH_HWASAN;
   uintptr_t crash_uptr = untag_address(UINTPTR_MAX - 15);
   StartProcess([crash_uptr]() {
     ASSERT_EQ(0, crash_call(crash_uptr));
diff --git a/debuggerd/handler/debuggerd_fallback.cpp b/debuggerd/handler/debuggerd_fallback.cpp
index 4c1f9eb..c8b25ae 100644
--- a/debuggerd/handler/debuggerd_fallback.cpp
+++ b/debuggerd/handler/debuggerd_fallback.cpp
@@ -73,14 +73,13 @@
     thread.registers.reset(
         unwindstack::Regs::CreateFromUcontext(unwindstack::Regs::CurrentArch(), ucontext));
 
-    // TODO: Create this once and store it in a global?
-    unwindstack::UnwinderFromPid unwinder(kMaxFrames, getpid());
     // Do not use the thread cache here because it will call pthread_key_create
     // which doesn't work in linker code. See b/189803009.
     // Use a normal cached object because the process is stopped, and there
     // is no chance of data changing between reads.
     auto process_memory = unwindstack::Memory::CreateProcessMemoryCached(getpid());
-    unwinder.SetProcessMemory(process_memory);
+    // TODO: Create this once and store it in a global?
+    unwindstack::UnwinderFromPid unwinder(kMaxFrames, getpid(), process_memory);
     dump_backtrace_thread(output_fd, &unwinder, thread);
   }
   __linker_disable_fallback_allocator();
diff --git a/debuggerd/libdebuggerd/gwp_asan.cpp b/debuggerd/libdebuggerd/gwp_asan.cpp
index b2077ba..3d96627 100644
--- a/debuggerd/libdebuggerd/gwp_asan.cpp
+++ b/debuggerd/libdebuggerd/gwp_asan.cpp
@@ -43,10 +43,13 @@
 static const gwp_asan::AllocationMetadata* retrieve_gwp_asan_metadata(
     unwindstack::Memory* process_memory, const gwp_asan::AllocatorState& state,
     uintptr_t metadata_addr) {
-  if (state.MaxSimultaneousAllocations > 1024) {
+  // 1 million GWP-ASan slots would take 4.1GiB of space. Thankfully, copying
+  // the metadata for that amount of slots is only 532MiB, and this really will
+  // only be used with some ridiculous torture-tests.
+  if (state.MaxSimultaneousAllocations > 1000000) {
     ALOGE(
         "Error when retrieving GWP-ASan metadata, MSA from state (%zu) "
-        "exceeds maximum allowed (1024).",
+        "exceeds maximum allowed (1,000,000).",
         state.MaxSimultaneousAllocations);
     return nullptr;
   }
diff --git a/debuggerd/libdebuggerd/tombstone.cpp b/debuggerd/libdebuggerd/tombstone.cpp
index 14caaf6..eda7182 100644
--- a/debuggerd/libdebuggerd/tombstone.cpp
+++ b/debuggerd/libdebuggerd/tombstone.cpp
@@ -101,10 +101,10 @@
     }
   }
 
-  unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch());
   auto process_memory =
       unwindstack::Memory::CreateProcessMemoryCached(getpid());
-  unwinder.SetProcessMemory(process_memory);
+  unwindstack::UnwinderFromPid unwinder(kMaxFrames, pid, unwindstack::Regs::CurrentArch(), nullptr,
+                                        process_memory);
   if (!unwinder.Init()) {
     async_safe_format_log(ANDROID_LOG_ERROR, LOG_TAG, "failed to init unwinder object");
     return;
diff --git a/debuggerd/rust/tombstoned_client/Android.bp b/debuggerd/rust/tombstoned_client/Android.bp
new file mode 100644
index 0000000..2007f39
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/Android.bp
@@ -0,0 +1,59 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_library_static {
+    name: "libtombstoned_client_wrapper",
+    srcs: [
+        "wrapper.cpp",
+    ],
+    generated_sources: [
+        "libtombstoned_client_rust_bridge_code"
+    ],
+    header_libs: [
+        "libbase_headers",
+        "libdebuggerd_common_headers",
+    ],
+    shared_libs: [
+        "libtombstoned_client",
+    ],
+    apex_available: ["com.android.virt"],
+}
+
+rust_defaults {
+    name: "libtombstoned_client_rust_defaults",
+    crate_name: "tombstoned_client",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    rustlibs: [
+        "libcxx",
+        "libthiserror",
+    ],
+    static_libs: [
+        "libtombstoned_client_wrapper",
+    ],
+    shared_libs: [
+        "libtombstoned_client",
+    ],
+}
+
+rust_library {
+    name: "libtombstoned_client_rust",
+    defaults: ["libtombstoned_client_rust_defaults"],
+    apex_available: ["com.android.virt"],
+}
+
+rust_test {
+    name: "libtombstoned_client_rust_test",
+    defaults: ["libtombstoned_client_rust_defaults"],
+    require_root: true,
+    test_suites: ["device-tests"],
+}
+
+genrule {
+    name: "libtombstoned_client_rust_bridge_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["src/lib.rs"],
+    out: ["libtombstoned_client_cxx_generated.cc"],
+}
diff --git a/debuggerd/rust/tombstoned_client/src/lib.rs b/debuggerd/rust/tombstoned_client/src/lib.rs
new file mode 100644
index 0000000..5c8abef
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/src/lib.rs
@@ -0,0 +1,153 @@
+// Copyright 2022, 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.
+
+//! Rust wrapper for tombstoned client.
+
+pub use ffi::DebuggerdDumpType;
+use std::fs::File;
+use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
+use thiserror::Error;
+
+/// Error communicating with tombstoned.
+#[derive(Clone, Debug, Error, Eq, PartialEq)]
+#[error("Error communicating with tombstoned")]
+pub struct Error;
+
+/// File descriptors for communicating with tombstoned.
+pub struct TombstonedConnection {
+    /// The socket connection to tombstoned.
+    ///
+    /// This is actually a Unix SOCK_SEQPACKET socket not a file, but the Rust standard library
+    /// doesn't have an appropriate type and it's not really worth bringing in a dependency on `uds`
+    /// or something when all we do is pass it back to C++ or close it.
+    tombstoned_socket: File,
+    /// The file descriptor for text output.
+    pub text_output: Option<File>,
+    /// The file descriptor for proto output.
+    pub proto_output: Option<File>,
+}
+
+impl TombstonedConnection {
+    unsafe fn from_raw_fds(
+        tombstoned_socket: RawFd,
+        text_output_fd: RawFd,
+        proto_output_fd: RawFd,
+    ) -> Self {
+        Self {
+            tombstoned_socket: File::from_raw_fd(tombstoned_socket),
+            text_output: if text_output_fd >= 0 {
+                Some(File::from_raw_fd(text_output_fd))
+            } else {
+                None
+            },
+            proto_output: if proto_output_fd >= 0 {
+                Some(File::from_raw_fd(proto_output_fd))
+            } else {
+                None
+            },
+        }
+    }
+
+    /// Connects to tombstoned.
+    pub fn connect(pid: i32, dump_type: DebuggerdDumpType) -> Result<Self, Error> {
+        let mut tombstoned_socket = -1;
+        let mut text_output_fd = -1;
+        let mut proto_output_fd = -1;
+        if ffi::tombstoned_connect_files(
+            pid,
+            &mut tombstoned_socket,
+            &mut text_output_fd,
+            &mut proto_output_fd,
+            dump_type,
+        ) {
+            Ok(unsafe { Self::from_raw_fds(tombstoned_socket, text_output_fd, proto_output_fd) })
+        } else {
+            Err(Error)
+        }
+    }
+
+    /// Notifies tombstoned that the dump is complete.
+    pub fn notify_completion(&self) -> Result<(), Error> {
+        if ffi::tombstoned_notify_completion(self.tombstoned_socket.as_raw_fd()) {
+            Ok(())
+        } else {
+            Err(Error)
+        }
+    }
+}
+
+#[cxx::bridge]
+mod ffi {
+    /// The type of dump.
+    enum DebuggerdDumpType {
+        /// A native backtrace.
+        #[cxx_name = "kDebuggerdNativeBacktrace"]
+        NativeBacktrace,
+        /// A tombstone.
+        #[cxx_name = "kDebuggerdTombstone"]
+        Tombstone,
+        /// A Java backtrace.
+        #[cxx_name = "kDebuggerdJavaBacktrace"]
+        JavaBacktrace,
+        /// Any intercept.
+        #[cxx_name = "kDebuggerdAnyIntercept"]
+        AnyIntercept,
+        /// A tombstone proto.
+        #[cxx_name = "kDebuggerdTombstoneProto"]
+        TombstoneProto,
+    }
+
+    unsafe extern "C++" {
+        include!("wrapper.hpp");
+
+        type DebuggerdDumpType;
+
+        fn tombstoned_connect_files(
+            pid: i32,
+            tombstoned_socket: &mut i32,
+            text_output_fd: &mut i32,
+            proto_output_fd: &mut i32,
+            dump_type: DebuggerdDumpType,
+        ) -> bool;
+
+        fn tombstoned_notify_completion(tombstoned_socket: i32) -> bool;
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::{io::Write, process};
+
+    // Verify that we can connect to tombstoned, write something to the file descriptor it returns,
+    // and notify completion, without any errors.
+    #[test]
+    fn test() {
+        let connection =
+            TombstonedConnection::connect(process::id() as i32, DebuggerdDumpType::Tombstone)
+                .expect("Failed to connect to tombstoned.");
+
+        assert!(connection.proto_output.is_none());
+        connection
+            .text_output
+            .as_ref()
+            .expect("No text output FD returned.")
+            .write_all(b"test data")
+            .expect("Failed to write to text output FD.");
+
+        connection
+            .notify_completion()
+            .expect("Failed to notify completion.");
+    }
+}
diff --git a/debuggerd/rust/tombstoned_client/wrapper.cpp b/debuggerd/rust/tombstoned_client/wrapper.cpp
new file mode 100644
index 0000000..7492329
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/wrapper.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2022, 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 "wrapper.hpp"
+
+#include <android-base/unique_fd.h>
+
+#include "tombstoned/tombstoned.h"
+
+using android::base::unique_fd;
+
+bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd,
+                              int& proto_output_fd, DebuggerdDumpType dump_type) {
+  unique_fd tombstoned_socket_unique, text_output_unique, proto_output_unique;
+
+  bool result = tombstoned_connect(pid, &tombstoned_socket_unique, &text_output_unique,
+                                   &proto_output_unique, dump_type);
+  if (result) {
+    tombstoned_socket = tombstoned_socket_unique.release();
+    text_output_fd = text_output_unique.release();
+    proto_output_fd = proto_output_unique.release();
+  }
+
+  return result;
+}
diff --git a/debuggerd/rust/tombstoned_client/wrapper.hpp b/debuggerd/rust/tombstoned_client/wrapper.hpp
new file mode 100644
index 0000000..95d3865
--- /dev/null
+++ b/debuggerd/rust/tombstoned_client/wrapper.hpp
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2022, 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 <sys/types.h>
+#include "tombstoned/tombstoned.h"
+
+bool tombstoned_connect_files(pid_t pid, int& tombstoned_socket, int& text_output_fd,
+                              int& proto_output_fd, DebuggerdDumpType dump_type);
diff --git a/fastboot/fastboot.bash b/fastboot/fastboot.bash
index e9bf9e9..911071a 100644
--- a/fastboot/fastboot.bash
+++ b/fastboot/fastboot.bash
@@ -109,7 +109,7 @@
 
     cur="${COMP_WORDS[COMP_CWORD]}"
     if [[ $i -eq $COMP_CWORD ]]; then
-        partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm"
+        partitions="boot bootloader dtbo init_boot modem odm odm_dlkm oem product pvmfw radio recovery system system_dlkm vbmeta vendor vendor_dlkm vendor_kernel_boot"
         COMPREPLY=( $(compgen -W "$partitions" -- $cur) )
     else
         _fastboot_util_complete_local_file "${cur}" '!*.img'
diff --git a/fastboot/fastboot.cpp b/fastboot/fastboot.cpp
index 39d86f9..79c3ac7 100644
--- a/fastboot/fastboot.cpp
+++ b/fastboot/fastboot.cpp
@@ -186,6 +186,11 @@
                   "vendor_dlkm.img",  "vendor_dlkm.sig",
                                                       "vendor_dlkm",
                                                                   true,  ImageType::Normal },
+    { "vendor_kernel_boot",
+                  "vendor_kernel_boot.img",
+                                      "vendor_kernel_boot.sig",
+                                                      "vendor_kernel_boot",
+                                                                  true,  ImageType::BootCritical },
     { nullptr,    "vendor_other.img", "vendor.sig",   "vendor",   true,  ImageType::Normal },
         // clang-format on
 };
diff --git a/fs_mgr/fs_mgr_fstab.cpp b/fs_mgr/fs_mgr_fstab.cpp
index b8b9262..8c719c8 100644
--- a/fs_mgr/fs_mgr_fstab.cpp
+++ b/fs_mgr/fs_mgr_fstab.cpp
@@ -653,6 +653,7 @@
                 entry->blk_device = partition;
                 // AVB keys for DSU should always be under kDsuKeysDir.
                 entry->avb_keys = kDsuKeysDir;
+                entry->fs_mgr_flags.logical = true;
             }
             // Make sure the ext4 is included to support GSI.
             auto partition_ext4 =
diff --git a/fs_mgr/liblp/builder_test.cpp b/fs_mgr/liblp/builder_test.cpp
index 72827eb..1759cf9 100644
--- a/fs_mgr/liblp/builder_test.cpp
+++ b/fs_mgr/liblp/builder_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/properties.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
@@ -31,6 +32,7 @@
 using ::testing::ElementsAre;
 using ::testing::NiceMock;
 using ::testing::Return;
+using android::base::GetProperty;
 
 class Environment : public ::testing::Environment {
   public:
diff --git a/fs_mgr/liblp/io_test.cpp b/fs_mgr/liblp/io_test.cpp
index e67fb33..d123304 100644
--- a/fs_mgr/liblp/io_test.cpp
+++ b/fs_mgr/liblp/io_test.cpp
@@ -20,7 +20,10 @@
 #include <sys/syscall.h>
 
 #include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/unique_fd.h>
+#include <fs_mgr.h>
+#include <fstab/fstab.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <liblp/builder.h>
@@ -38,6 +41,7 @@
 using ::testing::_;
 using ::testing::Return;
 using unique_fd = android::base::unique_fd;
+using android::base::GetProperty;
 
 // Our tests assume a 128KiB disk with two 512 byte metadata slots.
 static const size_t kDiskSize = 131072;
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
index 38b47d5..11da568 100644
--- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
+++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h
@@ -538,6 +538,9 @@
     // Unmap a COW and remove it from a MetadataBuilder.
     void UnmapAndDeleteCowPartition(MetadataBuilder* current_metadata);
 
+    // Remove invalid snapshots if any
+    void RemoveInvalidSnapshots(LockedFile* lock);
+
     // Unmap and remove all known snapshots.
     bool RemoveAllSnapshots(LockedFile* lock);
 
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index 797d627..a83f535 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -218,7 +218,10 @@
     if (!file) return false;
 
     UpdateState state = ReadUpdateState(file.get());
-    if (state == UpdateState::None) return true;
+    if (state == UpdateState::None) {
+        RemoveInvalidSnapshots(file.get());
+        return true;
+    }
 
     if (state == UpdateState::Initiated) {
         LOG(INFO) << "Update has been initiated, now canceling";
@@ -1903,6 +1906,33 @@
     return true;
 }
 
+void SnapshotManager::RemoveInvalidSnapshots(LockedFile* lock) {
+    std::vector<std::string> snapshots;
+
+    // Remove the stale snapshot metadata
+    //
+    // We make sure that all the three cases
+    // are valid before removing the snapshot metadata:
+    //
+    // 1: dm state is active
+    // 2: Root fs is not mounted off as a snapshot device
+    // 3: Snapshot slot suffix should match current device slot
+    if (!ListSnapshots(lock, &snapshots, device_->GetSlotSuffix()) || snapshots.empty()) {
+        return;
+    }
+
+    // We indeed have some invalid snapshots
+    for (const auto& name : snapshots) {
+        if (dm_.GetState(name) == DmDeviceState::ACTIVE && !IsSnapshotDevice(name)) {
+            if (!DeleteSnapshot(lock, name)) {
+                LOG(ERROR) << "Failed to delete invalid snapshot: " << name;
+            } else {
+                LOG(INFO) << "Invalid snapshot: " << name << " deleted";
+            }
+        }
+    }
+}
+
 bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
     std::vector<std::string> snapshots;
     if (!ListSnapshots(lock, &snapshots)) {
@@ -3205,15 +3235,27 @@
     status.set_compression_enabled(cow_creator.compression_enabled);
     if (cow_creator.compression_enabled) {
         if (!device()->IsTestDevice()) {
+            bool userSnapshotsEnabled = IsUserspaceSnapshotsEnabled();
+            const std::string UNKNOWN = "unknown";
+            const std::string vendor_release = android::base::GetProperty(
+                    "ro.vendor.build.version.release_or_codename", UNKNOWN);
+
+            // No user-space snapshots if vendor partition is on Android 12
+            if (vendor_release.find("12") != std::string::npos) {
+                LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
+                          << vendor_release;
+                userSnapshotsEnabled = false;
+            }
+
             // Userspace snapshots is enabled only if compression is enabled
-            status.set_userspace_snapshots(IsUserspaceSnapshotsEnabled());
-            if (IsUserspaceSnapshotsEnabled()) {
+            status.set_userspace_snapshots(userSnapshotsEnabled);
+            if (userSnapshotsEnabled) {
                 is_snapshot_userspace_ = true;
                 status.set_io_uring_enabled(IsIouringEnabled());
-                LOG(INFO) << "User-space snapshots enabled";
+                LOG(INFO) << "Userspace snapshots enabled";
             } else {
                 is_snapshot_userspace_ = false;
-                LOG(INFO) << "User-space snapshots disabled";
+                LOG(INFO) << "Userspace snapshots disabled";
             }
 
             // Terminate stale daemon if any
diff --git a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
index b86a802..484a9c4 100644
--- a/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/dm-snapshot-merge/cow_snapuserd_test.cpp
@@ -932,7 +932,6 @@
 
     ASSERT_EQ(area_sz, 2);
 
-    size_t new_chunk = 263;
     // Verify the partially filled area
     void* buffer = snapuserd_->GetExceptionBuffer(1);
     loff_t offset = 0;
@@ -941,7 +940,6 @@
         de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
         ASSERT_EQ(de->old_chunk, i);
         offset += sizeof(struct disk_exception);
-        new_chunk += 1;
     }
 
     de = reinterpret_cast<struct disk_exception*>((char*)buffer + offset);
diff --git a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
index c31772b..2f7775c 100644
--- a/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
+++ b/fs_mgr/libsnapshot/snapuserd/snapuserd_daemon.cpp
@@ -34,6 +34,17 @@
 namespace snapshot {
 
 bool Daemon::IsUserspaceSnapshotsEnabled() {
+    const std::string UNKNOWN = "unknown";
+    const std::string vendor_release =
+            android::base::GetProperty("ro.vendor.build.version.release_or_codename", UNKNOWN);
+
+    // No user-space snapshots if vendor partition is on Android 12
+    if (vendor_release.find("12") != std::string::npos) {
+        LOG(INFO) << "Userspace snapshots disabled as vendor partition is on Android: "
+                  << vendor_release;
+        return false;
+    }
+
     return android::base::GetBoolProperty("ro.virtual_ab.userspace.snapshots.enabled", false);
 }
 
diff --git a/init/OWNERS b/init/OWNERS
index 9e70e7d..4604d06 100644
--- a/init/OWNERS
+++ b/init/OWNERS
@@ -1 +1,2 @@
 dvander@google.com
+jiyong@google.com
diff --git a/init/bootchart.cpp b/init/bootchart.cpp
index b7db9b6..f46fb09 100644
--- a/init/bootchart.cpp
+++ b/init/bootchart.cpp
@@ -140,6 +140,20 @@
 static void bootchart_thread_main() {
   LOG(INFO) << "Bootcharting started";
 
+  // Unshare the mount namespace of this thread so that the init process itself can switch
+  // the mount namespace later while this thread is still running.
+  // Otherwise, setns() call invoked as part of `enter_default_mount_ns` fails with EINVAL.
+  //
+  // Note that after unshare()'ing the mount namespace from the main thread, this thread won't
+  // receive mount/unmount events from the other mount namespace unless the events are happening
+  // from under a sharable mount.
+  //
+  // The bootchart thread is safe to unshare the mount namespace because it only reads from /proc
+  // and write to /data which are not private mounts.
+  if (unshare(CLONE_NEWNS) == -1) {
+      PLOG(ERROR) << "Cannot create mount namespace";
+      return;
+  }
   // Open log files.
   auto stat_log = fopen_unique("/data/bootchart/proc_stat.log", "we");
   if (!stat_log) return;
diff --git a/init/compare-bootcharts.py b/init/compare-bootcharts.py
index 2057b55..009b639 100755
--- a/init/compare-bootcharts.py
+++ b/init/compare-bootcharts.py
@@ -56,24 +56,24 @@
     ]
 
     jw = jiffy_record['jiffy_to_wallclock']
-    print "process: baseline experiment (delta)"
-    print " - Unit is ms (a jiffy is %d ms on the system)" % jw
-    print "------------------------------------"
+    print("process: baseline experiment (delta)")
+    print(" - Unit is ms (a jiffy is %d ms on the system)" % jw)
+    print("------------------------------------")
     for p in processes_of_interest:
         # e.g., 32-bit system doesn't have zygote64
         if p in process_map1 and p in process_map2:
-            print "%s: %d %d (%+d)" % (
+            print("%s: %d %d (%+d)" % (
                 p, process_map1[p]['start_time'] * jw,
                 process_map2[p]['start_time'] * jw,
                 (process_map2[p]['start_time'] -
-                 process_map1[p]['start_time']) * jw)
+                 process_map1[p]['start_time']) * jw))
 
     # Print the last tick for the bootanimation process
-    print "bootanimation ends at: %d %d (%+d)" % (
+    print("bootanimation ends at: %d %d (%+d)" % (
         process_map1['/system/bin/bootanimation']['last_tick'] * jw,
         process_map2['/system/bin/bootanimation']['last_tick'] * jw,
         (process_map2['/system/bin/bootanimation']['last_tick'] -
-            process_map1['/system/bin/bootanimation']['last_tick']) * jw)
+            process_map1['/system/bin/bootanimation']['last_tick']) * jw))
 
 def parse_proc_file(pathname, process_map, jiffy_record=None):
     # Uncompress bootchart.tgz
@@ -83,7 +83,7 @@
             f = tf.extractfile('proc_ps.log')
 
             # Break proc_ps into chunks based on timestamps
-            blocks = f.read().split('\n\n')
+            blocks = f.read().decode('utf-8').split('\n\n')
             for b in blocks:
                 lines = b.split('\n')
                 if not lines[0]:
@@ -133,7 +133,7 @@
 
 def main():
     if len(sys.argv) != 3:
-        print "Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0]
+        print("Usage: %s base_bootchart_dir exp_bootchart_dir" % sys.argv[0])
         sys.exit(1)
 
     process_map1 = {}
diff --git a/init/epoll.cpp b/init/epoll.cpp
index 0580f86..74d8aac 100644
--- a/init/epoll.cpp
+++ b/init/epoll.cpp
@@ -23,8 +23,6 @@
 #include <functional>
 #include <map>
 
-#include <android-base/logging.h>
-
 namespace android {
 namespace init {
 
@@ -44,11 +42,8 @@
     if (!events) {
         return Error() << "Must specify events";
     }
-
-    Info info;
-    info.events = events;
-    info.handler = std::make_shared<decltype(handler)>(std::move(handler));
-    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(info));
+    auto sp = std::make_shared<decltype(handler)>(std::move(handler));
+    auto [it, inserted] = epoll_handlers_.emplace(fd, std::move(sp));
     if (!inserted) {
         return Error() << "Cannot specify two epoll handlers for a given FD";
     }
@@ -89,14 +84,8 @@
     }
     std::vector<std::shared_ptr<Handler>> pending_functions;
     for (int i = 0; i < num_events; ++i) {
-        auto& info = *reinterpret_cast<Info*>(ev[i].data.ptr);
-        if ((info.events & (EPOLLIN | EPOLLPRI)) == (EPOLLIN | EPOLLPRI) &&
-            (ev[i].events & EPOLLIN) != ev[i].events) {
-            // This handler wants to know about exception events, and just got one.
-            // Log something informational.
-            LOG(ERROR) << "Received unexpected epoll event set: " << ev[i].events;
-        }
-        pending_functions.emplace_back(info.handler);
+        auto sp = *reinterpret_cast<std::shared_ptr<Handler>*>(ev[i].data.ptr);
+        pending_functions.emplace_back(std::move(sp));
     }
 
     return pending_functions;
diff --git a/init/epoll.h b/init/epoll.h
index f58ae8d..0df5289 100644
--- a/init/epoll.h
+++ b/init/epoll.h
@@ -46,13 +46,8 @@
             std::optional<std::chrono::milliseconds> timeout);
 
   private:
-    struct Info {
-        std::shared_ptr<Handler> handler;
-        uint32_t events;
-    };
-
     android::base::unique_fd epoll_fd_;
-    std::map<int, Info> epoll_handlers_;
+    std::map<int, std::shared_ptr<Handler>> epoll_handlers_;
 };
 
 }  // namespace init
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 13ee37d..d050ed7 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -113,10 +113,10 @@
         LOG(INFO) << "hard linking " << src << " to " << dst << " succeeded";
         return;
     }
-    PLOG(FATAL) << "hard linking " << src << " to " << dst << " failed, falling back to copy.";
+    PLOG(FATAL) << "hard linking " << src << " to " << dst << " failed";
 }
 
-// Move e2fsck before switching root, so that it is available at the same path
+// Move snapuserd before switching root, so that it is available at the same path
 // after switching root.
 void PrepareSwitchRoot() {
     constexpr const char* src = "/system/bin/snapuserd";
diff --git a/init/init.cpp b/init/init.cpp
index 5a0b3a6..038f172 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -661,8 +661,7 @@
         PLOG(FATAL) << "failed to create signalfd";
     }
 
-    constexpr int flags = EPOLLIN | EPOLLPRI;
-    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd, flags); !result.ok()) {
+    if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd); !result.ok()) {
         LOG(FATAL) << result.error();
     }
 }
@@ -796,6 +795,10 @@
         InstallRebootSignalHandlers();
     }
 
+    // No threads should be spin up until signalfd
+    // is registered. If the threads are indeed required,
+    // each of these threads _should_ make sure SIGCHLD signal
+    // is blocked. See b/223076262
     boot_clock::time_point start_time = boot_clock::now();
 
     trigger_shutdown = [](const std::string& command) { shutdown_state.TriggerShutdown(command); };
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 6aa9912..41cf748 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -18,6 +18,7 @@
 
 #include <dirent.h>
 #include <fcntl.h>
+#include <linux/f2fs.h>
 #include <linux/fs.h>
 #include <linux/loop.h>
 #include <mntent.h>
@@ -218,7 +219,7 @@
                  << stat;
 }
 
-static bool IsDataMounted() {
+static bool IsDataMounted(const std::string& fstype) {
     std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(setmntent("/proc/mounts", "re"), endmntent);
     if (fp == nullptr) {
         PLOG(ERROR) << "Failed to open /proc/mounts";
@@ -227,7 +228,7 @@
     mntent* mentry;
     while ((mentry = getmntent(fp.get())) != nullptr) {
         if (mentry->mnt_dir == "/data"s) {
-            return true;
+            return fstype == "*" || mentry->mnt_type == fstype;
         }
     }
     return false;
@@ -633,7 +634,7 @@
 
     // If /data isn't mounted then we can skip the extra reboot steps below, since we don't need to
     // worry about unmounting it.
-    if (!IsDataMounted()) {
+    if (!IsDataMounted("*")) {
         sync();
         RebootSystem(cmd, reboot_target);
         abort();
@@ -758,6 +759,16 @@
     sem_post(&reboot_semaphore);
 
     // Reboot regardless of umount status. If umount fails, fsck after reboot will fix it.
+    if (IsDataMounted("f2fs")) {
+        uint32_t flag = F2FS_GOING_DOWN_FULLSYNC;
+        unique_fd fd(TEMP_FAILURE_RETRY(open("/data", O_RDONLY)));
+        int ret = ioctl(fd, F2FS_IOC_SHUTDOWN, &flag);
+        if (ret) {
+            PLOG(ERROR) << "Shutdown /data: ";
+        } else {
+            LOG(INFO) << "Shutdown /data";
+        }
+    }
     RebootSystem(cmd, reboot_target);
     abort();
 }
diff --git a/init/service_parser.cpp b/init/service_parser.cpp
index 35bd415..9e914ee 100644
--- a/init/service_parser.cpp
+++ b/init/service_parser.cpp
@@ -27,6 +27,7 @@
 #include <android-base/parseint.h>
 #include <android-base/strings.h>
 #include <hidl-util/FQName.h>
+#include <processgroup/processgroup.h>
 #include <system/thread_defs.h>
 
 #include "lmkd_service.h"
@@ -395,7 +396,15 @@
 
 Result<void> ServiceParser::ParseTaskProfiles(std::vector<std::string>&& args) {
     args.erase(args.begin());
-    service_->task_profiles_ = std::move(args);
+    if (service_->task_profiles_.empty()) {
+        service_->task_profiles_ = std::move(args);
+    } else {
+        // Some task profiles might have been added during writepid conversions
+        service_->task_profiles_.insert(service_->task_profiles_.end(),
+                                        std::make_move_iterator(args.begin()),
+                                        std::make_move_iterator(args.end()));
+        args.clear();
+    }
     return {};
 }
 
@@ -521,8 +530,37 @@
     return {};
 }
 
+// Convert legacy paths used to migrate processes between cgroups using writepid command.
+// We can't get these paths from TaskProfiles because profile definitions are changing
+// when we migrate to cgroups v2 while these hardcoded paths stay the same.
+static std::optional<const std::string> ConvertTaskFileToProfile(const std::string& file) {
+    static const std::map<const std::string, const std::string> map = {
+            {"/dev/stune/top-app/tasks", "MaxPerformance"},
+            {"/dev/stune/foreground/tasks", "HighPerformance"},
+            {"/dev/cpuset/camera-daemon/tasks", "CameraServiceCapacity"},
+            {"/dev/cpuset/foreground/tasks", "ProcessCapacityHigh"},
+            {"/dev/cpuset/system-background/tasks", "ServiceCapacityLow"},
+            {"/dev/stune/nnapi-hal/tasks", "NNApiHALPerformance"},
+            {"/dev/blkio/background/tasks", "LowIoPriority"},
+    };
+    auto iter = map.find(file);
+    return iter == map.end() ? std::nullopt : std::make_optional<const std::string>(iter->second);
+}
+
 Result<void> ServiceParser::ParseWritepid(std::vector<std::string>&& args) {
     args.erase(args.begin());
+    // Convert any cgroup writes into appropriate task_profiles
+    for (auto iter = args.begin(); iter != args.end();) {
+        auto task_profile = ConvertTaskFileToProfile(*iter);
+        if (task_profile) {
+            LOG(WARNING) << "'writepid " << *iter << "' is converted into 'task_profiles "
+                         << task_profile.value() << "' for service " << service_->name();
+            service_->task_profiles_.push_back(task_profile.value());
+            iter = args.erase(iter);
+        } else {
+            ++iter;
+        }
+    }
     service_->writepid_files_ = std::move(args);
     return {};
 }
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 263cb73..eed5c65 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -18,6 +18,7 @@
 
 #include <fcntl.h>
 #include <grp.h>
+#include <map>
 #include <sys/mount.h>
 #include <sys/prctl.h>
 #include <sys/wait.h>
@@ -305,6 +306,16 @@
     } else {
         LOG(ERROR) << "cpuset cgroup controller is not mounted!";
     }
+
+    // Issue a warning whenever writepid is being used with a cgroup. This can't be done during
+    // command parsing because cgroups might not be configured at the time or parsing.
+    for (const auto& file : *files) {
+        if (CgroupGetControllerFromPath(file, nullptr)) {
+            LOG(WARNING) << "writepid usage with cgroups path '" << file
+                         << "' is obsolete, please use task_profiles!";
+        }
+    }
+
     std::string pid_str = std::to_string(getpid());
     for (const auto& file : *files) {
         if (!WriteStringToFile(pid_str, file)) {
diff --git a/init/sigchld_handler.cpp b/init/sigchld_handler.cpp
index 6fc64df..9b2c7d9 100644
--- a/init/sigchld_handler.cpp
+++ b/init/sigchld_handler.cpp
@@ -95,10 +95,7 @@
         LOG(INFO) << name << " received signal " << siginfo.si_status << wait_string;
     }
 
-    if (!service) {
-        LOG(INFO) << name << " did not have an associated service entry and will not be reaped";
-        return pid;
-    }
+    if (!service) return pid;
 
     service->Reap(siginfo);
 
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index c6bf708..586e2cf 100644
--- a/init/ueventd.cpp
+++ b/init/ueventd.cpp
@@ -298,21 +298,31 @@
 
 static UeventdConfiguration GetConfiguration() {
     auto hardware = android::base::GetProperty("ro.hardware", "");
-    std::vector<std::string> legacy_paths{"/vendor/ueventd.rc", "/odm/ueventd.rc",
-                                          "/ueventd." + hardware + ".rc"};
+
+    struct LegacyPathInfo {
+        std::string legacy_path;
+        std::string preferred;
+    };
+    std::vector<LegacyPathInfo> legacy_paths{
+            {"/vendor/ueventd.rc", "/vendor/etc/ueventd.rc"},
+            {"/odm/ueventd.rc", "/odm/etc/ueventd.rc"},
+            {"/ueventd." + hardware + ".rc", "another ueventd.rc file"}};
 
     std::vector<std::string> canonical{"/system/etc/ueventd.rc"};
 
     if (android::base::GetIntProperty("ro.product.first_api_level", 10000) < __ANDROID_API_T__) {
         // TODO: Remove these legacy paths once Android S is no longer supported.
-        canonical.insert(canonical.end(), legacy_paths.begin(), legacy_paths.end());
+        for (const auto& info : legacy_paths) {
+            canonical.push_back(info.legacy_path);
+        }
     } else {
         // Warn if newer device is using legacy paths.
-        for (const auto& path : legacy_paths) {
-            if (access(path.c_str(), F_OK) == 0) {
+        for (const auto& info : legacy_paths) {
+            if (access(info.legacy_path.c_str(), F_OK) == 0) {
                 LOG(FATAL_WITHOUT_ABORT)
                         << "Legacy ueventd configuration file detected and will not be parsed: "
-                        << path;
+                        << info.legacy_path << ". Please move your configuration to "
+                        << info.preferred << " instead.";
             }
         }
     }
diff --git a/libprocessgroup/processgroup.cpp b/libprocessgroup/processgroup.cpp
index 54772b6..e3a80e9 100644
--- a/libprocessgroup/processgroup.cpp
+++ b/libprocessgroup/processgroup.cpp
@@ -159,6 +159,21 @@
     return TaskProfiles::GetInstance().SetTaskProfiles(tid, profiles, use_fd_cache);
 }
 
+// C wrapper for SetProcessProfiles.
+// No need to have this in the header file because this function is specifically for crosvm. Crosvm
+// which is written in Rust has its own declaration of this foreign function and doesn't rely on the
+// header. See
+// https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/3574427/5/src/linux/android.rs#12
+extern "C" bool android_set_process_profiles(uid_t uid, pid_t pid, size_t num_profiles,
+                                             const char* profiles[]) {
+    std::vector<std::string> profiles_;
+    profiles_.reserve(num_profiles);
+    for (size_t i = 0; i < num_profiles; i++) {
+        profiles_.emplace_back(profiles[i]);
+    }
+    return SetProcessProfiles(uid, pid, profiles_);
+}
+
 static std::string ConvertUidToPath(const char* cgroup, uid_t uid) {
     return StringPrintf("%s/uid_%d", cgroup, uid);
 }
diff --git a/libprocessgroup/profiles/task_profiles.json b/libprocessgroup/profiles/task_profiles.json
index 7e03964..f5533c2 100644
--- a/libprocessgroup/profiles/task_profiles.json
+++ b/libprocessgroup/profiles/task_profiles.json
@@ -224,6 +224,19 @@
       ]
     },
     {
+      "Name": "VMCompilationPerformance",
+      "Actions": [
+        {
+          "Name": "JoinCgroup",
+          "Params":
+          {
+            "Controller": "cpu",
+            "Path": "system"
+          }
+        }
+      ]
+    },
+    {
       "Name": "CpuPolicySpread",
       "Actions": [
         {
@@ -435,7 +448,6 @@
         }
       ]
     },
-
     {
       "Name": "LowIoPriority",
       "Actions": [
diff --git a/libprocessgroup/task_profiles.cpp b/libprocessgroup/task_profiles.cpp
index 27060ae..e1c5934 100644
--- a/libprocessgroup/task_profiles.cpp
+++ b/libprocessgroup/task_profiles.cpp
@@ -806,6 +806,7 @@
 
 bool TaskProfiles::SetProcessProfiles(uid_t uid, pid_t pid,
                                       const std::vector<std::string>& profiles, bool use_fd_cache) {
+    bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
         if (profile != nullptr) {
@@ -814,16 +815,19 @@
             }
             if (!profile->ExecuteForProcess(uid, pid)) {
                 PLOG(WARNING) << "Failed to apply " << name << " process profile";
+                success = false;
             }
         } else {
-            PLOG(WARNING) << "Failed to find " << name << "process profile";
+            PLOG(WARNING) << "Failed to find " << name << " process profile";
+            success = false;
         }
     }
-    return true;
+    return success;
 }
 
 bool TaskProfiles::SetTaskProfiles(int tid, const std::vector<std::string>& profiles,
                                    bool use_fd_cache) {
+    bool success = true;
     for (const auto& name : profiles) {
         TaskProfile* profile = GetProfile(name);
         if (profile != nullptr) {
@@ -832,10 +836,12 @@
             }
             if (!profile->ExecuteForTask(tid)) {
                 PLOG(WARNING) << "Failed to apply " << name << " task profile";
+                success = false;
             }
         } else {
-            PLOG(WARNING) << "Failed to find " << name << "task profile";
+            PLOG(WARNING) << "Failed to find " << name << " task profile";
+            success = false;
         }
     }
-    return true;
+    return success;
 }
diff --git a/libstats/pull_rust/stats_pull.rs b/libstats/pull_rust/stats_pull.rs
index 174125e..09b2623 100644
--- a/libstats/pull_rust/stats_pull.rs
+++ b/libstats/pull_rust/stats_pull.rs
@@ -68,7 +68,7 @@
     }
 
     /// Calls AStatsManager_PullAtomMetadata_setAdditiveFields.
-    pub fn set_additive_fields(&mut self, additive_fields: &mut Vec<i32>) {
+    pub fn set_additive_fields(&mut self, additive_fields: &mut [i32]) {
         // Safety: Metadata::new ensures that self.metadata is a valid object.
         unsafe {
             AStatsManager_PullAtomMetadata_setAdditiveFields(
diff --git a/libutils/Android.bp b/libutils/Android.bp
index a9bd7d9..1b29285 100644
--- a/libutils/Android.bp
+++ b/libutils/Android.bp
@@ -188,6 +188,7 @@
     defaults: ["libutils_defaults"],
     // TODO(b/153609531): remove when no longer needed.
     native_bridge_supported: true,
+    min_sdk_version: "29",
 
     srcs: [
         "CallStack.cpp",
diff --git a/libutils/VectorImpl.cpp b/libutils/VectorImpl.cpp
index c97a19b..d951b8b 100644
--- a/libutils/VectorImpl.cpp
+++ b/libutils/VectorImpl.cpp
@@ -279,14 +279,12 @@
 
 ssize_t VectorImpl::removeItemsAt(size_t index, size_t count)
 {
-    ALOG_ASSERT((index+count)<=size(),
-        "[%p] remove: index=%d, count=%d, size=%d",
-               this, (int)index, (int)count, (int)size());
-
-    if ((index+count) > size())
-        return BAD_VALUE;
-   _shrink(index, count);
-   return index;
+    size_t end;
+    LOG_ALWAYS_FATAL_IF(__builtin_add_overflow(index, count, &end), "overflow: index=%zu count=%zu",
+                        index, count);
+    if (end > size()) return BAD_VALUE;
+    _shrink(index, count);
+    return index;
 }
 
 void VectorImpl::finish_vector()
diff --git a/libutils/Vector_test.cpp b/libutils/Vector_test.cpp
index 5336c40..6d90eaa 100644
--- a/libutils/Vector_test.cpp
+++ b/libutils/Vector_test.cpp
@@ -136,4 +136,13 @@
   }
 }
 
+TEST_F(VectorTest, removeItemsAt_overflow) {
+    android::Vector<int> v;
+    for (int i = 0; i < 666; i++) v.add(i);
+
+    ASSERT_DEATH(v.removeItemsAt(SIZE_MAX, 666), "overflow");
+    ASSERT_DEATH(v.removeItemsAt(666, SIZE_MAX), "overflow");
+    ASSERT_DEATH(v.removeItemsAt(SIZE_MAX, SIZE_MAX), "overflow");
+}
+
 } // namespace android
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 7ad1c3c..aae28dc 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -824,9 +824,15 @@
     # directory used for on-device refresh metrics file.
     mkdir /data/misc/odrefresh 0777 system system
     # directory used for on-device signing key blob
-    mkdir /data/misc/odsign 0700 root root
-    # Directory for VirtualizationService temporary image files.
-    mkdir /data/misc/virtualizationservice 0700 virtualizationservice virtualizationservice
+    mkdir /data/misc/odsign 0710 root system
+    # directory used for odsign metrics
+    mkdir /data/misc/odsign/metrics 0770 root system
+
+    # Directory for VirtualizationService temporary image files. Always create
+    # a fresh new empty directory to remove any stale files from the previous
+    # boot.
+    rmdir /data/misc/virtualizationservice
+    mkdir /data/misc/virtualizationservice 0700 system system
 
     mkdir /data/preloads 0775 system system encryption=None
 
@@ -1303,3 +1309,15 @@
 
 on property:sys.boot_completed=1 && property:sys.init.userspace_reboot.in_progress=1
   setprop sys.init.userspace_reboot.in_progress ""
+
+# Multi-Gen LRU Experiment
+on property:persist.device_config.mglru_native.lru_gen_config=none
+  write /sys/kernel/mm/lru_gen/enabled 0
+on property:persist.device_config.mglru_native.lru_gen_config=core
+  write /sys/kernel/mm/lru_gen/enabled 1
+on property:persist.device_config.mglru_native.lru_gen_config=core_and_mm_walk
+  write /sys/kernel/mm/lru_gen/enabled 3
+on property:persist.device_config.mglru_native.lru_gen_config=core_and_nonleaf_young
+  write /sys/kernel/mm/lru_gen/enabled 5
+on property:persist.device_config.mglru_native.lru_gen_config=all
+  write /sys/kernel/mm/lru_gen/enabled 7
diff --git a/rootdir/ueventd.rc b/rootdir/ueventd.rc
index 3101974..a140c8c 100644
--- a/rootdir/ueventd.rc
+++ b/rootdir/ueventd.rc
@@ -67,9 +67,8 @@
 # CDMA radio interface MUX
 /dev/ppp                  0660   radio      vpn
 
-# Virtualization is managed by VirtualizationService.
-/dev/kvm                  0600   virtualizationservice root
-/dev/vhost-vsock          0600   virtualizationservice root
+/dev/kvm                  0600   system     system
+/dev/vhost-vsock          0600   system	    system
 
 # sysfs properties
 /sys/devices/platform/trusty.*      trusty_version        0440  root   log
diff --git a/rustfmt.toml b/rustfmt.toml
new file mode 120000
index 0000000..ee92d9e
--- /dev/null
+++ b/rustfmt.toml
@@ -0,0 +1 @@
+../../build/soong/scripts/rustfmt.toml
\ No newline at end of file
diff --git a/trusty/apploader/apploader.cpp b/trusty/apploader/apploader.cpp
index c72af40..278499f 100644
--- a/trusty/apploader/apploader.cpp
+++ b/trusty/apploader/apploader.cpp
@@ -223,6 +223,9 @@
         case APPLOADER_ERR_INVALID_VERSION:
             LOG(ERROR) << "Error: invalid application version";
             break;
+        case APPLOADER_ERR_POLICY_VIOLATION:
+            LOG(ERROR) << "Error: loading denied by policy engine";
+            break;
         default:
             LOG(ERROR) << "Unrecognized error: " << resp.error;
             break;
diff --git a/trusty/apploader/apploader_ipc.h b/trusty/apploader/apploader_ipc.h
index 6cda7c1..306596e 100644
--- a/trusty/apploader/apploader_ipc.h
+++ b/trusty/apploader/apploader_ipc.h
@@ -56,6 +56,7 @@
     APPLOADER_ERR_ALREADY_EXISTS,
     APPLOADER_ERR_INTERNAL,
     APPLOADER_ERR_INVALID_VERSION,
+    APPLOADER_ERR_POLICY_VIOLATION,
 };
 
 /**
diff --git a/trusty/apploader/fuzz/Android.bp b/trusty/apploader/fuzz/Android.bp
index e37dab1..c961b36 100644
--- a/trusty/apploader/fuzz/Android.bp
+++ b/trusty/apploader/fuzz/Android.bp
@@ -25,7 +25,10 @@
         "-DTRUSTY_APP_PORT=\"com.android.trusty.apploader\"",
         "-DTRUSTY_APP_UUID=\"081ba88f-f1ee-452e-b5e8-a7e9ef173a97\"",
         "-DTRUSTY_APP_FILENAME=\"apploader.syms.elf\"",
-    ]
+    ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 }
 
 // Fuzz app package sent to apploader.
@@ -37,4 +40,7 @@
     shared_libs: [
         "libdmabufheap",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 }
diff --git a/trusty/confirmationui/fuzz/Android.bp b/trusty/confirmationui/fuzz/Android.bp
index ba57191..4780943 100644
--- a/trusty/confirmationui/fuzz/Android.bp
+++ b/trusty/confirmationui/fuzz/Android.bp
@@ -25,6 +25,9 @@
         "-DTRUSTY_APP_UUID=\"7dee2364-c036-425b-b086-df0f6c233c1b\"",
         "-DTRUSTY_APP_FILENAME=\"confirmationui.syms.elf\"",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
 }
 
@@ -36,6 +39,9 @@
     shared_libs: [
         "libdmabufheap",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
     // The initial corpus for this fuzzer was derived by dumping messages from/to
     // HAL to/from TA triggered by VtsHalConfirmationUIV1_0TargetTest.
diff --git a/trusty/gatekeeper/fuzz/Android.bp b/trusty/gatekeeper/fuzz/Android.bp
index d084cb6..67d0c0f 100644
--- a/trusty/gatekeeper/fuzz/Android.bp
+++ b/trusty/gatekeeper/fuzz/Android.bp
@@ -25,6 +25,9 @@
         "-DTRUSTY_APP_UUID=\"38ba0cdc-df0e-11e4-9869-233fb6ae4795\"",
         "-DTRUSTY_APP_FILENAME=\"gatekeeper.syms.elf\"",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
     // The initial corpus for this fuzzer was derived by dumping messages from
     // the `secure_env` emulator interface for cuttlefish while enrolling a new
diff --git a/trusty/keymaster/fuzz/Android.bp b/trusty/keymaster/fuzz/Android.bp
index 8d7ee00..5f24bc6 100644
--- a/trusty/keymaster/fuzz/Android.bp
+++ b/trusty/keymaster/fuzz/Android.bp
@@ -25,6 +25,9 @@
         "-DTRUSTY_APP_UUID=\"5f902ace-5e5c-4cd8-ae54-87b88c22ddaf\"",
         "-DTRUSTY_APP_FILENAME=\"keymaster.syms.elf\"",
     ],
+    fuzz_config: {
+       cc: ["trong@google.com"],
+    },
 
     // The initial corpus for this fuzzer was derived by dumping messages from
     // the `secure_env` emulator interface for cuttlefish while running the
diff --git a/trusty/libtrusty-rs/Android.bp b/trusty/libtrusty-rs/Android.bp
new file mode 100644
index 0000000..bc1dcf6
--- /dev/null
+++ b/trusty/libtrusty-rs/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libtrusty-rs",
+    crate_name: "trusty",
+    srcs: [
+        "src/lib.rs"
+    ],
+    rustlibs: [
+        "libnix",
+        "liblibc",
+    ],
+}
+
+rust_test {
+    name: "libtrusty-rs-tests",
+    crate_name: "trusty_test",
+    srcs: ["tests/test.rs"],
+    rustlibs: [
+        "libtrusty-rs",
+        "liblibc",
+    ]
+}
diff --git a/trusty/libtrusty-rs/src/lib.rs b/trusty/libtrusty-rs/src/lib.rs
new file mode 100644
index 0000000..28ea075
--- /dev/null
+++ b/trusty/libtrusty-rs/src/lib.rs
@@ -0,0 +1,224 @@
+// Copyright (C) 2022 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.
+
+//! Functionality for communicating with Trusty services.
+//!
+//! This crate provides the [`TipcChannel`] type, which allows you to establish a
+//! connection to a Trusty service and then communicate with that service.
+//!
+//! # Usage
+//!
+//! To connect to a Trusty service you need two things:
+//!
+//! * The filesystem path to the Trusty IPC device. This is usually
+//!   `/dev/trusty-ipc-dev0`, which is exposed in the constant [`DEFAULT_DEVICE`].
+//! * The port name defined by the service, e.g. `com.android.ipc-unittest.srv.echo`.
+//!
+//! Pass these values to [`TipcChannel::connect`] to establish a connection to a
+//! service.
+//!
+//! Once connected use the [`send`][TipcChannel::send] and [`recv`][TipcChannel::recv]
+//! methods to communicate with the service. Messages are passed as byte buffers, and
+//! each Trusty service has its own protocol for what data messages are expected to
+//! contain. Consult the documentation for the service you are communicating with to
+//! determine how to format outgoing messages and interpret incoming ones.
+//!
+//! The connection is closed automatically when [`TipcChannel`] is dropped.
+//!
+//! # Examples
+//!
+//! This example is a simplified version of the echo test from `tipc-test-rs`:
+//!
+//! ```no_run
+//! use trusty::{DEFAULT_DEVICE, TipcChannel};
+//! use std::io::{Read, Write};
+//!
+//! let mut chann = TipcChannel::connect(
+//!     DEFAULT_DEVICE,
+//!     "com.android.ipc-unittest.srv.echo",
+//! ).unwrap();
+//!
+//! chann.send("Hello, world!".as_bytes()).unwrap();
+//!
+//! let mut read_buf = Vec::new();
+//! let read_len = stream.recv(&mut read_buf).unwrap();
+//!
+//! let response = std::str::from_utf8(&read_buf[..read_len]).unwrap();
+//! assert_eq!("Hello, world!", response);
+//!
+//! // The connection is closed here.
+//! ```
+
+use crate::sys::tipc_connect;
+use std::ffi::CString;
+use std::fs::File;
+use std::io::prelude::*;
+use std::io::{ErrorKind, Result};
+use std::os::unix::prelude::AsRawFd;
+use std::path::Path;
+
+mod sys;
+
+/// The default filesystem path for the Trusty IPC device.
+pub const DEFAULT_DEVICE: &str = "/dev/trusty-ipc-dev0";
+
+/// The maximum size an incoming TIPC message can be.
+///
+/// This can be used to pre-allocate buffer space in order to ensure that your
+/// read buffer can always hold an incoming message.
+pub const MAX_MESSAGE_SIZE: usize = 4096;
+
+/// A channel for communicating with a Trusty service.
+///
+/// See the [crate-level documentation][crate] for usage details and examples.
+#[derive(Debug)]
+pub struct TipcChannel(File);
+
+impl TipcChannel {
+    /// Attempts to establish a connection to the specified Trusty service.
+    ///
+    /// The first argument is the path of the Trusty device in the local filesystem,
+    /// e.g. `/dev/trusty-ipc-dev0`. The second argument is the name of the service
+    /// to connect to, e.g. `com.android.ipc-unittest.srv.echo`.
+    ///
+    /// # Panics
+    ///
+    /// This function will panic if `service` contains any intermediate `NUL`
+    /// bytes. This is handled with a panic because the service names are all
+    /// hard-coded constants, and so such an error should always be indicative of a
+    /// bug in the calling code.
+    pub fn connect(device: impl AsRef<Path>, service: &str) -> Result<Self> {
+        let file = File::options().read(true).write(true).open(device)?;
+
+        let srv_name = CString::new(service).expect("Service name contained null bytes");
+        unsafe {
+            tipc_connect(file.as_raw_fd(), srv_name.as_ptr())?;
+        }
+
+        Ok(TipcChannel(file))
+    }
+
+    /// Sends a message to the connected service.
+    ///
+    /// The entire contents of `buf` will be sent as a single message to the
+    /// connected service.
+    pub fn send(&mut self, buf: &[u8]) -> Result<()> {
+        let write_len = self.0.write(buf)?;
+
+        // Verify that the expected number of bytes were written. The entire message
+        // should always be written with a single `write` call, or an error should have
+        // been returned if the message couldn't be written. An assertion failure here
+        // potentially means a bug in the kernel driver.
+        assert_eq!(
+            buf.len(),
+            write_len,
+            "Failed to send full message ({} of {} bytes written)",
+            write_len,
+            buf.len(),
+        );
+
+        Ok(())
+    }
+
+    /// Reads the next incoming message.
+    ///
+    /// Attempts to read the next incoming message from the connected service if any
+    /// exist. If the initial capacity of `buf` is not enough to hold the incoming
+    /// message the function repeatedly attempts to reserve additional space until
+    /// it is able to fully read the message.
+    ///
+    /// Blocks until there is an incoming message if there is not already a message
+    /// ready to be received.
+    ///
+    /// # Errors
+    ///
+    /// If this function encounters an error of the kind [`ErrorKind::Interrupted`]
+    /// then the error is ignored and the operation will be tried again.
+    ///
+    /// If this function encounters an error with the error code `EMSGSIZE` then
+    /// additional space will be reserved in `buf` and the operation will be tried
+    /// again.
+    ///
+    /// If any other read error is encountered then this function immediately
+    /// returns the error to the caller, and the length of `buf` is set to 0.
+    pub fn recv(&mut self, buf: &mut Vec<u8>) -> Result<()> {
+        // If no space has been allocated in the buffer reserve enough space to hold any
+        // incoming message.
+        if buf.capacity() == 0 {
+            buf.reserve(MAX_MESSAGE_SIZE);
+        }
+
+        loop {
+            // Resize the vec to make its full capacity available to write into.
+            buf.resize(buf.capacity(), 0);
+
+            match self.0.read(buf.as_mut_slice()) {
+                Ok(len) => {
+                    buf.truncate(len);
+                    return Ok(());
+                }
+
+                Err(err) => {
+                    if let Some(libc::EMSGSIZE) = err.raw_os_error() {
+                        // Ensure that we didn't get `EMSGSIZE` when we already had enough capacity
+                        // to contain the maximum message size. This should never happen, but if it
+                        // does we don't want to hang by looping infinitely.
+                        assert!(
+                            buf.capacity() < MAX_MESSAGE_SIZE,
+                            "Received `EMSGSIZE` error when buffer capacity was already at maximum",
+                        );
+
+                        // If we didn't have enough space to hold the incoming message, reserve
+                        // enough space to fit the maximum message size regardless of how much
+                        // capacity the buffer already had.
+                        buf.reserve(MAX_MESSAGE_SIZE - buf.capacity());
+                    } else if err.kind() == ErrorKind::Interrupted {
+                        // If we get an interrupted error the operation can be retried as-is, i.e.
+                        // we don't need to allocate additional space.
+                        continue;
+                    } else {
+                        buf.truncate(0);
+                        return Err(err);
+                    }
+                }
+            }
+        }
+    }
+
+    /// Reads the next incoming message without allocating.
+    ///
+    /// Returns the number of bytes in the received message, or any error that
+    /// occurred when reading the message.
+    ///
+    /// Blocks until there is an incoming message if there is not already a message
+    /// ready to be received.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error with native error code `EMSGSIZE` if `buf` isn't large
+    /// enough to contain the incoming message. Use
+    /// [`raw_os_error`][std::io::Error::raw_os_error] to check the error code to
+    /// determine if you need to increase the size of `buf`. If error code
+    /// `EMSGSIZE` is returned the incoming message will not be dropped, and a
+    /// subsequent call to `recv_no_alloc` can still read it.
+    ///
+    /// An error of the [`ErrorKind::Interrupted`] kind is non-fatal and the read
+    /// operation should be retried if there is nothing else to do.
+    pub fn recv_no_alloc(&mut self, buf: &mut [u8]) -> Result<usize> {
+        self.0.read(buf)
+    }
+
+    // TODO: Add method that is equivalent to `tipc_send`, i.e. that supports
+    // sending shared memory buffers.
+}
diff --git a/trusty/libtrusty-rs/src/sys.rs b/trusty/libtrusty-rs/src/sys.rs
new file mode 100644
index 0000000..f1c8c5f
--- /dev/null
+++ b/trusty/libtrusty-rs/src/sys.rs
@@ -0,0 +1,31 @@
+// NOTE: The ioctl definitions are sequestered into this module because the
+// `ioctl_*!` macros provided by the nix crate generate public functions that we
+// don't want to be part of this crate's public API.
+//
+// NOTE: We are manually re-declaring the types and constants here instead of using
+// bindgen and a separate `-sys` crate because the defines used for the ioctl
+// numbers (`TIPC_IOC_CONNECT` and `TIPC_IOC_SEND_MSG`) can't currently be
+// translated by bindgen.
+
+use std::os::raw::c_char;
+
+const TIPC_IOC_MAGIC: u8 = b'r';
+
+// NOTE: We use `ioctl_write_ptr_bad!` here due to an error in how the ioctl
+// code is defined in `trusty/ipc.h`.
+//
+// If we were to do `ioctl_write_ptr!(TIPC_IOC_MAGIC, 0x80, c_char)` it would
+// generate a function that takes a `*const c_char` data arg and would use
+// `size_of::<c_char>()` when generating the ioctl number. However, in
+// `trusty/ipc.h` the definition for `TIPC_IOC_CONNECT` declares the ioctl with
+// `char*`, meaning we need to use `size_of::<*const c_char>()` to generate an
+// ioctl number that matches what Trusty expects.
+//
+// To maintain compatibility with the `trusty/ipc.h` and the kernel driver we
+// use `ioctl_write_ptr_bad!` and manually use `request_code_write!` to generate
+// the ioctl number using the correct size.
+nix::ioctl_write_ptr_bad!(
+    tipc_connect,
+    nix::request_code_write!(TIPC_IOC_MAGIC, 0x80, std::mem::size_of::<*const c_char>()),
+    c_char
+);
diff --git a/trusty/libtrusty-rs/tests/test.rs b/trusty/libtrusty-rs/tests/test.rs
new file mode 100644
index 0000000..6bff479
--- /dev/null
+++ b/trusty/libtrusty-rs/tests/test.rs
@@ -0,0 +1,86 @@
+use trusty::{TipcChannel, DEFAULT_DEVICE};
+
+const ECHO_NAME: &str = "com.android.ipc-unittest.srv.echo";
+
+#[test]
+fn recv_no_alloc() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a message to the echo TA.
+    let send_buf = [7u8; 32];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Receive the response message from the TA. The response message will be the
+    // same as the message we just sent.
+    let mut recv_buf = [0u8; 32];
+    let read_len = connection.recv_no_alloc(recv_buf.as_mut_slice()).unwrap();
+
+    assert_eq!(
+        send_buf.len(),
+        read_len,
+        "Received data was wrong size (expected {} bytes, received {})",
+        send_buf.len(),
+        read_len,
+    );
+    assert_eq!(send_buf, recv_buf, "Received data does not match sent data");
+}
+
+#[test]
+fn recv_small_buf() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a long message to the echo service so that we can test receiving a long
+    // message.
+    let send_buf = [7u8; 2048];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Attempt to receive the response message with a buffer that is too small to
+    // contain the message.
+    let mut recv_buf = [0u8; 32];
+    let err = connection.recv_no_alloc(recv_buf.as_mut_slice()).unwrap_err();
+
+    assert_eq!(
+        Some(libc::EMSGSIZE),
+        err.raw_os_error(),
+        "Unexpected error err when receiving incoming message: {:?}",
+        err,
+    );
+}
+
+#[test]
+fn recv_empty_vec() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a message to the echo TA.
+    let send_buf = [7u8; 2048];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Receive the response message. `recv_buf` is initially empty, and `recv` is
+    // responsible for allocating enough space to hold the message.
+    let mut recv_buf = Vec::new();
+    connection.recv(&mut recv_buf).unwrap();
+
+    assert_eq!(send_buf.as_slice(), recv_buf, "Received data does not match sent data");
+}
+
+#[test]
+fn recv_vec_existing_capacity() {
+    let mut connection = TipcChannel::connect(DEFAULT_DEVICE, ECHO_NAME)
+        .expect("Failed to connect to Trusty service");
+
+    // Send a message to the echo TA.
+    let send_buf = [7u8; 2048];
+    connection.send(send_buf.as_slice()).unwrap();
+
+    // Receive the response message into a buffer that already has enough capacity
+    // to hold the message. No additional capacity should be allocated when
+    // receiving the message.
+    let mut recv_buf = Vec::with_capacity(2048);
+    connection.recv(&mut recv_buf).unwrap();
+
+    assert_eq!(send_buf.as_slice(), recv_buf, "Received data does not match sent data");
+    assert_eq!(2048, recv_buf.capacity(), "Additional capacity was allocated when not needed");
+}