| // 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/check.h> |
| #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 { |
| |
| namespace { |
| |
| // The constants below are only defined here to ensure nobody else uses them. |
| // Interface exposed by the cryptohome daemon. |
| const char kCryptohomeServicePath[] = "/org/chromium/Cryptohome"; |
| const char kCryptohomeServiceName[] = "org.chromium.Cryptohome"; |
| |
| } // namespace |
| |
| 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::BindOnce(&ServiceBlocker::SetupBlockUntilDestinationIsOnline, |
| base::Unretained(this))); |
| |
| cryptohome_online.Wait(); |
| tpm_manager_online.Wait(); |
| |
| dbus_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&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::BindRepeating( |
| &ServiceBlocker::OnCryptohomeServiceChange, base::Unretained(this)); |
| on_tpm_manager_online_ = base::BindRepeating( |
| &ServiceBlocker::OnTpmManagerServiceChange, base::Unretained(this)); |
| |
| // Setup the callbacks. |
| bus_->ListenForServiceOwnerChange( |
| ::user_data_auth::kUserDataAuthServiceName, on_cryptohome_online_); |
| |
| bus_->GetServiceOwner(::user_data_auth::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( |
| ::user_data_auth::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 |