init: restore mounts under /sys after enter_namespace
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. In particular, unmounting tracefs (/sys/kernel/tracing) prevents
collecting ATrace traces from any init service that uses `enter_namespace
net`.
This CL makes init iterate over all current mounts and remount anything
under /sys/ after it remounts /sys for the service being started.
A minor change is to pass "sysfs" rather than the empty string as the
block device argument to mount. Otherwise, /proc/$PID/mounts inside the
process in network namespace contains a sysfs entry with no block device
name, which confuses the Fstab parser into thinking that /sys is the
block device, which made the included test fail.
Bug: 399071958
Test: th (test included)
Test: manual check: capture perfetto trace for a process started inside
Test: a network namespace, see ATrace traces showing up that weren't
Test: there before
Change-Id: Icc1e763dff115a06ac23b37f14cfbdd328bbe659
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 {};
}