Merge "Increase max entry for directories to 1000"
diff --git a/authfs/tests/benchmarks/Android.bp b/authfs/tests/benchmarks/Android.bp
index 9bdef7b..38ece79 100644
--- a/authfs/tests/benchmarks/Android.bp
+++ b/authfs/tests/benchmarks/Android.bp
@@ -23,7 +23,6 @@
         ":authfs_test_files",
         ":CtsApkVerityTestPrebuiltFiles",
         ":MicrodroidTestApp",
-        ":measure_io",
     ],
 }
 
@@ -36,3 +35,19 @@
         "libbase",
     ],
 }
+
+// Package measure_io binary into a jar, to bundle with the MicrodroidTestApp.
+// When MicrodroidTestApp is mounted inside the Microdroid, the zipfuse will
+// add the +x permission on it.
+java_genrule {
+    name: "measure_io_as_jar",
+    out: ["measure_io.jar"],
+    srcs: [
+        ":measure_io",
+    ],
+    cmd: "out_dir=$$(dirname $(out))" +
+        "&& bin_dir=\"bin\" " +
+        "&& mkdir -p $$out_dir/$$bin_dir" +
+        "&& cp $(in) $$out_dir/$$bin_dir" +
+        "&& jar cf $(out) -C $$out_dir $$bin_dir",
+}
diff --git a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
index 32eafb8..085d06e 100644
--- a/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
+++ b/authfs/tests/benchmarks/src/java/com/android/fs/benchmarks/AuthFsBenchmarks.java
@@ -18,7 +18,6 @@
 
 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestMetrics;
 
-import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -45,7 +44,6 @@
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.UseParametersRunnerFactory;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
@@ -57,11 +55,8 @@
 public class AuthFsBenchmarks extends BaseHostJUnit4Test {
     private static final int TRIAL_COUNT = 5;
 
-    /** Name of the measure_io binary on host. */
-    private static final String MEASURE_IO_BIN_NAME = "measure_io";
-
     /** Path to measure_io on Microdroid. */
-    private static final String MEASURE_IO_BIN_PATH = "/data/local/tmp/measure_io";
+    private static final String MEASURE_IO_BIN_PATH = "/mnt/apk/bin/measure_io";
 
     /** fs-verity digest (sha256) of testdata/input.4m */
     private static final String DIGEST_4M =
@@ -123,7 +118,6 @@
     }
 
     private void readRemoteFile(String mode) throws DeviceNotAvailableException {
-        pushMeasureIoBinToMicrodroid();
         // Cache the file in memory for the host.
         mAuthFsTestRule
                 .getAndroid()
@@ -146,7 +140,6 @@
     }
 
     private void writeRemoteFile(String mode) throws DeviceNotAvailableException {
-        pushMeasureIoBinToMicrodroid();
         String filePath = mAuthFsTestRule.MOUNT_DIR + "/5";
         int fileSizeMb = 8;
         String cmd = MEASURE_IO_BIN_PATH + " " + filePath + " " + fileSizeMb + " " + mode + " w";
@@ -165,14 +158,6 @@
         reportMetrics(rates, mode + "_write", "mb_per_sec");
     }
 
-    private void pushMeasureIoBinToMicrodroid() throws DeviceNotAvailableException {
-        File measureReadBin = mAuthFsTestRule.findTestFile(getBuild(), MEASURE_IO_BIN_NAME);
-        assertThat(measureReadBin.exists()).isTrue();
-        mAuthFsTestRule.getMicrodroidDevice().pushFile(measureReadBin, MEASURE_IO_BIN_PATH);
-        assertThat(mAuthFsTestRule.getMicrodroid().run("ls " + MEASURE_IO_BIN_PATH))
-                .isEqualTo(MEASURE_IO_BIN_PATH);
-    }
-
     private void reportMetrics(List<Double> metrics, String name, String unit) {
         Map<String, Double> stats = mMetricsProcessor.computeStats(metrics, name, unit);
         for (Map.Entry<String, Double> entry : stats.entrySet()) {
diff --git a/microdroid/init.rc b/microdroid/init.rc
index ce0cab4..70c22d4 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -98,7 +98,7 @@
     mount rootfs rootfs / remount bind ro nodev
 
     # TODO(b/185767624): change the hard-coded size?
-    mount tmpfs tmpfs /data noatime nosuid nodev rw size=128M
+    mount tmpfs tmpfs /data noatime nosuid nodev noexec rw size=128M
 
     # We chown/chmod /data again so because mount is run as root + defaults
     chown system system /data
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 4611134..66dbe4b 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -56,6 +56,12 @@
     /* get the content of the specified file. */
     String readFromFile(String path);
 
+    /* get file permissions of the give file by stat'ing it */
+    int getFilePermissions(String path);
+
+    /** Returns flags for the given mountPoint. */
+    int getMountFlags(String mountPoint);
+
     /**
      * Request the service to exit, triggering the termination of the VM. This may cause any
      * requests in flight to fail.
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 be3c1da..2d568d0 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
@@ -443,6 +443,8 @@
         public String mFileContent;
         public byte[] mBcc;
         public long[] mTimings;
+        public int mFileMode;
+        public int mMountFlags;
 
         public void assertNoException() {
             if (mException != null) {
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index bafab53..fe8f5c9 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -31,6 +31,7 @@
         "cbor-java",
         "truth-prebuilt",
         "compatibility-common-util-devicesidelib",
+        "measure_io_as_jar",
     ],
     jni_libs: [
         "MicrodroidTestNativeLib",
@@ -62,6 +63,7 @@
     static_libs: [
         "com.android.microdroid.testservice-ndk",
         "libbase",
+        "libfstab",
         "libfsverity_digests_proto_cc",
         "liblog",
         "libprotobuf-cpp-lite-ndk",
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 f3f1252..e0abe98 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -45,6 +45,7 @@
 import android.os.ParcelFileDescriptor.AutoCloseInputStream;
 import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
 import android.os.SystemProperties;
+import android.system.OsConstants;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -1779,6 +1780,70 @@
         }
     }
 
+    @Test
+    @CddTest(requirements = {"9.17/C-1-5"})
+    public void testFileUnderBinHasExecutePermission() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setMemoryBytes(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_perms", vmConfig);
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mFileMode = ts.getFilePermissions("/mnt/apk/bin/measure_io");
+                        });
+
+        testResults.assertNoException();
+        int allPermissionsMask =
+                OsConstants.S_IRUSR
+                        | OsConstants.S_IWUSR
+                        | OsConstants.S_IXUSR
+                        | OsConstants.S_IRGRP
+                        | OsConstants.S_IWGRP
+                        | OsConstants.S_IXGRP
+                        | OsConstants.S_IROTH
+                        | OsConstants.S_IWOTH
+                        | OsConstants.S_IXOTH;
+        assertThat(testResults.mFileMode & allPermissionsMask)
+                .isEqualTo(OsConstants.S_IRUSR | OsConstants.S_IXUSR);
+    }
+
+    // Taken from bionic/libs/kernel/uapi/linux/mounth.h.
+    private static final int MS_NOEXEC = 8;
+
+    @Test
+    public void dataIsMountedWithNoExec() throws Exception {
+        assumeSupportedKernel();
+
+        VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_data_mount", vmConfig);
+
+        TestResults testResults =
+                runVmTestService(
+                        TAG,
+                        vm,
+                        (ts, tr) -> {
+                            tr.mMountFlags = ts.getMountFlags("/data");
+                        });
+
+        assertThat(testResults.mException).isNull();
+        assertWithMessage("/data should be mounted with MS_NOEXEC")
+                .that(testResults.mMountFlags & MS_NOEXEC)
+                .isEqualTo(MS_NOEXEC);
+    }
+
     private static class VmShareServiceConnection implements ServiceConnection {
 
         private final CountDownLatch mLatch = new CountDownLatch(1);
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 19d6cd4..285dae9 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -21,6 +21,7 @@
 #include <android-base/scopeguard.h>
 #include <android/log.h>
 #include <fcntl.h>
+#include <fstab/fstab.h>
 #include <fsverity_digests.pb.h>
 #include <linux/vm_sockets.h>
 #include <stdint.h>
@@ -40,6 +41,10 @@
 using android::base::make_scope_guard;
 using android::base::Result;
 using android::base::unique_fd;
+using android::fs_mgr::Fstab;
+using android::fs_mgr::FstabEntry;
+using android::fs_mgr::GetEntryForMountPoint;
+using android::fs_mgr::ReadFstabFromFile;
 
 using aidl::com::android::microdroid::testservice::BnTestService;
 using ndk::ScopedAStatus;
@@ -251,6 +256,34 @@
             return ScopedAStatus::ok();
         }
 
+        ScopedAStatus getFilePermissions(const std::string& path, int32_t* out) override {
+            struct stat sb;
+            if (stat(path.c_str(), &sb) != -1) {
+                *out = sb.st_mode;
+            } else {
+                std::string msg = "stat " + path + " failed :  " + std::strerror(errno);
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   msg.c_str());
+            }
+            return ScopedAStatus::ok();
+        }
+
+        ScopedAStatus getMountFlags(const std::string& mount_point, int32_t* out) override {
+            Fstab fstab;
+            if (!ReadFstabFromFile("/proc/mounts", &fstab)) {
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   "Failed to read /proc/mounts");
+            }
+            FstabEntry* entry = GetEntryForMountPoint(&fstab, mount_point);
+            if (entry == nullptr) {
+                std::string msg = mount_point + " not found in /proc/mounts";
+                return ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                   msg.c_str());
+            }
+            *out = entry->flags;
+            return ScopedAStatus::ok();
+        }
+
         ScopedAStatus quit() override { exit(0); }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
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 278e1a2..467b98b 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
@@ -230,6 +230,16 @@
         }
 
         @Override
+        public int getFilePermissions(String path) throws RemoteException {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
+        public int getMountFlags(String path) throws RemoteException {
+            throw new UnsupportedOperationException("Not supported");
+        }
+
+        @Override
         public void quit() throws RemoteException {
             throw new UnsupportedOperationException("Not supported");
         }
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 6ac1658..942e69a 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -532,6 +532,9 @@
     /// Checks if ramdump has been created. If so, send it to tombstoned.
     fn handle_ramdump(&self) -> Result<(), Error> {
         let ramdump_path = self.temporary_directory.join("ramdump");
+        if !ramdump_path.as_path().try_exists()? {
+            return Ok(());
+        }
         if std::fs::metadata(&ramdump_path)?.len() > 0 {
             Self::send_ramdump_to_tombstoned(&ramdump_path)?;
         }
diff --git a/zipfuse/src/inode.rs b/zipfuse/src/inode.rs
index 3edbc49..ea63422 100644
--- a/zipfuse/src/inode.rs
+++ b/zipfuse/src/inode.rs
@@ -99,12 +99,8 @@
         InodeData { mode, size: 0, data: InodeDataData::Directory(HashMap::new()) }
     }
 
-    fn new_file(zip_index: ZipIndex, zip_file: &zip::read::ZipFile) -> InodeData {
-        InodeData {
-            mode: zip_file.unix_mode().unwrap_or(DEFAULT_FILE_MODE),
-            size: zip_file.size(),
-            data: InodeDataData::File(zip_index),
-        }
+    fn new_file(zip_index: ZipIndex, mode: u32, zip_file: &zip::read::ZipFile) -> InodeData {
+        InodeData { mode, size: zip_file.size(), data: InodeDataData::File(zip_index) }
     }
 
     fn add_to_directory(&mut self, name: CString, entry: DirectoryEntry) {
@@ -188,6 +184,16 @@
 
             let mut parent = ROOT;
             let mut iter = path.iter().peekable();
+
+            let mut file_mode = DEFAULT_FILE_MODE;
+            if path.starts_with("bin/") {
+                // Allow files under bin to have execute permission, this enables payloads to bundle
+                // 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;
+            }
+
             while let Some(name) = iter.next() {
                 // TODO(jiyong): remove this check by canonicalizing `path`
                 if name == ".." {
@@ -211,8 +217,11 @@
                 }
 
                 // No inode found. Create a new inode and add it to the inode table.
+                // At the moment of writing this comment the apk file doesn't specify any
+                // permissions (apart from the ones on lib/), but it might change in the future.
+                // TODO(b/270955654): should we control the file permissions ourselves?
                 let inode = if is_file {
-                    InodeData::new_file(i, &file)
+                    InodeData::new_file(i, file.unix_mode().unwrap_or(file_mode), &file)
                 } else if is_leaf {
                     InodeData::new_dir(file.unix_mode().unwrap_or(DEFAULT_DIR_MODE))
                 } else {