diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp index 35be2bf31..92e76757d 100644 --- a/debuggerd/handler/debuggerd_handler.cpp +++ b/debuggerd/handler/debuggerd_handler.cpp @@ -38,6 +38,8 @@ #include #include +#include +#include #include #include #include @@ -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 @@ static pid_t __gettid() { 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 @@ static void debuggerd_signal_handler(int signal_number, siginfo_t* info, void* c // 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 000000000..548a340d6 --- /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 000000000..bd3d0182c --- /dev/null +++ b/debuggerd/test_permissive_mte/AndroidTest.xml @@ -0,0 +1,29 @@ + + + + \ 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 000000000..97ad73fbc --- /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 +#include + +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 000000000..5ff2b5b8c --- /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); + } +}