| // Copyright 2019 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/arc_sideload_status.h" | |
| #include <string> | |
| #include <utility> | |
| #include <base/files/file_util.h> | |
| #include <base/functional/bind.h> | |
| #include <base/functional/callback.h> | |
| #include <base/functional/callback_helpers.h> | |
| #include <base/logging.h> | |
| #include <brillo/cryptohome.h> | |
| #include <brillo/dbus/dbus_object.h> | |
| #include <chromeos/dbus/service_constants.h> | |
| #include <dbus/message.h> | |
| #include <dbus/object_proxy.h> | |
| #include "arc/arc.pb.h" | |
| #include "bootlockbox/proto_bindings/boot_lockbox_rpc.pb.h" | |
| #include "login_manager/dbus_util.h" | |
| namespace login_manager { | |
| namespace { | |
| // Boot attribute used to track if the user has allowed sideloading on the | |
| // device. | |
| constexpr char kSideloadingAllowedBootAttribute[] = "arc_sideloading_allowed"; | |
| } // namespace | |
| ArcSideloadStatus::ArcSideloadStatus(dbus::ObjectProxy* boot_lockbox_proxy) | |
| : boot_lockbox_proxy_(boot_lockbox_proxy), | |
| sideload_status_(ArcSideloadStatusInterface::Status::UNDEFINED), | |
| weak_ptr_factory_(this) {} | |
| ArcSideloadStatus::~ArcSideloadStatus() {} | |
| void ArcSideloadStatus::Initialize() { | |
| boot_lockbox_proxy_->WaitForServiceToBeAvailable( | |
| base::BindOnce(&ArcSideloadStatus::OnBootLockboxServiceAvailable, | |
| weak_ptr_factory_.GetWeakPtr())); | |
| } | |
| bool ArcSideloadStatus::IsAdbSideloadAllowed() { | |
| return sideload_status_ == ArcSideloadStatusInterface::Status::ENABLED; | |
| } | |
| void ArcSideloadStatus::EnableAdbSideload(EnableAdbSideloadCallback callback) { | |
| // Must be called after initialized. | |
| if (sideload_status_ == ArcSideloadStatusInterface::Status::UNDEFINED) { | |
| std::move(callback).Run(ArcSideloadStatusInterface::Status::DISABLED, | |
| "D-Bus service not connected"); | |
| return; | |
| } | |
| // TODO(crbug.com/1026460): Move the flow into a new transition state, such | |
| // that an interleaved call will be queued and handled when the transition is | |
| // finished. Otherwise, a QueryAdbSideload call (potentially from another | |
| // client) can return with a outdated cached result. | |
| dbus::MethodCall method_call(bootlockbox::kBootLockboxInterface, | |
| bootlockbox::kBootLockboxStoreBootLockbox); | |
| bootlockbox::StoreBootLockboxRequest proto; | |
| proto.set_key(kSideloadingAllowedBootAttribute); | |
| proto.set_data("1"); | |
| dbus::MessageWriter writer(&method_call); | |
| writer.AppendProtoAsArrayOfBytes(proto); | |
| boot_lockbox_proxy_->CallMethod( | |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | |
| base::BindOnce(&ArcSideloadStatus::OnEnableAdbSideloadSet, | |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | |
| } | |
| void ArcSideloadStatus::QueryAdbSideload(QueryAdbSideloadCallback callback) { | |
| if (sideload_status_ != ArcSideloadStatusInterface::Status::UNDEFINED) { | |
| // If we know the status, just return it immediately. | |
| SendQueryAdbSideloadResponse(std::move(callback)); | |
| } else { | |
| // We don't know the status. Enqueue the callback for later handling when | |
| // the status becomes known. | |
| query_arc_sideload_callback_queue_.emplace(std::move(callback)); | |
| } | |
| } | |
| void ArcSideloadStatus::OnBootLockboxServiceAvailable(bool service_available) { | |
| if (!service_available) { | |
| LOG(ERROR) << "Failed to listen for bootlockbox service start. Continue as " | |
| << "sideloading is disallowed."; | |
| SetAdbSideloadStatusAndNotify(ArcSideloadStatusInterface::Status::DISABLED); | |
| return; | |
| } | |
| GetAdbSideloadAllowed(base::DoNothing()); | |
| } | |
| void ArcSideloadStatus::GetAdbSideloadAllowed( | |
| EnableAdbSideloadCallback callback) { | |
| dbus::MethodCall method_call(bootlockbox::kBootLockboxInterface, | |
| bootlockbox::kBootLockboxReadBootLockbox); | |
| bootlockbox::ReadBootLockboxRequest proto; | |
| proto.set_key(kSideloadingAllowedBootAttribute); | |
| dbus::MessageWriter writer(&method_call); | |
| writer.AppendProtoAsArrayOfBytes(proto); | |
| boot_lockbox_proxy_->CallMethod( | |
| &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, | |
| base::BindOnce(&ArcSideloadStatus::OnGotAdbSideloadAllowed, | |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | |
| } | |
| ArcSideloadStatusInterface::Status ArcSideloadStatus::ParseResponseFromRead( | |
| dbus::Response* response) { | |
| if (!response) { | |
| LOG(ERROR) << bootlockbox::kBootLockboxInterface << "." | |
| << bootlockbox::kBootLockboxReadBootLockbox | |
| << " request failed."; | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| } | |
| dbus::MessageReader reader(response); | |
| bootlockbox::ReadBootLockboxReply reply; | |
| if (!reader.PopArrayOfBytesAsProto(&reply)) { | |
| LOG(ERROR) << bootlockbox::kBootLockboxInterface << "." | |
| << bootlockbox::kBootLockboxReadBootLockbox | |
| << " unable to pop ReadBootLockboxReply proto."; | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| } | |
| if (reply.has_error()) { | |
| switch (reply.error()) { | |
| // When the attribute is unset, defaults to no sideloading. | |
| case bootlockbox::BOOTLOCKBOX_ERROR_MISSING_KEY: | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| // When boot lockbox is still uninitialized, which is normal after | |
| // powerwash. | |
| case bootlockbox::BOOTLOCKBOX_ERROR_NVSPACE_UNINITIALIZED: | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| // When boot lockbox is not yet defined, which is normal after | |
| // powerwash. | |
| case bootlockbox::BOOTLOCKBOX_ERROR_NVSPACE_UNDEFINED: | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| // When boot lockbox said we need a powerwash. This can happen to device | |
| // launched before boot lockbox was first introduced. | |
| case bootlockbox::BOOTLOCKBOX_ERROR_NEED_POWERWASH: | |
| return ArcSideloadStatusInterface::Status::NEED_POWERWASH; | |
| default: | |
| LOG(ERROR) << bootlockbox::kBootLockboxInterface << "." | |
| << bootlockbox::kBootLockboxReadBootLockbox | |
| << " returned error: " << reply.error(); | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| } | |
| } | |
| if (!reply.has_data()) { | |
| LOG(ERROR) << bootlockbox::kBootLockboxInterface << "." | |
| << bootlockbox::kBootLockboxReadBootLockbox | |
| << " missing data field in ReadBootLockboxReply."; | |
| return ArcSideloadStatusInterface::Status::DISABLED; | |
| } | |
| std::string arc_sideload_allowed = reply.data(); | |
| return arc_sideload_allowed == "1" | |
| ? ArcSideloadStatusInterface::Status::ENABLED | |
| : ArcSideloadStatusInterface::Status::DISABLED; | |
| } | |
| void ArcSideloadStatus::OnGotAdbSideloadAllowed( | |
| EnableAdbSideloadCallback callback, dbus::Response* response) { | |
| ArcSideloadStatusInterface::Status status = ParseResponseFromRead(response); | |
| SetAdbSideloadStatusAndNotify(status); | |
| std::move(callback).Run(status, nullptr); | |
| } | |
| void ArcSideloadStatus::OnEnableAdbSideloadSet( | |
| EnableAdbSideloadCallback callback, dbus::Response* result) { | |
| if (!result) { | |
| std::move(callback).Run(ArcSideloadStatusInterface::Status::DISABLED, | |
| "result is null"); | |
| return; | |
| } | |
| dbus::MessageReader reader(result); | |
| bootlockbox::StoreBootLockboxReply reply; | |
| if (!reader.PopArrayOfBytesAsProto(&reply)) { | |
| std::move(callback).Run(ArcSideloadStatusInterface::Status::DISABLED, | |
| "response is not a StoreBootLockboxReply"); | |
| return; | |
| } | |
| if (reply.has_error()) { | |
| if (reply.error() == bootlockbox::BOOTLOCKBOX_ERROR_NEED_POWERWASH) { | |
| std::move(callback).Run( | |
| ArcSideloadStatusInterface::Status::NEED_POWERWASH, nullptr); | |
| } else { | |
| std::move(callback).Run(ArcSideloadStatusInterface::Status::DISABLED, | |
| nullptr); | |
| } | |
| return; | |
| } | |
| // Callback later. Re-read setting from bootlockbox now that it has been | |
| // stored. | |
| GetAdbSideloadAllowed(std::move(callback)); | |
| } | |
| void ArcSideloadStatus::OverrideAdbSideloadStatusTestOnly(bool allowed) { | |
| sideload_status_ = allowed ? ArcSideloadStatusInterface::Status::ENABLED | |
| : ArcSideloadStatusInterface::Status::DISABLED; | |
| } | |
| void ArcSideloadStatus::SetAdbSideloadStatusAndNotify( | |
| ArcSideloadStatusInterface::Status status) { | |
| sideload_status_ = status; | |
| while (!query_arc_sideload_callback_queue_.empty()) { | |
| SendQueryAdbSideloadResponse( | |
| std::move(query_arc_sideload_callback_queue_.front())); | |
| query_arc_sideload_callback_queue_.pop(); | |
| } | |
| } | |
| void ArcSideloadStatus::SendQueryAdbSideloadResponse( | |
| QueryAdbSideloadCallback callback) { | |
| std::move(callback).Run(sideload_status_); | |
| } | |
| } // namespace login_manager |