blob: 22e96e4c5c5da12eb0e69d990ad6d17542691d47 [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 "login_manager/arc_sideload_status.h"
#include <string>
#include <utility>
#include <base/bind.h>
#include <base/callback.h>
#include <base/callback_helpers.h>
#include <base/files/file_util.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 "bootlockbox/proto_bindings/boot_lockbox_rpc.pb.h"
#include "login_manager/dbus_util.h"
#include "login_manager/proto_bindings/arc.pb.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";
// TODO(victorhsieh): switch to base::DoNothing() once libchrome is upreved.
void DoNothing(ArcSideloadStatusInterface::Status, const char*) {}
} // 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::Bind(&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(cryptohome::kBootLockboxInterface,
cryptohome::kBootLockboxStoreBootLockbox);
cryptohome::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::Bind(&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 cryptohome service start. Continue as "
<< "sideloading is disallowed.";
SetAdbSideloadStatusAndNotify(ArcSideloadStatusInterface::Status::DISABLED);
return;
}
GetAdbSideloadAllowed(base::Bind(&DoNothing));
}
void ArcSideloadStatus::GetAdbSideloadAllowed(
EnableAdbSideloadCallback callback) {
dbus::MethodCall method_call(cryptohome::kBootLockboxInterface,
cryptohome::kBootLockboxReadBootLockbox);
cryptohome::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::Bind(&ArcSideloadStatus::OnGotAdbSideloadAllowed,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
ArcSideloadStatusInterface::Status ArcSideloadStatus::ParseResponseFromRead(
dbus::Response* response) {
if (!response) {
LOG(ERROR) << cryptohome::kBootLockboxInterface << "."
<< cryptohome::kBootLockboxReadBootLockbox << " request failed.";
return ArcSideloadStatusInterface::Status::DISABLED;
}
dbus::MessageReader reader(response);
cryptohome::ReadBootLockboxReply reply;
if (!reader.PopArrayOfBytesAsProto(&reply)) {
LOG(ERROR) << cryptohome::kBootLockboxInterface << "."
<< cryptohome::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 cryptohome::BOOTLOCKBOX_ERROR_MISSING_KEY:
return ArcSideloadStatusInterface::Status::DISABLED;
// When boot lockbox is still uninitialized, which is normal after
// powerwash.
case cryptohome::BOOTLOCKBOX_ERROR_NVSPACE_UNINITIALIZED:
return ArcSideloadStatusInterface::Status::DISABLED;
// When boot lockbox is not yet defined. This can happen to device
// launched before boot lockbox was first introduced.
case cryptohome::BOOTLOCKBOX_ERROR_NVSPACE_UNDEFINED:
return ArcSideloadStatusInterface::Status::NEED_POWERWASH;
default:
LOG(ERROR) << cryptohome::kBootLockboxInterface << "."
<< cryptohome::kBootLockboxReadBootLockbox
<< " returned error: " << reply.error();
return ArcSideloadStatusInterface::Status::DISABLED;
}
}
if (!reply.has_data()) {
LOG(ERROR) << cryptohome::kBootLockboxInterface << "."
<< cryptohome::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);
cryptohome::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() == cryptohome::BOOTLOCKBOX_ERROR_NVSPACE_UNDEFINED) {
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) {
callback.Run(sideload_status_);
}
} // namespace login_manager