mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
9.6 KiB
C
301 lines
9.6 KiB
C
/*
|
|
* QEMU TDX Quote Generation Support
|
|
*
|
|
* Copyright (c) 2025 Intel Corporation
|
|
*
|
|
* Author:
|
|
* Xiaoyao Li <xiaoyao.li@intel.com>
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/error-report.h"
|
|
#include "qapi/error.h"
|
|
#include "qapi/qapi-visit-sockets.h"
|
|
|
|
#include "tdx-quote-generator.h"
|
|
|
|
#define QGS_MSG_LIB_MAJOR_VER 1
|
|
#define QGS_MSG_LIB_MINOR_VER 1
|
|
|
|
typedef enum _qgs_msg_type_t {
|
|
GET_QUOTE_REQ = 0,
|
|
GET_QUOTE_RESP = 1,
|
|
GET_COLLATERAL_REQ = 2,
|
|
GET_COLLATERAL_RESP = 3,
|
|
GET_PLATFORM_INFO_REQ = 4,
|
|
GET_PLATFORM_INFO_RESP = 5,
|
|
QGS_MSG_TYPE_MAX
|
|
} qgs_msg_type_t;
|
|
|
|
typedef struct _qgs_msg_header_t {
|
|
uint16_t major_version;
|
|
uint16_t minor_version;
|
|
uint32_t type;
|
|
uint32_t size; // size of the whole message, include this header, in byte
|
|
uint32_t error_code; // used in response only
|
|
} qgs_msg_header_t;
|
|
|
|
typedef struct _qgs_msg_get_quote_req_t {
|
|
qgs_msg_header_t header; // header.type = GET_QUOTE_REQ
|
|
uint32_t report_size; // cannot be 0
|
|
uint32_t id_list_size; // length of id_list, in byte, can be 0
|
|
} qgs_msg_get_quote_req_t;
|
|
|
|
typedef struct _qgs_msg_get_quote_resp_s {
|
|
qgs_msg_header_t header; // header.type = GET_QUOTE_RESP
|
|
uint32_t selected_id_size; // can be 0 in case only one id is sent in request
|
|
uint32_t quote_size; // length of quote_data, in byte
|
|
uint8_t id_quote[]; // selected id followed by quote
|
|
} qgs_msg_get_quote_resp_t;
|
|
|
|
#define HEADER_SIZE 4
|
|
|
|
static uint32_t decode_header(const char *buf, size_t len) {
|
|
if (len < HEADER_SIZE) {
|
|
return 0;
|
|
}
|
|
uint32_t msg_size = 0;
|
|
for (uint32_t i = 0; i < HEADER_SIZE; ++i) {
|
|
msg_size = msg_size * 256 + (buf[i] & 0xFF);
|
|
}
|
|
return msg_size;
|
|
}
|
|
|
|
static void encode_header(char *buf, size_t len, uint32_t size) {
|
|
assert(len >= HEADER_SIZE);
|
|
buf[0] = ((size >> 24) & 0xFF);
|
|
buf[1] = ((size >> 16) & 0xFF);
|
|
buf[2] = ((size >> 8) & 0xFF);
|
|
buf[3] = (size & 0xFF);
|
|
}
|
|
|
|
static void tdx_generate_quote_cleanup(TdxGenerateQuoteTask *task)
|
|
{
|
|
timer_del(&task->timer);
|
|
|
|
g_source_remove(task->watch);
|
|
qio_channel_close(QIO_CHANNEL(task->sioc), NULL);
|
|
object_unref(OBJECT(task->sioc));
|
|
|
|
task->completion(task);
|
|
}
|
|
|
|
static gboolean tdx_get_quote_read(QIOChannel *ioc, GIOCondition condition,
|
|
gpointer opaque)
|
|
{
|
|
TdxGenerateQuoteTask *task = opaque;
|
|
Error *err = NULL;
|
|
int ret;
|
|
|
|
ret = qio_channel_read(ioc, task->receive_buf + task->receive_buf_received,
|
|
task->payload_len - task->receive_buf_received, &err);
|
|
if (ret < 0) {
|
|
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
|
return G_SOURCE_CONTINUE;
|
|
} else {
|
|
error_report_err(err);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (ret == 0) {
|
|
error_report("End of file before reply received");
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
|
|
task->receive_buf_received += ret;
|
|
if (task->receive_buf_received >= HEADER_SIZE) {
|
|
uint32_t len = decode_header(task->receive_buf,
|
|
task->receive_buf_received);
|
|
if (len == 0 ||
|
|
len > (task->payload_len - HEADER_SIZE)) {
|
|
error_report("Message len %u must be non-zero & less than %zu",
|
|
len, (task->payload_len - HEADER_SIZE));
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
|
|
/* Now we know the size, shrink to fit */
|
|
task->payload_len = HEADER_SIZE + len;
|
|
task->receive_buf = g_renew(char,
|
|
task->receive_buf,
|
|
task->payload_len);
|
|
}
|
|
|
|
if (task->receive_buf_received >= (sizeof(qgs_msg_header_t) + HEADER_SIZE)) {
|
|
qgs_msg_header_t *hdr = (qgs_msg_header_t *)(task->receive_buf + HEADER_SIZE);
|
|
if (hdr->major_version != QGS_MSG_LIB_MAJOR_VER ||
|
|
hdr->minor_version != QGS_MSG_LIB_MINOR_VER) {
|
|
error_report("Invalid QGS message header version %d.%d",
|
|
hdr->major_version,
|
|
hdr->minor_version);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
if (hdr->type != GET_QUOTE_RESP) {
|
|
error_report("Invalid QGS message type %d",
|
|
hdr->type);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
if (hdr->size > (task->payload_len - HEADER_SIZE)) {
|
|
error_report("QGS message size %d exceeds payload capacity %zu",
|
|
hdr->size, task->payload_len);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
if (hdr->error_code != 0) {
|
|
error_report("QGS message error code %d",
|
|
hdr->error_code);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
}
|
|
if (task->receive_buf_received >= (sizeof(qgs_msg_get_quote_resp_t) + HEADER_SIZE)) {
|
|
qgs_msg_get_quote_resp_t *msg = (qgs_msg_get_quote_resp_t *)(task->receive_buf + HEADER_SIZE);
|
|
if (msg->selected_id_size != 0) {
|
|
error_report("QGS message selected ID was %d not 0",
|
|
msg->selected_id_size);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
|
|
if ((task->payload_len - HEADER_SIZE - sizeof(qgs_msg_get_quote_resp_t)) !=
|
|
msg->quote_size) {
|
|
error_report("QGS quote size %d should be %zu",
|
|
msg->quote_size,
|
|
(task->payload_len - sizeof(qgs_msg_get_quote_resp_t)));
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
if (task->receive_buf_received == task->payload_len) {
|
|
size_t strip = HEADER_SIZE + sizeof(qgs_msg_get_quote_resp_t);
|
|
memmove(task->receive_buf,
|
|
task->receive_buf + strip,
|
|
task->receive_buf_received - strip);
|
|
task->receive_buf_received -= strip;
|
|
task->status_code = TDX_VP_GET_QUOTE_SUCCESS;
|
|
goto end;
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
end:
|
|
tdx_generate_quote_cleanup(task);
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static gboolean tdx_send_report(QIOChannel *ioc, GIOCondition condition,
|
|
gpointer opaque)
|
|
{
|
|
TdxGenerateQuoteTask *task = opaque;
|
|
Error *err = NULL;
|
|
int ret;
|
|
|
|
ret = qio_channel_write(ioc, task->send_data + task->send_data_sent,
|
|
task->send_data_size - task->send_data_sent, &err);
|
|
if (ret < 0) {
|
|
if (ret == QIO_CHANNEL_ERR_BLOCK) {
|
|
ret = 0;
|
|
} else {
|
|
error_report_err(err);
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
tdx_generate_quote_cleanup(task);
|
|
goto end;
|
|
}
|
|
}
|
|
task->send_data_sent += ret;
|
|
|
|
if (task->send_data_sent == task->send_data_size) {
|
|
task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_IN,
|
|
tdx_get_quote_read, task, NULL);
|
|
goto end;
|
|
}
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
|
|
end:
|
|
return G_SOURCE_REMOVE;
|
|
}
|
|
|
|
static void tdx_quote_generator_connected(QIOTask *qio_task, gpointer opaque)
|
|
{
|
|
TdxGenerateQuoteTask *task = opaque;
|
|
Error *err = NULL;
|
|
int ret;
|
|
|
|
ret = qio_task_propagate_error(qio_task, &err);
|
|
if (ret) {
|
|
error_report_err(err);
|
|
task->status_code = TDX_VP_GET_QUOTE_QGS_UNAVAILABLE;
|
|
tdx_generate_quote_cleanup(task);
|
|
return;
|
|
}
|
|
|
|
task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_OUT,
|
|
tdx_send_report, task, NULL);
|
|
}
|
|
|
|
#define TRANSACTION_TIMEOUT 30000
|
|
|
|
static void getquote_expired(void *opaque)
|
|
{
|
|
TdxGenerateQuoteTask *task = opaque;
|
|
|
|
task->status_code = TDX_VP_GET_QUOTE_ERROR;
|
|
tdx_generate_quote_cleanup(task);
|
|
}
|
|
|
|
static void setup_get_quote_timer(TdxGenerateQuoteTask *task)
|
|
{
|
|
int64_t time;
|
|
|
|
timer_init_ms(&task->timer, QEMU_CLOCK_VIRTUAL, getquote_expired, task);
|
|
time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL);
|
|
timer_mod(&task->timer, time + TRANSACTION_TIMEOUT);
|
|
}
|
|
|
|
void tdx_generate_quote(TdxGenerateQuoteTask *task,
|
|
SocketAddress *qg_sock_addr)
|
|
{
|
|
QIOChannelSocket *sioc;
|
|
qgs_msg_get_quote_req_t msg;
|
|
|
|
/* Prepare a QGS message prelude */
|
|
msg.header.major_version = QGS_MSG_LIB_MAJOR_VER;
|
|
msg.header.minor_version = QGS_MSG_LIB_MINOR_VER;
|
|
msg.header.type = GET_QUOTE_REQ;
|
|
msg.header.size = sizeof(msg) + task->send_data_size;
|
|
msg.header.error_code = 0;
|
|
msg.report_size = task->send_data_size;
|
|
msg.id_list_size = 0;
|
|
|
|
/* Make room to add the QGS message prelude */
|
|
task->send_data = g_renew(char,
|
|
task->send_data,
|
|
task->send_data_size + sizeof(msg) + HEADER_SIZE);
|
|
memmove(task->send_data + sizeof(msg) + HEADER_SIZE,
|
|
task->send_data,
|
|
task->send_data_size);
|
|
memcpy(task->send_data + HEADER_SIZE,
|
|
&msg,
|
|
sizeof(msg));
|
|
encode_header(task->send_data, HEADER_SIZE, task->send_data_size + sizeof(msg));
|
|
task->send_data_size += sizeof(msg) + HEADER_SIZE;
|
|
|
|
sioc = qio_channel_socket_new();
|
|
task->sioc = sioc;
|
|
|
|
setup_get_quote_timer(task);
|
|
|
|
qio_channel_socket_connect_async(sioc, qg_sock_addr,
|
|
tdx_quote_generator_connected, task,
|
|
NULL, NULL);
|
|
}
|