Merge "Allow to re-enable MTE a specified time after a permissive fault" into main
diff --git a/debuggerd/handler/debuggerd_handler.cpp b/debuggerd/handler/debuggerd_handler.cpp
index e26746b..42f0aa0 100644
--- a/debuggerd/handler/debuggerd_handler.cpp
+++ b/debuggerd/handler/debuggerd_handler.cpp
@@ -36,10 +36,12 @@
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/wait.h>
+#include <time.h>
#include <unistd.h>
#include <android-base/macros.h>
#include <android-base/parsebool.h>
+#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/unique_fd.h>
#include <async_safe/log.h>
@@ -115,6 +117,59 @@
(permissive_env && ParseBool(permissive_env) == ParseBoolResult::kTrue);
}
+static bool parse_uint_with_error_reporting(const char* s, const char* name, int* v) {
+ if (android::base::ParseInt(s, v) && *v >= 0) {
+ return true;
+ }
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc", "invalid %s: %s", name, s);
+ return false;
+}
+
+// We cannot use base::GetIntProperty, because that internally uses
+// std::string, which allocates.
+static bool property_parse_int(const char* name, int* out) {
+ const prop_info* pi = __system_property_find(name);
+ if (!pi) return false;
+ struct cookie_t {
+ int* out;
+ bool empty;
+ } cookie{out, true};
+ __system_property_read_callback(
+ pi,
+ [](void* raw_cookie, const char* name, const char* value, uint32_t) {
+ // Property is set to empty value, ignoring.
+ if (!*value) return;
+ cookie_t* cookie = reinterpret_cast<cookie_t*>(raw_cookie);
+ if (parse_uint_with_error_reporting(value, name, cookie->out)) cookie->empty = false;
+ },
+ &cookie);
+ return !cookie.empty;
+}
+
+static int permissive_mte_renable_timer() {
+ if (char* env = getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) {
+ int v;
+ if (parse_uint_with_error_reporting(env, "MTE_PERMISSIVE_REENABLE_TIME_CPUMS", &v)) return v;
+ }
+
+ char process_sysprop_name[512];
+ async_safe_format_buffer(process_sysprop_name, sizeof(process_sysprop_name),
+ "persist.sys.mte.permissive_reenable_timer.process.%s", getprogname());
+ int v;
+ if (property_parse_int(process_sysprop_name, &v)) return v;
+ if (property_parse_int("persist.sys.mte.permissive_reenable_timer.default", &v)) return v;
+ char process_deviceconf_sysprop_name[512];
+ async_safe_format_buffer(
+ process_deviceconf_sysprop_name, sizeof(process_deviceconf_sysprop_name),
+ "persist.device_config.memory_safety_native.permissive_reenable_timer.process.%s",
+ getprogname());
+ if (property_parse_int(process_deviceconf_sysprop_name, &v)) return v;
+ if (property_parse_int(
+ "persist.device_config.memory_safety_native.permissive_reenable_timer.default", &v))
+ return v;
+ return 0;
+}
+
static inline void futex_wait(volatile void* ftx, int value) {
syscall(__NR_futex, ftx, FUTEX_WAIT, value, nullptr, nullptr, 0);
}
@@ -599,12 +654,40 @@
if (tagged_addr_ctrl < 0) {
fatal_errno("failed to PR_GET_TAGGED_ADDR_CTRL");
}
+ int previous = tagged_addr_ctrl & PR_MTE_TCF_MASK;
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.");
+ if (int reenable_timer = permissive_mte_renable_timer()) {
+ async_safe_format_log(ANDROID_LOG_ERROR, "libc",
+ "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH "
+ "MTE DISABLED FOR %d MS OF CPU TIME.",
+ reenable_timer);
+ timer_t timerid{};
+ struct sigevent sev {};
+ sev.sigev_signo = BIONIC_ENABLE_MTE;
+ sev.sigev_notify = SIGEV_THREAD_ID;
+ sev.sigev_value.sival_int = previous;
+ sev.sigev_notify_thread_id = __gettid();
+ // This MUST be CLOCK_THREAD_CPUTIME_ID. If we used CLOCK_MONOTONIC we could get stuck
+ // in an endless loop of re-running the same instruction, calling this signal handler,
+ // and re-enabling MTE before we had a chance to re-run the instruction.
+ if (timer_create(CLOCK_THREAD_CPUTIME_ID, &sev, &timerid) == -1) {
+ fatal_errno("timer_create() failed");
+ }
+ struct itimerspec its {};
+ its.it_value.tv_sec = reenable_timer / 1000;
+ its.it_value.tv_nsec = (reenable_timer % 1000) * 1000000;
+
+ if (timer_settime(timerid, 0, &its, nullptr) == -1) {
+ fatal_errno("timer_settime() failed");
+ }
+ } else {
+ async_safe_format_log(
+ ANDROID_LOG_ERROR, "libc",
+ "MTE ERROR DETECTED BUT RUNNING IN PERMISSIVE MODE. CONTINUING WITH MTE DISABLED.");
+ }
pthread_mutex_unlock(&crash_mutex);
}
diff --git a/debuggerd/test_permissive_mte/mte_crash.cpp b/debuggerd/test_permissive_mte/mte_crash.cpp
index 97ad73f..75b70ed 100644
--- a/debuggerd/test_permissive_mte/mte_crash.cpp
+++ b/debuggerd/test_permissive_mte/mte_crash.cpp
@@ -20,5 +20,14 @@
int main(int, char**) {
volatile char* f = (char*)malloc(1);
printf("%c\n", f[17]);
+#ifdef __aarch64__
+ if (getenv("MTE_PERMISSIVE_REENABLE_TIME_CPUMS")) {
+ // Burn some cycles because the MTE_PERMISSIVE_REENABLE_TIME_CPUMS is based on CPU clock.
+ for (int i = 0; i < 1000000000; ++i) {
+ asm("isb");
+ }
+ printf("%c\n", f[17]);
+ }
+#endif
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
index 0203adc..544299d 100644
--- a/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
+++ b/debuggerd/test_permissive_mte/src/com/android/tests/debuggerd/PermissiveMteTest.java
@@ -97,6 +97,34 @@
}
assertThat(numberTombstones).isEqualTo(1);
}
+
+ @Test
+ public void testReenableCrash() throws Exception {
+ CommandResult result =
+ getDevice().executeShellV2Command("MTE_PERMISSIVE=1 MTE_PERMISSIVE_REENABLE_TIME_CPUMS=1 "
+ + "/data/local/tmp/mte_crash testReenableCrash "
+ + 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("testReenableCrash"))) {
+ continue;
+ }
+ numberTombstones++;
+ }
+ assertThat(numberTombstones).isEqualTo(2);
+ }
+
@Test
public void testCrashProperty() throws Exception {
String prevValue = getDevice().getProperty("persist.sys.mte.permissive");