blob: b1d2b0f3ad3ee83ddbbb2050abc56c9428278efb [file]
// 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