Merge changes Idb791187,Iad926450 into main
* changes:
libprefetch: Remove `start` command
libprefetch: replay/record checks if they can run on its own
diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp
index b614aab..adfb16b 100644
--- a/fs_mgr/libsnapshot/snapshot.cpp
+++ b/fs_mgr/libsnapshot/snapshot.cpp
@@ -351,9 +351,14 @@
LOG(INFO) << "Removing all update state.";
- if (!RemoveAllSnapshots(lock)) {
- LOG(ERROR) << "Could not remove all snapshots";
- return false;
+ if (ReadUpdateState(lock) != UpdateState::None) {
+ // Only call this if we're actually cancelling an update. It's not
+ // expected to yield anything otherwise, and firing up gsid on normal
+ // boot is expensive.
+ if (!RemoveAllSnapshots(lock)) {
+ LOG(ERROR) << "Could not remove all snapshots";
+ return false;
+ }
}
// It's okay if these fail:
diff --git a/init/Android.bp b/init/Android.bp
index dbdf80b..b209c47 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -599,6 +599,7 @@
],
static_libs: [
"libbase",
+ "libfstab",
"libselinux",
"libpropertyinfoserializer",
"libpropertyinfoparser",
diff --git a/init/service_test.cpp b/init/service_test.cpp
index 53b53ed..d75d4f1 100644
--- a/init/service_test.cpp
+++ b/init/service_test.cpp
@@ -27,6 +27,7 @@
#include <android-base/file.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <fstab/fstab.h>
#include <selinux/selinux.h>
#include <sys/signalfd.h>
#include "lmkd_service.h"
@@ -280,5 +281,74 @@
INSTANTIATE_TEST_SUITE_P(service, ServiceStopTest, testing::Values(false, true));
+// Entering a network namespace requires remounting sysfs to update contents of
+// /sys/class/net whose contents depend on the network namespace of the process
+// that mounted it rather than the effective network namespace of the reading
+// process.
+//
+// A side effect of the remounting is unmounting all filesystems mounted under
+// /sys, like tracefs. Verify that init doesn't leave them unmounted by
+// accident.
+TEST(service, enter_namespace_net_preserves_mounts) {
+ if (getuid() != 0) {
+ GTEST_SKIP() << "Must be run as root.";
+ return;
+ }
+
+ struct ScopedNetNs {
+ std::string name;
+ ScopedNetNs(std::string n) : name(n) {
+ EXPECT_EQ(system(("/system/bin/ip netns add " + name).c_str()), 0);
+ }
+ ~ScopedNetNs() { EXPECT_EQ(system(("/system/bin/ip netns delete " + name).c_str()), 0); }
+ };
+ const ScopedNetNs netns("test_ns");
+
+ static constexpr std::string_view kServiceName = "ServiceA";
+ static constexpr std::string_view kScriptTemplate = R"init(
+service $name /system/bin/yes
+ user shell
+ group shell
+ seclabel $selabel
+ enter_namespace net /mnt/run/$ns_name
+)init";
+
+ std::string script = StringReplace(kScriptTemplate, "$name", kServiceName, false);
+ script = StringReplace(script, "$selabel", GetSecurityContext(), false);
+ script = StringReplace(script, "$ns_name", netns.name, false);
+
+ ServiceList& service_list = ServiceList::GetInstance();
+ Parser parser;
+ parser.AddSectionParser("service", std::make_unique<ServiceParser>(&service_list, nullptr));
+
+ TemporaryFile tf;
+ ASSERT_GE(tf.fd, 0);
+ ASSERT_TRUE(WriteStringToFd(script, tf.fd));
+ ASSERT_TRUE(parser.ParseConfig(tf.path));
+
+ Service* const service = ServiceList::GetInstance().FindService(kServiceName);
+ ASSERT_NE(service, nullptr);
+ ASSERT_RESULT_OK(service->Start());
+ ASSERT_TRUE(service->IsRunning());
+
+ android::fs_mgr::Fstab root_mounts;
+ ASSERT_TRUE(ReadFstabFromFile("/proc/mounts", &root_mounts));
+
+ android::fs_mgr::Fstab ns_mounts;
+ ASSERT_TRUE(ReadFstabFromFile(StringReplace("/proc/$pid/mounts", "$pid",
+ std::to_string(service->pid()), /*all=*/false),
+ &ns_mounts));
+
+ for (const auto& expected_mount : root_mounts) {
+ auto it = std::find_if(ns_mounts.begin(), ns_mounts.end(), [&](const auto& ns_mount) {
+ return ns_mount.mount_point == expected_mount.mount_point;
+ });
+ EXPECT_TRUE(it != ns_mounts.end()) << StringPrintf(
+ "entering network namespace unmounted %s", expected_mount.mount_point.c_str());
+ }
+
+ ServiceList::GetInstance().RemoveService(*service);
+}
+
} // namespace init
} // namespace android
diff --git a/init/service_utils.cpp b/init/service_utils.cpp
index 0e19bcc..f8821a0 100644
--- a/init/service_utils.cpp
+++ b/init/service_utils.cpp
@@ -18,11 +18,11 @@
#include <fcntl.h>
#include <grp.h>
-#include <map>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <map>
#include <android-base/file.h>
#include <android-base/logging.h>
@@ -31,6 +31,7 @@
#include <android-base/strings.h>
#include <cutils/android_get_control_file.h>
#include <cutils/sockets.h>
+#include <fstab/fstab.h>
#include <processgroup/processgroup.h>
#include "mount_namespace.h"
@@ -82,12 +83,29 @@
}
}
if (remount_sys) {
+ android::fs_mgr::Fstab mounts;
+ if (!ReadFstabFromFile("/proc/mounts", &mounts)) {
+ LOG(ERROR) << "Could not read /proc/mounts";
+ }
if (umount2("/sys", MNT_DETACH) == -1) {
return ErrnoError() << "Could not umount(/sys)";
}
- if (mount("", "/sys", "sysfs", kSafeFlags, "") == -1) {
+ if (mount("sysfs", "/sys", "sysfs", kSafeFlags, "") == -1) {
return ErrnoError() << "Could not mount(/sys)";
}
+ // Unmounting /sys also unmounts all nested mounts like tracefs.
+ //
+ // Look up the filesystems that were mounted under /sys before we wiped
+ // it and attempt to restore them.
+ for (const auto& entry : mounts) {
+ if (entry.mount_point.starts_with("/sys/")) {
+ if (mount(entry.blk_device.c_str(), entry.mount_point.c_str(),
+ entry.fs_type.c_str(), entry.flags, "")) {
+ LOG(WARNING) << "Could not mount(" << entry.mount_point
+ << ") after switching netns: " << ErrnoError().str();
+ }
+ }
+ }
}
return {};
}
diff --git a/libcutils/ashmem-dev.cpp b/libcutils/ashmem-dev.cpp
index cfc2d3a..80c4f4c 100644
--- a/libcutils/ashmem-dev.cpp
+++ b/libcutils/ashmem-dev.cpp
@@ -44,6 +44,8 @@
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
+#include "ashmem-internal.h"
+
/* ashmem identity */
static dev_t __ashmem_rdev;
/*
@@ -114,15 +116,8 @@
// Check if kernel support exists, otherwise fall back to ashmem.
// This code needs to build on old API levels, so we can't use the libc
// wrapper.
- //
- // MFD_NOEXEC_SEAL is used to match the semantics of the ashmem device,
- // which did not have executable permissions. This also seals the executable
- // permissions of the buffer (i.e. they cannot be changed by fchmod()).
- //
- // MFD_NOEXEC_SEAL implies MFD_ALLOW_SEALING.
-
android::base::unique_fd fd(
- syscall(__NR_memfd_create, "test_android_memfd", MFD_CLOEXEC | MFD_NOEXEC_SEAL));
+ syscall(__NR_memfd_create, "test_android_memfd", MFD_CLOEXEC | MFD_ALLOW_SEALING));
if (fd == -1) {
ALOGE("memfd_create failed: %m, no memfd support");
return false;
@@ -156,7 +151,7 @@
return true;
}
-static bool has_memfd_support() {
+bool has_memfd_support() {
static bool memfd_supported = __has_memfd_support();
return memfd_supported;
}
@@ -290,13 +285,7 @@
static int memfd_create_region(const char* name, size_t size) {
// This code needs to build on old API levels, so we can't use the libc
// wrapper.
- //
- // MFD_NOEXEC_SEAL to match the semantics of the ashmem device, which did
- // not have executable permissions. This also seals the executable
- // permissions of the buffer (i.e. they cannot be changed by fchmod()).
- //
- // MFD_NOEXEC_SEAL implies MFD_ALLOW_SEALING.
- android::base::unique_fd fd(syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_NOEXEC_SEAL));
+ android::base::unique_fd fd(syscall(__NR_memfd_create, name, MFD_CLOEXEC | MFD_ALLOW_SEALING));
if (fd == -1) {
ALOGE("memfd_create(%s, %zd) failed: %m", name, size);
diff --git a/libcutils/ashmem-internal.h b/libcutils/ashmem-internal.h
new file mode 100644
index 0000000..7bd037b
--- /dev/null
+++ b/libcutils/ashmem-internal.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2025 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.
+ */
+
+#pragma once
+
+bool has_memfd_support();
diff --git a/libcutils/ashmem_test.cpp b/libcutils/ashmem_test.cpp
index 96f20db..2bf274c 100644
--- a/libcutils/ashmem_test.cpp
+++ b/libcutils/ashmem_test.cpp
@@ -29,6 +29,8 @@
#include <cutils/ashmem.h>
#include <gtest/gtest.h>
+#include "ashmem-internal.h"
+
using android::base::unique_fd;
static void TestCreateRegion(size_t size, unique_fd &fd, int prot) {
@@ -316,3 +318,119 @@
ASSERT_NO_FATAL_FAILURE(ForkMultiRegionTest(fds, nRegions, size));
}
+
+class AshmemTestMemfdAshmemCompat : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ if (!has_memfd_support()){
+ GTEST_SKIP() << "No memfd support; skipping memfd-ashmem compat tests";
+ }
+ }
+};
+
+TEST_F(AshmemTestMemfdAshmemCompat, SetNameTest) {
+ unique_fd fd;
+
+ // ioctl() should fail, since memfd names cannot be changed after the buffer has been created.
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+ ASSERT_LT(ioctl(fd, ASHMEM_SET_NAME, "invalid-command"), 0);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, GetNameTest) {
+ unique_fd fd;
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+
+ char testBuf[ASHMEM_NAME_LEN];
+ ASSERT_EQ(0, ioctl(fd, ASHMEM_GET_NAME, &testBuf));
+ // ashmem_create_region(nullptr, ...) creates memfds with the name "none".
+ ASSERT_STREQ(testBuf, "none");
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, SetSizeTest) {
+ unique_fd fd;
+
+ // ioctl() should fail, since libcutils sets and seals the buffer size after creating it.
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+ ASSERT_LT(ioctl(fd, ASHMEM_SET_SIZE, 2 * getpagesize()), 0);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, GetSizeTest) {
+ unique_fd fd;
+ size_t bufSize = getpagesize();
+
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(bufSize, fd, PROT_READ | PROT_WRITE | PROT_EXEC));
+ ASSERT_EQ(static_cast<int>(bufSize), ioctl(fd, ASHMEM_GET_SIZE, 0));
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, ProtMaskTest) {
+ unique_fd fd;
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+
+ // We can only change PROT_WRITE for memfds since memfd implements ashmem's prot_mask through
+ // file seals, and only write seals exist.
+ //
+ // All memfd files start off as being writable (i.e. PROT_WRITE is part of the prot_mask).
+ // Test to ensure that the implementation only clears the PROT_WRITE bit when requested.
+ ASSERT_EQ(0, ioctl(fd, ASHMEM_SET_PROT_MASK, PROT_READ | PROT_WRITE | PROT_EXEC));
+ int prot = ioctl(fd, ASHMEM_GET_PROT_MASK, 0);
+ ASSERT_NE(prot, -1);
+ ASSERT_TRUE(prot & PROT_WRITE) << prot;
+
+ ASSERT_EQ(0, ioctl(fd, ASHMEM_SET_PROT_MASK, PROT_READ | PROT_EXEC));
+ prot = ioctl(fd, ASHMEM_GET_PROT_MASK, 0);
+ ASSERT_NE(prot, -1);
+ ASSERT_TRUE(!(prot & PROT_WRITE)) << prot;
+
+ // The shim layer should implement clearing PROT_WRITE via file seals, so check the file
+ // seals to ensure that F_SEAL_FUTURE_WRITE is set.
+ int seals = fcntl(fd, F_GET_SEALS, 0);
+ ASSERT_NE(seals, -1);
+ ASSERT_TRUE(seals & F_SEAL_FUTURE_WRITE) << seals;
+
+ // Similarly, ensure that file seals affect prot_mask
+ unique_fd fd2;
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd2, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+ ASSERT_EQ(0, fcntl(fd2, F_ADD_SEALS, F_SEAL_FUTURE_WRITE));
+ prot = ioctl(fd2, ASHMEM_GET_PROT_MASK, 0);
+ ASSERT_NE(prot, -1);
+ ASSERT_TRUE(!(prot & PROT_WRITE)) << prot;
+
+ // And finally, ensure that adding back permissions fails
+ ASSERT_LT(ioctl(fd2, ASHMEM_SET_PROT_MASK, PROT_READ | PROT_WRITE | PROT_EXEC), 0);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, FileIDTest) {
+ unique_fd fd;
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+
+ unsigned long ino;
+ ASSERT_EQ(0, ioctl(fd, ASHMEM_GET_FILE_ID, &ino));
+ struct stat st;
+ ASSERT_EQ(0, fstat(fd, &st));
+ ASSERT_EQ(ino, st.st_ino);
+}
+
+TEST_F(AshmemTestMemfdAshmemCompat, UnpinningTest) {
+ unique_fd fd;
+ size_t bufSize = getpagesize();
+ ASSERT_NO_FATAL_FAILURE(TestCreateRegion(getpagesize(), fd, PROT_READ | PROT_WRITE |
+ PROT_EXEC));
+
+ struct ashmem_pin pin = {
+ .offset = 0,
+ .len = static_cast<uint32_t>(bufSize),
+ };
+ ASSERT_EQ(0, ioctl(fd, ASHMEM_UNPIN, &pin));
+ // ASHMEM_UNPIN should just be a nop
+ ASSERT_EQ(ASHMEM_IS_PINNED, ioctl(fd, ASHMEM_GET_PIN_STATUS, 0));
+
+ // This shouldn't do anything; when we pin the page, it shouldn't have been purged.
+ ASSERT_EQ(0, ioctl(fd, ASHMEM_PURGE_ALL_CACHES, 0));
+ ASSERT_EQ(ASHMEM_NOT_PURGED, ioctl(fd, ASHMEM_PIN, &pin));
+}
\ No newline at end of file
diff --git a/libsync/sync.c b/libsync/sync.c
index b8c48c7..c4c4472 100644
--- a/libsync/sync.c
+++ b/libsync/sync.c
@@ -117,7 +117,7 @@
UAPI_MODERN,
UAPI_LEGACY
};
-static atomic_int g_uapi_version = ATOMIC_VAR_INIT(UAPI_UNKNOWN);
+static atomic_int g_uapi_version = UAPI_UNKNOWN;
// ---------------------------------------------------------------------------