diff --git a/init/Android.bp b/init/Android.bp index 9f5d17d8d..67688f225 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -110,6 +110,7 @@ cc_library_static { "init.cpp", "keychords.cpp", "modalias_handler.cpp", + "mount_namespace.cpp", "parser.cpp", "persistent_properties.cpp", "persistent_properties.proto", @@ -166,6 +167,7 @@ cc_binary { exclude_shared_libs: ["libbinder", "libutils"], }, }, + ldflags: ["-Wl,--rpath,/system/${LIB}/bootstrap"], } // Tests diff --git a/init/Android.mk b/init/Android.mk index 69c63e1f3..59d7f114e 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -47,6 +47,7 @@ LOCAL_SRC_FILES := \ first_stage_init.cpp \ first_stage_main.cpp \ first_stage_mount.cpp \ + mount_namespace.cpp \ reboot_utils.cpp \ selinux.cpp \ switch_root.cpp \ diff --git a/init/builtins.cpp b/init/builtins.cpp index 169edbe09..b41b03598 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -63,6 +63,7 @@ #include "action_manager.h" #include "bootchart.h" #include "init.h" +#include "mount_namespace.h" #include "parser.h" #include "property_service.h" #include "reboot.h" @@ -1098,6 +1099,14 @@ static Result do_parse_apex_configs(const BuiltinArguments& args) { } } +static Result do_setup_runtime_bionic(const BuiltinArguments& args) { + if (SwitchToDefaultMountNamespace()) { + return Success(); + } else { + return Error() << "Failed to setup runtime bionic"; + } +} + // Builtin-function-map start const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const { constexpr std::size_t kMax = std::numeric_limits::max(); @@ -1145,6 +1154,7 @@ const BuiltinFunctionMap::Map& BuiltinFunctionMap::map() const { {"rmdir", {1, 1, {true, do_rmdir}}}, {"setprop", {2, 2, {true, do_setprop}}}, {"setrlimit", {3, 3, {false, do_setrlimit}}}, + {"setup_runtime_bionic", {0, 0, {false, do_setup_runtime_bionic}}}, {"start", {1, 1, {false, do_start}}}, {"stop", {1, 1, {false, do_stop}}}, {"swapon_all", {1, 1, {false, do_swapon_all}}}, diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp index 3a4dc6a8c..79536e4d1 100644 --- a/init/first_stage_mount.cpp +++ b/init/first_stage_mount.cpp @@ -136,10 +136,6 @@ static inline bool IsDtVbmetaCompatible(const Fstab& fstab) { return is_android_dt_value_expected("vbmeta/compatible", "android,vbmeta"); } -static bool IsRecoveryMode() { - return access("/system/bin/recovery", F_OK) == 0; -} - static Fstab ReadFirstStageFstab() { Fstab fstab; if (!ReadFstabFromDt(&fstab)) { diff --git a/init/init.cpp b/init/init.cpp index d360fdd6b..4f4a15f1d 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -54,6 +54,7 @@ #include "first_stage_mount.h" #include "import_parser.h" #include "keychords.h" +#include "mount_namespace.h" #include "property_service.h" #include "reboot.h" #include "reboot_utils.h" @@ -666,6 +667,10 @@ int SecondStageMain(int argc, char** argv) { const BuiltinFunctionMap function_map; Action::set_function_map(&function_map); + if (!SetupMountNamespaces()) { + PLOG(FATAL) << "SetupMountNamespaces failed"; + } + subcontexts = InitializeSubcontexts(); ActionManager& am = ActionManager::GetInstance(); diff --git a/init/mount_namespace.cpp b/init/mount_namespace.cpp new file mode 100644 index 000000000..413fe8f50 --- /dev/null +++ b/init/mount_namespace.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2019 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 "mount_namespace.h" + +#include + +#include +#include + +#include +#include +#include +#include + +#include "util.h" + +namespace android { +namespace init { +namespace { + +static constexpr const char* kLinkerMountPoint = "/bionic/bin/linker"; +static constexpr const char* kBootstrapLinkerPath = "/system/bin/bootstrap/linker"; +static constexpr const char* kRuntimeLinkerPath = "/apex/com.android.runtime/bin/linker"; + +static constexpr const char* kBionicLibsMountPointDir = "/bionic/lib/"; +static constexpr const char* kBootstrapBionicLibsDir = "/system/lib/bootstrap/"; +static constexpr const char* kRuntimeBionicLibsDir = "/apex/com.android.runtime/lib/bionic/"; + +static constexpr const char* kLinkerMountPoint64 = "/bionic/bin/linker64"; +static constexpr const char* kBootstrapLinkerPath64 = "/system/bin/bootstrap/linker64"; +static constexpr const char* kRuntimeLinkerPath64 = "/apex/com.android.runtime/bin/linker64"; + +static constexpr const char* kBionicLibsMountPointDir64 = "/bionic/lib64/"; +static constexpr const char* kBootstrapBionicLibsDir64 = "/system/lib64/bootstrap/"; +static constexpr const char* kRuntimeBionicLibsDir64 = "/apex/com.android.runtime/lib64/bionic/"; + +static const std::vector kBionicLibFileNames = {"libc.so", "libm.so", "libdl.so"}; + +static bool BindMount(const std::string& source, const std::string& mount_point, + bool recursive = false) { + unsigned long mountflags = MS_BIND; + if (recursive) { + mountflags |= MS_REC; + } + if (mount(source.c_str(), mount_point.c_str(), nullptr, mountflags, nullptr) == -1) { + PLOG(ERROR) << "Could not bind-mount " << source << " to " << mount_point; + return false; + } + return true; +} + +static bool MakeShared(const std::string& mount_point, bool recursive = false) { + unsigned long mountflags = MS_SHARED; + if (recursive) { + mountflags |= MS_REC; + } + if (mount(nullptr, mount_point.c_str(), nullptr, mountflags, nullptr) == -1) { + PLOG(ERROR) << "Failed to change propagation type to shared"; + return false; + } + return true; +} + +static bool MakePrivate(const std::string& mount_point, bool recursive = false) { + unsigned long mountflags = MS_PRIVATE; + if (recursive) { + mountflags |= MS_REC; + } + if (mount(nullptr, mount_point.c_str(), nullptr, mountflags, nullptr) == -1) { + PLOG(ERROR) << "Failed to change propagation type to private"; + return false; + } + return true; +} + +static int OpenMountNamespace() { + int fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + PLOG(ERROR) << "Cannot open fd for current mount namespace"; + } + return fd; +} + +static std::string GetMountNamespaceId() { + std::string ret; + if (!android::base::Readlink("/proc/self/ns/mnt", &ret)) { + PLOG(ERROR) << "Failed to read namespace ID"; + return ""; + } + return ret; +} + +static bool BindMountBionic(const std::string& linker_source, const std::string& lib_dir_source, + const std::string& linker_mount_point, + const std::string& lib_mount_dir) { + if (access(linker_source.c_str(), F_OK) != 0) { + PLOG(INFO) << linker_source << " does not exist. skipping mounting bionic there."; + // This can happen for 64-bit bionic in 32-bit only device. + // It is okay to skip mounting the 64-bit bionic. + return true; + } + if (!BindMount(linker_source, linker_mount_point)) { + return false; + } + if (!MakePrivate(linker_mount_point)) { + return false; + } + for (const auto& libname : kBionicLibFileNames) { + std::string mount_point = lib_mount_dir + libname; + std::string source = lib_dir_source + libname; + if (!BindMount(source, mount_point)) { + return false; + } + if (!MakePrivate(mount_point)) { + return false; + } + } + return true; +} + +static bool IsBionicUpdatable() { + static bool result = android::base::GetBoolProperty("ro.apex.IsBionicUpdatable", false); + return result; +} + +static android::base::unique_fd bootstrap_ns_fd; +static android::base::unique_fd default_ns_fd; + +static std::string bootstrap_ns_id; +static std::string default_ns_id; + +} // namespace + +bool SetupMountNamespaces() { + // Set the propagation type of / as shared so that any mounting event (e.g. + // /data) is by default visible to all processes. When private mounting is + // needed for /foo/bar, then we will make /foo/bar as a mount point (by + // bind-mounting by to itself) and set the propagation type of the mount + // point to private. + if (!MakeShared("/", true /*recursive*/)) return false; + + // Since different files (bootstrap or runtime APEX) should be mounted to + // the same mount point paths (e.g. /bionic/bin/linker, /bionic/lib/libc.so, + // etc.) across the two mount namespaces, we create a private mount point at + // /bionic so that a mount event for the bootstrap bionic in the mount + // namespace for pre-apexd processes is not propagated to the other mount + // namespace for post-apexd process, and vice versa. + // + // Other mount points other than /bionic, however, are all still shared. + if (!BindMount("/bionic", "/bionic", true /*recursive*/)) return false; + if (!MakePrivate("/bionic")) return false; + + // Bind-mount bootstrap bionic. + if (!BindMountBionic(kBootstrapLinkerPath, kBootstrapBionicLibsDir, kLinkerMountPoint, + kBionicLibsMountPointDir)) + return false; + if (!BindMountBionic(kBootstrapLinkerPath64, kBootstrapBionicLibsDir64, kLinkerMountPoint64, + kBionicLibsMountPointDir64)) + return false; + + bootstrap_ns_fd.reset(OpenMountNamespace()); + bootstrap_ns_id = GetMountNamespaceId(); + + // When bionic is updatable via the runtime APEX, we create separate mount + // namespaces for processes that are started before and after the APEX is + // activated by apexd. In the namespace for pre-apexd processes, the bionic + // from the /system partition (that we call bootstrap bionic) is + // bind-mounted. In the namespace for post-apexd processes, the bionic from + // the runtime APEX is bind-mounted. + bool success = true; + if (IsBionicUpdatable() && !IsRecoveryMode()) { + // Creating a new namespace by cloning, saving, and switching back to + // the original namespace. + if (unshare(CLONE_NEWNS) == -1) { + PLOG(ERROR) << "Cannot create mount namespace"; + return false; + } + default_ns_fd.reset(OpenMountNamespace()); + default_ns_id = GetMountNamespaceId(); + + // By this unmount, the bootstrap bionic are not mounted in the default + // mount namespace. + if (umount2("/bionic", MNT_DETACH) == -1) { + PLOG(ERROR) << "Cannot unmount /bionic"; + // Don't return here. We have to switch back to the bootstrap + // namespace. + success = false; + } + + if (setns(bootstrap_ns_fd.get(), CLONE_NEWNS) == -1) { + PLOG(ERROR) << "Cannot switch back to bootstrap mount namespace"; + return false; + } + } else { + // Otherwise, default == bootstrap + default_ns_fd.reset(OpenMountNamespace()); + default_ns_id = GetMountNamespaceId(); + } + + LOG(INFO) << "SetupMountNamespaces done"; + return success; +} + +bool SwitchToDefaultMountNamespace() { + if (IsRecoveryMode()) { + // we don't have multiple namespaces in recovery mode + return true; + } + if (default_ns_id != GetMountNamespaceId()) { + if (setns(default_ns_fd.get(), CLONE_NEWNS) == -1) { + PLOG(ERROR) << "Failed to switch back to the default mount namespace."; + return false; + } + } + + // Bind-mount bionic from the runtime APEX since it is now available. Note + // that in case of IsBionicUpdatable() == false, these mounts are over the + // existing existing bind mounts for the bootstrap bionic, which effectively + // becomes hidden. + if (!BindMountBionic(kRuntimeLinkerPath, kRuntimeBionicLibsDir, kLinkerMountPoint, + kBionicLibsMountPointDir)) + return false; + if (!BindMountBionic(kRuntimeLinkerPath64, kRuntimeBionicLibsDir64, kLinkerMountPoint64, + kBionicLibsMountPointDir64)) + return false; + + LOG(INFO) << "Switched to default mount namespace"; + return true; +} + +bool SwitchToBootstrapMountNamespaceIfNeeded() { + if (IsRecoveryMode()) { + // we don't have multiple namespaces in recovery mode + return true; + } + if (bootstrap_ns_id != GetMountNamespaceId() && bootstrap_ns_fd.get() != -1 && + IsBionicUpdatable()) { + if (setns(bootstrap_ns_fd.get(), CLONE_NEWNS) == -1) { + PLOG(ERROR) << "Failed to switch to bootstrap mount namespace."; + return false; + } + } + return true; +} + +} // namespace init +} // namespace android diff --git a/init/mount_namespace.h b/init/mount_namespace.h new file mode 100644 index 000000000..c41a449f3 --- /dev/null +++ b/init/mount_namespace.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2019 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 + +namespace android { +namespace init { + +bool SetupMountNamespaces(); +bool SwitchToDefaultMountNamespace(); +bool SwitchToBootstrapMountNamespaceIfNeeded(); + +} // namespace init +} // namespace android diff --git a/init/service.cpp b/init/service.cpp index 272809f7c..a6eb7f7ab 100644 --- a/init/service.cpp +++ b/init/service.cpp @@ -50,6 +50,7 @@ #include #include "init.h" +#include "mount_namespace.h" #include "property_service.h" #include "selinux.h" #else @@ -207,6 +208,11 @@ static bool ExpandArgsAndExecv(const std::vector& args, bool sigsto return execv(c_strings[0], c_strings.data()) == 0; } +static bool IsRuntimeApexReady() { + struct stat buf; + return stat("/apex/com.android.runtime/", &buf) == 0; +} + unsigned long Service::next_start_order_ = 1; bool Service::is_exec_service_running_ = false; @@ -929,6 +935,14 @@ Result Service::Start() { scon = *result; } + if (!IsRuntimeApexReady() && !pre_apexd_) { + // If this service is started before the runtime APEX gets available, + // mark it as pre-apexd one. Note that this marking is permanent. So + // for example, if the service is re-launched (e.g., due to crash), + // it is still recognized as pre-apexd... for consistency. + pre_apexd_ = true; + } + LOG(INFO) << "starting service '" << name_ << "'..."; pid_t pid = -1; @@ -945,6 +959,15 @@ Result Service::Start() { LOG(FATAL) << "Service '" << name_ << "' could not enter namespaces: " << result.error(); } +#if defined(__ANDROID__) + if (pre_apexd_) { + if (!SwitchToBootstrapMountNamespaceIfNeeded()) { + LOG(FATAL) << "Service '" << name_ << "' could not enter " + << "into the bootstrap mount namespace"; + } + } +#endif + if (namespace_flags_ & CLONE_NEWNS) { if (auto result = SetUpMountNamespace(); !result) { LOG(FATAL) << "Service '" << name_ diff --git a/init/service.h b/init/service.h index 56e75b0bf..c29723a4e 100644 --- a/init/service.h +++ b/init/service.h @@ -242,6 +242,8 @@ class Service { std::vector args_; std::vector> reap_callbacks_; + + bool pre_apexd_ = false; }; class ServiceList { diff --git a/init/util.cpp b/init/util.cpp index 80fb03d6e..29d7a7698 100644 --- a/init/util.cpp +++ b/init/util.cpp @@ -441,5 +441,9 @@ void InitKernelLogging(char** argv, std::function abort_funct android::base::InitLogging(argv, &android::base::KernelLogger, std::move(abort_function)); } +bool IsRecoveryMode() { + return access("/system/bin/recovery", F_OK) == 0; +} + } // namespace init } // namespace android diff --git a/init/util.h b/init/util.h index 2b57910fc..2232a0f0b 100644 --- a/init/util.h +++ b/init/util.h @@ -64,7 +64,7 @@ bool is_android_dt_value_expected(const std::string& sub_path, const std::string bool IsLegalPropertyName(const std::string& name); void InitKernelLogging(char** argv, std::function abort_function); - +bool IsRecoveryMode(); } // namespace init } // namespace android diff --git a/rootdir/etc/ld.config.txt b/rootdir/etc/ld.config.txt index bc8568eaa..a61741871 100644 --- a/rootdir/etc/ld.config.txt +++ b/rootdir/etc/ld.config.txt @@ -73,6 +73,8 @@ namespace.default.permitted.paths += /%PRODUCT_SERVICES%/app namespace.default.permitted.paths += /%PRODUCT_SERVICES%/priv-app namespace.default.permitted.paths += /data namespace.default.permitted.paths += /mnt/expand +namespace.default.permitted.paths += /bionic/${LIB} +namespace.default.permitted.paths += /system/${LIB}/bootstrap namespace.default.asan.search.paths = /data/asan/system/${LIB} namespace.default.asan.search.paths += /system/${LIB} @@ -104,6 +106,8 @@ namespace.default.asan.permitted.paths += /%PRODUCT_SERVICES%/framework namespace.default.asan.permitted.paths += /%PRODUCT_SERVICES%/app namespace.default.asan.permitted.paths += /%PRODUCT_SERVICES%/priv-app namespace.default.asan.permitted.paths += /mnt/expand +namespace.default.asan.permitted.paths += /bionic/${LIB} +namespace.default.asan.permitted.paths += /system/${LIB}/bootstrap # Keep in sync with ld.config.txt in the com.android.runtime APEX. # If a shared library or an executable requests a shared library that diff --git a/rootdir/init.rc b/rootdir/init.rc index 0ec2c7da7..901b87318 100644 --- a/rootdir/init.rc +++ b/rootdir/init.rc @@ -12,6 +12,12 @@ import /init.usb.configfs.rc import /init.${ro.zygote}.rc on early-init + # Mount shared so changes propagate into child namespaces + # Do this before other processes are started from init. Otherwise, + # processes launched while the propagation type of / is 'private' + # won't get mount events from others. + mount rootfs rootfs / shared rec + # Set init and its forked children's oom_adj. write /proc/1/oom_score_adj -1000 @@ -346,8 +352,6 @@ on post-fs # Once everything is setup, no need to modify /. # The bind+remount combination allows this to work in containers. mount rootfs rootfs / remount bind ro nodev - # Mount shared so changes propagate into child namespaces - mount rootfs rootfs / shared rec # Mount default storage into root namespace mount none /mnt/runtime/default /storage bind rec mount none none /storage slave rec @@ -583,6 +587,14 @@ on post-fs-data # Check any timezone data in /data is newer than the copy in the runtime module, delete if not. exec - system system -- /system/bin/tzdatacheck /apex/com.android.runtime/etc/tz /data/misc/zoneinfo + # Wait for apexd to finish activating APEXes before starting more processes. + # This certainly reduces the parallelism but is required to make as many processes + # as possible to use the bionic libs from the runtime APEX. This takes less than 50ms + # so the impact on the booting time is not significant. + wait_for_prop apexd.status ready + setup_runtime_bionic + parse_apex_configs + # If there is no post-fs-data action in the init..rc file, you # must uncomment this line, otherwise encrypted filesystems # won't work. @@ -810,6 +822,3 @@ on property:ro.debuggable=1 service flash_recovery /system/bin/install-recovery.sh class main oneshot - -on property:apexd.status=ready - parse_apex_configs