Merge "pvmfw: config: Treat v1.x as latest version known" into main
diff --git a/OWNERS b/OWNERS
index 310add7..e560cec 100644
--- a/OWNERS
+++ b/OWNERS
@@ -12,16 +12,19 @@
 # Other owners
 alanstokes@google.com
 aliceywang@google.com
-ardb@google.com
-ascull@google.com
 inseob@google.com
+jaewan@google.com
+jakobvukalovic@google.com
 jeffv@google.com
 jooyung@google.com
-mzyngier@google.com
+keirf@google.com
 ptosi@google.com
 qperret@google.com
 qwandor@google.com
-serbanc@google.com
+sebastianene@google.com
+seungjaeyoo@google.com
 shikhapanwar@google.com
+smostafa@google.com
 tabba@google.com
+vdonnefort@google.com
 victorhsieh@google.com
diff --git a/apex/Android.bp b/apex/Android.bp
index 765372a..ccbdb3b 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -103,7 +103,6 @@
         "microdroid_initrd_normal",
         "microdroid.json",
         "microdroid_kernel",
-        // rialto_bin is a prebuilt target wrapping the signed bare-metal service VM.
         "rialto_bin",
     ],
     host_required: [
diff --git a/apex/virtualizationservice.rc b/apex/virtualizationservice.rc
index be90904..8283594 100644
--- a/apex/virtualizationservice.rc
+++ b/apex/virtualizationservice.rc
@@ -22,7 +22,7 @@
 
 service vfio_handler /apex/com.android.virt/bin/vfio_handler
     user root
-    group root
+    group system
     interface aidl android.system.virtualizationservice_internal.IVfioHandler
     disabled
     oneshot
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index 908e438..76da00a 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -63,7 +63,7 @@
     /// with accompanying sigature file.
     pub fn write_info_and_signature(self, info_path: &Path) -> Result<()> {
         let mut info = OdsignInfo::new();
-        info.file_hashes.extend(self.file_digests.into_iter());
+        info.file_hashes.extend(self.file_digests);
         let bytes = info.write_to_bytes()?;
 
         let signature = compos_key::sign(&bytes)?;
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 244d34e..4851321 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -192,7 +192,7 @@
                         .runTimedCmd(
                                 10000,
                                 validator.getAbsolutePath(),
-                                "verify-dice-chain",
+                                "dice-chain",
                                 bcc_file.getAbsolutePath());
         assertWithMessage("hwtrust failed").about(command_results()).that(result).isSuccess();
     }
diff --git a/docs/getting_started.md b/docs/getting_started.md
index d970c12..74f2012 100644
--- a/docs/getting_started.md
+++ b/docs/getting_started.md
@@ -99,7 +99,7 @@
 payload using the following command:
 
 ```shell
-package/modules/Virtualization/vm/vm_shell.sh start-microdroid --auto-connect -- --protected
+packages/modules/Virtualization/vm/vm_shell.sh start-microdroid --auto-connect -- --protected
 ```
 
 You will see the log messages like the below.
diff --git a/encryptedstore/Android.bp b/encryptedstore/Android.bp
index 94ebcfc..8ba5016 100644
--- a/encryptedstore/Android.bp
+++ b/encryptedstore/Android.bp
@@ -14,6 +14,7 @@
         "libclap",
         "libhex",
         "liblog_rust",
+        "libmicrodroid_uids",
         "libnix",
         "libdm_rust",
     ],
diff --git a/encryptedstore/src/main.rs b/encryptedstore/src/main.rs
index 1a16f49..2a698ea 100644
--- a/encryptedstore/src/main.rs
+++ b/encryptedstore/src/main.rs
@@ -21,24 +21,32 @@
 use anyhow::{ensure, Context, Result};
 use clap::arg;
 use dm::{crypt::CipherType, util};
-use log::info;
+use log::{error, info};
 use std::ffi::CString;
 use std::fs::{create_dir_all, OpenOptions};
 use std::io::{Error, Read, Write};
 use std::os::unix::ffi::OsStrExt;
-use std::os::unix::fs::FileTypeExt;
+use std::os::unix::fs::{FileTypeExt, PermissionsExt};
 use std::path::{Path, PathBuf};
 use std::process::Command;
 
 const MK2FS_BIN: &str = "/system/bin/mke2fs";
 const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
 
-fn main() -> Result<()> {
+fn main() {
     android_logger::init_once(
         android_logger::Config::default()
             .with_tag("encryptedstore")
             .with_min_level(log::Level::Info),
     );
+
+    if let Err(e) = try_main() {
+        error!("{:?}", e);
+        std::process::exit(1)
+    }
+}
+
+fn try_main() -> Result<()> {
     info!("Starting encryptedstore binary");
 
     let matches = clap_command().get_matches();
@@ -47,10 +55,12 @@
     let key = matches.get_one::<String>("key").unwrap();
     let mountpoint = Path::new(matches.get_one::<String>("mountpoint").unwrap());
     // Note this error context is used in MicrodroidTests.
-    encryptedstore_init(blkdevice, key, mountpoint).context(format!(
-        "Unable to initialize encryptedstore on {:?} & mount at {:?}",
-        blkdevice, mountpoint
-    ))?;
+    encryptedstore_init(blkdevice, key, mountpoint).with_context(|| {
+        format!(
+            "Unable to initialize encryptedstore on {:?} & mount at {:?}",
+            blkdevice, mountpoint
+        )
+    })?;
     Ok(())
 }
 
@@ -65,7 +75,7 @@
 fn encryptedstore_init(blkdevice: &Path, key: &str, mountpoint: &Path) -> Result<()> {
     ensure!(
         std::fs::metadata(blkdevice)
-            .context(format!("Failed to get metadata of {:?}", blkdevice))?
+            .with_context(|| format!("Failed to get metadata of {:?}", blkdevice))?
             .file_type()
             .is_block_device(),
         "The path:{:?} is not of a block device",
@@ -82,7 +92,12 @@
         info!("Freshly formatting the crypt device");
         format_ext4(&crypt_device)?;
     }
-    mount(&crypt_device, mountpoint).context(format!("Unable to mount {:?}", crypt_device))?;
+    mount(&crypt_device, mountpoint)
+        .with_context(|| format!("Unable to mount {:?}", crypt_device))?;
+    if needs_formatting {
+        std::fs::set_permissions(mountpoint, PermissionsExt::from_mode(0o770))
+            .context("Failed to chmod root directory")?;
+    }
     Ok(())
 }
 
@@ -124,6 +139,11 @@
 }
 
 fn format_ext4(device: &Path) -> Result<()> {
+    let root_dir_uid_gid = format!(
+        "root_owner={}:{}",
+        microdroid_uids::ROOT_UID,
+        microdroid_uids::MICRODROID_PAYLOAD_GID
+    );
     let mkfs_options = [
         "-j", // Create appropriate sized journal
         /* metadata_csum: enabled for filesystem integrity
@@ -131,20 +151,22 @@
          * 64bit: larger fields afforded by this feature enable full-strength checksumming.
          */
         "-O metadata_csum, extents, 64bit",
-        "-b 4096", // block size in the filesystem
+        "-b 4096", // block size in the filesystem,
+        "-E",
+        &root_dir_uid_gid,
     ];
     let mut cmd = Command::new(MK2FS_BIN);
     let status = cmd
         .args(mkfs_options)
         .arg(device)
         .status()
-        .context(format!("failed to execute {}", MK2FS_BIN))?;
+        .with_context(|| format!("failed to execute {}", MK2FS_BIN))?;
     ensure!(status.success(), "mkfs failed with {:?}", status);
     Ok(())
 }
 
 fn mount(source: &Path, mountpoint: &Path) -> Result<()> {
-    create_dir_all(mountpoint).context(format!("Failed to create {:?}", &mountpoint))?;
+    create_dir_all(mountpoint).with_context(|| format!("Failed to create {:?}", &mountpoint))?;
     let mount_options = CString::new(
         "fscontext=u:object_r:encryptedstore_fs:s0,context=u:object_r:encryptedstore_file:s0",
     )
diff --git a/libs/libfdt/src/lib.rs b/libs/libfdt/src/lib.rs
index afc36d0..a305e03 100644
--- a/libs/libfdt/src/lib.rs
+++ b/libs/libfdt/src/lib.rs
@@ -16,8 +16,6 @@
 //! to a bare-metal environment.
 
 #![no_std]
-#![deny(unsafe_op_in_unsafe_fn)]
-#![deny(clippy::undocumented_unsafe_blocks)]
 
 mod iterators;
 
diff --git a/libs/microdroid_uids/Android.bp b/libs/microdroid_uids/Android.bp
new file mode 100644
index 0000000..497948d
--- /dev/null
+++ b/libs/microdroid_uids/Android.bp
@@ -0,0 +1,15 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libmicrodroid_uids",
+    crate_name: "microdroid_uids",
+    srcs: ["src/lib.rs"],
+    edition: "2021",
+    // TODO(b/296393106): Figure out how/when to enable this
+    // cfgs: ["payload_not_root"],
+    apex_available: [
+        "com.android.virt",
+    ],
+}
diff --git a/libs/microdroid_uids/src/lib.rs b/libs/microdroid_uids/src/lib.rs
new file mode 100644
index 0000000..1f09c65
--- /dev/null
+++ b/libs/microdroid_uids/src/lib.rs
@@ -0,0 +1,24 @@
+// Copyright 2023 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.
+
+//! User and group IDs within Microdroid
+
+/// Always the user ID of Root.
+pub const ROOT_UID: u32 = 0;
+
+/// Group ID shared by all payload users.
+pub const MICRODROID_PAYLOAD_GID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
+
+/// User ID for the initial payload user.
+pub const MICRODROID_PAYLOAD_UID: u32 = if cfg!(payload_not_root) { 6000 } else { 0 };
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 2d3f084..1e594b7 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -54,6 +54,8 @@
     deps: [
         "init_second_stage.microdroid",
         "microdroid_build_prop",
+        "microdroid_etc_passwd",
+        "microdroid_etc_group",
         "microdroid_init_debug_policy",
         "microdroid_init_rc",
         "microdroid_ueventd_rc",
@@ -156,6 +158,20 @@
     installable: false, // avoid collision with system partition's ueventd.rc
 }
 
+prebuilt_etc {
+    name: "microdroid_etc_passwd",
+    src: "microdroid_passwd",
+    filename: "passwd",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "microdroid_etc_group",
+    src: "microdroid_group",
+    filename: "group",
+    installable: false,
+}
+
 prebuilt_root {
     name: "microdroid_build_prop",
     filename: "build.prop",
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 42033d6..c257cdb 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -12,6 +12,11 @@
 
 # Cgroups are mounted right before early-init using list from /etc/cgroups.json
 on early-init
+    # Android doesn't need kernel module autoloading, and it causes SELinux
+    # denials.  So disable it by setting modprobe to the empty string.  Note: to
+    # explicitly set a sysctl to an empty string, a trailing newline is needed.
+    write /proc/sys/kernel/modprobe \n
+
     # set RLIMIT_NICE to allow priorities from 19 to -20
     setrlimit nice 40 40
 
@@ -28,6 +33,10 @@
 on init
     mkdir /mnt/apk 0755 system system
     mkdir /mnt/extra-apk 0755 root root
+
+    # Allow the payload access to the console (default is 0600)
+    chmod 0666 /dev/console
+
     # Microdroid_manager starts apkdmverity/zipfuse/apexd
     start microdroid_manager
 
diff --git a/microdroid/microdroid_group b/microdroid/microdroid_group
new file mode 100644
index 0000000..4eb8fa5
--- /dev/null
+++ b/microdroid/microdroid_group
@@ -0,0 +1 @@
+system_payload::6000:
diff --git a/microdroid/microdroid_passwd b/microdroid/microdroid_passwd
new file mode 100644
index 0000000..bd15182
--- /dev/null
+++ b/microdroid/microdroid_passwd
@@ -0,0 +1 @@
+system_payload_0::6000:6000:::
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 23f174e..fe0cf6a 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -32,6 +32,7 @@
         "liblog_rust",
         "libmicrodroid_metadata",
         "libmicrodroid_payload_config",
+        "libmicrodroid_uids",
         "libnix",
         "libonce_cell",
         "libopenssl",
diff --git a/microdroid_manager/microdroid_manager.rc b/microdroid_manager/microdroid_manager.rc
index e257547..da38564 100644
--- a/microdroid_manager/microdroid_manager.rc
+++ b/microdroid_manager/microdroid_manager.rc
@@ -8,8 +8,8 @@
     # TODO(jooyung) remove this when microdroid_manager becomes a daemon
     oneshot
     # CAP_SYS_BOOT is required to exec kexecload from microdroid_manager
-    # CAP_SETCAP is required to allow microdroid_manager to drop capabilities
+    # CAP_SETPCAP is required to allow microdroid_manager to drop capabilities
     #   before executing the payload
-    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP
+    capabilities AUDIT_CONTROL SYS_ADMIN SYS_BOOT SETPCAP SETUID SETGID
     user root
     socket vm_payload_service stream 0666 system system
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 319d2df..a48d540 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -528,8 +528,6 @@
 }
 
 impl Zipfuse {
-    const MICRODROID_PAYLOAD_UID: u32 = 0; // TODO(b/264861173) should be non-root
-    const MICRODROID_PAYLOAD_GID: u32 = 0; // TODO(b/264861173) should be non-root
     fn mount(
         &mut self,
         noexec: MountForExec,
@@ -542,9 +540,13 @@
         if let MountForExec::Disallowed = noexec {
             cmd.arg("--noexec");
         }
+        // Let root own the files in APK, so we can access them, but set the group to
+        // allow all payloads to have access too.
+        let (uid, gid) = (microdroid_uids::ROOT_UID, microdroid_uids::MICRODROID_PAYLOAD_GID);
+
         cmd.args(["-p", &ready_prop, "-o", option]);
-        cmd.args(["-u", &Self::MICRODROID_PAYLOAD_UID.to_string()]);
-        cmd.args(["-g", &Self::MICRODROID_PAYLOAD_GID.to_string()]);
+        cmd.args(["-u", &uid.to_string()]);
+        cmd.args(["-g", &gid.to_string()]);
         cmd.arg(zip_path).arg(mount_dir);
         self.ready_properties.push(ready_prop);
         cmd.spawn().with_context(|| format!("Failed to run zipfuse for {mount_dir:?}"))
@@ -850,10 +852,15 @@
 fn exec_task(task: &Task, service: &Strong<dyn IVirtualMachineService>) -> Result<i32> {
     info!("executing main task {:?}...", task);
     let mut command = match task.type_ {
-        TaskType::Executable => Command::new(&task.command),
+        TaskType::Executable => {
+            // TODO(b/296393106): Run system payloads as non-root.
+            Command::new(&task.command)
+        }
         TaskType::MicrodroidLauncher => {
             let mut command = Command::new("/system/bin/microdroid_launcher");
             command.arg(find_library_path(&task.command)?);
+            command.uid(microdroid_uids::MICRODROID_PAYLOAD_UID);
+            command.gid(microdroid_uids::MICRODROID_PAYLOAD_GID);
             command
         }
     };
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index bbe00b5..1aa5935 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -7,8 +7,6 @@
     crate_name: "pvmfw",
     defaults: ["vmbase_ffi_defaults"],
     srcs: ["src/main.rs"],
-    // Require unsafe blocks for inside unsafe functions.
-    flags: ["-Dunsafe_op_in_unsafe_fn"],
     features: [
         "legacy",
     ],
diff --git a/pvmfw/avb/Android.bp b/pvmfw/avb/Android.bp
index 49c4717..4efee6a 100644
--- a/pvmfw/avb/Android.bp
+++ b/pvmfw/avb/Android.bp
@@ -7,8 +7,6 @@
     crate_name: "pvmfw_avb",
     srcs: ["src/lib.rs"],
     prefer_rlib: true,
-    // Require unsafe blocks for inside unsafe functions.
-    flags: ["-Dunsafe_op_in_unsafe_fn"],
     rustlibs: [
         "libavb_bindgen_nostd",
         "libtinyvec_nostd",
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 28271d3..9542429 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -18,8 +18,8 @@
 use core::mem::size_of;
 use core::slice;
 use diced_open_dice::{
-    bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceMode, Hash,
-    InputValues, HIDDEN_SIZE,
+    bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
+    Hash, InputValues, HIDDEN_SIZE,
 };
 use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
 use vmbase::cstr;
@@ -63,12 +63,10 @@
         next_bcc: &mut [u8],
     ) -> diced_open_dice::Result<()> {
         let mut config_descriptor_buffer = [0; 128];
-        let config_descriptor_size = bcc_format_config_descriptor(
-            Some(cstr!("vm_entry")),
-            None,  // component_version
-            false, // resettable
-            &mut config_descriptor_buffer,
-        )?;
+        let config_values =
+            DiceConfigValues { component_name: Some(cstr!("vm_entry")), ..Default::default() };
+        let config_descriptor_size =
+            bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
         let config = &config_descriptor_buffer[..config_descriptor_size];
 
         let dice_inputs = InputValues::new(
diff --git a/pvmfw/src/memory.rs b/pvmfw/src/memory.rs
index 27ab719..06158dd 100644
--- a/pvmfw/src/memory.rs
+++ b/pvmfw/src/memory.rs
@@ -14,8 +14,6 @@
 
 //! Low-level allocation and tracking of main memory.
 
-#![deny(unsafe_op_in_unsafe_fn)]
-
 use crate::helpers::PVMFW_PAGE_SIZE;
 use aarch64_paging::paging::VirtualAddress;
 use aarch64_paging::MapError;
diff --git a/rialto/Android.bp b/rialto/Android.bp
index ed9a284..7ac7136 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -76,6 +76,7 @@
 }
 
 prebuilt_etc {
+    // rialto_bin is a prebuilt target wrapping the signed bare-metal service VM.
     name: "rialto_bin",
     filename: "rialto.bin",
     target: {
diff --git a/service_vm/client_apk/src/main.rs b/service_vm/client_apk/src/main.rs
index 1f8db96..672dd4a 100644
--- a/service_vm/client_apk/src/main.rs
+++ b/service_vm/client_apk/src/main.rs
@@ -49,12 +49,7 @@
 fn request_certificate(csr: &[u8]) -> Vec<u8> {
     // SAFETY: It is safe as we only request the size of the certificate in this call.
     let certificate_size = unsafe {
-        AVmPayload_requestCertificate(
-            csr.as_ptr() as *const c_void,
-            csr.len(),
-            [].as_mut_ptr() as *mut c_void,
-            0,
-        )
+        AVmPayload_requestCertificate(csr.as_ptr() as *const c_void, csr.len(), [].as_mut_ptr(), 0)
     };
     let mut certificate = vec![0u8; certificate_size];
     // SAFETY: It is safe as we only write the data into the given buffer within the buffer
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 8d467cd..e81f6d7 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -55,6 +55,9 @@
     /** Returns a mask of effective capabilities that the process running the payload binary has. */
     String[] getEffectiveCapabilities();
 
+    /* Return the uid of the process running the binary. */
+    int getUid();
+
     /* write the content into the specified file. */
     void writeToFile(String content, String path);
 
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index e6d90ea..9f03ab7 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -451,6 +451,7 @@
         public String mApkContentsPath;
         public String mEncryptedStoragePath;
         public String[] mEffectiveCapabilities;
+        public int mUid;
         public String mFileContent;
         public byte[] mBcc;
         public long[] mTimings;
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 8a31c21..526f240 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -2,6 +2,17 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+android_app_certificate {
+    name: "MicrodroidTestAppCert",
+
+    // The default app cert is the same as the default platform cert
+    // (on a test-keys build), which means we end up getting assigned
+    // the permissions via signature and can't reliably disclaim
+    // them. So instead we use our own custom cert. See b/290582742.
+    // Created via: development/tools/make_key microdroid_test_app '/CN=microdroid_test_app'
+    certificate: "microdroid_test_app",
+}
+
 java_defaults {
     name: "MicrodroidTestAppsDefaults",
     test_suites: [
@@ -12,6 +23,7 @@
         "com.android.microdroid.testservice-java",
         "com.android.microdroid.test.vmshare_service-java",
     ],
+    certificate: ":MicrodroidTestAppCert",
     sdk_version: "test_current",
     jni_uses_platform_apis: true,
     use_embedded_native_libs: true,
diff --git a/tests/testapk/microdroid_test_app.pk8 b/tests/testapk/microdroid_test_app.pk8
new file mode 100644
index 0000000..dc012bd
--- /dev/null
+++ b/tests/testapk/microdroid_test_app.pk8
Binary files differ
diff --git a/tests/testapk/microdroid_test_app.x509.pem b/tests/testapk/microdroid_test_app.x509.pem
new file mode 100644
index 0000000..9a0309c
--- /dev/null
+++ b/tests/testapk/microdroid_test_app.x509.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDHzCCAgegAwIBAgIUNnOI4tOMieX67OtyD+6BjTsLm0IwDQYJKoZIhvcNAQEL
+BQAwHjEcMBoGA1UEAwwTbWljcm9kcm9pZF90ZXN0X2FwcDAgFw0yMzA4MTgxNDA4
+MDZaGA8yMDUxMDEwMzE0MDgwNlowHjEcMBoGA1UEAwwTbWljcm9kcm9pZF90ZXN0
+X2FwcDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK7B9xDTD2kS4xFQ
+gwQThRqnxKzOmckYqv2XznXq7tCuhU+RgXDrub7Aiq+QgA25Ouw8ORM5FkZAxD6j
+hCRSVo8cyXdNfPygRY/56umL6KqLMqB0tXLHPst3Lh8fl2su2S+jWL71lUwdOBmu
+nBIa1UqxI9PChR/uIqGyDxNRlUnqOA5/FgyX95P9wj8zmXEFe5No8rL/9hjpBvw1
+cOJCH4hea6JKDA15XYxDaTyj5pkmGb228ZbQb10XwOIhtS94CVxIvqmREzZHL7b0
+cjzCwFDDF6sQoVDi71eFYSWInxSNErDU6wv5h2t6+PV+9mGwTi/AJuxTmevSUoAp
+tGwq0NMCAwEAAaNTMFEwHQYDVR0OBBYEFI2m/0SoaNew99YPQlo6oYPJfh7lMB8G
+A1UdIwQYMBaAFI2m/0SoaNew99YPQlo6oYPJfh7lMA8GA1UdEwEB/wQFMAMBAf8w
+DQYJKoZIhvcNAQELBQADggEBABxIQ66ACIrSnDCiI/DqdPPwHf4vva2Y0bVJ5tXN
+ufFQN0Hr4UnttDzWPtfZHQTnrA478b9Z/g4Y0qg/tj2g5oZP50coF9a39mPe6v2k
+vazkMp2H/+ilG4c8L6QsC7UKXn7Lxxznn3ijlh1lYVJ3E6nMibGRKrfaVFpEwtvy
+zT0K8eK9KUZIyG5nf1v8On4Vfu7MnavuxNubKoUhfu0B8hSd5JKiGDuUkSk3MiFX
+uctYmJZEUD1xLI787SzqrhuYMGfuwmrrI0N46yvUgRgxpkVj2s6GNWqRD3F/fOG+
+qFbeenHjFoMJN9HIAZaz4OqzgGfhfMf596rn+HPAJnRMtsI=
+-----END CERTIFICATE-----
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index f6dc1b8..a928dcf 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -72,6 +72,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.function.ThrowingRunnable;
@@ -1523,6 +1524,30 @@
     }
 
     @Test
+    @Ignore // Figure out how to run this conditionally
+    @CddTest(requirements = {"9.17/C-1-1"})
+    public void payloadIsNotRoot() throws Exception {
+        assumeSupportedDevice();
+
+        VirtualMachineConfig config =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setMemoryBytes(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config);
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mUid = ts.getUid();
+                        });
+        testResults.assertNoException();
+        assertThat(testResults.mUid).isNotEqualTo(0);
+    }
+
+    @Test
     @CddTest(requirements = {"9.17/C-1-1"})
     public void encryptedStorageIsPersistent() throws Exception {
         assumeSupportedDevice();
@@ -1971,8 +1996,12 @@
                         | OsConstants.S_IROTH
                         | OsConstants.S_IWOTH
                         | OsConstants.S_IXOTH;
-        assertThat(testResults.mFileMode & allPermissionsMask)
-                .isEqualTo(OsConstants.S_IRUSR | OsConstants.S_IXUSR);
+        int expectedPermissions =
+                OsConstants.S_IRUSR
+                        | OsConstants.S_IXUSR
+                        | OsConstants.S_IRGRP
+                        | OsConstants.S_IXGRP;
+        assertThat(testResults.mFileMode & allPermissionsMask).isEqualTo(expectedPermissions);
     }
 
     // Taken from bionic/libc/kernel/uapi/linux/mount.h
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 297b505..c9b5e3a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -248,6 +248,11 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus getUid(int* out) override {
+            *out = getuid();
+            return ScopedAStatus::ok();
+        }
+
         ScopedAStatus runEchoReverseServer() override {
             auto result = start_echo_reverse_server();
             if (result.ok()) {
diff --git a/tests/testapk/test.keystore b/tests/testapk/test.keystore
deleted file mode 100644
index 2946641..0000000
--- a/tests/testapk/test.keystore
+++ /dev/null
Binary files differ
diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
index 0ddf70b..dc8908b 100644
--- a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
+++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java
@@ -220,6 +220,11 @@
         }
 
         @Override
+        public int getUid() throws RemoteException {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
         public void writeToFile(String content, String path) throws RemoteException {
             throw new UnsupportedOperationException("Not supported");
         }
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 68cc7f2..6372fa8 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -529,8 +529,10 @@
                         MemoryTrimLevel::TRIM_MEMORY_RUNNING_MODERATE => 10,
                         _ => bail!("Invalid memory trim level {:?}", level),
                     };
-                    let command =
-                        BalloonControlCommand::Adjust { num_bytes: total_memory * pct / 100 };
+                    let command = BalloonControlCommand::Adjust {
+                        num_bytes: total_memory * pct / 100,
+                        wait_for_success: false,
+                    };
                     if let Err(e) = vm_control::client::handle_request(
                         &VmRequest::BalloonCommand(command),
                         &self.crosvm_control_socket_path,
diff --git a/virtualizationservice/vfio_handler/Android.bp b/virtualizationservice/vfio_handler/Android.bp
index 9ed17f2..66662d5 100644
--- a/virtualizationservice/vfio_handler/Android.bp
+++ b/virtualizationservice/vfio_handler/Android.bp
@@ -27,6 +27,8 @@
         "liblazy_static",
         "liblog_rust",
         "libnix",
+        "librustutils",
+        "libzerocopy",
     ],
     apex_available: ["com.android.virt"],
 }
diff --git a/virtualizationservice/vfio_handler/src/aidl.rs b/virtualizationservice/vfio_handler/src/aidl.rs
index a826c75..bb9faf1 100644
--- a/virtualizationservice/vfio_handler/src/aidl.rs
+++ b/virtualizationservice/vfio_handler/src/aidl.rs
@@ -19,9 +19,15 @@
 use android_system_virtualizationservice_internal::binder::ParcelFileDescriptor;
 use binder::{self, ExceptionCode, Interface, IntoBinderResult};
 use lazy_static::lazy_static;
-use std::fs::{read_link, write};
-use std::io::Write;
-use std::path::Path;
+use std::fs::{read_link, write, File};
+use std::io::{Read, Seek, SeekFrom, Write};
+use std::mem::size_of;
+use std::path::{Path, PathBuf};
+use rustutils::system_properties;
+use zerocopy::{
+    byteorder::{BigEndian, U32},
+    FromBytes,
+};
 
 #[derive(Debug, Default)]
 pub struct VfioHandler {}
@@ -45,18 +51,10 @@
             return Err(anyhow!("VFIO-platform not supported"))
                 .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
         }
-
         devices.iter().try_for_each(|x| bind_device(Path::new(x)))?;
 
-        let mut dtbo = dtbo
-            .as_ref()
-            .try_clone()
-            .context("Failed to clone File from ParcelFileDescriptor")
-            .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?;
-        // TODO(b/291191362): write DTBO for devices to dtbo.
-        dtbo.write(b"\n")
-            .context("Can't write to ParcelFileDescriptor")
-            .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?;
+        write_dtbo(dtbo)?;
+
         Ok(())
     }
 }
@@ -65,13 +63,52 @@
 const SYSFS_PLATFORM_DEVICES_PATH: &str = "/sys/devices/platform/";
 const VFIO_PLATFORM_DRIVER_PATH: &str = "/sys/bus/platform/drivers/vfio-platform";
 const SYSFS_PLATFORM_DRIVERS_PROBE_PATH: &str = "/sys/bus/platform/drivers_probe";
+const DT_TABLE_MAGIC: u32 = 0xd7b7ab1e;
 
-lazy_static! {
-    static ref IS_VFIO_SUPPORTED: bool = is_vfio_supported();
+/// The structure of DT table header in dtbo.img.
+/// https://source.android.com/docs/core/architecture/dto/partitions
+#[repr(C)]
+#[derive(Debug, FromBytes)]
+struct DtTableHeader {
+    /// DT_TABLE_MAGIC
+    magic: U32<BigEndian>,
+    /// includes dt_table_header + all dt_table_entry and all dtb/dtbo
+    _total_size: U32<BigEndian>,
+    /// sizeof(dt_table_header)
+    header_size: U32<BigEndian>,
+    /// sizeof(dt_table_entry)
+    dt_entry_size: U32<BigEndian>,
+    /// number of dt_table_entry
+    dt_entry_count: U32<BigEndian>,
+    /// offset to the first dt_table_entry from head of dt_table_header
+    dt_entries_offset: U32<BigEndian>,
+    /// flash page size we assume
+    _page_size: U32<BigEndian>,
+    /// DTBO image version, the current version is 0. The version will be
+    /// incremented when the dt_table_header struct is updated.
+    _version: U32<BigEndian>,
 }
 
-fn is_vfio_supported() -> bool {
-    Path::new(DEV_VFIO_PATH).exists() && Path::new(VFIO_PLATFORM_DRIVER_PATH).exists()
+/// The structure of each DT table entry (v0) in dtbo.img.
+/// https://source.android.com/docs/core/architecture/dto/partitions
+#[repr(C)]
+#[derive(Debug, FromBytes)]
+struct DtTableEntry {
+    /// size of each DT
+    dt_size: U32<BigEndian>,
+    /// offset from head of dt_table_header
+    dt_offset: U32<BigEndian>,
+    /// optional, must be zero if unused
+    _id: U32<BigEndian>,
+    /// optional, must be zero if unused
+    _rev: U32<BigEndian>,
+    /// optional, must be zero if unused
+    _custom: [U32<BigEndian>; 4],
+}
+
+lazy_static! {
+    static ref IS_VFIO_SUPPORTED: bool =
+        Path::new(DEV_VFIO_PATH).exists() && Path::new(VFIO_PLATFORM_DRIVER_PATH).exists();
 }
 
 fn check_platform_device(path: &Path) -> binder::Result<()> {
@@ -158,3 +195,109 @@
     check_platform_device(&path)?;
     bind_vfio_driver(&path)
 }
+
+fn get_dtbo_img_path() -> binder::Result<PathBuf> {
+    let slot_suffix = system_properties::read("ro.boot.slot_suffix")
+        .context("Failed to read ro.boot.slot_suffix")
+        .or_service_specific_exception(-1)?
+        .ok_or_else(|| anyhow!("slot_suffix is none"))
+        .or_service_specific_exception(-1)?;
+    Ok(PathBuf::from(format!("/dev/block/by-name/dtbo{slot_suffix}")))
+}
+
+fn read_values(file: &mut File, size: usize, offset: u64) -> binder::Result<Vec<u8>> {
+    file.seek(SeekFrom::Start(offset))
+        .context("Cannot seek the offset")
+        .or_service_specific_exception(-1)?;
+    let mut buffer = vec![0_u8; size];
+    file.read_exact(&mut buffer)
+        .context("Failed to read buffer")
+        .or_service_specific_exception(-1)?;
+    Ok(buffer)
+}
+
+fn get_dt_table_header(file: &mut File) -> binder::Result<DtTableHeader> {
+    let values = read_values(file, size_of::<DtTableHeader>(), 0)?;
+    let dt_table_header = DtTableHeader::read_from(values.as_slice())
+        .context("DtTableHeader is invalid")
+        .or_service_specific_exception(-1)?;
+    if dt_table_header.magic.get() != DT_TABLE_MAGIC
+        || dt_table_header.header_size.get() as usize != size_of::<DtTableHeader>()
+    {
+        return Err(anyhow!("DtTableHeader is invalid")).or_service_specific_exception(-1)?;
+    }
+    Ok(dt_table_header)
+}
+
+fn get_dt_table_entry(
+    file: &mut File,
+    header: &DtTableHeader,
+    index: u32,
+) -> binder::Result<DtTableEntry> {
+    if index >= header.dt_entry_count.get() {
+        return Err(anyhow!("Invalid dtbo index {index}")).or_service_specific_exception(-1)?;
+    }
+    let Some(prev_dt_entry_total_size) = header.dt_entry_size.get().checked_mul(index) else {
+        return Err(anyhow!("Unexpected arithmetic result"))
+            .or_binder_exception(ExceptionCode::ILLEGAL_STATE);
+    };
+    let Some(dt_entry_offset) =
+        prev_dt_entry_total_size.checked_add(header.dt_entries_offset.get())
+    else {
+        return Err(anyhow!("Unexpected arithmetic result"))
+            .or_binder_exception(ExceptionCode::ILLEGAL_STATE);
+    };
+    let values = read_values(file, size_of::<DtTableEntry>(), dt_entry_offset.into())?;
+    let dt_table_entry = DtTableEntry::read_from(values.as_slice())
+        .with_context(|| format!("DtTableEntry at index {index} is invalid."))
+        .or_service_specific_exception(-1)?;
+    Ok(dt_table_entry)
+}
+
+fn filter_dtbo_from_img(
+    dtbo_img_file: &mut File,
+    entry: &DtTableEntry,
+    dtbo_fd: &ParcelFileDescriptor,
+) -> binder::Result<()> {
+    let dt_size = entry
+        .dt_size
+        .get()
+        .try_into()
+        .context("Failed to convert type")
+        .or_binder_exception(ExceptionCode::ILLEGAL_STATE)?;
+    let buffer = read_values(dtbo_img_file, dt_size, entry.dt_offset.get().into())?;
+
+    let mut dtbo_fd = dtbo_fd
+        .as_ref()
+        .try_clone()
+        .context("Failed to clone File from ParcelFileDescriptor")
+        .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?;
+
+    // TODO(b/296796644): Filter dtbo.img, not writing all information.
+    dtbo_fd
+        .write_all(&buffer)
+        .context("Failed to write dtbo file")
+        .or_service_specific_exception(-1)?;
+    Ok(())
+}
+
+fn write_dtbo(dtbo_fd: &ParcelFileDescriptor) -> binder::Result<()> {
+    let dtbo_path = get_dtbo_img_path()?;
+    let mut dtbo_img = File::open(dtbo_path)
+        .context("Failed to open DTBO partition")
+        .or_service_specific_exception(-1)?;
+
+    let dt_table_header = get_dt_table_header(&mut dtbo_img)?;
+    let vm_dtbo_idx = system_properties::read("ro.boot.hypervisor.vm_dtbo_idx")
+        .context("Failed to read vm_dtbo_idx")
+        .or_service_specific_exception(-1)?
+        .ok_or_else(|| anyhow!("vm_dtbo_idx is none"))
+        .or_service_specific_exception(-1)?;
+    let vm_dtbo_idx = vm_dtbo_idx
+        .parse()
+        .context("vm_dtbo_idx is not an integer")
+        .or_service_specific_exception(-1)?;
+    let dt_table_entry = get_dt_table_entry(&mut dtbo_img, &dt_table_header, vm_dtbo_idx)?;
+    filter_dtbo_from_img(&mut dtbo_img, &dt_table_entry, dtbo_fd)?;
+    Ok(())
+}
diff --git a/vm_payload/Android.bp b/vm_payload/Android.bp
index ae0d1a6..49b7f5f 100644
--- a/vm_payload/Android.bp
+++ b/vm_payload/Android.bp
@@ -10,8 +10,6 @@
     srcs: ["src/*.rs"],
     include_dirs: ["include"],
     prefer_rlib: true,
-    // Require unsafe blocks for inside unsafe functions.
-    flags: ["-Dunsafe_op_in_unsafe_fn"],
     rustlibs: [
         "android.system.virtualization.payload-rust",
         "libandroid_logger",
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index a6f3bfa..ebd981c 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -16,8 +16,6 @@
 
 #![no_main]
 #![no_std]
-#![deny(unsafe_op_in_unsafe_fn)]
-#![deny(clippy::undocumented_unsafe_blocks)]
 
 mod exceptions;
 mod layout;
diff --git a/vmbase/src/lib.rs b/vmbase/src/lib.rs
index ca8756d..431e899 100644
--- a/vmbase/src/lib.rs
+++ b/vmbase/src/lib.rs
@@ -15,8 +15,6 @@
 //! Basic functionality for bare-metal binaries to run in a VM under crosvm.
 
 #![no_std]
-#![deny(unsafe_op_in_unsafe_fn)]
-#![deny(clippy::undocumented_unsafe_blocks)]
 
 extern crate alloc;
 
diff --git a/zipfuse/src/inode.rs b/zipfuse/src/inode.rs
index ef48389..3175a30 100644
--- a/zipfuse/src/inode.rs
+++ b/zipfuse/src/inode.rs
@@ -31,10 +31,11 @@
 const INVALID: Inode = 0;
 const ROOT: Inode = 1;
 
-const DEFAULT_DIR_MODE: u32 = libc::S_IRUSR | libc::S_IXUSR;
+const DEFAULT_DIR_MODE: u32 = libc::S_IRUSR | libc::S_IXUSR | libc::S_IRGRP | libc::S_IXGRP;
 // b/264668376 some files in APK don't have unix permissions specified. Default to 400
 // otherwise those files won't be readable even by the owner.
-const DEFAULT_FILE_MODE: u32 = libc::S_IRUSR;
+const DEFAULT_FILE_MODE: u32 = libc::S_IRUSR | libc::S_IRGRP;
+const EXECUTABLE_FILE_MODE: u32 = DEFAULT_FILE_MODE | libc::S_IXUSR | libc::S_IXGRP;
 
 /// `InodeData` represents an inode which has metadata about a file or a directory
 #[derive(Debug)]
@@ -191,7 +192,7 @@
                 // additional binaries that they might want to execute.
                 // An example of such binary is measure_io one used in the authfs performance tests.
                 // More context available at b/265261525 and b/270955654.
-                file_mode |= libc::S_IXUSR;
+                file_mode = EXECUTABLE_FILE_MODE;
             }
 
             while let Some(name) = iter.next() {