Mount /data with MS_NOEXEC

We want to ensure that W^X is guaranteed for Microdroid VMs. This change
doesn't guarantee that W^X is enforced for FULL_DEBUG VMs, as a user can
enable adb root, and remount the /data without MS_NOEXEC flag.

This is intended, as it allows developers to debug & get familiar with
the Microdroid execution environment.

Bug: 265261525
Test: atest MicrodroidTestApp
Change-Id: I622e3d95d9d8fd6d26bfb690499acf7208ca4d52
diff --git a/microdroid/init.rc b/microdroid/init.rc
index bc42791..d2c9c41 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 7ee1f01..aa5b32d 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -55,4 +55,7 @@
 
     /* get the content of the specified file. */
     String readFromFile(String path);
+
+    /** Returns flags for the given mountPoint. */
+    int getMountFlags(String mountPoint);
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index e3c9961..9eda246 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -47,6 +47,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 e1a2e40..aba6f65 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -24,6 +24,7 @@
 import static android.system.virtualmachine.VirtualMachineManager.CAPABILITY_PROTECTED_VM;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 import static com.google.common.truth.TruthJUnit.assume;
 
 import static org.junit.Assert.assertThrows;
@@ -1400,6 +1401,34 @@
         assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
     }
 
+    // Take from bionic/libs/kernel/uapi/linux/mounth.h.
+    private static final int MS_NOEXEC = 8;
+
+    @Test
+    public void dataIsMountedWithNoExec() throws Exception {
+        assumeSupportedKernel();
+
+        final VirtualMachineConfig vmConfig =
+                newVmConfigBuilder()
+                        .setPayloadBinaryName("MicrodroidTestNativeLib.so")
+                        .setMemoryMib(minMemoryRequired())
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .build();
+        final VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_data_mount", vmConfig);
+
+        final TestResults testResults =
+                runVmTestService(
+                        vm,
+                        (ts, tr) -> {
+                            tr.mountFlags = ts.getMountFlags("/data");
+                        });
+
+        assertThat(testResults.mException).isNull();
+        assertWithMessage("/data should be mounted with MS_NOEXEC")
+                .that(testResults.mountFlags & MS_NOEXEC)
+                .isEqualTo(MS_NOEXEC);
+    }
+
     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
             throws IOException {
         File file1 = getVmFile(vmName1, fileName);
@@ -1456,6 +1485,7 @@
         String mEncryptedStoragePath;
         String[] mEffectiveCapabilities;
         String mFileContent;
+        int mountFlags;
     }
 
     private TestResults runVmTestService(VirtualMachine vm, RunTestsAgainstTestService testsToRun)
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 4ba502a..f5f6783 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>
@@ -31,6 +32,7 @@
 #include <vm_main.h>
 #include <vm_payload_restricted.h>
 
+#include <cstdint>
 #include <string>
 #include <thread>
 
@@ -40,6 +42,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;
@@ -255,6 +261,22 @@
             }
             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();
+        }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();