/* * QEMU TDX Quote Generation Support * * Copyright (c) 2025 Intel Corporation * * Author: * Xiaoyao Li * * 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); if (task->watch) { 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); }