Add permissive MTE mode.
This is not meant to be enabled long-term, but can be used to assess system
stability with MTE before enabling it.
Bug: 202037138
Change-Id: I9fb9b63ff94da2de0a814fd7150f51559d3af079
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index 35be2bf..92e7675 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -38,6 +38,8 @@
#include <unistd.h>
#include <android-base/macros.h>
+#include <android-base/parsebool.h>
+#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
#include <bionic/reserved_signals.h>
@@ -49,7 +51,10 @@
#include "handler/fallback.h"
-using android::base::Pipe;
+using ::android::base::GetBoolProperty;
+using ::android::base::ParseBool;
+using ::android::base::ParseBoolResult;
+using ::android::base::Pipe;
// We muck with our fds in a 'thread' that doesn't share the same fd table.
// Close fds in that thread with a raw close syscall instead of going through libc.
@@ -82,6 +87,13 @@
return syscall(__NR_gettid);
}
+static bool is_permissive_mte() {
+ // Environment variable for testing or local use from shell.
+ char* permissive_env = getenv("MTE_PERMISSIVE");
+ return GetBoolProperty("persist.sys.mte.permissive", false) ||
+ (permissive_env && ParseBool(permissive_env) == ParseBoolResult::kTrue);
+}
+
static inline void futex_wait(volatile void* ftx, int value) {
syscall(__NR_futex, ftx, FUTEX_WAIT, value, nullptr, nullptr, 0);
}
@@ -592,7 +604,28 @@
// If the signal is fatal, don't unlock the mutex to prevent other crashing threads from
// starting to dump right before our death.
pthread_mutex_unlock(&crash_mutex);
- } else {
+ }
+#ifdef __aarch64__
+ else if (info->si_signo == SIGSEGV &&
+ (info->si_code == SEGV_MTESERR || info->si_code == SEGV_MTEAERR) &&
+ is_permissive_mte()) {
+ // If we are in permissive MTE mode, we do not crash, but instead disable MTE on this thread,
+ // and then let the failing instruction be retried. The second time should work (except
+ // if there is another non-MTE fault).
+ int tagged_addr_ctrl = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+ if (tagged_addr_ctrl < 0) {
+ fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL");
+ }
+ tagged_addr_ctrl = (tagged_addr_ctrl & ~PR_MTE_TCF_MASK) | PR_MTE_TCF_NONE;
+ if (prctl(PR_SET_TAGGED_ADDR_CTRL, tagged_addr_ctrl, 0, 0, 0) < 0) {
+ fatal_errno("failed to PR_SET_TAGGED_ADDR_CTRL");
+ }
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc",
+ "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING.");
+ pthread_mutex_unlock(&crash_mutex);
+ }
+#endif
+ else {
// Resend the signal, so that either the debugger or the parent's waitpid sees it.
resend_signal(info);
}
diff --git a/debuggerd/test_permissive_mte/Android.bp b/debuggerd/test_permissive_mte/Android.bp
new file mode 100644
index 0000000..548a340
--- /dev/null
+++ b/debuggerd/test_permissive_mte/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+cc_binary {
+ name: "mte_crash",
+ srcs: ["mte_crash.cpp"],
+ sanitize: {
+ memtag_heap: true,
+ diag: {
+ memtag_heap: true,
+ },
+ },
+}
+
+java_test_host {
+ name: "permissive_mte_test",
+ libs: ["tradefed"],
+ static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
+ srcs: ["src/**/PermissiveMteTest.java", ":libtombstone_proto-src"],
+ data: [":mte_crash"],
+ test_config: "AndroidTest.xml",
+ test_suites: ["general-tests"],
+}
diff --git a/debuggerd/test_permissive_mte/AndroidTest.xml b/debuggerd/test_permissive_mte/AndroidTest.xml
new file mode 100644
index 0000000..bd3d018
--- /dev/null
+++ b/debuggerd/test_permissive_mte/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs the permissive MTE tests">
+ <option name="test-suite-tag" value="init_test_upgrade_mte" />
+ <option name="test-suite-tag" value="apct" />
+
+ <!-- For tombstone inspection. -->
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="mte_crash->/data/local/tmp/mte_crash" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="permissive_mte_test.jar" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/debuggerd/test_permissive_mte/mte_crash.cpp b/debuggerd/test_permissive_mte/mte_crash.cpp
new file mode 100644
index 0000000..97ad73f
--- /dev/null
+++ b/debuggerd/test_permissive_mte/mte_crash.cpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int, char**) {
+ volatile char* f = (char*)malloc(1);
+ printf("%c\n", f[17]);
+ return 0;
+}
diff --git a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
new file mode 100644
index 0000000..5ff2b5b
--- /dev/null
+++ b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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 com.android.tests.init;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.server.os.TombstoneProtos.Tombstone;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Arrays;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PermissiveMteTest extends BaseHostJUnit4Test {
+ String mUUID;
+
+ @Before
+ public void setUp() throws Exception {
+ mUUID = java.util.UUID.randomUUID().toString();
+ CommandResult result =
+ getDevice().executeShellV2Command("/data/local/tmp/mte_crash setUp " + mUUID);
+ assumeTrue("mte_crash needs to segfault", result.getExitCode() == 139);
+ }
+
+ Tombstone parseTombstone(String tombstonePath) throws Exception {
+ File tombstoneFile = getDevice().pullFile(tombstonePath);
+ InputStream istr = new FileInputStream(tombstoneFile);
+ Tombstone tombstoneProto;
+ try {
+ tombstoneProto = Tombstone.parseFrom(istr);
+ } finally {
+ istr.close();
+ }
+ return tombstoneProto;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ String[] tombstones = getDevice().getChildren("/data/tombstones");
+ for (String tombstone : tombstones) {
+ if (!tombstone.endsWith(".pb")) {
+ continue;
+ }
+ String tombstonePath = "/data/tombstones/" + tombstone;
+ Tombstone tombstoneProto = parseTombstone(tombstonePath);
+ if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) {
+ continue;
+ }
+ getDevice().deleteFile(tombstonePath);
+ // remove the non .pb file as well.
+ getDevice().deleteFile(tombstonePath.substring(0, tombstonePath.length() - 3));
+ }
+ }
+
+ @Test
+ public void testCrash() throws Exception {
+ CommandResult result = getDevice().executeShellV2Command(
+ "MTE_PERMISSIVE=1 /data/local/tmp/mte_crash testCrash " + mUUID);
+ assertThat(result.getExitCode()).isEqualTo(0);
+ int numberTombstones = 0;
+ String[] tombstones = getDevice().getChildren("/data/tombstones");
+ for (String tombstone : tombstones) {
+ if (!tombstone.endsWith(".pb")) {
+ continue;
+ }
+ String tombstonePath = "/data/tombstones/" + tombstone;
+ Tombstone tombstoneProto = parseTombstone(tombstonePath);
+ if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(mUUID))) {
+ continue;
+ }
+ if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains("testCrash"))) {
+ continue;
+ }
+ numberTombstones++;
+ }
+ assertThat(numberTombstones).isEqualTo(1);
+ }
+}