| // Copyright 2024 Google LLC |
| // |
| // 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. |
| |
| #include "google3/host_commands.h" |
| |
| #include "ec_util.hpp" |
| #include "host_command_mock.hpp" |
| #include "payload_update.hpp" |
| |
| #include <xyz/openbmc_project/Control/Hoth/error.hpp> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| // NOLINTNEXTLINE(google-build-using-namespace) |
| using namespace std::literals; |
| using sdbusplus::error::xyz::openbmc_project::control::hoth::ResponseFailure; |
| |
| using ::testing::_; |
| using ::testing::Return; |
| |
| namespace google |
| { |
| namespace hoth |
| { |
| namespace internal |
| { |
| namespace |
| { |
| |
| const auto goodResponseStr = "\x03\xfd\x00\x00\x00\x00\x00\x00"s; |
| const auto getTpmLocalityResponseStr = |
| "\x03\xfb\x00\x00\x08\x00\x00\x00\x01\x00\x00\x00\x01"s; |
| |
| class EcUtilTest : public ::testing::Test |
| { |
| protected: |
| EcUtilTest() : ecUtil(&hostCmd) {} |
| |
| // Host Command interface handle |
| internal::HostCommandMock hostCmd; |
| |
| // EcUtil interface handle |
| internal::EcUtilImpl ecUtil; |
| }; |
| |
| MATCHER_P(TpmSetLocalityReq, expected_locality, "") |
| { |
| const auto* req = static_cast<const ec_request_tpm_control*>(arg); |
| if (req->operation != TPM_CONTROL_SET_LOCALITY) |
| { |
| return false; |
| } |
| return req->locality == expected_locality; |
| } |
| |
| MATCHER(TpmGetLocalityReq, "") |
| { |
| const auto* req = static_cast<const ec_request_tpm_control*>(arg); |
| if (req->operation != TPM_CONTROL_GET_LOCALITY) |
| { |
| return false; |
| } |
| return req->payload_size == 0; |
| } |
| |
| TEST_F(EcUtilTest, setTpmLocalityReturnsGoodResponseSuccess) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| |
| size_t request_size = sizeof(ec_request_tpm_control); |
| |
| EXPECT_CALL( |
| hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_TPM_CONTROL, |
| ecUtil.kVersionZero, TpmSetLocalityReq(1), request_size)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_NO_THROW(ecUtil.setTpmLocality(1)); |
| } |
| |
| TEST_F(EcUtilTest, getTpmLocalityReturnsGoodResponseSuccess) |
| { |
| std::vector<uint8_t> rsp(getTpmLocalityResponseStr.begin(), |
| getTpmLocalityResponseStr.end()); |
| size_t request_size = sizeof(ec_request_tpm_control); |
| |
| EXPECT_CALL( |
| hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + EC_PRV_CMD_HOTH_TPM_CONTROL, |
| ecUtil.kVersionZero, TpmGetLocalityReq(), request_size)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_EQ(ecUtil.getTpmLocality(), 1); |
| } |
| |
| class EcUtilStatisticTest : public EcUtilTest |
| {}; |
| |
| TEST_F(EcUtilStatisticTest, sendCommandReturnsBadResultFails) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| // Change the RspHeader.result to something other than EC_RES_SUCCESS |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_GET_STATISTICS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getHothStatistics(), CommandRunException); |
| } |
| |
| TEST_F(EcUtilStatisticTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| for (uint16_t i = 0; i < 256; i++) |
| { |
| rsp.push_back(0); |
| } |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_GET_STATISTICS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_NO_THROW(ecUtil.getHothStatistics()); |
| } |
| |
| class EcUtilPersistentPanicTest : public EcUtilTest |
| {}; |
| |
| struct panic_host_command_response |
| { |
| RspHeader hdr; |
| uint8_t body[HOTH_PERSISTENT_PANIC_INFO_CHUNK_SIZE]; |
| }; |
| |
| // Template of a persistent panic response, with the correct data_len, checksum |
| // and panic magic. |
| const panic_host_command_response kPanicResponseTemplate = { |
| .hdr = |
| { |
| .struct_version = 3, |
| .checksum = 0xb9, |
| .result = 0, |
| .data_len = HOTH_PERSISTENT_PANIC_INFO_CHUNK_SIZE, |
| .reserved = 0, |
| }, |
| .body = |
| { |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x50, 0x6e, 0x63, 0x21, |
| }, |
| }; |
| |
| TEST_F(EcUtilPersistentPanicTest, incorrectResponseSizeThrows) |
| { |
| std::vector<uint8_t> rsp_buf(sizeof(kPanicResponseTemplate), 0); |
| auto* rsp = reinterpret_cast<panic_host_command_response*>(rsp_buf.data()); |
| *rsp = kPanicResponseTemplate; |
| rsp->hdr.data_len -= 1; |
| rsp->hdr.checksum += 1; |
| rsp_buf.resize(sizeof(kPanicResponseTemplate) - 1); |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PERSISTENT_PANIC_INFO, |
| ecUtil.kVersionZero, _, |
| sizeof(ec_request_persistent_panic_info))) |
| .Times(2) |
| .WillRepeatedly(Return(rsp_buf)); |
| EXPECT_THROW(ecUtil.checkHothPersistentPanicInfo(), ResponseFailure); |
| EXPECT_THROW(ecUtil.getHothPersistentPanicInfo(), ResponseFailure); |
| } |
| |
| TEST_F(EcUtilPersistentPanicTest, incorrectPanicMagicReturnsNullopt) |
| { |
| std::vector<uint8_t> rsp_buf(sizeof(kPanicResponseTemplate), 0); |
| auto* rsp = reinterpret_cast<panic_host_command_response*>(rsp_buf.data()); |
| *rsp = kPanicResponseTemplate; |
| rsp->body[143] -= 1; |
| rsp->hdr.checksum += 1; |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PERSISTENT_PANIC_INFO, |
| ecUtil.kVersionZero, _, |
| sizeof(ec_request_persistent_panic_info))) |
| .Times(2) |
| .WillRepeatedly(Return(rsp_buf)); |
| EXPECT_FALSE(ecUtil.checkHothPersistentPanicInfo()); |
| EXPECT_FALSE(ecUtil.getHothPersistentPanicInfo()); |
| } |
| |
| TEST_F(EcUtilPersistentPanicTest, correctHostCommandReturnsFullPanicRecord) |
| { |
| std::vector<uint8_t> rsp_bufs[12]; |
| for (int i = 0; i < 12; ++i) |
| { |
| rsp_bufs[i].resize(sizeof(panic_host_command_response)); |
| auto* rsp = |
| reinterpret_cast<panic_host_command_response*>(rsp_bufs[i].data()); |
| *rsp = kPanicResponseTemplate; |
| // Tweak the response a little bit to make each chunk slightly |
| // different. |
| rsp->body[0] += i; |
| rsp->body[HOTH_PERSISTENT_PANIC_INFO_CHUNK_SIZE - 1] -= i; |
| } |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PERSISTENT_PANIC_INFO, |
| ecUtil.kVersionZero, _, |
| sizeof(ec_request_persistent_panic_info))) |
| .Times(13) |
| .WillOnce(Return(rsp_bufs[0])) |
| .WillOnce(Return(rsp_bufs[0])) |
| .WillOnce(Return(rsp_bufs[1])) |
| .WillOnce(Return(rsp_bufs[2])) |
| .WillOnce(Return(rsp_bufs[3])) |
| .WillOnce(Return(rsp_bufs[4])) |
| .WillOnce(Return(rsp_bufs[5])) |
| .WillOnce(Return(rsp_bufs[6])) |
| .WillOnce(Return(rsp_bufs[7])) |
| .WillOnce(Return(rsp_bufs[8])) |
| .WillOnce(Return(rsp_bufs[9])) |
| .WillOnce(Return(rsp_bufs[10])) |
| .WillOnce(Return(rsp_bufs[11])); |
| |
| EXPECT_TRUE(ecUtil.checkHothPersistentPanicInfo()); |
| |
| auto panic = ecUtil.getHothPersistentPanicInfo(); |
| std::span<uint8_t> panic_buf(reinterpret_cast<uint8_t*>(&panic.value()), |
| sizeof(panic.value())); |
| for (uint8_t i = 0; i < 12; ++i) |
| { |
| size_t chunk_start = |
| static_cast<uint32_t>(i) * HOTH_PERSISTENT_PANIC_INFO_CHUNK_SIZE; |
| EXPECT_EQ(panic_buf[chunk_start], i); |
| EXPECT_EQ( |
| panic_buf[chunk_start + HOTH_PERSISTENT_PANIC_INFO_CHUNK_SIZE - 1], |
| static_cast<uint8_t>(-i)); |
| } |
| } |
| |
| class EcUtilAuthRecordTest : public EcUtilTest |
| {}; |
| |
| TEST_F(EcUtilAuthRecordTest, authRecordNotSupported) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| rsp.push_back(0); |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_IS_HOST_COMMAND_SUPPORTED, |
| _, _, _)) |
| .WillOnce(Return(rsp)); |
| EXPECT_THROW(ecUtil.getHothAuthRecord(), CommandNotSupportedException); |
| } |
| |
| class EcUtilKeyRotationTest : public EcUtilTest |
| {}; |
| |
| TEST_F(EcUtilKeyRotationTest, authRecordNotSupported) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| rsp.push_back(0); |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_IS_HOST_COMMAND_SUPPORTED, |
| _, _, _)) |
| .WillOnce(Return(rsp)); |
| EXPECT_THROW(ecUtil.getHothKeyRotationStatus(), |
| CommandNotSupportedException); |
| } |
| |
| class EcUtilSecureBootEnforcementTest : public EcUtilTest |
| {}; |
| |
| TEST_F(EcUtilSecureBootEnforcementTest, commandNotSupported) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| rsp.push_back(0); |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_IS_HOST_COMMAND_SUPPORTED, |
| _, _, _)) |
| .WillOnce(Return(rsp)); |
| EXPECT_THROW(ecUtil.getSecureBootEnforcementState(), |
| CommandNotSupportedException); |
| } |
| |
| TEST_F(EcUtilSecureBootEnforcementTest, sendCommandReturnsBadResultFails) |
| { |
| std::vector<uint8_t> rsp_supported(goodResponseStr.begin(), |
| goodResponseStr.end()); |
| rsp_supported.push_back(1); |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_IS_HOST_COMMAND_SUPPORTED, |
| _, _, _)) |
| .WillOnce(Return(rsp_supported)); |
| |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| // Change the RspHeader.result to something other than EC_RES_SUCCESS |
| rsp[2] = internal::EC_RES_ERROR; |
| |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_GET_SECURE_BOOT_ENFORCEMENT, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getSecureBootEnforcementState(), CommandRunException); |
| } |
| |
| TEST_F(EcUtilSecureBootEnforcementTest, sendCommandReturnsGoodResponseSuccess) |
| { |
| std::vector<uint8_t> rsp_supported(goodResponseStr.begin(), |
| goodResponseStr.end()); |
| rsp_supported.push_back(1); |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_IS_HOST_COMMAND_SUPPORTED, |
| _, _, _)) |
| .WillOnce(Return(rsp_supported)); |
| |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| secure_boot_enforcement_state expected = {.enabled = 1, .reserved0 = {}}; |
| const uint8_t* body_ptr = reinterpret_cast<const uint8_t*>(&expected); |
| rsp.insert(rsp.end(), body_ptr, body_ptr + sizeof(expected)); |
| |
| EXPECT_CALL(hostCmd, |
| sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_GET_SECURE_BOOT_ENFORCEMENT, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| secure_boot_enforcement_state actual = |
| ecUtil.getSecureBootEnforcementState(); |
| EXPECT_EQ(actual.enabled, expected.enabled); |
| } |
| |
| class EcUtilPayloadStatusTest : public EcUtilTest |
| {}; |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusSuccess) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| payload_status_response status = {}; |
| status.header.version = PAYLOAD_STATUS_RESPONSE_VERSION; |
| const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&status); |
| rsp.insert(rsp.end(), ptr, ptr + sizeof(status)); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_NO_THROW(ecUtil.getPayloadStatus()); |
| } |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusSuccessVersion2Truncate) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| payload_status_response status = {}; |
| status.header.version = PAYLOAD_STATUS_RESPONSE_VERSION + 1; |
| const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&status); |
| rsp.insert(rsp.end(), ptr, ptr + sizeof(status)); |
| rsp.push_back(0xAA); |
| rsp.push_back(0xBB); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_NO_THROW(ecUtil.getPayloadStatus()); |
| } |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusErrorVersion0) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| payload_status_response status = {}; |
| status.header.version = 0; |
| const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&status); |
| rsp.insert(rsp.end(), ptr, ptr + sizeof(status)); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getPayloadStatus(), ResponseFailure); |
| } |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusErrorVersion1TooSmall) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| payload_status_response status = {}; |
| status.header.version = PAYLOAD_STATUS_RESPONSE_VERSION; |
| const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&status); |
| rsp.insert(rsp.end(), ptr, ptr + sizeof(status) - 1); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getPayloadStatus(), ResponseFailure); |
| } |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusErrorVersion1TooLarge) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| payload_status_response status = {}; |
| status.header.version = PAYLOAD_STATUS_RESPONSE_VERSION; |
| const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&status); |
| rsp.insert(rsp.end(), ptr, ptr + sizeof(status)); |
| rsp.push_back(0xAA); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getPayloadStatus(), ResponseFailure); |
| } |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusErrorVersion2TooSmall) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| payload_status_response status = {}; |
| status.header.version = PAYLOAD_STATUS_RESPONSE_VERSION + 1; |
| const uint8_t* ptr = reinterpret_cast<const uint8_t*>(&status); |
| rsp.insert(rsp.end(), ptr, ptr + sizeof(status) - 1); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getPayloadStatus(), ResponseFailure); |
| } |
| |
| TEST_F(EcUtilPayloadStatusTest, PayloadStatusErrorEmptyResponse) |
| { |
| std::vector<uint8_t> rsp(goodResponseStr.begin(), goodResponseStr.end()); |
| |
| EXPECT_CALL(hostCmd, sendCommand(EC_CMD_BOARD_SPECIFIC_BASE + |
| EC_PRV_CMD_HOTH_PAYLOAD_STATUS, |
| ecUtil.kVersionZero, nullptr, 0)) |
| .WillOnce(Return(rsp)); |
| |
| EXPECT_THROW(ecUtil.getPayloadStatus(), ResponseFailure); |
| } |
| |
| } // namespace |
| } // namespace internal |
| } // namespace hoth |
| } // namespace google |