From 2ef47f8f6d3d71ea08da7655c1a5edad91a64670 Mon Sep 17 00:00:00 2001 From: Florian Mayer Date: Tue, 10 May 2022 17:55:08 -0700 Subject: [PATCH] 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 --- init/service.cpp | 22 +++ init/service.h | 3 + init/test_upgrade_mte/Android.bp | 37 +++++ init/test_upgrade_mte/AndroidTest.xml | 30 ++++ init/test_upgrade_mte/mte_upgrade_test.rc | 24 +++ .../mte_upgrade_test_helper.cpp | 66 ++++++++ .../android/tests/init/MteUpgradeTest.java | 151 ++++++++++++++++++ 7 files changed, 333 insertions(+) create mode 100644 init/test_upgrade_mte/Android.bp create mode 100644 init/test_upgrade_mte/AndroidTest.xml create mode 100644 init/test_upgrade_mte/mte_upgrade_test.rc create mode 100644 init/test_upgrade_mte/mte_upgrade_test_helper.cpp create mode 100644 init/test_upgrade_mte/src/com/android/tests/init/MteUpgradeTest.java diff --git a/init/service.cpp b/init/service.cpp index 01dd68561..d5d9a3a25 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -36,6 +36,8 @@ #include #include +#include + #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 @@ void Service::Reap(const siginfo_t& siginfo) { #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 @@ void Service::RunService(const std::optional& override_mount_nam 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 @@ Result Service::Start() { 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 d233cbf43..a82ea7780 100644 --- a/init/service.h +++ b/init/service.h @@ -168,6 +168,7 @@ class Service { 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 fatal_reboot_target_; // reboot target of fatal handler @@ -180,6 +181,8 @@ class Service { std::vector sockets_; std::vector files_; std::vector> environment_vars_; + // Environment variables that only get applied to the next run. + std::vector> 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 000000000..eec48dbdd --- /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 000000000..b89cde8df --- /dev/null +++ b/init/test_upgrade_mte/AndroidTest.xml @@ -0,0 +1,30 @@ + + + + \ 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 000000000..a3e596ccd --- /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 000000000..10af06b36 --- /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 +#include +#include +#include +#include +#include +#include +#include + +int MaybeDowngrade() { + int res = prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0); + if (res == -1) return 1; + if (static_cast(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(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 000000000..f4e4a9c93 --- /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 segvCodeNames = new ArrayList(); + 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 segvCodeNames = new ArrayList(); + 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 + } +}