Upgrade MTE to SYNC after ASYNC crash.

Bug: 169277947
Test: atest mte_ugprade_test on emulator.
Test: ASSUMPTION_FAILED on non-MTE
Test: ASSUMPTION_FAILED on HWASan
Change-Id: I5328d094ffb106abaa548feb76058c9ebd11d745
diff --git a/init/service.cpp b/init/service.cpp
index 01dd685..d5d9a3a 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -36,6 +36,8 @@
 #include <processgroup/processgroup.h>
 #include <selinux/selinux.h>
 
+#include <string>
+
 #include "lmkd_service.h"
 #include "service_list.h"
 #include "util.h"
@@ -53,6 +55,7 @@
 
 using android::base::boot_clock;
 using android::base::GetBoolProperty;
+using android::base::GetIntProperty;
 using android::base::GetProperty;
 using android::base::Join;
 using android::base::make_scope_guard;
@@ -317,6 +320,20 @@
 #endif
     const bool is_process_updatable = !use_bootstrap_ns_ && is_apex_updatable;
 
+#ifdef SEGV_MTEAERR
+    // As a precaution, we only upgrade a service once per reboot, to limit
+    // the potential impact.
+    // TODO(b/244471804): Once we have a kernel API to get sicode, compare it to MTEAERR here.
+    bool should_upgrade_mte = siginfo.si_code != CLD_EXITED && siginfo.si_status == SIGSEGV &&
+                              !upgraded_mte_;
+
+    if (should_upgrade_mte) {
+        LOG(INFO) << "Upgrading service " << name_ << " to sync MTE";
+        once_environment_vars_.emplace_back("BIONIC_MEMTAG_UPGRADE_SECS", "60");
+        upgraded_mte_ = true;
+    }
+#endif
+
     // If we crash > 4 times in 'fatal_crash_window_' minutes or before boot_completed,
     // reboot into bootloader or set crashing property
     boot_clock::time_point now = boot_clock::now();
@@ -481,6 +498,9 @@
         LOG(FATAL) << "Service '" << name_ << "' failed to set up namespaces: " << result.error();
     }
 
+    for (const auto& [key, value] : once_environment_vars_) {
+        setenv(key.c_str(), value.c_str(), 1);
+    }
     for (const auto& [key, value] : environment_vars_) {
         setenv(key.c_str(), value.c_str(), 1);
     }
@@ -642,6 +662,8 @@
         return ErrnoError() << "Failed to fork";
     }
 
+    once_environment_vars_.clear();
+
     if (oom_score_adjust_ != DEFAULT_OOM_SCORE_ADJUST) {
         std::string oom_str = std::to_string(oom_score_adjust_);
         std::string oom_file = StringPrintf("/proc/%d/oom_score_adj", pid);
diff --git a/init/service.h b/init/service.h
index d233cbf..a82ea77 100644
--- a/init/service.h
+++ b/init/service.h
@@ -168,6 +168,7 @@
     android::base::boot_clock::time_point time_started_;  // time of last start
     android::base::boot_clock::time_point time_crashed_;  // first crash within inspection window
     int crash_count_;                     // number of times crashed within window
+    bool upgraded_mte_ = false;           // whether we upgraded async MTE -> sync MTE before
     std::chrono::minutes fatal_crash_window_ = 4min;  // fatal() when more than 4 crashes in it
     std::optional<std::string> fatal_reboot_target_;  // reboot target of fatal handler
 
@@ -180,6 +181,8 @@
     std::vector<SocketDescriptor> sockets_;
     std::vector<FileDescriptor> files_;
     std::vector<std::pair<std::string, std::string>> environment_vars_;
+    // Environment variables that only get applied to the next run.
+    std::vector<std::pair<std::string, std::string>> once_environment_vars_;
 
     Subcontext* subcontext_;
     Action onrestart_;  // Commands to execute on restart.
diff --git a/init/test_upgrade_mte/Android.bp b/init/test_upgrade_mte/Android.bp
new file mode 100644
index 0000000..eec48db
--- /dev/null
+++ b/init/test_upgrade_mte/Android.bp
@@ -0,0 +1,37 @@
+// 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_upgrade_test_helper",
+  srcs: ["mte_upgrade_test_helper.cpp"],
+  sanitize: {
+    memtag_heap: true,
+    diag: {
+      memtag_heap: false,
+    },
+  },
+  init_rc: [
+    "mte_upgrade_test.rc",
+  ],
+}
+
+java_test_host {
+    name: "mte_upgrade_test",
+    libs: ["tradefed"],
+    static_libs: ["frameworks-base-hostutils", "cts-install-lib-host"],
+    srcs:  ["src/**/MteUpgradeTest.java", ":libtombstone_proto-src"],
+    data: [":mte_upgrade_test_helper", "mte_upgrade_test.rc" ],
+    test_config: "AndroidTest.xml",
+    test_suites: ["general-tests"],
+}
diff --git a/init/test_upgrade_mte/AndroidTest.xml b/init/test_upgrade_mte/AndroidTest.xml
new file mode 100644
index 0000000..b89cde8
--- /dev/null
+++ b/init/test_upgrade_mte/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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 MTE upgrade tests">
+    <option name="test-suite-tag" value="init_test_upgrade_mte" />
+    <option name="test-suite-tag" value="apct" />
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+      <option name="cleanup" value="true" />
+      <option name="remount-system" value="true" />
+      <option name="push" value="mte_upgrade_test.rc->/system/etc/init/mte_upgrade_test.rc" />
+      <option name="push" value="mte_upgrade_test_helper->/system/bin/mte_upgrade_test_helper" />
+      <option name="push" value="mte_upgrade_test_helper->/data/local/tmp/app_process64" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="mte_upgrade_test.jar" />
+    </test>
+</configuration>
\ No newline at end of file
diff --git a/init/test_upgrade_mte/mte_upgrade_test.rc b/init/test_upgrade_mte/mte_upgrade_test.rc
new file mode 100644
index 0000000..a3e596c
--- /dev/null
+++ b/init/test_upgrade_mte/mte_upgrade_test.rc
@@ -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.
+
+service mte_upgrade_test_helper /system/bin/mte_upgrade_test_helper ${sys.mte_crash_test_uuid}
+  class late_start
+  disabled
+  seclabel u:r:su:s0
+
+service mte_upgrade_test_helper_overridden /system/bin/mte_upgrade_test_helper ${sys.mte_crash_test_uuid}
+  class late_start
+  disabled
+  seclabel u:r:su:s0
+  setenv BIONIC_MEMTAG_UPGRADE_SECS 0
diff --git a/init/test_upgrade_mte/mte_upgrade_test_helper.cpp b/init/test_upgrade_mte/mte_upgrade_test_helper.cpp
new file mode 100644
index 0000000..10af06b
--- /dev/null
+++ b/init/test_upgrade_mte/mte_upgrade_test_helper.cpp
@@ -0,0 +1,66 @@
+/*
+ * 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 <linux/prctl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <time.h>
+#include <unistd.h>
+
+int MaybeDowngrade() {
+    int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+    if (res == -1) return 1;
+    if (static_cast<unsigned long>(res) & PR_MTE_TCF_ASYNC) return 2;
+    time_t t = time(nullptr);
+    while (time(nullptr) - t < 100) {
+        res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+        if (static_cast<unsigned long>(res) & PR_MTE_TCF_ASYNC) {
+            return 0;
+        }
+        sleep(1);
+    }
+    return 3;
+}
+
+int main(int argc, char** argv) {
+    if (argc == 2 && strcmp(argv[1], "--check-downgrade") == 0) {
+        return MaybeDowngrade();
+    }
+    int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0);
+    if (res == -1) abort();
+    if (argc == 2 && strcmp(argv[1], "--get-mode") == 0) {
+        if (res & PR_MTE_TCF_ASYNC) {
+            return 1;
+        }
+        if (res & PR_MTE_TCF_SYNC) {
+            return 2;
+        }
+        abort();
+    }
+
+    if (res & PR_MTE_TCF_ASYNC && res & PR_MTE_TCF_SYNC) {
+        // Disallow automatic upgrade from ASYNC mode.
+        if (prctl(PR_SET_TAGGED_ADDR_CTRL, res & ~PR_MTE_TCF_SYNC, 0, 0, 0) == -1) abort();
+    }
+    volatile char* f = (char*)malloc(1);
+    f[17] = 'x';
+    char buf[1];
+    read(1, buf, 1);
+    return 0;
+}
diff --git a/init/test_upgrade_mte/src/com/android/tests/init/MteUpgradeTest.java b/init/test_upgrade_mte/src/com/android/tests/init/MteUpgradeTest.java
new file mode 100644
index 0000000..f4e4a9c
--- /dev/null
+++ b/init/test_upgrade_mte/src/com/android/tests/init/MteUpgradeTest.java
@@ -0,0 +1,151 @@
+/*
+ * 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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class MteUpgradeTest extends BaseHostJUnit4Test {
+    @Before
+    public void setUp() throws Exception {
+        CommandResult result =
+                getDevice().executeShellV2Command("/system/bin/mte_upgrade_test_helper --checking");
+        assumeTrue("mte_upgrade_test_binary needs to segfault", result.getExitCode() == 139);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Easier here than in a finally in testCrash, and doesn't really hurt.
+        getDevice().executeShellV2Command("stop mte_upgrade_test_helper");
+        getDevice().executeShellV2Command("stop mte_upgrade_test_helper_overridden");
+        getDevice().setProperty("sys.mte_crash_test_uuid", "");
+    }
+
+    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;
+    }
+
+    @Test
+    public void testCrash() throws Exception {
+        String uuid = java.util.UUID.randomUUID().toString();
+        getDevice().reboot();
+        assertThat(getDevice().setProperty("sys.mte_crash_test_uuid", uuid)).isTrue();
+
+        CommandResult result = getDevice().executeShellV2Command("start mte_upgrade_test_helper");
+        assertThat(result.getExitCode()).isEqualTo(0);
+        java.lang.Thread.sleep(20000);
+        String[] tombstonesAfter = getDevice().getChildren("/data/tombstones");
+        ArrayList<String> segvCodeNames = new ArrayList<String>();
+        for (String tombstone : tombstonesAfter) {
+            if (!tombstone.endsWith(".pb")) {
+                continue;
+            }
+            String tombstoneFilename = "/data/tombstones/" + tombstone;
+            Tombstone tombstoneProto = parseTombstone(tombstoneFilename);
+            if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(uuid))) {
+                continue;
+            }
+            assertThat(tombstoneProto.getSignalInfo().getName()).isEqualTo("SIGSEGV");
+            segvCodeNames.add(tombstoneProto.getSignalInfo().getCodeName());
+            getDevice().deleteFile(tombstoneFilename);
+            // remove the non .pb file as well.
+            getDevice().deleteFile(tombstoneFilename.substring(0, tombstoneFilename.length() - 3));
+        }
+        assertThat(segvCodeNames.size()).isAtLeast(3);
+        assertThat(segvCodeNames.get(0)).isEqualTo("SEGV_MTEAERR");
+        assertThat(segvCodeNames.get(1)).isEqualTo("SEGV_MTESERR");
+        assertThat(segvCodeNames.get(2)).isEqualTo("SEGV_MTEAERR");
+    }
+
+    @Test
+    public void testCrashOverridden() throws Exception {
+        String uuid = java.util.UUID.randomUUID().toString();
+        getDevice().reboot();
+        assertThat(getDevice().setProperty("sys.mte_crash_test_uuid", uuid)).isTrue();
+
+        CommandResult result =
+                getDevice().executeShellV2Command("start mte_upgrade_test_helper_overridden");
+        assertThat(result.getExitCode()).isEqualTo(0);
+        java.lang.Thread.sleep(20000);
+        String[] tombstonesAfter = getDevice().getChildren("/data/tombstones");
+        ArrayList<String> segvCodeNames = new ArrayList<String>();
+        for (String tombstone : tombstonesAfter) {
+            if (!tombstone.endsWith(".pb")) {
+                continue;
+            }
+            String tombstoneFilename = "/data/tombstones/" + tombstone;
+            Tombstone tombstoneProto = parseTombstone(tombstoneFilename);
+            if (!tombstoneProto.getCommandLineList().stream().anyMatch(x -> x.contains(uuid))) {
+                continue;
+            }
+            assertThat(tombstoneProto.getSignalInfo().getName()).isEqualTo("SIGSEGV");
+            segvCodeNames.add(tombstoneProto.getSignalInfo().getCodeName());
+            getDevice().deleteFile(tombstoneFilename);
+            // remove the non .pb file as well.
+            getDevice().deleteFile(tombstoneFilename.substring(0, tombstoneFilename.length() - 3));
+        }
+        assertThat(segvCodeNames.size()).isAtLeast(3);
+        assertThat(segvCodeNames.get(0)).isEqualTo("SEGV_MTEAERR");
+        assertThat(segvCodeNames.get(1)).isEqualTo("SEGV_MTEAERR");
+        assertThat(segvCodeNames.get(2)).isEqualTo("SEGV_MTEAERR");
+    }
+
+    @Test
+    public void testDowngrade() throws Exception {
+        CommandResult result =
+                getDevice()
+                        .executeShellV2Command(
+                                "MEMTAG_OPTIONS=async BIONIC_MEMTAG_UPGRADE_SECS=5"
+                                        + " /system/bin/mte_upgrade_test_helper --check-downgrade");
+        assertThat(result.getExitCode()).isEqualTo(0);
+    }
+
+    @Test
+    public void testAppProcess() throws Exception {
+        CommandResult result =
+                getDevice()
+                        .executeShellV2Command(
+                                "MEMTAG_OPTIONS=async BIONIC_MEMTAG_UPGRADE_SECS=5"
+                                        + " /data/local/tmp/app_process64 --get-mode");
+        assertThat(result.getExitCode()).isEqualTo(1);  // ASYNC
+    }
+}