/* * Copyright (C) 2018 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. */ #define LOG_TAG "apexd" #include "apexd_prepostinstall.h" #include <algorithm> #include <vector> #include <fcntl.h> #include <sys/mount.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <android-base/logging.h> #include <android-base/macros.h> #include <android-base/scopeguard.h> #include <android-base/strings.h> #include "apex_file.h" #include "apexd.h" #include "apexd_private.h" #include "apexd_utils.h" #include "string_log.h" namespace android { namespace apex { namespace { void CloseSTDDescriptors() { // exec()d process will reopen STD* file descriptors as // /dev/null close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); } template <typename Fn> Status StageFnInstall(const std::vector<ApexFile>& apexes, Fn fn, const char* arg, const char* name) { // TODO: Support a session with more than one pre-install hook. const ApexFile* hook_file = nullptr; for (const ApexFile& f : apexes) { if (!(f.GetManifest().*fn)().empty()) { if (hook_file != nullptr) { return Status::Fail(StringLog() << "Missing support for multiple " << name << " hooks"); } hook_file = &f; } } CHECK(hook_file != nullptr); LOG(VERBOSE) << name << " for " << hook_file->GetPath(); std::vector<const ApexFile*> mounted_apexes; std::vector<std::string> activation_dirs; auto preinstall_guard = android::base::make_scope_guard([&]() { for (const ApexFile* f : mounted_apexes) { Status st = apexd_private::UnmountPackage(*f); if (!st.Ok()) { LOG(ERROR) << "Failed to unmount " << f->GetPath() << " after " << name << ": " << st.ErrorMessage(); } } for (const std::string& active_point : activation_dirs) { if (0 != rmdir(active_point.c_str())) { PLOG(ERROR) << "Could not delete temporary active point " << active_point; } } }); for (const ApexFile& apex : apexes) { // 1) Mount the package, if necessary. std::string mount_point = apexd_private::GetPackageMountPoint(apex.GetManifest()); if (!apexd_private::IsMounted(apex.GetManifest().name(), apex.GetPath())) { Status mountStatus = apexd_private::MountPackage(apex, mount_point); if (!mountStatus.Ok()) { return mountStatus; } mounted_apexes.push_back(&apex); } // 2) Ensure there is an activation point, and we will clean it up. std::string active_point = apexd_private::GetActiveMountPoint(apex.GetManifest()); if (0 == mkdir(active_point.c_str(), kMkdirMode)) { activation_dirs.emplace_back(std::move(active_point)); } else { int saved_errno = errno; if (saved_errno != EEXIST) { return Status::Fail(StringLog() << "Unable to create mount point" << active_point << ": " << strerror(saved_errno)); } } } // 3) Create invocation args. std::vector<std::string> args{ "/system/bin/apexd", arg, hook_file->GetPath(), // Make the APEX with hook first. }; for (const ApexFile& apex : apexes) { if (&apex != hook_file) { args.push_back(apex.GetPath()); } } std::string error_msg; int res = ForkAndRun(args, &error_msg); return res == 0 ? Status::Success() : Status::Fail(error_msg); } template <typename Fn> int RunFnInstall(char** in_argv, Fn fn, const char* name) { // 1) Unshare. if (unshare(CLONE_NEWNS) != 0) { PLOG(ERROR) << "Failed to unshare() for apex " << name; _exit(200); } // 2) Make everything private, so that our (and hook's) changes do not // propagate. if (mount(nullptr, "/", nullptr, MS_PRIVATE | MS_REC, nullptr) == -1) { PLOG(ERROR) << "Failed to mount private."; _exit(201); } std::string hook_path; { auto bind_fn = [&fn, name](const std::string& apex) { std::string hook; std::string mount_point; std::string active_point; { StatusOr<ApexFile> apex_file = ApexFile::Open(apex); if (!apex_file.Ok()) { LOG(ERROR) << "Could not open apex " << apex << " for " << name << ": " << apex_file.ErrorMessage(); _exit(202); } const ApexManifest& manifest = apex_file->GetManifest(); hook = (manifest.*fn)(); mount_point = apexd_private::GetPackageMountPoint(manifest); active_point = apexd_private::GetActiveMountPoint(manifest); } // 3) Activate the new apex. Status bind_status = apexd_private::BindMount(active_point, mount_point); if (!bind_status.Ok()) { LOG(ERROR) << "Failed to bind-mount " << mount_point << " to " << active_point << ": " << bind_status.ErrorMessage(); _exit(203); } return std::make_pair(active_point, hook); }; // First/main APEX. auto [active_point, hook] = bind_fn(in_argv[2]); hook_path = active_point + "/" + hook; for (size_t i = 3;; ++i) { if (in_argv[i] == nullptr) { break; } bind_fn(in_argv[i]); // Ignore result, hook will be empty. } } // 4) Run the hook. // For now, just run sh. But this probably needs to run the new linker. std::vector<std::string> args{ hook_path, }; std::vector<const char*> argv; argv.resize(args.size() + 1, nullptr); std::transform(args.begin(), args.end(), argv.begin(), [](const std::string& in) { return in.c_str(); }); LOG(ERROR) << "execv of " << android::base::Join(args, " "); // Close all file descriptors. They are coming from the caller, we do not // want to pass them on across our fork/exec into a different domain. CloseSTDDescriptors(); execv(argv[0], const_cast<char**>(argv.data())); PLOG(ERROR) << "execv of " << android::base::Join(args, " ") << " failed"; _exit(204); } } // namespace Status StagePreInstall(const std::vector<ApexFile>& apexes) { return StageFnInstall(apexes, &ApexManifest::preinstallhook, "--pre-install", "pre-install"); } int RunPreInstall(char** in_argv) { return RunFnInstall(in_argv, &ApexManifest::preinstallhook, "pre-install"); } Status StagePostInstall(const std::vector<ApexFile>& apexes) { return StageFnInstall(apexes, &ApexManifest::postinstallhook, "--post-install", "post-install"); } int RunPostInstall(char** in_argv) { return RunFnInstall(in_argv, &ApexManifest::postinstallhook, "post-install"); } } // namespace apex } // namespace android