blob: 262b542529548be85f65616cd369863d105a5fb1 [file] [log] [blame]
// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "login_manager/session_manager_service.h"
#include <dbus/dbus.h> // C dbus library header. Used in FilterMessage().
#include <fcntl.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include <base/bind.h>
#include <base/bind_helpers.h>
#include <base/callback.h>
#include <base/command_line.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/optional.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
#include <base/time/time.h>
#include <brillo/message_loops/message_loop.h>
#include <chromeos/dbus/service_constants.h>
#include <chromeos/switches/chrome_switches.h>
#include <dbus/bus.h>
#include <dbus/exported_object.h>
#include <dbus/message.h>
#include <dbus/object_proxy.h>
#include <dbus/scoped_dbus_error.h>
#include "login_manager/browser_job.h"
#include "login_manager/child_exit_dispatcher.h"
#include "login_manager/chrome_features_service_client.h"
#include "login_manager/key_generator.h"
#include "login_manager/liveness_checker_impl.h"
#include "login_manager/login_metrics.h"
#include "login_manager/nss_util.h"
#include "login_manager/session_manager_impl.h"
#include "login_manager/system_utils.h"
#include "login_manager/systemd_unit_starter.h"
#include "login_manager/upstart_signal_emitter.h"
#if USE_ARC_ADB_SIDELOADING
#include "login_manager/arc_sideload_status.h"
#else
#include "login_manager/arc_sideload_status_stub.h"
#endif
namespace em = enterprise_management;
namespace login_manager {
namespace {
const int kSignals[] = {SIGTERM, SIGINT, SIGHUP};
const int kNumSignals = sizeof(kSignals) / sizeof(int);
// The only path where containers are allowed to be installed. They must be
// part of the read-only, signed root image.
const char kContainerInstallDirectory[] = "/opt/google/containers";
// The path where the pid of an aborted browser process is written. This is done
// so that crash reporting tools can detect an abort that originated from
// session_manager.
const char kAbortedBrowserPidPath[] = "/run/chrome/aborted_browser_pid";
// The path where the pid of browser process is written if it took too long to
// shutdown. This is done so that crash reporting tools can detect an abort that
// originated from session_manager.
const char kShutdownBrowserPidPath[] = "/run/chrome/shutdown_browser_pid";
// How long to wait before timing out on a StopAllVms message. Wait up to 2
// minutes as there may be multiple VMs and they may each take some time to
// cleanly shut down.
constexpr int kStopAllVmsTimeoutMs = 120000;
// Long kill time out. Used instead of the default one when chrome feature
// 'SessionManagerLongKillTimeout' is enabled. Note that this must be less than
// the 20-second kill timeout granted to session_manager in ui.conf.
constexpr base::TimeDelta kLongKillTimeout = base::TimeDelta::FromSeconds(12);
// A flag file of whether to dump chrome crashes on dev/test image.
constexpr char kCollectChromeFile[] =
"/mnt/stateful_partition/etc/collect_chrome_crashes";
constexpr char kFeatureNamelSessionManagerLongKillTimeout[] =
"SessionManagerLongKillTimeout";
// I need a do-nothing action for SIGALRM, or using alarm() will kill me.
void DoNothing(int signal) {}
// Nothing to do for handling a response to a StopAllVms D-Bus request. We
// should replace this with base::DoNothing() if we ever uprev libchrome.
void HandleStopAllVmsResponse(dbus::Response*) {}
const char* ExitCodeToString(SessionManagerService::ExitCode code) {
switch (code) {
case SessionManagerService::SUCCESS:
return "exiting cleanly";
case SessionManagerService::CRASH_WHILE_RESTART_DISABLED:
return "got crash while restart disabled";
case SessionManagerService::CHILD_EXITING_TOO_FAST:
return "child exiting too fast";
case SessionManagerService::MUST_WIPE_DEVICE:
return "must wipe device";
}
NOTREACHED() << "Invalid exit code " << code;
return "unknown";
}
} // anonymous namespace
void SessionManagerService::TestApi::ScheduleChildExit(pid_t pid, int status) {
siginfo_t info;
info.si_pid = pid;
if (WIFEXITED(status)) {
info.si_code = CLD_EXITED;
info.si_status = WEXITSTATUS(status);
} else {
info.si_status = WTERMSIG(status);
}
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&SessionManagerService::HandleExit),
session_manager_service_, info));
}
SessionManagerService::SessionManagerService(
std::unique_ptr<BrowserJobInterface> child_job,
uid_t uid,
base::Optional<base::FilePath> ns_path,
base::TimeDelta kill_timeout,
bool enable_browser_abort_on_hang,
base::TimeDelta hang_detection_interval,
LoginMetrics* metrics,
SystemUtils* utils)
: browser_(std::move(child_job)),
chrome_mount_ns_path_(ns_path),
kill_timeout_(kill_timeout),
match_rule_(base::StringPrintf("type='method_call', interface='%s'",
kSessionManagerInterface)),
login_metrics_(metrics),
system_(utils),
nss_(NssUtil::Create()),
owner_key_(nss_->GetOwnerKeyFilePath(), nss_.get()),
key_gen_(uid, utils),
state_key_generator_(utils, metrics),
vpd_process_(utils),
android_container_(std::make_unique<AndroidOciWrapper>(
utils, base::FilePath(kContainerInstallDirectory))),
enable_browser_abort_on_hang_(enable_browser_abort_on_hang),
liveness_checking_interval_(hang_detection_interval),
aborted_browser_pid_path_(kAbortedBrowserPidPath),
shutdown_browser_pid_path_(kShutdownBrowserPidPath) {
DCHECK(browser_);
SetUpHandlers();
}
SessionManagerService::~SessionManagerService() {
RevertHandlers();
}
bool SessionManagerService::Initialize() {
LOG(INFO) << "SessionManagerService starting";
InitializeDBus();
screen_lock_dbus_proxy_ =
bus_->GetObjectProxy(chromeos::kScreenLockServiceName,
dbus::ObjectPath(chromeos::kScreenLockServicePath));
powerd_dbus_proxy_ = bus_->GetObjectProxy(
power_manager::kPowerManagerServiceName,
dbus::ObjectPath(power_manager::kPowerManagerServicePath));
vm_concierge_dbus_proxy_ = bus_->GetObjectProxy(
vm_tools::concierge::kVmConciergeServiceName,
dbus::ObjectPath(vm_tools::concierge::kVmConciergeServicePath));
vm_concierge_dbus_proxy_->SetNameOwnerChangedCallback(base::Bind(
&SessionManagerService::VmConciergeOwnerChanged, base::Unretained(this)));
vm_concierge_dbus_proxy_->WaitForServiceToBeAvailable(base::Bind(
&SessionManagerService::VmConciergeAvailable, base::Unretained(this)));
dbus::ObjectProxy* system_clock_proxy = bus_->GetObjectProxy(
system_clock::kSystemClockServiceName,
dbus::ObjectPath(system_clock::kSystemClockServicePath));
debugd_dbus_proxy_ = bus_->GetObjectProxy(
debugd::kDebugdServiceName, dbus::ObjectPath(debugd::kDebugdServicePath));
#if USE_SYSTEMD
using InitDaemonControllerImpl = SystemdUnitStarter;
#else
using InitDaemonControllerImpl = UpstartSignalEmitter;
#endif
dbus::ObjectProxy* init_dbus_proxy =
bus_->GetObjectProxy(InitDaemonControllerImpl::kServiceName,
dbus::ObjectPath(InitDaemonControllerImpl::kPath));
dbus::ObjectProxy* liveness_proxy =
bus_->GetObjectProxy(chromeos::kLivenessServiceName,
dbus::ObjectPath(chromeos::kLivenessServicePath));
liveness_checker_.reset(new LivenessCheckerImpl(this, liveness_proxy,
enable_browser_abort_on_hang_,
liveness_checking_interval_));
#if USE_ARC_ADB_SIDELOADING
boot_lockbox_dbus_proxy_ = bus_->GetObjectProxy(
cryptohome::kBootLockboxServiceName,
dbus::ObjectPath(cryptohome::kBootLockboxServicePath));
ArcSideloadStatusInterface* arc_sideload_status =
new ArcSideloadStatus(boot_lockbox_dbus_proxy_);
#else
ArcSideloadStatusInterface* arc_sideload_status = new ArcSideloadStatusStub();
#endif
chrome_features_service_client_ =
std::make_unique<ChromeFeaturesServiceClient>(bus_->GetObjectProxy(
chromeos::kChromeFeaturesServiceName,
dbus::ObjectPath(chromeos::kChromeFeaturesServicePath)));
// Initially store in derived-type pointer, so that we can initialize
// appropriately below.
impl_ = std::make_unique<SessionManagerImpl>(
this /* delegate */,
std::make_unique<InitDaemonControllerImpl>(init_dbus_proxy), bus_,
&key_gen_, &state_key_generator_,
this /* manager, i.e. ProcessManagerServiceInterface */, login_metrics_,
nss_.get(), chrome_mount_ns_path_, system_, &crossystem_, &vpd_process_,
&owner_key_, android_container_.get(), &install_attributes_reader_,
powerd_dbus_proxy_, system_clock_proxy, debugd_dbus_proxy_,
arc_sideload_status);
if (!InitializeImpl())
return false;
// Set any flags that were specified system-wide.
browser_->SetExtraArguments(impl_->GetStartUpSwitches());
browser_->SetFeatureFlags(impl_->GetFeatureFlags());
CHECK(impl_->StartDBusService())
<< "Unable to start " << kSessionManagerServiceName << " D-Bus service.";
return true;
}
void SessionManagerService::Finalize() {
LOG(INFO) << "SessionManagerService exiting";
impl_->Finalize();
ShutDownDBus();
}
void SessionManagerService::LockScreen() {
dbus::MethodCall call(chromeos::kScreenLockServiceInterface,
chromeos::kScreenLockServiceShowLockScreenMethod);
screen_lock_dbus_proxy_->CallMethod(
&call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, base::DoNothing());
}
void SessionManagerService::RestartDevice(const std::string& description) {
dbus::MethodCall call(power_manager::kPowerManagerInterface,
power_manager::kRequestRestartMethod);
dbus::MessageWriter writer(&call);
writer.AppendInt32(power_manager::REQUEST_RESTART_OTHER);
writer.AppendString(description);
powerd_dbus_proxy_->CallMethodAndBlock(
&call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT);
}
void SessionManagerService::ScheduleShutdown() {
SetExitAndScheduleShutdown(SUCCESS);
}
void SessionManagerService::RunBrowser() {
browser_->RunInBackground();
DLOG(INFO) << "Browser is " << browser_->CurrentPid();
liveness_checker_->Start();
// |chrome_features_service_client_| is null in test.
if (chrome_features_service_client_) {
chrome_features_service_client_->IsFeatureEnabled(
kFeatureNamelSessionManagerLongKillTimeout,
base::Bind(&SessionManagerService::OnLongKillTimeoutEnabled,
base::Unretained(this)));
}
// Note that |child_exit_handler_| will catch browser process termination and
// call HandleExit().
}
void SessionManagerService::AbortBrowserForHang() {
LOG(INFO) << "Browser did not respond to DBus liveness check.";
WriteBrowserPidFile(aborted_browser_pid_path_);
browser_->AbortAndKillAll(GetKillTimeout());
}
void SessionManagerService::SetBrowserTestArgs(
const std::vector<std::string>& args) {
browser_->SetTestArguments(args);
}
void SessionManagerService::SetBrowserArgs(
const std::vector<std::string>& args) {
browser_->SetArguments(args);
}
void SessionManagerService::SetBrowserAdditionalEnvironmentalVariables(
const std::vector<std::string>& env_vars) {
browser_->SetAdditionalEnvironmentVariables(env_vars);
}
void SessionManagerService::RestartBrowser() {
// Waiting for Chrome to shutdown takes too much time.
// We're killing it immediately hoping that data Chrome uses before
// logging in is not corrupted.
// TODO(avayvod): Remove RestartJob when crosbug.com/6924 is fixed.
if (browser_->CurrentPid() > 0)
browser_->KillEverything(SIGKILL, "Restarting browser on-demand.");
// The browser will be restarted in HandleExit().
}
void SessionManagerService::SetBrowserSessionForUser(
const std::string& account_id, const std::string& userhash) {
browser_->StartSession(account_id, userhash);
}
void SessionManagerService::SetFlagsForUser(
const std::string& account_id, const std::vector<std::string>& flags) {
browser_->SetExtraArguments(flags);
}
void SessionManagerService::SetFeatureFlagsForUser(
const std::string& account_id,
const std::vector<std::string>& feature_flags) {
browser_->SetExtraArguments({});
browser_->SetFeatureFlags(feature_flags);
}
bool SessionManagerService::IsBrowser(pid_t pid) {
return (browser_->CurrentPid() > 0 && pid == browser_->CurrentPid());
}
base::TimeTicks SessionManagerService::GetLastBrowserRestartTime() {
return last_browser_restart_time_;
}
bool SessionManagerService::HandleExit(const siginfo_t& status) {
if (!IsBrowser(status.si_pid))
return false;
LOG(INFO) << "Browser process " << status.si_pid << " exited with "
<< GetExitDescription(status);
// Clears up the whole job's process group.
browser_->KillEverything(SIGKILL, "Ensuring browser processes are gone.");
DLOG(INFO) << "Waiting up to " << GetKillTimeout().InSeconds()
<< " seconds for "
<< "browser process group to exit";
if (!browser_->WaitForExit(GetKillTimeout())) {
LOG(ERROR) << "Browser process still around after SIGKILL and "
<< GetKillTimeout().InSeconds() << " seconds.";
}
browser_->ClearPid();
// Also ensure all containers are gone.
android_container_->RequestJobExit(ArcContainerStopReason::BROWSER_SHUTDOWN);
android_container_->EnsureJobExit(SessionManagerImpl::kContainerTimeout);
// Do nothing if already shutting down.
if (shutting_down_)
return true;
liveness_checker_->Stop();
std::string end_reason;
if (impl_->ShouldEndSession(&end_reason)) {
LOG(ERROR) << "Ending session rather than restarting browser: "
<< end_reason << ".";
SetExitAndScheduleShutdown(CRASH_WHILE_RESTART_DISABLED);
return true;
}
if (browser_->ShouldStop()) {
LOG(WARNING) << "Child stopped, shutting down";
SetExitAndScheduleShutdown(CHILD_EXITING_TOO_FAST);
} else if (browser_->ShouldRunBrowser()) {
// TODO(cmasone): deal with fork failing in RunBrowser()
RunBrowser();
last_browser_restart_time_ = base::TimeTicks::Now();
} else {
LOG(INFO) << "Should NOT run " << browser_->GetName() << " again.";
AllowGracefulExitOrRunForever();
}
return true;
}
DBusHandlerResult SessionManagerService::FilterMessage(DBusConnection* conn,
DBusMessage* message,
void* data) {
SessionManagerService* service = static_cast<SessionManagerService*>(data);
if (::dbus_message_is_method_call(message, kSessionManagerInterface,
kSessionManagerRestartJob)) {
const char* sender = ::dbus_message_get_sender(message);
if (!sender) {
LOG(ERROR) << "Call to RestartJob has no sender";
return DBUS_HANDLER_RESULT_HANDLED;
}
LOG(INFO) << "Received RestartJob from " << sender;
DBusMessage* get_pid = ::dbus_message_new_method_call(
"org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus",
"GetConnectionUnixProcessID");
CHECK(get_pid);
::dbus_message_append_args(get_pid, DBUS_TYPE_STRING, &sender,
DBUS_TYPE_INVALID);
DBusMessage* got_pid =
::dbus_connection_send_with_reply_and_block(conn, get_pid, -1, nullptr);
::dbus_message_unref(get_pid);
if (!got_pid) {
LOG(ERROR) << "Could not look up sender of RestartJob.";
return DBUS_HANDLER_RESULT_HANDLED;
}
uint32_t pid;
if (!::dbus_message_get_args(got_pid, nullptr, DBUS_TYPE_UINT32, &pid,
DBUS_TYPE_INVALID)) {
::dbus_message_unref(got_pid);
LOG(ERROR) << "Could not extract pid of sender of RestartJob.";
return DBUS_HANDLER_RESULT_HANDLED;
}
::dbus_message_unref(got_pid);
if (!service->IsBrowser(pid)) {
LOG(WARNING) << "Sender of RestartJob (PID " << pid
<< ") is no child of mine!";
DBusMessage* denial = dbus_message_new_error(
message, DBUS_ERROR_ACCESS_DENIED, "Sender is not browser.");
if (!denial || !::dbus_connection_send(conn, denial, nullptr))
LOG(ERROR) << "Could not create error response to RestartJob.";
return DBUS_HANDLER_RESULT_HANDLED;
}
}
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
void SessionManagerService::SetUpHandlers() {
// I have to ignore SIGUSR1, because Xorg sends it to this process when it's
// got no clients and is ready for new ones. If we don't ignore it, we die.
struct sigaction action;
memset(&action, 0, sizeof(action));
action.sa_handler = SIG_IGN;
CHECK_EQ(sigaction(SIGUSR1, &action, nullptr), 0);
action.sa_handler = DoNothing;
CHECK_EQ(sigaction(SIGALRM, &action, nullptr), 0);
signal_handler_.Init();
DCHECK(!child_exit_dispatcher_.get());
child_exit_dispatcher_ = std::make_unique<ChildExitDispatcher>(
&signal_handler_,
std::vector<ChildExitHandler*>{this, &key_gen_, &vpd_process_,
android_container_.get()});
for (int i = 0; i < kNumSignals; ++i) {
signal_handler_.RegisterHandler(
kSignals[i], base::Bind(&SessionManagerService::OnTerminationSignal,
base::Unretained(this)));
}
}
void SessionManagerService::RevertHandlers() {
struct sigaction action = {};
action.sa_handler = SIG_DFL;
RAW_CHECK(sigaction(SIGUSR1, &action, nullptr) == 0);
RAW_CHECK(sigaction(SIGALRM, &action, nullptr) == 0);
}
base::TimeDelta SessionManagerService::GetKillTimeout() {
// When Chrome is configured to write core files (which only happens during
// testing), give it extra time to exit.
if (base::PathExists(base::FilePath(kCollectChromeFile)))
return kLongKillTimeout;
if (use_long_kill_timeout_)
return kLongKillTimeout;
return kill_timeout_;
}
bool SessionManagerService::InitializeImpl() {
if (!impl_->Initialize()) {
LOG(ERROR) << "Policy key is likely corrupt. Initiating device wipe.";
impl_->InitiateDeviceWipe("bad_policy_key");
impl_->Finalize();
exit_code_ = MUST_WIPE_DEVICE;
return false;
}
return true;
}
void SessionManagerService::InitializeDBus() {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = new dbus::Bus(options);
CHECK(bus_->Connect());
CHECK(bus_->SetUpAsyncOperations());
bus_->AddFilterFunction(&SessionManagerService::FilterMessage, this);
dbus::ScopedDBusError error;
bus_->AddMatch(match_rule_, error.get());
CHECK(!error.is_set()) << "Failed to add match to bus: " << error.name()
<< ", message="
<< (error.message() ? error.message() : "unknown.");
}
void SessionManagerService::ShutDownDBus() {
dbus::ScopedDBusError error;
bus_->RemoveMatch(match_rule_, error.get());
if (error.is_set()) {
LOG(ERROR) << "Failed to remove match from bus: " << error.name()
<< ", message="
<< (error.message() ? error.message() : "unknown.");
}
bus_->RemoveFilterFunction(&SessionManagerService::FilterMessage, this);
bus_->ShutdownAndBlock();
}
void SessionManagerService::AllowGracefulExitOrRunForever() {
if (exit_on_child_done_) {
LOG(INFO) << "SessionManagerService set to exit on child done";
brillo::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&SessionManagerService::ScheduleShutdown),
this));
} else {
DLOG(INFO) << "OK, running forever...";
}
}
void SessionManagerService::SetExitAndScheduleShutdown(ExitCode code) {
LoginMetrics::SessionExitType exit_type =
LoginMetrics::SessionExitType::NORMAL_EXIT;
if (code == CHILD_EXITING_TOO_FAST) {
exit_type = LoginMetrics::SessionExitType::LOGIN_CRASH_LOOP;
}
login_metrics_->SendSessionExitType(exit_type);
// Stop the VMs from this session as their data will no longer be accessible.
MaybeStopAllVms();
shutting_down_ = true;
exit_code_ = code;
impl_->AnnounceSessionStoppingIfNeeded();
child_exit_dispatcher_.reset();
liveness_checker_->Stop();
CleanupChildrenBeforeExit(code);
impl_->AnnounceSessionStopped();
brillo::MessageLoop::current()->PostTask(
FROM_HERE, base::Bind(&brillo::MessageLoop::BreakLoop,
base::Unretained(brillo::MessageLoop::current())));
LOG(INFO) << "SessionManagerService quitting run loop";
}
void SessionManagerService::CleanupChildrenBeforeExit(ExitCode code) {
const std::string reason = ExitCodeToString(code);
const base::TimeTicks browser_exit_start_time = base::TimeTicks::Now();
browser_->Kill(SIGTERM, reason);
key_gen_.RequestJobExit(reason);
android_container_->RequestJobExit(
code == ExitCode::SUCCESS
? ArcContainerStopReason::SESSION_MANAGER_SHUTDOWN
: ArcContainerStopReason::BROWSER_SHUTDOWN);
DLOG(INFO) << "Waiting up to "
<< SessionManagerImpl::kBrowserTimeout.InSeconds()
<< " seconds for browser process group to exit";
// We're going to wait several times for various processes to exit, but we
// want those timeouts to be running in parallel. That is, if we end up
// waiting 5 seconds for the browser to stop, we should reduce the later
// timeouts by that time.
const base::TimeTicks timeout_start = base::TimeTicks::Now();
if (!browser_->WaitForExit(SessionManagerImpl::kBrowserTimeout)) {
LOG(WARNING) << "Browser process did not exit "
<< SessionManagerImpl::kBrowserTimeout.InSeconds()
<< " seconds after SIGTERM.";
WriteBrowserPidFile(shutdown_browser_pid_path_);
browser_->AbortAndKillAll(GetKillTimeout());
}
if (code == SessionManagerService::SUCCESS) {
// Only record shutdown time for normal exit.
login_metrics_->SendBrowserShutdownTime(base::TimeTicks::Now() -
browser_exit_start_time);
}
key_gen_.EnsureJobExit(std::max(
base::TimeDelta(), SessionManagerImpl::kKeyGenTimeout -
(base::TimeTicks::Now() - timeout_start)));
android_container_->EnsureJobExit(std::max(
base::TimeDelta(), SessionManagerImpl::kContainerTimeout -
(base::TimeTicks::Now() - timeout_start)));
}
bool SessionManagerService::OnTerminationSignal(
const struct signalfd_siginfo& info) {
ScheduleShutdown();
return true;
}
void SessionManagerService::VmConciergeOwnerChanged(
const std::string& old_owner, const std::string& new_owner) {
vm_concierge_available_ = !new_owner.empty();
}
void SessionManagerService::VmConciergeAvailable(bool is_available) {
vm_concierge_available_ = is_available;
}
void SessionManagerService::MaybeStopAllVms() {
if (!vm_concierge_available_) {
// The vm_concierge D-Bus service is not running so there are no VMs to
// stop.
return;
}
// Stop all running VMs. We do this asynchronously as we don't need to wait
// for the VMs to exit before restarting chrome.
dbus::MethodCall method_call(vm_tools::concierge::kVmConciergeInterface,
vm_tools::concierge::kStopAllVmsMethod);
vm_concierge_dbus_proxy_->CallMethod(&method_call, kStopAllVmsTimeoutMs,
base::Bind(&HandleStopAllVmsResponse));
}
void SessionManagerService::WriteBrowserPidFile(base::FilePath path) {
// This is safe from symlink attacks because /run/chrome is guaranteed to be a
// root-owned directory (/run is in the rootfs, /run/chrome is created by
// session_manager as a directory).
if (!base::DeleteFile(path)) {
PLOG(ERROR) << "Failed to delete " << path.value();
return;
}
// Note that we pass O_CREAT | O_EXCL to make this fail should the file
// already exist. This avoids race conditions with malicious chronos processes
// attempting to recreate e.g. a symlink at the path to redirect our write
// elsewhere.
base::ScopedFD browser_pid_fd(open(
path.value().c_str(),
O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC | O_NOFOLLOW | O_NONBLOCK, 0644));
if (!browser_pid_fd.is_valid()) {
PLOG(ERROR) << "Could not create " << path.value();
return;
}
std::string pid_string = base::NumberToString(browser_->CurrentPid());
if (!base::WriteFileDescriptor(browser_pid_fd.get(), pid_string.c_str(),
pid_string.size())) {
PLOG(ERROR) << "Failed to write " << path.value();
return;
}
// Change the file to be owned by the user and group of the containing
// directory. crash_reporter, which reads this file, is run by chrome using
// the chronos user.
struct stat sbuf;
if (stat(path.DirName().value().c_str(), &sbuf) != 0) {
PLOG(ERROR) << "Could not stat: " << path.DirName().value();
return;
}
if (fchown(browser_pid_fd.get(), sbuf.st_uid, sbuf.st_gid) < 0) {
PLOG(ERROR) << "Could not chown: " << path.value();
}
}
void SessionManagerService::OnLongKillTimeoutEnabled(
base::Optional<bool> enabled) {
if (!enabled.has_value()) {
LOG(ERROR) << "Failed to check kSessionManagerLongKillTimeout feature.";
use_long_kill_timeout_ = false;
return;
}
use_long_kill_timeout_ = enabled.value();
}
} // namespace login_manager