Merge "vts_libsnapshot_test: Fix test flakiness."
diff --git a/fs_mgr/fs_mgr.cpp b/fs_mgr/fs_mgr.cpp
index 1f54f5b..9fe8e18 100644
--- a/fs_mgr/fs_mgr.cpp
+++ b/fs_mgr/fs_mgr.cpp
@@ -903,9 +903,9 @@
// Deal with alternate entries for the same point which are required to be all following
// each other.
if (mounted) {
- LERROR << __FUNCTION__ << "(): skipping fstab dup mountpoint=" << fstab[i].mount_point
- << " rec[" << i << "].fs_type=" << fstab[i].fs_type << " already mounted as "
- << fstab[*attempted_idx].fs_type;
+ LINFO << __FUNCTION__ << "(): skipping fstab dup mountpoint=" << fstab[i].mount_point
+ << " rec[" << i << "].fs_type=" << fstab[i].fs_type << " already mounted as "
+ << fstab[*attempted_idx].fs_type;
continue;
}
@@ -933,9 +933,9 @@
*attempted_idx = i;
mounted = true;
if (i != start_idx) {
- LERROR << __FUNCTION__ << "(): Mounted " << fstab[i].blk_device << " on "
- << fstab[i].mount_point << " with fs_type=" << fstab[i].fs_type
- << " instead of " << fstab[start_idx].fs_type;
+ LINFO << __FUNCTION__ << "(): Mounted " << fstab[i].blk_device << " on "
+ << fstab[i].mount_point << " with fs_type=" << fstab[i].fs_type
+ << " instead of " << fstab[start_idx].fs_type;
}
fs_stat &= ~FS_STAT_FULL_MOUNT_FAILED;
mount_errno = 0;
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 202a86a..107e99a 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -295,9 +295,6 @@
// stage init
CHECKCALL(mount("tmpfs", kSecondStageRes, "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
"mode=0755,uid=0,gid=0"))
-
- // First stage init stores Mainline sepolicy here.
- CHECKCALL(mkdir("/dev/selinux", 0744));
#undef CHECKCALL
SetStdioToDevNull(argv);
diff --git a/init/init_test.cpp b/init/init_test.cpp
index 05cf3fd..8362390 100644
--- a/init/init_test.cpp
+++ b/init/init_test.cpp
@@ -280,6 +280,10 @@
}
TEST(init, StopServiceByApexName) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string_view script_template = R"init(
service apex_test_service /system/bin/yes
user shell
@@ -291,6 +295,10 @@
}
TEST(init, StopMultipleServicesByApexName) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string_view script_template = R"init(
service apex_test_service_multiple_a /system/bin/yes
user shell
@@ -307,6 +315,10 @@
}
TEST(init, StopServicesFromMultipleApexes) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string_view apex_script_template = R"init(
service apex_test_service_multi_apex_a /system/bin/yes
user shell
@@ -332,6 +344,10 @@
}
TEST(init, StopServicesFromApexAndNonApex) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string_view apex_script_template = R"init(
service apex_test_service_apex_a /system/bin/yes
user shell
@@ -357,6 +373,10 @@
}
TEST(init, StopServicesFromApexMixed) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
std::string_view script_template = R"init(
service apex_test_service_mixed_a /system/bin/yes
user shell
diff --git a/init/selinux.cpp b/init/selinux.cpp
index be8c554..ab5b0a0 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -237,9 +237,9 @@
// If there is an odm partition, precompiled_sepolicy will be in
// odm/etc/selinux. Otherwise it will be in vendor/etc/selinux.
static constexpr const char vendor_precompiled_sepolicy[] =
- "/vendor/etc/selinux/precompiled_sepolicy";
+ "/vendor/etc/selinux/precompiled_sepolicy";
static constexpr const char odm_precompiled_sepolicy[] =
- "/odm/etc/selinux/precompiled_sepolicy";
+ "/odm/etc/selinux/precompiled_sepolicy";
if (access(odm_precompiled_sepolicy, R_OK) == 0) {
precompiled_sepolicy = odm_precompiled_sepolicy;
} else if (access(vendor_precompiled_sepolicy, R_OK) == 0) {
@@ -525,6 +525,32 @@
"apex_service_contexts", "apex_seapp_contexts",
"apex_test"};
+Result<void> CreateTmpfsDir() {
+ mode_t mode = 0744;
+ struct stat stat_data;
+ if (stat(kTmpfsDir.c_str(), &stat_data) != 0) {
+ if (errno != ENOENT) {
+ return ErrnoError() << "Could not stat " << kTmpfsDir;
+ }
+ if (mkdir(kTmpfsDir.c_str(), mode) != 0) {
+ return ErrnoError() << "Could not mkdir " << kTmpfsDir;
+ }
+ } else {
+ if (!S_ISDIR(stat_data.st_mode)) {
+ return Error() << kTmpfsDir << " exists and is not a directory.";
+ }
+ LOG(WARNING) << "Directory " << kTmpfsDir << " already exists";
+ }
+
+ // Need to manually call chmod because mkdir will create a folder with
+ // permissions mode & ~umask.
+ if (chmod(kTmpfsDir.c_str(), mode) != 0) {
+ return ErrnoError() << "Could not chmod " << kTmpfsDir;
+ }
+
+ return {};
+}
+
Result<void> PutFileInTmpfs(ZipArchiveHandle archive, const std::string& fileName) {
ZipEntry entry;
std::string dstPath = kTmpfsDir + fileName;
@@ -538,7 +564,7 @@
unique_fd fd(TEMP_FAILURE_RETRY(
open(dstPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR)));
if (fd == -1) {
- return Error() << "Failed to open " << dstPath;
+ return ErrnoError() << "Failed to open " << dstPath;
}
ret = ExtractEntryToFile(archive, &entry, fd);
@@ -568,6 +594,11 @@
auto handle_guard = android::base::make_scope_guard([&handle] { CloseArchive(handle); });
+ auto create = CreateTmpfsDir();
+ if (!create.ok()) {
+ return create.error();
+ }
+
for (const auto& file : kApexSepolicy) {
auto extract = PutFileInTmpfs(handle, file);
if (!extract.ok()) {
diff --git a/init/service.cpp b/init/service.cpp
index 99a0367..4cf409c 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;
@@ -320,6 +323,20 @@
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 @@
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 @@
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 6d9a0ca..ab19865 100644
--- a/init/service.h
+++ b/init/service.h
@@ -171,6 +171,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
@@ -183,6 +184,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..1bfc76c
--- /dev/null
+++ b/init/test_upgrade_mte/Android.bp
@@ -0,0 +1,41 @@
+// 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 {
+ default_applicable_licenses: ["system_core_init_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
+ }
+}
diff --git a/libcutils/Android.bp b/libcutils/Android.bp
index c8bfb01..fde30ad 100644
--- a/libcutils/Android.bp
+++ b/libcutils/Android.bp
@@ -168,6 +168,9 @@
target: {
linux_bionic: {
enabled: true,
+ static_libs: [
+ "libasync_safe",
+ ],
},
not_windows: {
srcs: libcutils_nonwindows_sources + [
@@ -190,6 +193,9 @@
],
},
android: {
+ static_libs: [
+ "libasync_safe",
+ ],
srcs: libcutils_nonwindows_sources + [
"android_reboot.cpp",
"ashmem-dev.cpp",
diff --git a/libcutils/include/cutils/native_handle.h b/libcutils/include/cutils/native_handle.h
index 4f07456..e46e7cd 100644
--- a/libcutils/include/cutils/native_handle.h
+++ b/libcutils/include/cutils/native_handle.h
@@ -49,18 +49,28 @@
typedef const native_handle_t* buffer_handle_t;
/*
- * native_handle_close
- *
- * closes the file descriptors contained in this native_handle_t
- *
+ * Closes the file descriptors contained in this native_handle_t, which may
+ * either be untagged or tagged for ownership by this native_handle_t via
+ * native_handle_set_tag(). Mixing untagged and tagged fds in the same
+ * native_handle_t is not permitted and triggers an fdsan exception, but
+ * native_handle_set_fdsan_tag() can be used to bring consistency if this is
+ * intentional.
+ *
+ * If it's known that fds are tagged, prefer native_handle_close_with_tag() for
+ * better safety.
+ *
* return 0 on success, or a negative error code on failure
- *
*/
int native_handle_close(const native_handle_t* h);
/*
- * native_handle_init
- *
+ * Equivalent to native_handle_close(), but throws an fdsan exception if the fds
+ * are untagged. Use if it's known that the fds in this native_handle_t were
+ * previously tagged via native_handle_set_tag().
+ */
+int native_handle_close_with_tag(const native_handle_t* h);
+
+/*
* Initializes a native_handle_t from storage. storage must be declared with
* NATIVE_HANDLE_DECLARE_STORAGE. numFds and numInts must not respectively
* exceed maxFds and maxInts used to declare the storage.
@@ -68,33 +78,42 @@
native_handle_t* native_handle_init(char* storage, int numFds, int numInts);
/*
- * native_handle_create
- *
- * creates a native_handle_t and initializes it. must be destroyed with
+ * Creates a native_handle_t and initializes it. Must be destroyed with
* native_handle_delete(). Note that numFds must be <= NATIVE_HANDLE_MAX_FDS,
* numInts must be <= NATIVE_HANDLE_MAX_INTS, and both must be >= 0.
- *
*/
native_handle_t* native_handle_create(int numFds, int numInts);
/*
- * native_handle_clone
- *
- * creates a native_handle_t and initializes it from another native_handle_t.
+ * Updates the fdsan tag for any file descriptors contained in the supplied
+ * handle to indicate that they are owned by this handle and should only be
+ * closed via native_handle_close()/native_handle_close_with_tag(). Each fd in
+ * the handle must have a tag of either 0 (unset) or the tag associated with
+ * this handle, otherwise an fdsan exception will be triggered.
+ */
+void native_handle_set_fdsan_tag(const native_handle_t* handle);
+
+/*
+ * Clears the fdsan tag for any file descriptors contained in the supplied
+ * native_handle_t. Use if this native_handle_t is giving up ownership of its
+ * fds, but the fdsan tags were previously set. Each fd in the handle must have
+ * a tag of either 0 (unset) or the tag associated with this handle, otherwise
+ * an fdsan exception will be triggered.
+ */
+void native_handle_unset_fdsan_tag(const native_handle_t* handle);
+
+/*
+ * Creates a native_handle_t and initializes it from another native_handle_t.
* Must be destroyed with native_handle_delete().
- *
*/
native_handle_t* native_handle_clone(const native_handle_t* handle);
/*
- * native_handle_delete
- *
- * frees a native_handle_t allocated with native_handle_create().
+ * Frees a native_handle_t allocated with native_handle_create().
* This ONLY frees the memory allocated for the native_handle_t, but doesn't
* close the file descriptors; which can be achieved with native_handle_close().
- *
+ *
* return 0 on success, or a negative error code on failure
- *
*/
int native_handle_delete(native_handle_t* h);
diff --git a/libcutils/native_handle.cpp b/libcutils/native_handle.cpp
index 5804ab1..b85c93b 100644
--- a/libcutils/native_handle.cpp
+++ b/libcutils/native_handle.cpp
@@ -22,13 +22,74 @@
#include <string.h>
#include <unistd.h>
+// Needs to come after stdlib includes to capture the __BIONIC__ definition
+#ifdef __BIONIC__
+#include <android/fdsan.h>
+#endif
+
+namespace {
+
+#if !defined(__BIONIC__)
+// fdsan stubs when not linked against bionic
+#define ANDROID_FDSAN_OWNER_TYPE_NATIVE_HANDLE 0
+
+uint64_t android_fdsan_create_owner_tag(int /*type*/, uint64_t /*tag*/) {
+ return 0;
+}
+uint64_t android_fdsan_get_owner_tag(int /*fd*/) {
+ return 0;
+}
+int android_fdsan_close_with_tag(int fd, uint64_t /*tag*/) {
+ return close(fd);
+}
+void android_fdsan_exchange_owner_tag(int /*fd*/, uint64_t /*expected_tag*/, uint64_t /*tag*/) {}
+#endif // !__BIONIC__
+
+uint64_t get_fdsan_tag(const native_handle_t* handle) {
+ return android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_NATIVE_HANDLE,
+ reinterpret_cast<uint64_t>(handle));
+}
+
+int close_internal(const native_handle_t* h, bool allowUntagged) {
+ if (!h) return 0;
+
+ if (h->version != sizeof(native_handle_t)) return -EINVAL;
+
+ const int numFds = h->numFds;
+ uint64_t tag;
+ if (allowUntagged && numFds > 0 && android_fdsan_get_owner_tag(h->data[0]) == 0) {
+ tag = 0;
+ } else {
+ tag = get_fdsan_tag(h);
+ }
+ int saved_errno = errno;
+ for (int i = 0; i < numFds; ++i) {
+ android_fdsan_close_with_tag(h->data[i], tag);
+ }
+ errno = saved_errno;
+ return 0;
+}
+
+void swap_fdsan_tags(const native_handle_t* handle, uint64_t expected_tag, uint64_t new_tag) {
+ if (!handle || handle->version != sizeof(native_handle_t)) return;
+
+ for (int i = 0; i < handle->numFds; i++) {
+ // allow for idempotence to make the APIs easier to use
+ if (android_fdsan_get_owner_tag(handle->data[i]) != new_tag) {
+ android_fdsan_exchange_owner_tag(handle->data[i], expected_tag, new_tag);
+ }
+ }
+}
+
+} // anonymous namespace
+
native_handle_t* native_handle_init(char* storage, int numFds, int numInts) {
- if ((uintptr_t) storage % alignof(native_handle_t)) {
+ if ((uintptr_t)storage % alignof(native_handle_t)) {
errno = EINVAL;
return NULL;
}
- native_handle_t* handle = (native_handle_t*) storage;
+ native_handle_t* handle = (native_handle_t*)storage;
handle->version = sizeof(native_handle_t);
handle->numFds = numFds;
handle->numInts = numInts;
@@ -52,6 +113,14 @@
return h;
}
+void native_handle_set_fdsan_tag(const native_handle_t* handle) {
+ swap_fdsan_tags(handle, 0, get_fdsan_tag(handle));
+}
+
+void native_handle_unset_fdsan_tag(const native_handle_t* handle) {
+ swap_fdsan_tags(handle, get_fdsan_tag(handle), 0);
+}
+
native_handle_t* native_handle_clone(const native_handle_t* handle) {
native_handle_t* clone = native_handle_create(handle->numFds, handle->numInts);
if (clone == NULL) return NULL;
@@ -81,15 +150,9 @@
}
int native_handle_close(const native_handle_t* h) {
- if (!h) return 0;
+ return close_internal(h, /*allowUntagged=*/true);
+}
- if (h->version != sizeof(native_handle_t)) return -EINVAL;
-
- int saved_errno = errno;
- const int numFds = h->numFds;
- for (int i = 0; i < numFds; ++i) {
- close(h->data[i]);
- }
- errno = saved_errno;
- return 0;
+int native_handle_close_with_tag(const native_handle_t* h) {
+ return close_internal(h, /*allowUntagged=*/false);
}