blob: 4bd859732c3a93de7e981b21c84b8c542de7bca8 [file] [log] [blame]
// Copyright 2022 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/dev_mode_unblock_broker.h"
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/memory/ptr_util.h>
#include <dbus/error.h>
#include <dbus/exported_object.h>
#include <dbus/message.h>
#include <dbus/object_proxy.h>
#include <libcrossystem/crossystem.h>
#include "login_manager/dbus_util.h"
#include "login_manager/session_manager_impl.h"
#include "login_manager/system_utils.h"
namespace login_manager {
constexpr char DevModeUnblockBroker::kFirmwareVariantPath[] =
"/run/chromeos-config/v1/modem/firmware-variant";
constexpr char DevModeUnblockBroker::kSysfsRwVdpBlockDevModePath[] =
"/sys/firmware/vpd/rw/block_devmode";
constexpr char DevModeUnblockBroker::kCarrierLockUnblockedFlag[] =
"/mnt/stateful_partition/dev_mode_unblock_broker/carrier_lock_unblocked";
constexpr char DevModeUnblockBroker::kInitStateDeterminationUnblockedFlag[] =
"/mnt/stateful_partition/dev_mode_unblock_broker/"
"init_state_determination_unblocked";
constexpr char DevModeUnblockBroker::kEnrollmentUnblockedFlag[] =
"/mnt/stateful_partition/dev_mode_unblock_broker/enrollment_unblocked";
std::unique_ptr<DevModeUnblockBroker> DevModeUnblockBroker::Create(
SystemUtils* system,
crossystem::Crossystem* crossystem,
VpdProcess* vpd_process,
dbus::ObjectProxy* fwmp_proxy) {
return std::make_unique<DevModeUnblockBroker>(system, crossystem, vpd_process,
fwmp_proxy);
}
DevModeUnblockBroker::DevModeUnblockBroker(SystemUtils* system,
crossystem::Crossystem* crossystem,
VpdProcess* vpd_process,
dbus::ObjectProxy* fwmp_proxy)
: system_(system),
crossystem_(crossystem),
vpd_process_(vpd_process),
fwmp_proxy_(fwmp_proxy) {
// Check if this is a cellular device. For non-cellular
// devices, broker will not wait for unblock from carrier lock
// module.
// Also check persistent config if we already received
// unblock from these modules.
awaiting_unblock_carrier_lock_ =
IsCellularDevice() == true &&
!system_->Exists(base::FilePath(kCarrierLockUnblockedFlag));
awaiting_unblock_enrollment_ =
!system_->Exists(base::FilePath(kEnrollmentUnblockedFlag));
awaiting_unblock_init_state_determination_ =
!system_->Exists(base::FilePath(kInitStateDeterminationUnblockedFlag));
LOG(INFO) << __func__ << " awaiting_unblock_init_state_determination: "
<< awaiting_unblock_init_state_determination_
<< " awaiting_unblock_enrollment: " << awaiting_unblock_enrollment_
<< " awaiting_unblock_carrier_lock: "
<< awaiting_unblock_carrier_lock_;
// Check current status of dev mode
fwmp_proxy_->WaitForServiceToBeAvailable(
base::BindOnce(&DevModeUnblockBroker::UpdateCurrentDevModeStatus,
weak_ptr_factory_.GetWeakPtr()));
}
DevModeUnblockBroker::~DevModeUnblockBroker() = default;
void DevModeUnblockBroker::UnblockDevModeForInitialStateDetermination(
CompletionCallback completion) {
DVLOG(1) << __func__;
awaiting_unblock_init_state_determination_ = false;
system_->AtomicFileWrite(base::FilePath(kInitStateDeterminationUnblockedFlag),
"1");
UnblockDevModeVpdFwmpIfReady(std::move(completion));
}
void DevModeUnblockBroker::UnblockDevModeForEnrollment(
CompletionCallback completion) {
DVLOG(1) << __func__;
awaiting_unblock_enrollment_ = false;
system_->AtomicFileWrite(base::FilePath(kEnrollmentUnblockedFlag), "1");
UnblockDevModeVpdFwmpIfReady(std::move(completion));
}
void DevModeUnblockBroker::UnblockDevModeForCarrierLock(
CompletionCallback completion) {
DVLOG(1) << __func__;
awaiting_unblock_carrier_lock_ = false;
system_->AtomicFileWrite(base::FilePath(kCarrierLockUnblockedFlag), "1");
UnblockDevModeVpdFwmpIfReady(std::move(completion));
}
bool DevModeUnblockBroker::IsDevModeBlockedForCarrierLock() const {
return awaiting_unblock_carrier_lock_;
}
bool DevModeUnblockBroker::IsDevModeBlockedForEnrollment() const {
return awaiting_unblock_enrollment_;
}
bool DevModeUnblockBroker::IsDevModeBlockedForInitialStateDetermination()
const {
return awaiting_unblock_init_state_determination_;
}
bool DevModeUnblockBroker::IsCellularDevice() {
bool is_cellular = false;
const base::FilePath modem_path = base::FilePath(kFirmwareVariantPath);
if (system_->Exists(modem_path)) {
is_cellular = true;
// If Carrier Lock is expected only for a specific set of modems
// check that include list here.
}
DVLOG(1) << "is_cellular " << is_cellular;
return is_cellular;
}
bool DevModeUnblockBroker::IsDevModeBlocked() {
// Block_devmode exists at multiple locations.
// Use the logic used by init script to detect current state
// of dev mode.
// - Check for FWMP space with DEVELOPER_DISABLE_BOOT flag.
// - VPD sysyfs entry
// - crossystem::Crossystem
bool block_dev_mode_fwmp = IsDevModeBlockedInFwmp();
LOG(INFO) << "block_devmode_fwmp " << block_dev_mode_fwmp;
const base::FilePath rw_vpd_block_devmode_path =
base::FilePath(kSysfsRwVdpBlockDevModePath);
if (system_->Exists(rw_vpd_block_devmode_path)) {
std::string block_devmode_sysfs;
base::ReadFileToString(rw_vpd_block_devmode_path, &block_devmode_sysfs);
LOG(INFO) << "block_devmode_sysfs " << block_devmode_sysfs;
if (block_devmode_sysfs == "1")
return true;
}
std::optional<int> block_devmode_system = crossystem_->VbGetSystemPropertyInt(
crossystem::Crossystem::kBlockDevmode);
if (!block_devmode_system) {
LOG(ERROR) << "Failed to read block_devmode flag!";
return false;
}
LOG(INFO) << "block_devmode_system " << *block_devmode_system;
return *block_devmode_system == 1;
}
bool DevModeUnblockBroker::IsDevModeBlockedInFwmp() {
dbus::MethodCall method_call(
user_data_auth::kInstallAttributesInterface,
user_data_auth::kGetFirmwareManagementParameters);
user_data_auth::GetFirmwareManagementParametersRequest request;
user_data_auth::GetFirmwareManagementParametersReply reply;
dbus::MessageWriter writer(&method_call);
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR)
<< "Failed to append GetFirmwareManagementParametersRequest protobuf"
"when calling InstallAttributes method ";
return false;
}
base::expected<std::unique_ptr<dbus::Response>, dbus::Error> response(
fwmp_proxy_->CallMethodAndBlock(&method_call,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
if (!response.has_value()) {
LOG(ERROR) << "Error contacting Cryptohomed to get FWMP.";
return false;
}
if (!response.value()) {
LOG(ERROR) << "Cannot retrieve FWMP.";
return false;
}
dbus::MessageReader reader(response.value().get());
if (!reader.PopArrayOfBytesAsProto(&reply)) {
LOG(ERROR) << "Failed to parse GetFirmwareManagementParameters"
" response message from cryptohomed";
return false;
}
if (reply.error() !=
user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET) {
LOG(ERROR) << "Failed to get firmware management parameters:"
<< reply.error();
return false;
}
LOG(INFO) << "FWMP Flags: " << reply.fwmp().flags();
return (reply.fwmp().flags() & cryptohome::DEVELOPER_DISABLE_BOOT);
}
void DevModeUnblockBroker::UnblockDevModeVpdFwmpIfReady(
CompletionCallback completion) {
LOG(INFO) << __func__ << " awaiting_unblock_init_state_determination: "
<< awaiting_unblock_init_state_determination_
<< " awaiting_unblock_enrollment: " << awaiting_unblock_enrollment_
<< " awaiting_unblock_carrier_lock: "
<< awaiting_unblock_carrier_lock_
<< " DevModeUnblocked: " << dev_mode_unblocked_;
// Dev mode is already unblocked
if (dev_mode_unblocked_) {
if (!completion.is_null())
std::move(completion).Run(brillo::ErrorPtr());
return;
}
if (awaiting_unblock_init_state_determination_ ||
awaiting_unblock_enrollment_ || awaiting_unblock_carrier_lock_) {
if (!completion.is_null())
std::move(completion).Run(brillo::ErrorPtr());
return;
}
UnblockDevModeInFwmp(std::move(completion));
}
void DevModeUnblockBroker::UnblockDevModeInFwmp(CompletionCallback completion) {
// D-Bus services may not be available yet, so we call
// WaitForServiceToBeAvailable.
fwmp_proxy_->WaitForServiceToBeAvailable(base::BindOnce(
&DevModeUnblockBroker::StartRemoveFirmwareManagementParameters,
weak_ptr_factory_.GetWeakPtr(), std::move(completion)));
}
void DevModeUnblockBroker::StartRemoveFirmwareManagementParameters(
CompletionCallback completion, bool service_is_ready) {
if (!service_is_ready) {
LOG(ERROR) << "Failed waiting for cryptohome D-Bus service availability.";
std::move(completion)
.Run(CreateError(dbus_error::kFwmpRemovalFailed,
"Cryptohome D-Bus service unavailable."));
return;
}
dbus::MethodCall method_call(
user_data_auth::kInstallAttributesInterface,
user_data_auth::kRemoveFirmwareManagementParameters);
dbus::MessageWriter writer(&method_call);
user_data_auth::RemoveFirmwareManagementParametersRequest request;
if (!writer.AppendProtoAsArrayOfBytes(request)) {
LOG(ERROR) << __func__
<< "Failed to append RemoveFirmwareManagementParameters protobuf"
"when calling InstallAttributes method ";
std::move(completion)
.Run(CreateError(
dbus_error::kFwmpRemovalFailed,
"Failed to append RemoveFirmwareManagementParameters protobuf."));
return;
}
fwmp_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(
&DevModeUnblockBroker::OnFirmwareManagementParametersRemoved,
weak_ptr_factory_.GetWeakPtr(), std::move(completion)));
}
void DevModeUnblockBroker::OnFirmwareManagementParametersRemoved(
CompletionCallback completion, dbus::Response* response) {
user_data_auth::RemoveFirmwareManagementParametersReply reply;
if (!response) {
LOG(ERROR) << "No response from cryptohomed";
std::move(completion)
.Run(CreateError(dbus_error::kFwmpRemovalFailed,
"No Response from cryptohomed."));
return;
}
dbus::MessageReader reader(response);
if (!reader.PopArrayOfBytesAsProto(&reply)) {
LOG(ERROR) << "Failed to parse response message from cryptohomed";
std::move(completion)
.Run(CreateError(dbus_error::kFwmpRemovalFailed,
"Failed to parse response message from cryptohomed."));
return;
}
if (reply.error() !=
user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_NOT_SET) {
LOG(ERROR) << "Failed to remove firmware management parameters.";
std::move(completion)
.Run(CreateError(dbus_error::kFwmpRemovalFailed,
"Failed to remove firmware management parameters."));
return;
}
UnblockDevModeInVpd(std::move(completion));
}
void DevModeUnblockBroker::UnblockDevModeInVpd(CompletionCallback completion) {
// The block_devmode system property needs to be set to 0 as well to unblock
// dev mode. It is stored independently from VPD and firmware management
// parameters.
if (!crossystem_->VbSetSystemPropertyInt(
crossystem::Crossystem::kBlockDevmode, 0)) {
LOG(ERROR) << "Failed to set system property ";
std::move(completion)
.Run(CreateError(dbus_error::kSystemPropertyUpdateFailed,
"Failed to set block_devmode system property to 0."));
return;
}
// Clear any existing nvram_cleared flag after updating block_devmode
// value in VPD so that init script will try to read from VPD directly
// if sysfs entry for block_devmode is not present.
std::optional<int> nvram_cleared_value = crossystem_->VbGetSystemPropertyInt(
crossystem::Crossystem::kNvramCleared);
if (!nvram_cleared_value) {
LOG(ERROR) << "Failed to read nvram_cleared flag!";
std::move(completion)
.Run(CreateError(dbus_error::kNvramClearedReadFailed,
"Failed to read nvram_cleared flag."));
return;
}
if (*nvram_cleared_value != 0 &&
!crossystem_->VbSetSystemPropertyInt(
crossystem::Crossystem::kNvramCleared, 0)) {
LOG(ERROR) << "Failed to clear nvram_cleared flag!";
std::move(completion)
.Run(CreateError(dbus_error::kNvramClearedUpdateFailed,
"Failed to clear nvram_cleared flag."));
return;
}
if (!vpd_process_->RunInBackground(
{{crossystem::Crossystem::kBlockDevmode, "0"},
{crossystem::Crossystem::kCheckEnrollment, "0"}},
false,
base::BindOnce(&DevModeUnblockBroker::HandleVpdDevModeUnblockResult,
weak_ptr_factory_.GetWeakPtr(), false,
std::move(completion)))) {
LOG(ERROR) << "Failed to update VPD in background ";
std::move(completion)
.Run(CreateError(dbus_error::kVpdUpdateFailed,
"Failed to run VPD update in the background."));
}
}
void DevModeUnblockBroker::UpdateVpdDevModeUnblockResult(bool success) {
if (success)
dev_mode_unblocked_ = true;
}
void DevModeUnblockBroker::HandleVpdDevModeUnblockResult(
bool ignore_error,
DevModeUnblockBroker::CompletionCallback completion,
bool success) {
DVLOG(1) << __func__ << " res=" << success;
// Update when block_devmode is cleared successfully in FWMP and VPD
UpdateVpdDevModeUnblockResult(success);
if (completion.is_null())
return;
if (success || ignore_error) {
std::move(completion).Run(brillo::ErrorPtr());
return;
}
LOG(ERROR) << "Failed to update VPD";
std::move(completion)
.Run(CreateError(dbus_error::kVpdUpdateFailed, "Failed to update VPD"));
}
void DevModeUnblockBroker::UnblockAtInit(brillo::ErrorPtr error) {
if (!error) {
UpdateVpdDevModeUnblockResult(true);
return;
}
LOG(ERROR) << "error code: " << error->GetCode()
<< " error message: " << error->GetMessage();
}
void DevModeUnblockBroker::UpdateCurrentDevModeStatus(bool service_is_ready) {
if (!service_is_ready) {
LOG(ERROR) << "Failed waiting for cryptohome D-Bus service availability.";
return;
}
if (!IsDevModeBlocked()) {
awaiting_unblock_carrier_lock_ = false;
awaiting_unblock_enrollment_ = false;
awaiting_unblock_init_state_determination_ = false;
dev_mode_unblocked_ = true;
return;
}
// Dev mode is not yet unblocked but we have already received unblock from all
// the required modules. Try to unblock dev mode here in case last VPD/FWMP
// update was interrupted by unexpected events like reboot.
if (!awaiting_unblock_init_state_determination_ &&
!awaiting_unblock_enrollment_ && !awaiting_unblock_carrier_lock_) {
UnblockDevModeVpdFwmpIfReady(base::BindOnce(
&DevModeUnblockBroker::UnblockAtInit, weak_ptr_factory_.GetWeakPtr()));
}
}
} // namespace login_manager