| // Copyright 2014 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "login_manager/subprocess.h" | |
| #include <errno.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/mount.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <algorithm> | |
| #include <memory> | |
| #include <vector> | |
| #include <base/check.h> | |
| #include <base/check_op.h> | |
| #include <base/logging.h> | |
| #include <base/posix/file_descriptor_shuffle.h> | |
| #include <base/process/launch.h> | |
| #include <base/time/time.h> | |
| #include <vboot/crossystem.h> | |
| #include <libminijail.h> | |
| #include <scoped_minijail.h> | |
| #include "login_manager/landlock_policy.h" | |
| #include "login_manager/session_manager_service.h" | |
| #include "login_manager/system_utils.h" | |
| namespace login_manager { | |
| namespace { | |
| // Checks if Landlock sandboxing should be applied. | |
| bool ShouldApplyLandlockPolicy() { | |
| return !!USE_APPLY_LANDLOCK_POLICY; | |
| } | |
| // Checks if no_new_privs should be applied. | |
| bool ShouldApplyNoNewPrivs() { | |
| return !!USE_APPLY_NO_NEW_PRIVS; | |
| } | |
| // Checks if sudo in Dev mode should be allowed. | |
| bool ShouldEnableCroshSudo() { | |
| return !!USE_ENABLE_CROSH_SUDO; | |
| } | |
| // Returns true if the system is in Developer mode. | |
| bool IsDevMode() { | |
| return VbGetSystemPropertyInt("cros_debug") == 1; | |
| } | |
| } // anonymous namespace | |
| Subprocess::Subprocess(uid_t uid, SystemUtils* system) | |
| : pid_(-1), | |
| desired_uid_(uid), | |
| new_mount_namespace_(false), | |
| system_(system) {} | |
| Subprocess::~Subprocess() {} | |
| void Subprocess::UseNewMountNamespace() { | |
| new_mount_namespace_ = true; | |
| ns_mnt_path_.reset(); | |
| } | |
| void Subprocess::EnterExistingMountNamespace( | |
| const base::FilePath& ns_mnt_path) { | |
| ns_mnt_path_ = ns_mnt_path; | |
| new_mount_namespace_ = false; | |
| } | |
| bool Subprocess::ForkAndExec(const std::vector<std::string>& args, | |
| const std::vector<std::string>& env_vars) { | |
| gid_t gid = 0; | |
| std::vector<gid_t> groups; | |
| if (desired_uid_ != 0 && | |
| !system_->GetGidAndGroups(desired_uid_, &gid, &groups)) { | |
| LOG(ERROR) << "Can't get group info for UID " << desired_uid_; | |
| return false; | |
| } | |
| ScopedMinijail j(minijail_new()); | |
| if (desired_uid_ != 0) { | |
| minijail_change_uid(j.get(), desired_uid_); | |
| minijail_change_gid(j.get(), gid); | |
| minijail_set_supplementary_gids(j.get(), groups.size(), groups.data()); | |
| minijail_create_session(j.get()); | |
| } | |
| minijail_preserve_fd(j.get(), STDIN_FILENO, STDIN_FILENO); | |
| // Landlock is currently supported on kernels 5.10+. | |
| if (ShouldApplyLandlockPolicy() && minijail_is_fs_restriction_available()) { | |
| LandlockPolicy fs_policy; | |
| fs_policy.SetupPolicy(j.get()); | |
| } | |
| minijail_preserve_fd(j.get(), STDOUT_FILENO, STDOUT_FILENO); | |
| minijail_preserve_fd(j.get(), STDERR_FILENO, STDERR_FILENO); | |
| minijail_close_open_fds(j.get()); | |
| // Reset signal handlers in the child since they'll be blocked below. | |
| minijail_reset_signal_mask(j.get()); | |
| minijail_reset_signal_handlers(j.get()); | |
| if (new_mount_namespace_) { | |
| minijail_namespace_vfs(j.get()); | |
| } else if (ns_mnt_path_.has_value()) { | |
| minijail_namespace_enter_vfs(j.get(), ns_mnt_path_.value().value().c_str()); | |
| } | |
| if (ns_mnt_path_.has_value() || new_mount_namespace_) { | |
| // Remount all shared mount points as MS_SLAVE to allow shared mount points | |
| // from outside this namespace to propagate in. This is necessary for users | |
| // to be able to access USB drives/SD cards. cros-disks runs in its own | |
| // mount namespace and uses a shared mount point, /media, to make any | |
| // devices it mounts available outside its mount namespace. | |
| minijail_remount_mode(j.get(), MS_SLAVE); | |
| } | |
| // Block all signals before running the child so that we can avoid a race | |
| // in which the child executes configured signal handlers before the default | |
| // handlers are installed. In the parent, we restore original signal blocks | |
| // immediately after SystemUtils::RunInMinijail(). | |
| sigset_t filled_sigset, old_sigset; | |
| sigfillset(&filled_sigset); | |
| CHECK_EQ(0, sigprocmask(SIG_SETMASK, &filled_sigset, &old_sigset)); | |
| // Set no_new_privs if configured and the system is in Verified mode. We | |
| // don’t set this if we’re in Dev and ShouldEnableCroshSudo is set, because | |
| // the user will need new_privs for sudo commands. | |
| if (ShouldApplyNoNewPrivs() && !(IsDevMode() && ShouldEnableCroshSudo())) { | |
| minijail_no_new_privs(j.get()); | |
| } | |
| pid_t child_pid = 0; | |
| bool success = system_->RunInMinijail(j, args, env_vars, &child_pid); | |
| CHECK_EQ(0, sigprocmask(SIG_SETMASK, &old_sigset, nullptr)); | |
| if (success) { | |
| pid_ = child_pid; | |
| } | |
| return pid_.has_value(); | |
| } | |
| void Subprocess::KillEverything(int signal) { | |
| DCHECK(pid_.has_value()); | |
| if (system_->kill(-pid_.value(), desired_uid_, signal) == 0) | |
| return; | |
| // If we failed to kill the process group (maybe it doesn't exist yet because | |
| // the forked process hasn't had a chance to call setsid()), just kill the | |
| // child directly. If it hasn't called setsid() yet, then it hasn't called | |
| // setuid() either, so kill it as root instead of as |desired_uid_|. | |
| system_->kill(pid_.value(), 0, signal); | |
| } | |
| void Subprocess::Kill(int signal) { | |
| DCHECK(pid_.has_value()); | |
| system_->kill(pid_.value(), desired_uid_, signal); | |
| } | |
| pid_t Subprocess::GetPid() const { | |
| return pid_.value_or(-1); | |
| } | |
| void Subprocess::ClearPid() { | |
| pid_.reset(); | |
| } | |
| }; // namespace login_manager |