| /* | 
 |  * Copyright (C) 2020 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 "snapuserd_transition.h" | 
 |  | 
 | #include <sys/mman.h> | 
 | #include <sys/socket.h> | 
 | #include <sys/syscall.h> | 
 | #include <sys/xattr.h> | 
 | #include <unistd.h> | 
 |  | 
 | #include <filesystem> | 
 | #include <string> | 
 |  | 
 | #include <android-base/file.h> | 
 | #include <android-base/logging.h> | 
 | #include <android-base/parseint.h> | 
 | #include <android-base/strings.h> | 
 | #include <android-base/unique_fd.h> | 
 | #include <cutils/sockets.h> | 
 | #include <libsnapshot/snapshot.h> | 
 | #include <libsnapshot/snapuserd_client.h> | 
 | #include <private/android_filesystem_config.h> | 
 | #include <selinux/android.h> | 
 |  | 
 | #include "block_dev_initializer.h" | 
 | #include "service_utils.h" | 
 | #include "util.h" | 
 |  | 
 | namespace android { | 
 | namespace init { | 
 |  | 
 | using namespace std::string_literals; | 
 |  | 
 | using android::base::unique_fd; | 
 | using android::snapshot::SnapshotManager; | 
 | using android::snapshot::SnapuserdClient; | 
 |  | 
 | static constexpr char kSnapuserdPath[] = "/system/bin/snapuserd"; | 
 | static constexpr char kSnapuserdFirstStagePidVar[] = "FIRST_STAGE_SNAPUSERD_PID"; | 
 | static constexpr char kSnapuserdFirstStageFdVar[] = "FIRST_STAGE_SNAPUSERD_FD"; | 
 | static constexpr char kSnapuserdLabel[] = "u:object_r:snapuserd_exec:s0"; | 
 | static constexpr char kSnapuserdSocketLabel[] = "u:object_r:snapuserd_socket:s0"; | 
 |  | 
 | void LaunchFirstStageSnapuserd() { | 
 |     SocketDescriptor socket_desc; | 
 |     socket_desc.name = android::snapshot::kSnapuserdSocket; | 
 |     socket_desc.type = SOCK_STREAM; | 
 |     socket_desc.perm = 0660; | 
 |     socket_desc.uid = AID_SYSTEM; | 
 |     socket_desc.gid = AID_SYSTEM; | 
 |  | 
 |     // We specify a label here even though it technically is not needed. During | 
 |     // first_stage_mount there is no sepolicy loaded. Once sepolicy is loaded, | 
 |     // we bypass the socket entirely. | 
 |     auto socket = socket_desc.Create(kSnapuserdSocketLabel); | 
 |     if (!socket.ok()) { | 
 |         LOG(FATAL) << "Could not create snapuserd socket: " << socket.error(); | 
 |     } | 
 |  | 
 |     pid_t pid = fork(); | 
 |     if (pid < 0) { | 
 |         PLOG(FATAL) << "Cannot launch snapuserd; fork failed"; | 
 |     } | 
 |     if (pid == 0) { | 
 |         socket->Publish(); | 
 |         char arg0[] = "/system/bin/snapuserd"; | 
 |         char* const argv[] = {arg0, nullptr}; | 
 |         if (execv(arg0, argv) < 0) { | 
 |             PLOG(FATAL) << "Cannot launch snapuserd; execv failed"; | 
 |         } | 
 |         _exit(127); | 
 |     } | 
 |  | 
 |     setenv(kSnapuserdFirstStagePidVar, std::to_string(pid).c_str(), 1); | 
 |  | 
 |     LOG(INFO) << "Relaunched snapuserd with pid: " << pid; | 
 | } | 
 |  | 
 | std::optional<pid_t> GetSnapuserdFirstStagePid() { | 
 |     const char* pid_str = getenv(kSnapuserdFirstStagePidVar); | 
 |     if (!pid_str) { | 
 |         return {}; | 
 |     } | 
 |  | 
 |     int pid = 0; | 
 |     if (!android::base::ParseInt(pid_str, &pid)) { | 
 |         LOG(FATAL) << "Could not parse pid in environment, " << kSnapuserdFirstStagePidVar << "=" | 
 |                    << pid_str; | 
 |     } | 
 |     return {pid}; | 
 | } | 
 |  | 
 | static void RelabelLink(const std::string& link) { | 
 |     selinux_android_restorecon(link.c_str(), 0); | 
 |  | 
 |     std::string path; | 
 |     if (android::base::Readlink(link, &path)) { | 
 |         selinux_android_restorecon(path.c_str(), 0); | 
 |     } | 
 | } | 
 |  | 
 | static void RelabelDeviceMapper() { | 
 |     selinux_android_restorecon("/dev/device-mapper", 0); | 
 |  | 
 |     std::error_code ec; | 
 |     for (auto& iter : std::filesystem::directory_iterator("/dev/block", ec)) { | 
 |         const auto& path = iter.path(); | 
 |         if (android::base::StartsWith(path.string(), "/dev/block/dm-")) { | 
 |             selinux_android_restorecon(path.string().c_str(), 0); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | static std::optional<int> GetRamdiskSnapuserdFd() { | 
 |     const char* fd_str = getenv(kSnapuserdFirstStageFdVar); | 
 |     if (!fd_str) { | 
 |         return {}; | 
 |     } | 
 |  | 
 |     int fd; | 
 |     if (!android::base::ParseInt(fd_str, &fd)) { | 
 |         LOG(FATAL) << "Could not parse fd in environment, " << kSnapuserdFirstStageFdVar << "=" | 
 |                    << fd_str; | 
 |     } | 
 |     return {fd}; | 
 | } | 
 |  | 
 | void RestoreconRamdiskSnapuserd(int fd) { | 
 |     if (fsetxattr(fd, XATTR_NAME_SELINUX, kSnapuserdLabel, strlen(kSnapuserdLabel) + 1, 0) < 0) { | 
 |         PLOG(FATAL) << "fsetxattr snapuserd failed"; | 
 |     } | 
 | } | 
 |  | 
 | SnapuserdSelinuxHelper::SnapuserdSelinuxHelper(std::unique_ptr<SnapshotManager>&& sm, pid_t old_pid) | 
 |     : sm_(std::move(sm)), old_pid_(old_pid) { | 
 |     // Only dm-user device names change during transitions, so the other | 
 |     // devices are expected to be present. | 
 |     sm_->SetUeventRegenCallback([this](const std::string& device) -> bool { | 
 |         if (android::base::StartsWith(device, "/dev/dm-user/")) { | 
 |             return block_dev_init_.InitDmUser(android::base::Basename(device)); | 
 |         } | 
 |         return true; | 
 |     }); | 
 | } | 
 |  | 
 | void SnapuserdSelinuxHelper::StartTransition() { | 
 |     LOG(INFO) << "Starting SELinux transition of snapuserd"; | 
 |  | 
 |     // The restorecon path reads from /system etc, so make sure any reads have | 
 |     // been cached before proceeding. | 
 |     auto handle = selinux_android_file_context_handle(); | 
 |     if (!handle) { | 
 |         LOG(FATAL) << "Could not create SELinux file context handle"; | 
 |     } | 
 |     selinux_android_set_sehandle(handle); | 
 |  | 
 |     // We cannot access /system after the transition, so make sure init is | 
 |     // pinned in memory. | 
 |     if (mlockall(MCL_CURRENT) < 0) { | 
 |         LOG(FATAL) << "mlockall failed"; | 
 |     } | 
 |  | 
 |     argv_.emplace_back("snapuserd"); | 
 |     argv_.emplace_back("-no_socket"); | 
 |     if (!sm_->DetachSnapuserdForSelinux(&argv_)) { | 
 |         LOG(FATAL) << "Could not perform selinux transition"; | 
 |     } | 
 |  | 
 |     // Make sure the process is gone so we don't have any selinux audits. | 
 |     KillFirstStageSnapuserd(old_pid_); | 
 | } | 
 |  | 
 | void SnapuserdSelinuxHelper::FinishTransition() { | 
 |     RelabelLink("/dev/block/by-name/super"); | 
 |     RelabelDeviceMapper(); | 
 |  | 
 |     selinux_android_restorecon("/dev/null", 0); | 
 |     selinux_android_restorecon("/dev/urandom", 0); | 
 |     selinux_android_restorecon("/dev/kmsg", 0); | 
 |     selinux_android_restorecon("/dev/dm-user", SELINUX_ANDROID_RESTORECON_RECURSE); | 
 |  | 
 |     RelaunchFirstStageSnapuserd(); | 
 |  | 
 |     if (munlockall() < 0) { | 
 |         PLOG(ERROR) << "munlockall failed"; | 
 |     } | 
 | } | 
 |  | 
 | void SnapuserdSelinuxHelper::RelaunchFirstStageSnapuserd() { | 
 |     auto fd = GetRamdiskSnapuserdFd(); | 
 |     if (!fd) { | 
 |         LOG(FATAL) << "Environment variable " << kSnapuserdFirstStageFdVar << " was not set!"; | 
 |     } | 
 |     unsetenv(kSnapuserdFirstStageFdVar); | 
 |  | 
 |     RestoreconRamdiskSnapuserd(fd.value()); | 
 |  | 
 |     pid_t pid = fork(); | 
 |     if (pid < 0) { | 
 |         PLOG(FATAL) << "Fork to relaunch snapuserd failed"; | 
 |     } | 
 |     if (pid > 0) { | 
 |         // We don't need the descriptor anymore, and it should be closed to | 
 |         // avoid leaking into subprocesses. | 
 |         close(fd.value()); | 
 |  | 
 |         setenv(kSnapuserdFirstStagePidVar, std::to_string(pid).c_str(), 1); | 
 |  | 
 |         LOG(INFO) << "Relaunched snapuserd with pid: " << pid; | 
 |         return; | 
 |     } | 
 |  | 
 |     // Make sure the descriptor is gone after we exec. | 
 |     if (fcntl(fd.value(), F_SETFD, FD_CLOEXEC) < 0) { | 
 |         PLOG(FATAL) << "fcntl FD_CLOEXEC failed for snapuserd fd"; | 
 |     } | 
 |  | 
 |     std::vector<char*> argv; | 
 |     for (auto& arg : argv_) { | 
 |         argv.emplace_back(arg.data()); | 
 |     } | 
 |     argv.emplace_back(nullptr); | 
 |  | 
 |     int rv = syscall(SYS_execveat, fd.value(), "", reinterpret_cast<char* const*>(argv.data()), | 
 |                      nullptr, AT_EMPTY_PATH); | 
 |     if (rv < 0) { | 
 |         PLOG(FATAL) << "Failed to execveat() snapuserd"; | 
 |     } | 
 | } | 
 |  | 
 | std::unique_ptr<SnapuserdSelinuxHelper> SnapuserdSelinuxHelper::CreateIfNeeded() { | 
 |     if (IsRecoveryMode()) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     auto old_pid = GetSnapuserdFirstStagePid(); | 
 |     if (!old_pid) { | 
 |         return nullptr; | 
 |     } | 
 |  | 
 |     auto sm = SnapshotManager::NewForFirstStageMount(); | 
 |     if (!sm) { | 
 |         LOG(FATAL) << "Unable to create SnapshotManager"; | 
 |     } | 
 |     return std::make_unique<SnapuserdSelinuxHelper>(std::move(sm), old_pid.value()); | 
 | } | 
 |  | 
 | void KillFirstStageSnapuserd(pid_t pid) { | 
 |     if (kill(pid, SIGTERM) < 0 && errno != ESRCH) { | 
 |         LOG(ERROR) << "Kill snapuserd pid failed: " << pid; | 
 |     } else { | 
 |         LOG(INFO) << "Sent SIGTERM to snapuserd process " << pid; | 
 |     } | 
 | } | 
 |  | 
 | void CleanupSnapuserdSocket() { | 
 |     auto socket_path = ANDROID_SOCKET_DIR "/"s + android::snapshot::kSnapuserdSocket; | 
 |     if (access(socket_path.c_str(), F_OK) != 0) { | 
 |         return; | 
 |     } | 
 |  | 
 |     // Tell the daemon to stop accepting connections and to gracefully exit | 
 |     // once all outstanding handlers have terminated. | 
 |     if (auto client = SnapuserdClient::Connect(android::snapshot::kSnapuserdSocket, 3s)) { | 
 |         client->DetachSnapuserd(); | 
 |     } | 
 |  | 
 |     // Unlink the socket so we can create it again in second-stage. | 
 |     if (unlink(socket_path.c_str()) < 0) { | 
 |         PLOG(FATAL) << "unlink " << socket_path << " failed"; | 
 |     } | 
 | } | 
 |  | 
 | void SaveRamdiskPathToSnapuserd() { | 
 |     int fd = open(kSnapuserdPath, O_PATH); | 
 |     if (fd < 0) { | 
 |         PLOG(FATAL) << "Unable to open snapuserd: " << kSnapuserdPath; | 
 |     } | 
 |  | 
 |     auto value = std::to_string(fd); | 
 |     if (setenv(kSnapuserdFirstStageFdVar, value.c_str(), 1) < 0) { | 
 |         PLOG(FATAL) << "setenv failed: " << kSnapuserdFirstStageFdVar << "=" << value; | 
 |     } | 
 | } | 
 |  | 
 | bool IsFirstStageSnapuserdRunning() { | 
 |     return GetSnapuserdFirstStagePid().has_value(); | 
 | } | 
 |  | 
 | }  // namespace init | 
 | }  // namespace android |