init: Add an selinux transition for snapuserd.
With compressed VAB updates, it is not possible to mount /system without
first running snapuserd, which is the userspace component to the dm-user
kernel module. This poses a problem because as soon as selinux
enforcement is enabled, snapuserd (running in a kernel context) does not
have access to read and decompress the underlying system partition.
To account for this, we split SelinuxInitialize into multiple steps:
First, sepolicy is read into an in-memory string.
Second, the device-mapper tables for all snapshots are rebuilt. This
flushes any pending reads and creates new dm-user devices. The original
kernel-privileged snapuserd is then killed.
Third, sepolicy is loaded from the in-memory string.
Fourth, we re-launch snapuserd and connect it to the newly created
dm-user devices. As part of this step we restorecon device-mapper
devices and /dev/block/by-name/super, since the new snapuserd is in a
limited context.
Finally, we set enforcing mode.
This sequence ensures that snapuserd has appropriate privileges with a
minimal number of permissive audits.
Bug: 173476209
Test: full OTA with VABC applies and boots
Change-Id: Ie4e0f5166b01c31a6f337afc26fc58b96217604e
diff --git a/init/Android.bp b/init/Android.bp
index 19ba21b..cd295cf 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -60,6 +60,7 @@
"selabel.cpp",
"selinux.cpp",
"sigchld_handler.cpp",
+ "snapuserd_transition.cpp",
"switch_root.cpp",
"uevent_listener.cpp",
"ueventd.cpp",
diff --git a/init/Android.mk b/init/Android.mk
index c881e2f..561d641 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -57,6 +57,8 @@
reboot_utils.cpp \
selabel.cpp \
selinux.cpp \
+ service_utils.cpp \
+ snapuserd_transition.cpp \
switch_root.cpp \
uevent_listener.cpp \
util.cpp \
diff --git a/init/block_dev_initializer.h b/init/block_dev_initializer.h
index 79fe4ec..ec39ce0 100644
--- a/init/block_dev_initializer.h
+++ b/init/block_dev_initializer.h
@@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#pragma once
+
#include <memory>
#include <set>
#include <string>
diff --git a/init/first_stage_init.cpp b/init/first_stage_init.cpp
index 0949fc5..6954c03 100644
--- a/init/first_stage_init.cpp
+++ b/init/first_stage_init.cpp
@@ -42,6 +42,7 @@
#include "first_stage_mount.h"
#include "reboot_utils.h"
#include "second_stage_resources.h"
+#include "snapuserd_transition.h"
#include "switch_root.h"
#include "util.h"
@@ -90,6 +91,12 @@
}
}
}
+ } else if (de->d_type == DT_REG) {
+ // Do not free snapuserd if we will need the ramdisk copy during the
+ // selinux transition.
+ if (de->d_name == "snapuserd"s && IsFirstStageSnapuserdRunning()) {
+ continue;
+ }
}
unlinkat(dfd, de->d_name, is_dir ? AT_REMOVEDIR : 0);
}
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index a0511cc..7c46281 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -44,6 +44,7 @@
#include "block_dev_initializer.h"
#include "devices.h"
+#include "snapuserd_transition.h"
#include "switch_root.h"
#include "uevent.h"
#include "uevent_listener.h"
@@ -87,6 +88,7 @@
protected:
bool InitRequiredDevices(std::set<std::string> devices);
bool CreateLogicalPartitions();
+ bool CreateSnapshotPartitions(android::snapshot::SnapshotManager* sm);
bool MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
Fstab::iterator* end = nullptr);
@@ -109,6 +111,7 @@
bool need_dm_verity_;
bool dsu_not_on_userdata_ = false;
+ bool use_snapuserd_ = false;
Fstab fstab_;
// The super path is only set after InitDevices, and is invalid before.
@@ -338,21 +341,7 @@
return false;
}
if (sm->NeedSnapshotsInFirstStageMount()) {
- // When COW images are present for snapshots, they are stored on
- // the data partition.
- if (!InitRequiredDevices({"userdata"})) {
- return false;
- }
- sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
- if (android::base::StartsWith(device, "/dev/block/dm-")) {
- return block_dev_init_.InitDmDevice(device);
- }
- if (android::base::StartsWith(device, "/dev/dm-user/")) {
- return block_dev_init_.InitDmUser(android::base::Basename(device));
- }
- return block_dev_init_.InitDevices({device});
- });
- return sm->CreateLogicalAndSnapshotPartitions(super_path_);
+ return CreateSnapshotPartitions(sm.get());
}
}
@@ -367,6 +356,37 @@
return android::fs_mgr::CreateLogicalPartitions(*metadata.get(), super_path_);
}
+bool FirstStageMount::CreateSnapshotPartitions(SnapshotManager* sm) {
+ // When COW images are present for snapshots, they are stored on
+ // the data partition.
+ if (!InitRequiredDevices({"userdata"})) {
+ return false;
+ }
+
+ use_snapuserd_ = sm->IsSnapuserdRequired();
+ if (use_snapuserd_) {
+ LaunchFirstStageSnapuserd();
+ }
+
+ sm->SetUeventRegenCallback([this](const std::string& device) -> bool {
+ if (android::base::StartsWith(device, "/dev/block/dm-")) {
+ return block_dev_init_.InitDmDevice(device);
+ }
+ if (android::base::StartsWith(device, "/dev/dm-user/")) {
+ return block_dev_init_.InitDmUser(android::base::Basename(device));
+ }
+ return block_dev_init_.InitDevices({device});
+ });
+ if (!sm->CreateLogicalAndSnapshotPartitions(super_path_)) {
+ return false;
+ }
+
+ if (use_snapuserd_) {
+ CleanupSnapuserdSocket();
+ }
+ return true;
+}
+
bool FirstStageMount::MountPartition(const Fstab::iterator& begin, bool erase_same_mounts,
Fstab::iterator* end) {
// Sets end to begin + 1, so we can just return on failure below.
@@ -466,6 +486,10 @@
if (system_partition == fstab_.end()) return true;
+ if (use_snapuserd_) {
+ SaveRamdiskPathToSnapuserd();
+ }
+
if (MountPartition(system_partition, false /* erase_same_mounts */)) {
if (dsu_not_on_userdata_ && fs_mgr_verity_is_check_at_most_once(*system_partition)) {
LOG(ERROR) << "check_most_at_once forbidden on external media";
diff --git a/init/init.cpp b/init/init.cpp
index d1998a5..d6753f5 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -79,6 +79,7 @@
#include "service.h"
#include "service_parser.h"
#include "sigchld_handler.h"
+#include "snapuserd_transition.h"
#include "subcontext.h"
#include "system/core/init/property_service.pb.h"
#include "util.h"
@@ -741,10 +742,15 @@
return {};
}
svc->Start();
+ svc->SetShutdownCritical();
- if (!sm->PerformSecondStageTransition()) {
+ if (!sm->PerformSecondStageInitTransition()) {
LOG(FATAL) << "Failed to transition snapuserd to second-stage";
}
+
+ if (auto pid = GetSnapuserdFirstStagePid()) {
+ KillFirstStageSnapuserd(pid.value());
+ }
return {};
}
diff --git a/init/selinux.cpp b/init/selinux.cpp
index f03ca6b..0336936 100644
--- a/init/selinux.cpp
+++ b/init/selinux.cpp
@@ -45,7 +45,7 @@
// 2) If these hashes do not match, then either /system or /system_ext or /product (or some of them)
// have been updated out of sync with /vendor (or /odm if it is present) and the init needs to
// compile the SEPolicy. /system contains the SEPolicy compiler, secilc, and it is used by the
-// LoadSplitPolicy() function below to compile the SEPolicy to a temp directory and load it.
+// OpenSplitPolicy() function below to compile the SEPolicy to a temp directory and load it.
// That function contains even more documentation with the specific implementation details of how
// the SEPolicy is compiled if needed.
@@ -74,6 +74,7 @@
#include "block_dev_initializer.h"
#include "debug_ramdisk.h"
#include "reboot_utils.h"
+#include "snapuserd_transition.h"
#include "util.h"
using namespace std::string_literals;
@@ -298,7 +299,12 @@
return access(plat_policy_cil_file, R_OK) != -1;
}
-bool LoadSplitPolicy() {
+struct PolicyFile {
+ unique_fd fd;
+ std::string path;
+};
+
+bool OpenSplitPolicy(PolicyFile* policy_file) {
// IMPLEMENTATION NOTE: Split policy consists of three CIL files:
// * platform -- policy needed due to logic contained in the system image,
// * non-platform -- policy needed due to logic contained in the vendor image,
@@ -325,10 +331,8 @@
if (!use_userdebug_policy && FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) {
unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY));
if (fd != -1) {
- if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) {
- LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file;
- return false;
- }
+ policy_file->fd = std::move(fd);
+ policy_file->path = std::move(precompiled_sepolicy_file);
return true;
}
}
@@ -446,34 +450,39 @@
}
unlink(compiled_sepolicy);
- LOG(INFO) << "Loading compiled SELinux policy";
- if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) {
- LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy;
- return false;
- }
-
+ policy_file->fd = std::move(compiled_sepolicy_fd);
+ policy_file->path = compiled_sepolicy;
return true;
}
-bool LoadMonolithicPolicy() {
- LOG(VERBOSE) << "Loading SELinux policy from monolithic file";
- if (selinux_android_load_policy() < 0) {
- PLOG(ERROR) << "Failed to load monolithic SELinux policy";
+bool OpenMonolithicPolicy(PolicyFile* policy_file) {
+ static constexpr char kSepolicyFile[] = "/sepolicy";
+
+ LOG(VERBOSE) << "Opening SELinux policy from monolithic file";
+ policy_file->fd.reset(open(kSepolicyFile, O_RDONLY | O_CLOEXEC | O_NOFOLLOW));
+ if (policy_file->fd < 0) {
+ PLOG(ERROR) << "Failed to open monolithic SELinux policy";
return false;
}
+ policy_file->path = kSepolicyFile;
return true;
}
-bool LoadPolicy() {
- return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy();
-}
+void ReadPolicy(std::string* policy) {
+ PolicyFile policy_file;
-void SelinuxInitialize() {
- LOG(INFO) << "Loading SELinux policy";
- if (!LoadPolicy()) {
- LOG(FATAL) << "Unable to load SELinux policy";
+ bool ok = IsSplitPolicyDevice() ? OpenSplitPolicy(&policy_file)
+ : OpenMonolithicPolicy(&policy_file);
+ if (!ok) {
+ LOG(FATAL) << "Unable to open SELinux policy";
}
+ if (!android::base::ReadFdToString(policy_file.fd, policy)) {
+ PLOG(FATAL) << "Failed to read policy file: " << policy_file.path;
+ }
+}
+
+void SelinuxSetEnforcement() {
bool kernel_enforcing = (security_getenforce() == 1);
bool is_enforcing = IsEnforcing();
if (kernel_enforcing != is_enforcing) {
@@ -670,6 +679,30 @@
}
}
+static void LoadSelinuxPolicy(std::string& policy) {
+ LOG(INFO) << "Loading SELinux policy";
+
+ set_selinuxmnt("/sys/fs/selinux");
+ if (security_load_policy(policy.data(), policy.size()) < 0) {
+ PLOG(FATAL) << "SELinux: Could not load policy";
+ }
+}
+
+// The SELinux setup process is carefully orchestrated around snapuserd. Policy
+// must be loaded off dynamic partitions, and during an OTA, those partitions
+// cannot be read without snapuserd. But, with kernel-privileged snapuserd
+// running, loading the policy will immediately trigger audits.
+//
+// We use a five-step process to address this:
+// (1) Read the policy into a string, with snapuserd running.
+// (2) Rewrite the snapshot device-mapper tables, to generate new dm-user
+// devices and to flush I/O.
+// (3) Kill snapuserd, which no longer has any dm-user devices to attach to.
+// (4) Load the sepolicy and issue critical restorecons in /dev, carefully
+// avoiding anything that would read from /system.
+// (5) Re-launch snapuserd and attach it to the dm-user devices from step (2).
+//
+// After this sequence, it is safe to enable enforcing mode and continue booting.
int SetupSelinux(char** argv) {
SetStdioToDevNull(argv);
InitKernelLogging(argv);
@@ -682,9 +715,31 @@
MountMissingSystemPartitions();
- // Set up SELinux, loading the SELinux policy.
SelinuxSetupKernelLogging();
- SelinuxInitialize();
+
+ LOG(INFO) << "Opening SELinux policy";
+
+ // Read the policy before potentially killing snapuserd.
+ std::string policy;
+ ReadPolicy(&policy);
+
+ auto snapuserd_helper = SnapuserdSelinuxHelper::CreateIfNeeded();
+ if (snapuserd_helper) {
+ // Kill the old snapused to avoid audit messages. After this we cannot
+ // read from /system (or other dynamic partitions) until we call
+ // FinishTransition().
+ snapuserd_helper->StartTransition();
+ }
+
+ LoadSelinuxPolicy(policy);
+
+ if (snapuserd_helper) {
+ // Before enforcing, finish the pending snapuserd transition.
+ snapuserd_helper->FinishTransition();
+ snapuserd_helper = nullptr;
+ }
+
+ SelinuxSetEnforcement();
// We're in the kernel domain and want to transition to the init domain. File systems that
// store SELabels in their xattrs, such as ext4 do not need an explicit restorecon here,
diff --git a/init/snapuserd_transition.cpp b/init/snapuserd_transition.cpp
new file mode 100644
index 0000000..19b5c57
--- /dev/null
+++ b/init/snapuserd_transition.cpp
@@ -0,0 +1,305 @@
+/*
+ * 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
diff --git a/init/snapuserd_transition.h b/init/snapuserd_transition.h
new file mode 100644
index 0000000..a5ab652
--- /dev/null
+++ b/init/snapuserd_transition.h
@@ -0,0 +1,87 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <sys/types.h>
+
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <libsnapshot/snapshot.h>
+
+#include "block_dev_initializer.h"
+
+namespace android {
+namespace init {
+
+// Fork and exec a new copy of snapuserd.
+void LaunchFirstStageSnapuserd();
+
+class SnapuserdSelinuxHelper final {
+ using SnapshotManager = android::snapshot::SnapshotManager;
+
+ public:
+ SnapuserdSelinuxHelper(std::unique_ptr<SnapshotManager>&& sm, pid_t old_pid);
+
+ void StartTransition();
+ void FinishTransition();
+
+ // Return a helper for facilitating the selinux transition of snapuserd.
+ // If snapuserd is not in use, null is returned. StartTransition() should
+ // be called after reading policy. FinishTransition() should be called
+ // after loading policy. In between, no reads of /system or other dynamic
+ // partitions are possible.
+ static std::unique_ptr<SnapuserdSelinuxHelper> CreateIfNeeded();
+
+ private:
+ void RelaunchFirstStageSnapuserd();
+ void ExecSnapuserd();
+
+ std::unique_ptr<SnapshotManager> sm_;
+ BlockDevInitializer block_dev_init_;
+ pid_t old_pid_;
+ std::vector<std::string> argv_;
+};
+
+// Remove /dev/socket/snapuserd. This ensures that (1) the existing snapuserd
+// will receive no new requests, and (2) the next copy we transition to can
+// own the socket.
+void CleanupSnapuserdSocket();
+
+// Kill an instance of snapuserd given a pid.
+void KillFirstStageSnapuserd(pid_t pid);
+
+// Save an open fd to /system/bin (in the ramdisk) into an environment. This is
+// used to later execveat() snapuserd.
+void SaveRamdiskPathToSnapuserd();
+
+// Returns true if first-stage snapuserd is running.
+bool IsFirstStageSnapuserdRunning();
+
+// Return the pid of the first-stage instances of snapuserd, if it was started.
+std::optional<pid_t> GetSnapuserdFirstStagePid();
+
+// Save an open fd to /system/bin (in the ramdisk) into an environment. This is
+// used to later execveat() snapuserd.
+void SaveRamdiskPathToSnapuserd();
+
+// Returns true if first-stage snapuserd is running.
+bool IsFirstStageSnapuserdRunning();
+
+} // namespace init
+} // namespace android