1 /* 2 * QEMU TDX Quote Generation Support 3 * 4 * Copyright (c) 2025 Intel Corporation 5 * 6 * Author: 7 * Xiaoyao Li <xiaoyao.li@intel.com> 8 * 9 * SPDX-License-Identifier: GPL-2.0-or-later 10 */ 11 12 #include "qemu/osdep.h" 13 #include "qemu/error-report.h" 14 #include "qapi/error.h" 15 #include "qapi/qapi-visit-sockets.h" 16 17 #include "tdx-quote-generator.h" 18 19 #define QGS_MSG_LIB_MAJOR_VER 1 20 #define QGS_MSG_LIB_MINOR_VER 1 21 22 typedef enum _qgs_msg_type_t { 23 GET_QUOTE_REQ = 0, 24 GET_QUOTE_RESP = 1, 25 GET_COLLATERAL_REQ = 2, 26 GET_COLLATERAL_RESP = 3, 27 GET_PLATFORM_INFO_REQ = 4, 28 GET_PLATFORM_INFO_RESP = 5, 29 QGS_MSG_TYPE_MAX 30 } qgs_msg_type_t; 31 32 typedef struct _qgs_msg_header_t { 33 uint16_t major_version; 34 uint16_t minor_version; 35 uint32_t type; 36 uint32_t size; // size of the whole message, include this header, in byte 37 uint32_t error_code; // used in response only 38 } qgs_msg_header_t; 39 40 typedef struct _qgs_msg_get_quote_req_t { 41 qgs_msg_header_t header; // header.type = GET_QUOTE_REQ 42 uint32_t report_size; // cannot be 0 43 uint32_t id_list_size; // length of id_list, in byte, can be 0 44 } qgs_msg_get_quote_req_t; 45 46 typedef struct _qgs_msg_get_quote_resp_s { 47 qgs_msg_header_t header; // header.type = GET_QUOTE_RESP 48 uint32_t selected_id_size; // can be 0 in case only one id is sent in request 49 uint32_t quote_size; // length of quote_data, in byte 50 uint8_t id_quote[]; // selected id followed by quote 51 } qgs_msg_get_quote_resp_t; 52 53 #define HEADER_SIZE 4 54 55 static uint32_t decode_header(const char *buf, size_t len) { 56 if (len < HEADER_SIZE) { 57 return 0; 58 } 59 uint32_t msg_size = 0; 60 for (uint32_t i = 0; i < HEADER_SIZE; ++i) { 61 msg_size = msg_size * 256 + (buf[i] & 0xFF); 62 } 63 return msg_size; 64 } 65 66 static void encode_header(char *buf, size_t len, uint32_t size) { 67 assert(len >= HEADER_SIZE); 68 buf[0] = ((size >> 24) & 0xFF); 69 buf[1] = ((size >> 16) & 0xFF); 70 buf[2] = ((size >> 8) & 0xFF); 71 buf[3] = (size & 0xFF); 72 } 73 74 static void tdx_generate_quote_cleanup(TdxGenerateQuoteTask *task) 75 { 76 timer_del(&task->timer); 77 78 if (task->watch) { 79 g_source_remove(task->watch); 80 } 81 qio_channel_close(QIO_CHANNEL(task->sioc), NULL); 82 object_unref(OBJECT(task->sioc)); 83 84 task->completion(task); 85 } 86 87 static gboolean tdx_get_quote_read(QIOChannel *ioc, GIOCondition condition, 88 gpointer opaque) 89 { 90 TdxGenerateQuoteTask *task = opaque; 91 Error *err = NULL; 92 int ret; 93 94 ret = qio_channel_read(ioc, task->receive_buf + task->receive_buf_received, 95 task->payload_len - task->receive_buf_received, &err); 96 if (ret < 0) { 97 if (ret == QIO_CHANNEL_ERR_BLOCK) { 98 return G_SOURCE_CONTINUE; 99 } else { 100 error_report_err(err); 101 task->status_code = TDX_VP_GET_QUOTE_ERROR; 102 goto end; 103 } 104 } 105 106 if (ret == 0) { 107 error_report("End of file before reply received"); 108 task->status_code = TDX_VP_GET_QUOTE_ERROR; 109 goto end; 110 } 111 112 task->receive_buf_received += ret; 113 if (task->receive_buf_received >= HEADER_SIZE) { 114 uint32_t len = decode_header(task->receive_buf, 115 task->receive_buf_received); 116 if (len == 0 || 117 len > (task->payload_len - HEADER_SIZE)) { 118 error_report("Message len %u must be non-zero & less than %zu", 119 len, (task->payload_len - HEADER_SIZE)); 120 task->status_code = TDX_VP_GET_QUOTE_ERROR; 121 goto end; 122 } 123 124 /* Now we know the size, shrink to fit */ 125 task->payload_len = HEADER_SIZE + len; 126 task->receive_buf = g_renew(char, 127 task->receive_buf, 128 task->payload_len); 129 } 130 131 if (task->receive_buf_received >= (sizeof(qgs_msg_header_t) + HEADER_SIZE)) { 132 qgs_msg_header_t *hdr = (qgs_msg_header_t *)(task->receive_buf + HEADER_SIZE); 133 if (hdr->major_version != QGS_MSG_LIB_MAJOR_VER || 134 hdr->minor_version != QGS_MSG_LIB_MINOR_VER) { 135 error_report("Invalid QGS message header version %d.%d", 136 hdr->major_version, 137 hdr->minor_version); 138 task->status_code = TDX_VP_GET_QUOTE_ERROR; 139 goto end; 140 } 141 if (hdr->type != GET_QUOTE_RESP) { 142 error_report("Invalid QGS message type %d", 143 hdr->type); 144 task->status_code = TDX_VP_GET_QUOTE_ERROR; 145 goto end; 146 } 147 if (hdr->size > (task->payload_len - HEADER_SIZE)) { 148 error_report("QGS message size %d exceeds payload capacity %zu", 149 hdr->size, task->payload_len); 150 task->status_code = TDX_VP_GET_QUOTE_ERROR; 151 goto end; 152 } 153 if (hdr->error_code != 0) { 154 error_report("QGS message error code %d", 155 hdr->error_code); 156 task->status_code = TDX_VP_GET_QUOTE_ERROR; 157 goto end; 158 } 159 } 160 if (task->receive_buf_received >= (sizeof(qgs_msg_get_quote_resp_t) + HEADER_SIZE)) { 161 qgs_msg_get_quote_resp_t *msg = (qgs_msg_get_quote_resp_t *)(task->receive_buf + HEADER_SIZE); 162 if (msg->selected_id_size != 0) { 163 error_report("QGS message selected ID was %d not 0", 164 msg->selected_id_size); 165 task->status_code = TDX_VP_GET_QUOTE_ERROR; 166 goto end; 167 } 168 169 if ((task->payload_len - HEADER_SIZE - sizeof(qgs_msg_get_quote_resp_t)) != 170 msg->quote_size) { 171 error_report("QGS quote size %d should be %zu", 172 msg->quote_size, 173 (task->payload_len - sizeof(qgs_msg_get_quote_resp_t))); 174 task->status_code = TDX_VP_GET_QUOTE_ERROR; 175 goto end; 176 } 177 } 178 179 if (task->receive_buf_received == task->payload_len) { 180 size_t strip = HEADER_SIZE + sizeof(qgs_msg_get_quote_resp_t); 181 memmove(task->receive_buf, 182 task->receive_buf + strip, 183 task->receive_buf_received - strip); 184 task->receive_buf_received -= strip; 185 task->status_code = TDX_VP_GET_QUOTE_SUCCESS; 186 goto end; 187 } 188 189 return G_SOURCE_CONTINUE; 190 191 end: 192 tdx_generate_quote_cleanup(task); 193 return G_SOURCE_REMOVE; 194 } 195 196 static gboolean tdx_send_report(QIOChannel *ioc, GIOCondition condition, 197 gpointer opaque) 198 { 199 TdxGenerateQuoteTask *task = opaque; 200 Error *err = NULL; 201 int ret; 202 203 ret = qio_channel_write(ioc, task->send_data + task->send_data_sent, 204 task->send_data_size - task->send_data_sent, &err); 205 if (ret < 0) { 206 if (ret == QIO_CHANNEL_ERR_BLOCK) { 207 ret = 0; 208 } else { 209 error_report_err(err); 210 task->status_code = TDX_VP_GET_QUOTE_ERROR; 211 tdx_generate_quote_cleanup(task); 212 goto end; 213 } 214 } 215 task->send_data_sent += ret; 216 217 if (task->send_data_sent == task->send_data_size) { 218 task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_IN, 219 tdx_get_quote_read, task, NULL); 220 goto end; 221 } 222 223 return G_SOURCE_CONTINUE; 224 225 end: 226 return G_SOURCE_REMOVE; 227 } 228 229 static void tdx_quote_generator_connected(QIOTask *qio_task, gpointer opaque) 230 { 231 TdxGenerateQuoteTask *task = opaque; 232 Error *err = NULL; 233 int ret; 234 235 ret = qio_task_propagate_error(qio_task, &err); 236 if (ret) { 237 error_report_err(err); 238 task->status_code = TDX_VP_GET_QUOTE_QGS_UNAVAILABLE; 239 tdx_generate_quote_cleanup(task); 240 return; 241 } 242 243 task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_OUT, 244 tdx_send_report, task, NULL); 245 } 246 247 #define TRANSACTION_TIMEOUT 30000 248 249 static void getquote_expired(void *opaque) 250 { 251 TdxGenerateQuoteTask *task = opaque; 252 253 task->status_code = TDX_VP_GET_QUOTE_ERROR; 254 tdx_generate_quote_cleanup(task); 255 } 256 257 static void setup_get_quote_timer(TdxGenerateQuoteTask *task) 258 { 259 int64_t time; 260 261 timer_init_ms(&task->timer, QEMU_CLOCK_VIRTUAL, getquote_expired, task); 262 time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); 263 timer_mod(&task->timer, time + TRANSACTION_TIMEOUT); 264 } 265 266 void tdx_generate_quote(TdxGenerateQuoteTask *task, 267 SocketAddress *qg_sock_addr) 268 { 269 QIOChannelSocket *sioc; 270 qgs_msg_get_quote_req_t msg; 271 272 /* Prepare a QGS message prelude */ 273 msg.header.major_version = QGS_MSG_LIB_MAJOR_VER; 274 msg.header.minor_version = QGS_MSG_LIB_MINOR_VER; 275 msg.header.type = GET_QUOTE_REQ; 276 msg.header.size = sizeof(msg) + task->send_data_size; 277 msg.header.error_code = 0; 278 msg.report_size = task->send_data_size; 279 msg.id_list_size = 0; 280 281 /* Make room to add the QGS message prelude */ 282 task->send_data = g_renew(char, 283 task->send_data, 284 task->send_data_size + sizeof(msg) + HEADER_SIZE); 285 memmove(task->send_data + sizeof(msg) + HEADER_SIZE, 286 task->send_data, 287 task->send_data_size); 288 memcpy(task->send_data + HEADER_SIZE, 289 &msg, 290 sizeof(msg)); 291 encode_header(task->send_data, HEADER_SIZE, task->send_data_size + sizeof(msg)); 292 task->send_data_size += sizeof(msg) + HEADER_SIZE; 293 294 sioc = qio_channel_socket_new(); 295 task->sioc = sioc; 296 297 setup_get_quote_timer(task); 298 299 qio_channel_socket_connect_async(sioc, qg_sock_addr, 300 tdx_quote_generator_connected, task, 301 NULL, NULL); 302 } 303