blob: 0edf842e86284cbe54bf25fab2cf9604c316dad0 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 "src/devices/tee/drivers/optee/optee-client.h"
#include <endian.h>
#include <fidl/fuchsia.hardware.rpmb/cpp/wire.h>
#include <fidl/fuchsia.tee.manager/cpp/wire.h>
#include <fidl/fuchsia.tee/cpp/wire.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/fake-bti/bti.h>
#include <lib/fake-resource/resource.h>
#include <lib/fidl/cpp/wire/client.h>
#include <lib/fidl/cpp/wire/server.h>
#include <lib/mmio/mmio-buffer.h>
#include <lib/sync/completion.h>
#include <lib/zx/bti.h>
#include <stdlib.h>
#include <zircon/types.h>
#include <memory>
#include <set>
#include <ddktl/suspend-txn.h>
#include <tee-client-api/tee-client-types.h>
#include <zxtest/zxtest.h>
#include "optee-message.h"
#include "src/devices/tee/drivers/optee/optee-controller.h"
#include "src/devices/tee/drivers/optee/optee-rpmb.h"
#include "src/devices/tee/drivers/optee/optee-smc.h"
#include "src/devices/tee/drivers/optee/tee-smc.h"
#include "src/devices/testing/mock-ddk/mock-device.h"
namespace optee {
namespace {
namespace frpmb = fuchsia_hardware_rpmb;
constexpr fuchsia_tee::wire::Uuid kOpteeOsUuid = {
0x486178E0, 0xE7F8, 0x11E3, {0xBC, 0x5E, 0x00, 0x02, 0xA5, 0xD5, 0xC5, 0x1B}};
class OpteeClientTestBase : public OpteeControllerBase, public zxtest::Test {
public:
static const size_t kMaxParamCount = 4;
struct MessageRaw {
MessageHeader hdr;
MessageParam params[kMaxParamCount];
};
OpteeClientTestBase() : loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
ASSERT_OK(loop_.StartThread("thread-id-1"));
ASSERT_OK(loop_.StartThread("thread-id-2"));
// Allocate memory for shared memory buffer
constexpr size_t kSharedMemorySize = 0x20000;
fake_bti_create(fake_bti_.reset_and_get_address());
zx::vmo fake_vmo;
size_t size = 0x20000;
EXPECT_OK(zx::vmo::create_contiguous(fake_bti_, size, 0, &fake_vmo));
EXPECT_OK(fake_bti_.pin(ZX_BTI_PERM_READ | ZX_BTI_CONTIGUOUS, fake_vmo, 0, kSharedMemorySize,
&shared_memory_paddr_, 1, &pmt_));
zx::result<fdf::MmioBuffer> mmio =
fdf::MmioBuffer::Create(0, size, std::move(fake_vmo), ZX_CACHE_POLICY_CACHED);
ASSERT_OK(mmio.status_value());
shared_memory_vaddr_ = reinterpret_cast<zx_vaddr_t>(mmio.value().get());
EXPECT_OK(SharedMemoryManager::Create(std::move(mmio.value()), shared_memory_paddr_,
&shared_memory_manager_));
}
SharedMemoryManager::DriverMemoryPool *driver_pool() const override {
return shared_memory_manager_->driver_pool();
}
SharedMemoryManager::ClientMemoryPool *client_pool() const override {
return shared_memory_manager_->client_pool();
}
zx::result<fidl::ClientEnd<frpmb::Rpmb>> RpmbConnectServer() const override {
return zx::error(ZX_ERR_UNAVAILABLE);
}
zx_device_t *GetDevice() const override { return parent_.get(); }
void SetUp() override {}
void TearDown() override {}
protected:
void AllocMemory(size_t size, uint64_t *paddr, uint64_t *mem_id, RpcHandler &rpc_handler) {
RpcFunctionArgs args;
RpcFunctionResult result;
args.generic.status = kReturnRpcPrefix | kRpcFunctionIdAllocateMemory;
args.allocate_memory.size = size;
EXPECT_OK(rpc_handler(args, &result));
*paddr = result.allocate_memory.phys_addr_upper32;
*paddr = (*paddr << 32) | result.allocate_memory.phys_addr_lower32;
*mem_id = result.allocate_memory.mem_id_upper32;
*mem_id = (*mem_id << 32) | result.allocate_memory.mem_id_lower32;
EXPECT_TRUE(*paddr > shared_memory_paddr_);
}
void FreeMemory(uint64_t &mem_id, RpcHandler &rpc_handler) {
RpcFunctionArgs args;
RpcFunctionResult result;
args.generic.status = kReturnRpcPrefix | kRpcFunctionIdFreeMemory;
args.free_memory.mem_id_upper32 = mem_id >> 32;
args.free_memory.mem_id_lower32 = mem_id & 0xFFFFFFFF;
EXPECT_OK(rpc_handler(args, &result));
}
std::shared_ptr<MockDevice> parent_ = MockDevice::FakeRootParent();
std::unique_ptr<SharedMemoryManager> shared_memory_manager_;
zx::bti fake_bti_;
zx::pmt pmt_;
zx_paddr_t shared_memory_paddr_;
zx_vaddr_t shared_memory_vaddr_;
async::Loop loop_;
};
class OpteeClientTest : public OpteeClientTestBase {
public:
OpteeClientTest() {}
CallResult CallWithMessage(const optee::Message &message, RpcHandler rpc_handler) override {
size_t offset = message.paddr() - shared_memory_paddr_;
MessageHeader *hdr = reinterpret_cast<MessageHeader *>(shared_memory_vaddr_ + offset);
hdr->return_origin = TEEC_ORIGIN_TEE;
hdr->return_code = TEEC_SUCCESS;
switch (hdr->command) {
case Message::Command::kOpenSession: {
hdr->session_id = next_session_id_++;
open_sessions_.insert(hdr->session_id);
break;
}
case Message::Command::kCloseSession: {
EXPECT_EQ(open_sessions_.erase(hdr->session_id), 1u);
break;
}
default:
hdr->return_code = TEEC_ERROR_NOT_IMPLEMENTED;
}
return CallResult{.return_code = kReturnOk};
}
const std::set<uint32_t> &open_sessions() const { return open_sessions_; }
private:
uint32_t next_session_id_ = 1;
std::set<uint32_t> open_sessions_;
};
TEST_F(OpteeClientTest, OpenSessionsClosedOnClientUnbind) {
auto [client_end, server_end] = fidl::Endpoints<fuchsia_tee::Application>::Create();
auto optee_client = std::make_unique<OpteeClient>(
this, fidl::ClientEnd<fuchsia_tee_manager::Provider>(), optee::Uuid{kOpteeOsUuid});
sync_completion_t unbound = {};
fidl::BindServer(
loop_.dispatcher(), std::move(server_end), optee_client.get(),
[&unbound](OpteeClient *, fidl::UnbindInfo, fidl::ServerEnd<fuchsia_tee::Application>) {
sync_completion_signal(&unbound);
});
{
fidl::WireSyncClient<fuchsia_tee::Application> fidl_client(std::move(client_end));
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client->OpenSession2(std::move(parameter_set));
EXPECT_OK(res.status());
ASSERT_FALSE(open_sessions().empty());
} // ~WireSyncClient will close the channel, so just wait until it has been unbound and then we
// can destroy the client
sync_completion_wait(&unbound, ZX_TIME_INFINITE);
ASSERT_FALSE(open_sessions().empty());
optee_client = nullptr;
EXPECT_TRUE(open_sessions().empty());
}
class FakeRpmb : public fidl::WireServer<frpmb::Rpmb> {
public:
using RpmbRequestCallback = fit::function<void(fuchsia_hardware_rpmb::wire::Request &request,
RequestCompleter::Sync &completer)>;
using GetInfoCallback = fit::function<void(GetDeviceInfoCompleter::Sync &completer)>;
FakeRpmb() {}
void GetDeviceInfo(GetDeviceInfoCompleter::Sync &completer) override {
if (info_callback_) {
info_callback_(completer);
} else {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
}
void Request(RequestRequestView request, RequestCompleter::Sync &completer) override {
if (request_callback_) {
request_callback_(request->request, completer);
} else {
completer.Close(ZX_ERR_NOT_SUPPORTED);
}
}
void Reset() {
info_callback_ = nullptr;
request_callback_ = nullptr;
}
void SetRequestCallback(RpmbRequestCallback &&callback) {
request_callback_ = std::move(callback);
}
void SetInfoCallback(GetInfoCallback &&callback) { info_callback_ = std::move(callback); }
private:
RpmbRequestCallback request_callback_{nullptr};
GetInfoCallback info_callback_{nullptr};
};
class OpteeClientTestRpmb : public OpteeClientTestBase {
public:
static const size_t kMaxFramesSize = 4096;
const size_t kMessageSize = 160;
const int kDefaultSessionId = 1;
const int kDefaultCommand = 1;
static constexpr uint8_t kMarker[] = {0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf};
OpteeClientTestRpmb() : rpmb_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
ASSERT_OK(rpmb_loop_.StartThread());
auto [client_end, server_end] = fidl::Endpoints<fuchsia_tee::Application>::Create();
optee_client_.reset(new OpteeClient(this, fidl::ClientEnd<fuchsia_tee_manager::Provider>(),
optee::Uuid{kOpteeOsUuid}));
fidl::BindServer(loop_.dispatcher(), std::move(server_end), optee_client_.get());
optee_client_fidl_ = fidl::WireSyncClient<fuchsia_tee::Application>(std::move(client_end));
// Create fake RPMB
fake_rpmb_.reset(new FakeRpmb());
}
CallResult CallWithMessage(const optee::Message &message, RpcHandler rpc_handler) override {
size_t offset = message.paddr() - shared_memory_paddr_;
MessageHeader *hdr = reinterpret_cast<MessageHeader *>(shared_memory_vaddr_ + offset);
hdr->return_origin = TEEC_ORIGIN_TEE;
hdr->return_code = TEEC_SUCCESS;
switch (hdr->command) {
case Message::Command::kOpenSession: {
AllocMemory(kMessageSize, &message_paddr_, &message_mem_id_, rpc_handler);
AllocMemory(kMaxFramesSize, &tx_frames_paddr_, &tx_frames_mem_id_, rpc_handler);
AllocMemory(kMaxFramesSize, &rx_frames_paddr_, &rx_frames_mem_id_, rpc_handler);
hdr->session_id = kDefaultSessionId;
break;
}
case Message::Command::kCloseSession: {
EXPECT_EQ(hdr->session_id, kDefaultSessionId);
FreeMemory(message_mem_id_, rpc_handler);
FreeMemory(tx_frames_mem_id_, rpc_handler);
FreeMemory(rx_frames_mem_id_, rpc_handler);
break;
}
case Message::Command::kInvokeCommand: {
offset = message_paddr_ - shared_memory_paddr_;
MessageRaw *rpmb_access = reinterpret_cast<MessageRaw *>(shared_memory_vaddr_ + offset);
rpmb_access->hdr.command = RpcMessage::Command::kAccessReplayProtectedMemoryBlock;
rpmb_access->hdr.num_params = 2;
rpmb_access->params[0].attribute = MessageParam::kAttributeTypeTempMemInput;
rpmb_access->params[0].payload.temporary_memory.shared_memory_reference = tx_frames_mem_id_;
rpmb_access->params[0].payload.temporary_memory.buffer = tx_frames_paddr_;
rpmb_access->params[0].payload.temporary_memory.size = tx_frames_size_;
rpmb_access->params[1].attribute = MessageParam::kAttributeTypeTempMemOutput;
rpmb_access->params[1].payload.temporary_memory.shared_memory_reference = rx_frames_mem_id_;
rpmb_access->params[1].payload.temporary_memory.buffer = rx_frames_paddr_;
rpmb_access->params[1].payload.temporary_memory.size = rx_frames_size_;
RpcFunctionArgs args;
RpcFunctionResult result;
args.generic.status = kReturnRpcPrefix | kRpcFunctionIdExecuteCommand;
args.execute_command.msg_mem_id_upper32 = message_mem_id_ >> 32;
args.execute_command.msg_mem_id_lower32 = message_mem_id_ & 0xFFFFFFFF;
zx_status_t status = rpc_handler(args, &result);
if (status != ZX_OK) {
hdr->return_code = rpmb_access->hdr.return_code;
}
break;
}
default:
hdr->return_code = TEEC_ERROR_NOT_IMPLEMENTED;
}
return CallResult{.return_code = kReturnOk};
}
zx::result<fidl::ClientEnd<frpmb::Rpmb>> RpmbConnectServer() const override {
auto endpoints = fidl::CreateEndpoints<frpmb::Rpmb>();
if (endpoints.is_error()) {
return endpoints.take_error();
}
fidl::BindServer(rpmb_loop_.dispatcher(), std::move(endpoints->server), fake_rpmb_.get());
return zx::ok(std::move(endpoints->client));
}
void SetUp() override {
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->OpenSession2(std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().session_id, kDefaultSessionId);
}
void TearDown() override {
auto res = optee_client_fidl_->CloseSession(kDefaultSessionId);
EXPECT_OK(res.status());
message_paddr_ = 0;
message_mem_id_ = 0;
tx_frames_paddr_ = 0;
tx_frames_mem_id_ = 0;
rx_frames_paddr_ = 0;
rx_frames_mem_id_ = 0;
tx_frames_size_ = 0;
rx_frames_size_ = 0;
fake_rpmb_->Reset();
}
protected:
uint8_t *GetTxBuffer() const {
size_t offset = tx_frames_paddr_ - shared_memory_paddr_;
return reinterpret_cast<uint8_t *>(shared_memory_vaddr_ + offset);
}
uint8_t *GetRxBuffer() const {
size_t offset = rx_frames_paddr_ - shared_memory_paddr_;
return reinterpret_cast<uint8_t *>(shared_memory_vaddr_ + offset);
}
uint64_t message_paddr_{0};
uint64_t message_mem_id_{0};
uint64_t tx_frames_paddr_{0};
uint64_t tx_frames_mem_id_{0};
uint64_t rx_frames_paddr_{0};
uint64_t rx_frames_mem_id_{0};
size_t tx_frames_size_{0};
size_t rx_frames_size_{0};
std::unique_ptr<FakeRpmb> fake_rpmb_;
async::Loop rpmb_loop_;
std::unique_ptr<OpteeClient> optee_client_;
fidl::WireSyncClient<fuchsia_tee::Application> optee_client_fidl_;
};
TEST_F(OpteeClientTestRpmb, InvalidRequestCommand) {
rx_frames_size_ = 512;
tx_frames_size_ = 512;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = 5;
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
}
TEST_F(OpteeClientTestRpmb, RpmbError) {
int req_cnt = 0;
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestKey);
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
req_cnt++;
completer.ReplyError(ZX_ERR_UNAVAILABLE);
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_ITEM_NOT_FOUND);
EXPECT_EQ(req_cnt, 1);
}
TEST_F(OpteeClientTestRpmb, RpmbCommunicationError) {
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestKey);
fake_rpmb_->SetRequestCallback(
[&](auto &request, auto &completer) { completer.Close(ZX_ERR_NOT_SUPPORTED); });
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_COMMUNICATION);
}
TEST_F(OpteeClientTestRpmb, GetDeviceInfo) {
tx_frames_size_ = sizeof(RpmbReq);
rx_frames_size_ = sizeof(RpmbDevInfo);
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdGetDevInfo;
fake_rpmb_->SetInfoCallback([&](auto &completer) {
using DeviceInfo = fuchsia_hardware_rpmb::wire::DeviceInfo;
using EmmcDeviceInfo = fuchsia_hardware_rpmb::wire::EmmcDeviceInfo;
EmmcDeviceInfo emmc_info = {};
emmc_info.rpmb_size = 0x74;
emmc_info.reliable_write_sector_count = 1;
EmmcDeviceInfo aligned_emmc_info(emmc_info);
auto emmc_info_ptr = fidl::ObjectView<EmmcDeviceInfo>::FromExternal(&aligned_emmc_info);
completer.Reply(DeviceInfo::WithEmmcInfo(emmc_info_ptr));
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
RpmbDevInfo *info = reinterpret_cast<RpmbDevInfo *>(GetRxBuffer());
EXPECT_EQ(info->ret_code, RpmbDevInfo::kRpmbCmdRetOK);
EXPECT_EQ(info->rpmb_size, 0x74);
EXPECT_EQ(info->rel_write_sector_count, 1);
}
TEST_F(OpteeClientTestRpmb, GetDeviceInfoWrongFrameSize) {
tx_frames_size_ = sizeof(RpmbReq) + 1;
rx_frames_size_ = sizeof(RpmbDevInfo);
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdGetDevInfo;
printf("Size of RpmbReq %zu, RpmbFrame %zu\n", sizeof(RpmbReq), sizeof(RpmbFrame));
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
}
TEST_F(OpteeClientTestRpmb, InvalidDataRequest) {
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = 10;
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
}
TEST_F(OpteeClientTestRpmb, InvalidDataRequestFrameSize) {
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame) + 1;
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = 10;
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
}
TEST_F(OpteeClientTestRpmb, RequestKeyOk) {
int req_cnt = 0;
uint8_t data[fuchsia_hardware_rpmb::wire::kFrameSize];
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestKey);
memcpy(rpmb_req->frames->stuff, kMarker, sizeof(kMarker));
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
if (req_cnt == 0) { // first call
EXPECT_EQ(request.tx_frames.size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_FALSE(request.rx_frames);
EXPECT_OK(request.tx_frames.vmo.read(data, request.tx_frames.offset, sizeof(kMarker)));
EXPECT_EQ(memcmp(data, kMarker, sizeof(kMarker)), 0);
} else if (req_cnt == 1) { // second call
EXPECT_EQ(request.tx_frames.size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_TRUE(request.rx_frames);
EXPECT_EQ(request.rx_frames->size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_OK(request.tx_frames.vmo.read(data, request.tx_frames.offset, sizeof(data)));
RpmbFrame *frame = reinterpret_cast<RpmbFrame *>(data);
EXPECT_EQ(frame->request, htobe16(RpmbFrame::kRpmbRequestStatus));
EXPECT_OK(request.rx_frames->vmo.write(kMarker, request.rx_frames->offset, sizeof(kMarker)));
}
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
EXPECT_EQ(req_cnt, 2);
EXPECT_EQ(memcmp(GetRxBuffer(), kMarker, sizeof(kMarker)), 0);
}
TEST_F(OpteeClientTestRpmb, RequestKeyInvalid) {
int req_cnt = 0;
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize * 2;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestKey);
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
EXPECT_EQ(req_cnt, 0);
}
TEST_F(OpteeClientTestRpmb, RequestWCounterOk) {
int req_cnt = 0;
uint8_t data[sizeof(kMarker)];
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestWCounter);
memcpy(rpmb_req->frames->stuff, kMarker, sizeof(kMarker));
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
EXPECT_EQ(request.tx_frames.size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_TRUE(request.rx_frames);
EXPECT_EQ(request.rx_frames->size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_OK(request.tx_frames.vmo.read(data, request.tx_frames.offset, sizeof(kMarker)));
EXPECT_EQ(memcmp(data, kMarker, sizeof(kMarker)), 0);
EXPECT_OK(request.rx_frames->vmo.write(kMarker, request.rx_frames->offset, sizeof(kMarker)));
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
EXPECT_EQ(req_cnt, 1);
EXPECT_EQ(memcmp(GetRxBuffer(), kMarker, sizeof(kMarker)), 0);
}
TEST_F(OpteeClientTestRpmb, RequestWCounterInvalid) {
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize * 2;
int req_cnt = 0;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestWCounter);
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
EXPECT_EQ(req_cnt, 0);
}
TEST_F(OpteeClientTestRpmb, ReadDataOk) {
int req_cnt = 0;
uint8_t data[sizeof(kMarker)];
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize * 2;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestReadData);
memcpy(rpmb_req->frames->stuff, kMarker, sizeof(kMarker));
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
EXPECT_EQ(request.tx_frames.size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_TRUE(request.rx_frames);
EXPECT_OK(request.tx_frames.vmo.read(data, request.tx_frames.offset, sizeof(kMarker)));
EXPECT_EQ(memcmp(data, kMarker, sizeof(kMarker)), 0);
EXPECT_OK(request.rx_frames->vmo.write(kMarker, request.rx_frames->offset, sizeof(kMarker)));
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
EXPECT_EQ(req_cnt, 1);
EXPECT_EQ(memcmp(GetRxBuffer(), kMarker, sizeof(kMarker)), 0);
}
TEST_F(OpteeClientTestRpmb, RequestReadInvalid) {
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame) + fuchsia_hardware_rpmb::wire::kFrameSize;
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
int req_cnt = 0;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestReadData);
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
EXPECT_EQ(req_cnt, 0);
}
TEST_F(OpteeClientTestRpmb, WriteDataOk) {
int req_cnt = 0;
uint8_t data[fuchsia_hardware_rpmb::wire::kFrameSize];
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestWriteData);
memcpy(rpmb_req->frames->stuff, kMarker, sizeof(kMarker));
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
if (req_cnt == 0) { // first call
EXPECT_EQ(request.tx_frames.size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_FALSE(request.rx_frames);
EXPECT_OK(request.tx_frames.vmo.read(data, request.tx_frames.offset, sizeof(kMarker)));
EXPECT_EQ(memcmp(data, kMarker, sizeof(kMarker)), 0);
} else if (req_cnt == 1) { // second call
EXPECT_EQ(request.tx_frames.size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_TRUE(request.rx_frames);
EXPECT_EQ(request.rx_frames->size, fuchsia_hardware_rpmb::wire::kFrameSize);
EXPECT_OK(request.tx_frames.vmo.read(data, request.tx_frames.offset, sizeof(data)));
RpmbFrame *frame = reinterpret_cast<RpmbFrame *>(data);
EXPECT_EQ(frame->request, htobe16(RpmbFrame::kRpmbRequestStatus));
EXPECT_OK(request.rx_frames->vmo.write(kMarker, request.rx_frames->offset, sizeof(kMarker)));
}
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
EXPECT_EQ(req_cnt, 2);
EXPECT_EQ(memcmp(GetRxBuffer(), kMarker, sizeof(kMarker)), 0);
}
TEST_F(OpteeClientTestRpmb, RequestWriteInvalid) {
tx_frames_size_ = sizeof(RpmbReq) + sizeof(RpmbFrame);
rx_frames_size_ = fuchsia_hardware_rpmb::wire::kFrameSize * 2;
int req_cnt = 0;
RpmbReq *rpmb_req = reinterpret_cast<RpmbReq *>(GetTxBuffer());
rpmb_req->cmd = RpmbReq::kCmdDataRequest;
rpmb_req->frames->request = htobe16(RpmbFrame::kRpmbRequestWriteData);
fake_rpmb_->SetRequestCallback([&](auto &request, auto &completer) {
req_cnt++;
completer.ReplySuccess();
});
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = optee_client_fidl_->InvokeCommand(kDefaultSessionId, kDefaultCommand,
std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_ERROR_BAD_PARAMETERS);
EXPECT_EQ(req_cnt, 0);
}
class OpteeClientTestWaitQueue : public OpteeClientTestBase {
public:
const int kInitSessionId = 0;
static constexpr int kSleepCommand = 1;
static constexpr int kWakeUpCommand = 2;
static constexpr int kNopeCommand = 3;
OpteeClientTestWaitQueue() : clients_loop_(&kAsyncLoopConfigNoAttachToCurrentThread) {
ASSERT_OK(clients_loop_.StartThread());
}
CallResult CallWithMessage(const optee::Message &message, RpcHandler rpc_handler) override {
size_t offset = message.paddr() - shared_memory_paddr_;
MessageHeader *hdr = reinterpret_cast<MessageHeader *>(shared_memory_vaddr_ + offset);
hdr->return_origin = TEEC_ORIGIN_TEE;
hdr->return_code = TEEC_SUCCESS;
switch (hdr->command) {
case Message::Command::kOpenSession: {
hdr->session_id = ++cur_sid_;
break;
}
case Message::Command::kInvokeCommand: {
EXPECT_LE(hdr->session_id, cur_sid_);
switch (hdr->app_function) {
case kSleepCommand:
hdr->return_code = handle_wq_message(rpc_handler, WaitQueueRpcMessage::Command::kSleep);
break;
case kWakeUpCommand:
hdr->return_code =
handle_wq_message(rpc_handler, WaitQueueRpcMessage::Command::kWakeUp);
break;
case kNopeCommand:
// do nothing
break;
default:
EXPECT_TRUE(false);
}
invoke_done_cnt_++;
break;
}
case Message::Command::kCloseSession: {
break;
}
default:
hdr->return_code = TEEC_ERROR_NOT_IMPLEMENTED;
}
return CallResult{.return_code = kReturnOk};
}
uint32_t handle_wq_message(RpcHandler &rpc_handler, uint16_t cmd) {
uint64_t message_paddr = 0;
uint64_t message_mem_id = 0;
uint32_t ret = TEEC_SUCCESS;
AllocMemory(sizeof(MessageRaw), &message_paddr, &message_mem_id, rpc_handler);
uint64_t offset = message_paddr - shared_memory_paddr_;
MessageRaw *wq_msg = reinterpret_cast<MessageRaw *>(shared_memory_vaddr_ + offset);
wq_msg->hdr.command = RpcMessage::Command::kWaitQueue;
wq_msg->hdr.num_params = 1;
wq_msg->params[0].attribute = MessageParam::kAttributeTypeValueInput;
wq_msg->params[0].payload.value.wait_queue.key = sleep_key_;
wq_msg->params[0].payload.value.wait_queue.command = cmd;
RpcFunctionArgs args;
RpcFunctionResult result;
args.generic.status = kReturnRpcPrefix | kRpcFunctionIdExecuteCommand;
args.execute_command.msg_mem_id_upper32 = message_mem_id >> 32;
args.execute_command.msg_mem_id_lower32 = message_mem_id & 0xFFFFFFFF;
zx_status_t status = rpc_handler(args, &result);
if (status != ZX_OK) {
ret = wq_msg->hdr.return_code;
}
FreeMemory(message_mem_id, rpc_handler);
return ret;
}
void SetUp() override {
cur_sid_ = kInitSessionId;
sleep_key_ = 0;
invoke_done_cnt_ = 0;
}
void TearDown() override {}
protected:
int cur_sid_{kInitSessionId};
int sleep_key_{0};
int invoke_done_cnt_{0};
async::Loop clients_loop_;
};
TEST_F(OpteeClientTestWaitQueue, WakeUpBeforeSleep) {
auto [client1_end, server1_end] = fidl::Endpoints<fuchsia_tee::Application>::Create();
auto [client2_end, server2_end] = fidl::Endpoints<fuchsia_tee::Application>::Create();
auto optee1_client = std::make_unique<OpteeClient>(
this, fidl::ClientEnd<fuchsia_tee_manager::Provider>(), optee::Uuid{kOpteeOsUuid});
auto optee2_client = std::make_unique<OpteeClient>(
this, fidl::ClientEnd<fuchsia_tee_manager::Provider>(), optee::Uuid{kOpteeOsUuid});
fidl::BindServer(loop_.dispatcher(), std::move(server1_end), optee1_client.get());
fidl::BindServer(loop_.dispatcher(), std::move(server2_end), optee2_client.get());
fidl::WireSharedClient fidl_client1(std::move(client1_end), clients_loop_.dispatcher());
fidl::WireSyncClient<fuchsia_tee::Application> fidl_client2(std::move(client2_end));
sync_completion_t completion;
uint32_t sid1;
uint32_t sid2;
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
fidl_client1->OpenSession2(parameter_set)
.ThenExactlyOnce(
[&](::fidl::WireUnownedResult<::fuchsia_tee::Application::OpenSession2> &result) {
if (!result.ok()) {
FAIL("OpenSession2 failed: %s", result.error().FormatDescription().c_str());
return;
}
auto *resp = result.Unwrap();
EXPECT_EQ(resp->session_id, cur_sid_);
sid1 = resp->session_id;
sync_completion_signal(&completion);
});
}
sync_completion_wait(&completion, ZX_TIME_INFINITE);
sync_completion_reset(&completion);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->OpenSession2(std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().session_id, cur_sid_);
sid2 = res.value().session_id;
}
sleep_key_ = 1;
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->InvokeCommand(sid2, kWakeUpCommand, std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
}
EXPECT_EQ(invoke_done_cnt_, 1);
EXPECT_EQ(this->WaitQueueSize(), 1);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->InvokeCommand(sid2, kWakeUpCommand, std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
}
EXPECT_EQ(invoke_done_cnt_, 2);
EXPECT_EQ(this->WaitQueueSize(), 1);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->InvokeCommand(sid2, kNopeCommand, std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
}
EXPECT_EQ(invoke_done_cnt_, 3);
EXPECT_EQ(this->WaitQueueSize(), 1);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
fidl_client1->InvokeCommand(sid1, kSleepCommand, parameter_set)
.ThenExactlyOnce(
[&](::fidl::WireUnownedResult<::fuchsia_tee::Application::InvokeCommand> &result) {
if (!result.ok()) {
FAIL("InvokeCommand failed: %s", result.error().FormatDescription().c_str());
return;
}
auto *resp = result.Unwrap();
EXPECT_EQ(resp->op_result.return_code(), TEEC_SUCCESS);
sync_completion_signal(&completion);
});
}
sync_completion_wait(&completion, ZX_TIME_INFINITE);
EXPECT_EQ(invoke_done_cnt_, 4);
EXPECT_EQ(this->WaitQueueSize(), 0);
}
TEST_F(OpteeClientTestWaitQueue, SleepWakeup) {
auto [client1_end, server1_end] = fidl::Endpoints<fuchsia_tee::Application>::Create();
auto [client2_end, server2_end] = fidl::Endpoints<fuchsia_tee::Application>::Create();
auto optee1_client = std::make_unique<OpteeClient>(
this, fidl::ClientEnd<fuchsia_tee_manager::Provider>(), optee::Uuid{kOpteeOsUuid});
auto optee2_client = std::make_unique<OpteeClient>(
this, fidl::ClientEnd<fuchsia_tee_manager::Provider>(), optee::Uuid{kOpteeOsUuid});
fidl::BindServer(loop_.dispatcher(), std::move(server1_end), optee1_client.get());
fidl::BindServer(loop_.dispatcher(), std::move(server2_end), optee2_client.get());
fidl::WireSharedClient fidl_client1(std::move(client1_end), clients_loop_.dispatcher());
fidl::WireSyncClient<fuchsia_tee::Application> fidl_client2(std::move(client2_end));
sync_completion_t completion;
zx_status_t status;
uint32_t sid1;
uint32_t sid2;
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
fidl_client1->OpenSession2(parameter_set)
.ThenExactlyOnce(
[&](::fidl::WireUnownedResult<::fuchsia_tee::Application::OpenSession2> &result) {
if (!result.ok()) {
FAIL("OpenSession2 failed: %s", result.error().FormatDescription().c_str());
return;
}
auto *resp = result.Unwrap();
EXPECT_EQ(resp->session_id, cur_sid_);
sid1 = resp->session_id;
sync_completion_signal(&completion);
});
}
sync_completion_wait(&completion, ZX_TIME_INFINITE);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->OpenSession2(std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().session_id, cur_sid_);
sid2 = res.value().session_id;
}
sync_completion_reset(&completion);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
fidl_client1->InvokeCommand(sid1, kSleepCommand, parameter_set)
.ThenExactlyOnce(
[&](::fidl::WireUnownedResult<::fuchsia_tee::Application::InvokeCommand> &result) {
if (!result.ok()) {
FAIL("InvokeCommand failed: %s", result.error().FormatDescription().c_str());
return;
}
auto *resp = result.Unwrap();
EXPECT_EQ(resp->op_result.return_code(), TEEC_SUCCESS);
sync_completion_signal(&completion);
});
}
EXPECT_EQ(invoke_done_cnt_, 0);
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->InvokeCommand(sid2, kNopeCommand, std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
}
EXPECT_EQ(invoke_done_cnt_, 1);
EXPECT_FALSE(sync_completion_signaled(&completion));
{
fidl::VectorView<fuchsia_tee::wire::Parameter> parameter_set;
auto res = fidl_client2->InvokeCommand(sid2, kWakeUpCommand, std::move(parameter_set));
EXPECT_OK(res.status());
EXPECT_EQ(res.value().op_result.return_code(), TEEC_SUCCESS);
}
status = sync_completion_wait(&completion, ZX_TIME_INFINITE);
EXPECT_OK(status);
EXPECT_EQ(invoke_done_cnt_, 3);
EXPECT_EQ(this->WaitQueueSize(), 0);
}
} // namespace
} // namespace optee