/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "bootcontrolhal" #include "BootControl.h" #include #include #include #include #include #include #include #include #include "DevInfo.h" #include "GptUtils.h" using HIDLMergeStatus = ::android::bootable::BootControl::MergeStatus; using ndk::ScopedAStatus; using android::bootable::GetMiscVirtualAbMergeStatus; using android::bootable::InitMiscVirtualAbMessageIfNeeded; using android::bootable::SetMiscVirtualAbMergeStatus; namespace aidl::android::hardware::boot { namespace { // clang-format off #define BOOT_A_PATH "/dev/block/by-name/boot_a" #define BOOT_B_PATH "/dev/block/by-name/boot_b" #define DEVINFO_PATH "/dev/block/by-name/devinfo" #define BLOW_AR_PATH "/sys/kernel/boot_control/blow_ar" // slot flags #define AB_ATTR_PRIORITY_SHIFT 52 #define AB_ATTR_PRIORITY_MASK (3UL << AB_ATTR_PRIORITY_SHIFT) #define AB_ATTR_ACTIVE_SHIFT 54 #define AB_ATTR_ACTIVE (1UL << AB_ATTR_ACTIVE_SHIFT) #define AB_ATTR_RETRY_COUNT_SHIFT (55) #define AB_ATTR_RETRY_COUNT_MASK (7UL << AB_ATTR_RETRY_COUNT_SHIFT) #define AB_ATTR_SUCCESSFUL (1UL << 58) #define AB_ATTR_UNBOOTABLE (1UL << 59) #define AB_ATTR_MAX_PRIORITY 3UL #define AB_ATTR_MAX_RETRY_COUNT 3UL // clang-format on static std::string getDevPath(int32_t in_slot) { char real_path[PATH_MAX]; const char *path = in_slot == 0 ? BOOT_A_PATH : BOOT_B_PATH; int ret = readlink(path, real_path, sizeof real_path); if (ret < 0) { ALOGE("readlink failed for boot device %s\n", strerror(errno)); return std::string(); } std::string dp(real_path); // extract /dev/sda.. part return dp.substr(0, sizeof "/dev/block/sdX" - 1); } static bool isSlotFlagSet(int32_t in_slot, uint64_t flag) { std::string dev_path = getDevPath(in_slot); if (dev_path.empty()) { ALOGI("Could not get device path for slot %d\n", in_slot); return false; } GptUtils gpt(dev_path); if (gpt.Load()) { ALOGI("failed to load gpt data\n"); return false; } gpt_entry *e = gpt.GetPartitionEntry(in_slot ? "boot_b" : "boot_a"); if (e == nullptr) { ALOGI("failed to get gpt entry\n"); return false; } return !!(e->attr & flag); } static bool setSlotFlag(int32_t in_slot, uint64_t flag) { std::string dev_path = getDevPath(in_slot); if (dev_path.empty()) { ALOGI("Could not get device path for slot %d\n", in_slot); return false; } GptUtils gpt(dev_path); if (gpt.Load()) { ALOGI("failed to load gpt data\n"); return false; } gpt_entry *e = gpt.GetPartitionEntry(in_slot ? "boot_b" : "boot_a"); if (e == nullptr) { ALOGI("failed to get gpt entry\n"); return false; } e->attr |= flag; gpt.Sync(); return true; } static bool is_devinfo_valid; static bool is_devinfo_initialized; static std::mutex devinfo_lock; static devinfo_t devinfo; static bool isDevInfoValid() { const std::lock_guard lock(devinfo_lock); if (is_devinfo_initialized) { return is_devinfo_valid; } is_devinfo_initialized = true; ::android::base::unique_fd fd(open(DEVINFO_PATH, O_RDONLY)); ::android::base::ReadFully(fd, &devinfo, sizeof devinfo); if (devinfo.magic != DEVINFO_MAGIC) { return is_devinfo_valid; } uint32_t version = ((uint32_t)devinfo.ver_major << 16) | devinfo.ver_minor; // only version 3.3+ supports A/B data if (version >= 0x0003'0003) { is_devinfo_valid = true; } return is_devinfo_valid; } static bool DevInfoSync() { if (!isDevInfoValid()) { return false; } ::android::base::unique_fd fd(open(DEVINFO_PATH, O_WRONLY | O_DSYNC)); return ::android::base::WriteFully(fd, &devinfo, sizeof devinfo); } static void DevInfoInitSlot(devinfo_ab_slot_data_t &slot_data) { slot_data.retry_count = AB_ATTR_MAX_RETRY_COUNT; slot_data.unbootable = 0; slot_data.successful = 0; slot_data.active = 1; slot_data.fastboot_ok = 0; } static int blow_otp_AR(bool secure) { static const char *dev_name = "/dev/trusty-ipc-dev0"; static const char *otp_name = "com.android.trusty.otp_manager.tidl"; int fd = 1, ret = 0; uint32_t cmd = secure? OTP_CMD_write_antirbk_secure_ap : OTP_CMD_write_antirbk_non_secure_ap; fd = tipc_connect(dev_name, otp_name); if (fd < 0) { ALOGI("Failed to connect to OTP_MGR ns TA - is it missing?\n"); ret = -1; return ret; } struct otp_mgr_req_base req = { .command = cmd, .resp_payload_size = 0, }; struct iovec iov[] = { { .iov_base = &req, .iov_len = sizeof(req), }, }; size_t rc = tipc_send(fd, iov, 1, NULL, 0); if (rc != sizeof(req)) { ALOGI("Send fail! %zx\n", rc); return rc; } struct otp_mgr_rsp_base resp; rc = read(fd, &resp, sizeof(resp)); if (rc < 0) { ALOGI("Read fail! %zx\n", rc); return rc; } if (rc < sizeof(resp)) { ALOGI("Not enough data! %zx\n", rc); return -EIO; } if (resp.command != (cmd | OTP_RESP_BIT)) { ALOGI("Wrong command! %x\n", resp.command); return -EINVAL; } if (resp.result != 0) { fprintf(stderr, "AR writing error! %x\n", resp.result); return -EINVAL; } tipc_close(fd); return 0; } static bool blowAR_zuma() { int ret = blow_otp_AR(true); if (ret) { ALOGI("Blow secure anti-rollback OTP failed"); return false; } ret = blow_otp_AR(false); if (ret) { ALOGI("Blow non-secure anti-rollback OTP failed"); return false; } return true; } static bool blowAR_gs101() { ::android::base::unique_fd fd(open(BLOW_AR_PATH, O_WRONLY | O_DSYNC)); return ::android::base::WriteStringToFd("1", fd); } static bool blowAR() { char platform[PROPERTY_VALUE_MAX]; property_get("ro.boot.hardware.platform", platform, ""); if (std::string(platform) == "gs101") { return blowAR_gs101(); } else if (std::string(platform) == "gs201" || std::string(platform) == "zuma") { return blowAR_zuma(); } return true; } static constexpr MergeStatus ToAIDLMergeStatus(HIDLMergeStatus status) { switch (status) { case HIDLMergeStatus::NONE: return MergeStatus::NONE; case HIDLMergeStatus::UNKNOWN: return MergeStatus::UNKNOWN; case HIDLMergeStatus::SNAPSHOTTED: return MergeStatus::SNAPSHOTTED; case HIDLMergeStatus::MERGING: return MergeStatus::MERGING; case HIDLMergeStatus::CANCELLED: return MergeStatus::CANCELLED; } } static constexpr HIDLMergeStatus ToHIDLMergeStatus(MergeStatus status) { switch (status) { case MergeStatus::NONE: return HIDLMergeStatus::NONE; case MergeStatus::UNKNOWN: return HIDLMergeStatus::UNKNOWN; case MergeStatus::SNAPSHOTTED: return HIDLMergeStatus::SNAPSHOTTED; case MergeStatus::MERGING: return HIDLMergeStatus::MERGING; case MergeStatus::CANCELLED: return HIDLMergeStatus::CANCELLED; } } } // namespace BootControl::BootControl() { CHECK(InitMiscVirtualAbMessageIfNeeded()); } ScopedAStatus BootControl::getActiveBootSlot(int32_t* _aidl_return) { int32_t slots = 0; getNumberSlots(&slots); if (slots == 0) { *_aidl_return = 0; return ScopedAStatus::ok(); } if (isDevInfoValid()) { *_aidl_return = devinfo.ab_data.slots[1].active ? 1 : 0; return ScopedAStatus::ok(); } *_aidl_return = isSlotFlagSet(1, AB_ATTR_ACTIVE) ? 1 : 0; return ScopedAStatus::ok(); } ScopedAStatus BootControl::getCurrentSlot(int32_t* _aidl_return) { char suffix[PROPERTY_VALUE_MAX]; property_get("ro.boot.slot_suffix", suffix, "_a"); *_aidl_return = std::string(suffix) == "_b" ? 1 : 0; return ScopedAStatus::ok(); } ScopedAStatus BootControl::getNumberSlots(int32_t* _aidl_return) { int32_t slots = 0; if (access(BOOT_A_PATH, F_OK) == 0) slots++; if (access(BOOT_B_PATH, F_OK) == 0) slots++; *_aidl_return = slots; return ScopedAStatus::ok(); } ScopedAStatus BootControl::getSnapshotMergeStatus(MergeStatus* _aidl_return) { HIDLMergeStatus status; int32_t current_slot = 0; getCurrentSlot(¤t_slot); if (!GetMiscVirtualAbMergeStatus(current_slot, &status)) { *_aidl_return = MergeStatus::UNKNOWN; return ScopedAStatus::ok(); } *_aidl_return = ToAIDLMergeStatus(status); return ScopedAStatus::ok(); } ScopedAStatus BootControl::getSuffix(int32_t in_slot, std::string* _aidl_return) { *_aidl_return = in_slot == 0 ? "_a" : in_slot == 1 ? "_b" : ""; return ScopedAStatus::ok(); } ScopedAStatus BootControl::isSlotBootable(int32_t in_slot, bool* _aidl_return) { int32_t slots = 0; getNumberSlots(&slots); if (slots == 0) { *_aidl_return = false; return ScopedAStatus::ok(); } if (in_slot >= slots) return ScopedAStatus::fromServiceSpecificErrorWithMessage( INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); bool unbootable; if (isDevInfoValid()) { auto &slot_data = devinfo.ab_data.slots[in_slot]; unbootable = !!slot_data.unbootable; } else { unbootable = isSlotFlagSet(in_slot, AB_ATTR_UNBOOTABLE); } *_aidl_return = unbootable ? false: true; return ScopedAStatus::ok(); } ScopedAStatus BootControl::isSlotMarkedSuccessful(int32_t in_slot, bool* _aidl_return) { int32_t slots = 0; getNumberSlots(&slots); if (slots == 0) { // just return true so that we don't we another call trying to mark it as successful // when there is no slots *_aidl_return = true; return ScopedAStatus::ok(); } if (in_slot >= slots) return ScopedAStatus::fromServiceSpecificErrorWithMessage( INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); bool successful; if (isDevInfoValid()) { auto &slot_data = devinfo.ab_data.slots[in_slot]; successful = !!slot_data.successful; } else { successful = isSlotFlagSet(in_slot, AB_ATTR_SUCCESSFUL); } *_aidl_return = successful ? true : false; return ScopedAStatus::ok(); } ScopedAStatus BootControl::markBootSuccessful() { int32_t slots = 0; getNumberSlots(&slots); if (slots == 0) { // no slots, just return true otherwise Android keeps trying return ScopedAStatus::ok(); } bool ret; int32_t current_slot = 0; getCurrentSlot(¤t_slot); if (isDevInfoValid()) { auto const slot = current_slot; devinfo.ab_data.slots[slot].successful = 1; ret = DevInfoSync(); } else { ret = setSlotFlag(current_slot, AB_ATTR_SUCCESSFUL); } if (!ret) { return ScopedAStatus::fromServiceSpecificErrorWithMessage(COMMAND_FAILED, "Failed to set successful flag"); } if (!blowAR()) { ALOGE("Failed to blow anti-rollback counter"); // Ignore the error, since ABL will re-trigger it on reboot } return ScopedAStatus::ok(); } ScopedAStatus BootControl::setActiveBootSlot(int32_t in_slot) { if (in_slot >= 2) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); } if (isDevInfoValid()) { auto &active_slot_data = devinfo.ab_data.slots[in_slot]; auto &inactive_slot_data = devinfo.ab_data.slots[!in_slot]; inactive_slot_data.active = 0; DevInfoInitSlot(active_slot_data); if (!DevInfoSync()) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "Could not update DevInfo data"); } } else { std::string dev_path = getDevPath(in_slot); if (dev_path.empty()) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "Could not get device path for slot"); } GptUtils gpt(dev_path); if (gpt.Load()) { return ScopedAStatus::fromServiceSpecificErrorWithMessage(COMMAND_FAILED, "failed to load gpt data"); } gpt_entry *active_entry = gpt.GetPartitionEntry(in_slot == 0 ? "boot_a" : "boot_b"); gpt_entry *inactive_entry = gpt.GetPartitionEntry(in_slot == 0 ? "boot_b" : "boot_a"); if (active_entry == nullptr || inactive_entry == nullptr) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "failed to get entries for boot partitions"); } ALOGV("slot active attributes %lx\n", active_entry->attr); ALOGV("slot inactive attributes %lx\n", inactive_entry->attr); // update attributes for active and inactive inactive_entry->attr &= ~AB_ATTR_ACTIVE; active_entry->attr = AB_ATTR_ACTIVE | (AB_ATTR_MAX_PRIORITY << AB_ATTR_PRIORITY_SHIFT) | (AB_ATTR_MAX_RETRY_COUNT << AB_ATTR_RETRY_COUNT_SHIFT); } char boot_dev[PROPERTY_VALUE_MAX]; property_get("ro.boot.bootdevice", boot_dev, ""); if (boot_dev[0] == '\0') { ALOGI("failed to get ro.boot.bootdevice. try ro.boot.boot_devices\n"); property_get("ro.boot.boot_devices", boot_dev, ""); if (boot_dev[0] == '\0') { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "invalid ro.boot.bootdevice and ro.boot.boot_devices prop"); } } std::string boot_lun_path = std::string("/sys/devices/platform/") + boot_dev + "/pixel/boot_lun_enabled"; int fd = open(boot_lun_path.c_str(), O_RDWR | O_DSYNC); if (fd < 0) { // Try old path for kernels < 5.4 // TODO: remove once kernel 4.19 support is deprecated std::string boot_lun_path = std::string("/sys/devices/platform/") + boot_dev + "/attributes/boot_lun_enabled"; fd = open(boot_lun_path.c_str(), O_RDWR | O_DSYNC); if (fd < 0) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "failed to open ufs attr boot_lun_enabled"); } } // // bBootLunEn // 0x1 => Boot LU A = enabled, Boot LU B = disable // 0x2 => Boot LU A = disable, Boot LU B = enabled // int ret = ::android::base::WriteStringToFd(in_slot == 0 ? "1" : "2", fd); close(fd); if (ret < 0) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "faied to write boot_lun_enabled attribute"); } return ScopedAStatus::ok(); } ScopedAStatus BootControl::setSlotAsUnbootable(int32_t in_slot) { if (in_slot >= 2) return ScopedAStatus::fromServiceSpecificErrorWithMessage( INVALID_SLOT, (std::string("Invalid slot ") + std::to_string(in_slot)).c_str()); if (isDevInfoValid()) { auto &slot_data = devinfo.ab_data.slots[in_slot]; slot_data.unbootable = 1; if (!DevInfoSync()) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "Could not update DevInfo data"); } } else { std::string dev_path = getDevPath(in_slot); if (dev_path.empty()) { return ScopedAStatus::fromServiceSpecificErrorWithMessage( COMMAND_FAILED, "Could not get device path for slot"); } GptUtils gpt(dev_path); gpt.Load(); gpt_entry *e = gpt.GetPartitionEntry(in_slot ? "boot_b" : "boot_a"); e->attr |= AB_ATTR_UNBOOTABLE; gpt.Sync(); } return ScopedAStatus::ok(); } ScopedAStatus BootControl::setSnapshotMergeStatus(MergeStatus in_status) { int32_t current_slot = 0; getCurrentSlot(¤t_slot); if (!SetMiscVirtualAbMergeStatus(current_slot, ToHIDLMergeStatus(in_status))) return ScopedAStatus::fromServiceSpecificErrorWithMessage(COMMAND_FAILED, "Operation failed"); return ScopedAStatus::ok(); } } // namespace aidl::android::hardware::boot