blob: 83632145f1f0f338b61b9efa04e4f5bbd1475fa0 [file] [log] [blame]
// Copyright 2019 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 <string>
#include <base/synchronization/waitable_event.h>
#include <base/message_loop/message_pump_type.h>
#include <base/threading/thread.h>
#include <brillo/dbus/dbus_connection.h>
#include <brillo/dbus/introspectable_helper.h>
#include <dbus/cryptohome/dbus-constants.h>
#include <dbus/tpm_manager/dbus-constants.h>
#include "cryptohome/proxy/dbus_proxy_service.h"
namespace cryptohome {
using brillo::dbus_utils::AsyncEventSequencer;
constexpr char kBlockerThreadName[] = "BlockerDBusThread";
class ServiceBlocker {
public:
ServiceBlocker()
: dbus_thread_(kBlockerThreadName),
cryptohome_online(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED),
tpm_manager_online(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED) {}
// Calling this will block until both cryptohome and tpm_manager is online.
// This should be called from the caller's origin thread.
// Note that the reason why we'll need to wait for both cryptohome and
// tpm_manager is because some users of cryptohome's legacy API expects all
// cryptohome APIs are available when any one of them is available, but that
// is not the case with cryptohome-proxy, whereby some APIs handled by
// tpm_manager could be available earlier than those handled by
// cryptohome/UserDataAuth.
void BlockUntilDestinationIsOnline() {
// Start the dbus thread. Note that this will need to be an I/O thread
// because ListenForServiceOwnerChange() needs it.
base::Thread::Options options;
options.message_pump_type = base::MessagePumpType::IO;
dbus_thread_.StartWithOptions(options);
dbus_thread_.task_runner()->PostTask(
FROM_HERE,
base::Bind(&ServiceBlocker::SetupBlockUntilDestinationIsOnline,
base::Unretained(this)));
cryptohome_online.Wait();
tpm_manager_online.Wait();
dbus_thread_.task_runner()->PostTask(
FROM_HERE,
base::Bind(&ServiceBlocker::Cleanup, base::Unretained(this)));
dbus_thread_.Stop();
}
private:
// The thread on which we'll establish the dbus connection and wait for
// services to be online.
base::Thread dbus_thread_;
// These events will be signaled once cryptohome/tpm_manager is online.
base::WaitableEvent cryptohome_online;
base::WaitableEvent tpm_manager_online;
// We need to keep these in order to unlisten/unregister the events.
::dbus::Bus::ServiceOwnerChangeCallback on_cryptohome_online_;
::dbus::Bus::ServiceOwnerChangeCallback on_tpm_manager_online_;
// The separate dbus connection that we'll use to monitor the service status.
scoped_refptr<::dbus::Bus> bus_;
std::unique_ptr<brillo::DBusConnection> bus_connection_;
// This setup the callbacks that listen for service owner change. This is
// should be called on this class's dbus_thread_
void SetupBlockUntilDestinationIsOnline() {
// Note that the reason why another MessageLoop/DBus connection is needed
// because we are currently blocking the other (original) connection's dbus
// thread, and thus we'll not be able to wait for services to come online as
// no messages are delivered there while blocked.
// Create another connection to DBus.
bus_connection_.reset(new brillo::DBusConnection);
bus_ = bus_connection_->Connect();
CHECK(bus_);
on_cryptohome_online_ = base::Bind(
&ServiceBlocker::OnCryptohomeServiceChange, base::Unretained(this));
on_tpm_manager_online_ = base::Bind(
&ServiceBlocker::OnTpmManagerServiceChange, base::Unretained(this));
// Setup the callbacks.
bus_->ListenForServiceOwnerChange(kUserDataAuthServiceName,
on_cryptohome_online_);
bus_->GetServiceOwner(kUserDataAuthServiceName, on_cryptohome_online_);
bus_->ListenForServiceOwnerChange(tpm_manager::kTpmManagerServiceName,
on_tpm_manager_online_);
bus_->GetServiceOwner(tpm_manager::kTpmManagerServiceName,
on_tpm_manager_online_);
}
// This is called by dbus when cryptohome's owner changes.
void OnCryptohomeServiceChange(const std::string& service_owner) {
if (!service_owner.empty()) {
bus_->UnlistenForServiceOwnerChange(kUserDataAuthServiceName,
on_cryptohome_online_);
cryptohome_online.Signal();
}
}
// This is called by dbus when cryptohome's owner changes.
void OnTpmManagerServiceChange(const std::string& service_owner) {
if (!service_owner.empty()) {
bus_->UnlistenForServiceOwnerChange(tpm_manager::kTpmManagerServiceName,
on_tpm_manager_online_);
tpm_manager_online.Signal();
}
}
void Cleanup() {
// Shutdown dbus.
bus_->ShutdownAndBlock();
// Destructor of bus_ needs to run on dbus thread.
bus_connection_.reset();
bus_ = nullptr;
}
};
CryptohomeProxyService::CryptohomeProxyService(scoped_refptr<dbus::Bus> bus)
: bus_(bus) {}
void CryptohomeProxyService::OnInit() {
scoped_refptr<AsyncEventSequencer> sequencer(new AsyncEventSequencer());
DCHECK(!dbus_object_);
dbus_object_ = std::make_unique<brillo::dbus_utils::DBusObject>(
nullptr, bus_, dbus::ObjectPath(kCryptohomeServicePath));
adaptor_.reset(
new LegacyCryptohomeInterfaceAdaptor(bus_, dbus_object_.get()));
adaptor_->RegisterAsync();
brillo::dbus_utils::IntrospectableInterfaceHelper introspection;
introspection.AddInterfaceXml(adaptor_->GetIntrospectionXml());
introspection.RegisterWithDBusObject(dbus_object_.get());
dbus_object_->RegisterAsync(
sequencer->GetHandler("RegisterAsync() failed", true));
sequencer->OnAllTasksCompletedCall({base::Bind(
&CryptohomeProxyService::TakeServiceOwnership, base::Unretained(this))});
}
void CryptohomeProxyService::TakeServiceOwnership(bool success) {
CHECK(success) << "Init of one or more DBus objects has failed.";
CHECK(bus_->RequestOwnershipAndBlock(kCryptohomeServiceName,
dbus::Bus::REQUIRE_PRIMARY))
<< "Unable to take ownership of " << kCryptohomeServiceName;
// Note that since we've RequestOwnershipAndBlock(), the cryptohome's DBus
// service is now online, and all incoming requests will be queued on current
// thread's MessageLoop. However, it is possible for either tpm_manager or
// cryptohome to be still initializing, so we'll now use the ServiceBlocker to
// wait until they are both online. The
// ServiceBlocker::BlockUntilDestinationIsOnline() will block the original
// thread, thus causing all incoming dbus method calls to be blocked. They'll
// be unblocked once both services are up, and then it'll be forwarded and
// successfully serviced, as opposed to forwarding them when the services are
// still initializing, causing an error.
// Once the service is online, wait for our destination service (cryptohome
// and tpm_manager) to be online.
ServiceBlocker blocker;
blocker.BlockUntilDestinationIsOnline();
}
} // namespace cryptohome