Merge "Upgrade MTE to SYNC after ASYNC crash."

This commit is contained in:
Treehugger Robot 2022-09-06 21:29:14 +00:00 committed by Gerrit Code Review
commit c113dc3a95
7 changed files with 333 additions and 0 deletions

View file

@ -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;
@ -320,6 +323,20 @@ void Service::Reap(const siginfo_t& siginfo) {
mount_namespace_.has_value() && *mount_namespace_ == NS_DEFAULT;
const bool is_process_updatable = use_default_mount_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();
@ -484,6 +501,9 @@ void Service::RunService(const std::vector<Descriptor>& descriptors,
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);
}
@ -628,6 +648,8 @@ Result<void> 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);

View file

@ -171,6 +171,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<std::string> fatal_reboot_target_; // reboot target of fatal handler
@ -183,6 +184,8 @@ class Service {
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.

View file

@ -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"],
}

View file

@ -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>

View file

@ -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

View file

@ -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;
}

View file

@ -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
}
}