| 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 | g_clear_handle_id(&task->watch, g_source_remove); |
| 78 | qio_channel_close(QIO_CHANNEL(task->sioc), NULL); |
| 79 | object_unref(OBJECT(task->sioc)); |
| 80 | |
| 81 | task->completion(task); |
| 82 | } |
| 83 | |
| 84 | static gboolean tdx_get_quote_read(QIOChannel *ioc, GIOCondition condition, |
| 85 | gpointer opaque) |
| 86 | { |
| 87 | TdxGenerateQuoteTask *task = opaque; |
| 88 | Error *err = NULL; |
| 89 | int ret; |
| 90 | |
| 91 | ret = qio_channel_read(ioc, task->receive_buf + task->receive_buf_received, |
| 92 | task->payload_len - task->receive_buf_received, &err); |
| 93 | if (ret < 0) { |
| 94 | if (ret == QIO_CHANNEL_ERR_BLOCK) { |
| 95 | return G_SOURCE_CONTINUE; |
| 96 | } else { |
| 97 | error_report_err(err); |
| 98 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 99 | goto end; |
| 100 | } |
| 101 | } |
| 102 | |
| 103 | if (ret == 0) { |
| 104 | error_report("End of file before reply received"); |
| 105 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 106 | goto end; |
| 107 | } |
| 108 | |
| 109 | task->receive_buf_received += ret; |
| 110 | if (task->receive_buf_received >= HEADER_SIZE) { |
| 111 | uint32_t len = decode_header(task->receive_buf, |
| 112 | task->receive_buf_received); |
| 113 | if (len == 0 || |
| 114 | len > (task->payload_len - HEADER_SIZE)) { |
| 115 | error_report("Message len %u must be non-zero & less than %zu", |
| 116 | len, (task->payload_len - HEADER_SIZE)); |
| 117 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 118 | goto end; |
| 119 | } |
| 120 | |
| 121 | /* Now we know the size, shrink to fit */ |
| 122 | task->payload_len = HEADER_SIZE + len; |
| 123 | task->receive_buf = g_renew(char, |
| 124 | task->receive_buf, |
| 125 | task->payload_len); |
| 126 | } |
| 127 | |
| 128 | if (task->receive_buf_received >= (sizeof(qgs_msg_header_t) + HEADER_SIZE)) { |
| 129 | qgs_msg_header_t *hdr = (qgs_msg_header_t *)(task->receive_buf + HEADER_SIZE); |
| 130 | if (hdr->major_version != QGS_MSG_LIB_MAJOR_VER || |
| 131 | hdr->minor_version != QGS_MSG_LIB_MINOR_VER) { |
| 132 | error_report("Invalid QGS message header version %d.%d", |
| 133 | hdr->major_version, |
| 134 | hdr->minor_version); |
| 135 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 136 | goto end; |
| 137 | } |
| 138 | if (hdr->type != GET_QUOTE_RESP) { |
| 139 | error_report("Invalid QGS message type %d", |
| 140 | hdr->type); |
| 141 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 142 | goto end; |
| 143 | } |
| 144 | if (hdr->size > (task->payload_len - HEADER_SIZE)) { |
| 145 | error_report("QGS message size %d exceeds payload capacity %zu", |
| 146 | hdr->size, task->payload_len); |
| 147 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 148 | goto end; |
| 149 | } |
| 150 | if (hdr->error_code != 0) { |
| 151 | error_report("QGS message error code %d", |
| 152 | hdr->error_code); |
| 153 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 154 | goto end; |
| 155 | } |
| 156 | } |
| 157 | if (task->receive_buf_received >= (sizeof(qgs_msg_get_quote_resp_t) + HEADER_SIZE)) { |
| 158 | qgs_msg_get_quote_resp_t *msg = (qgs_msg_get_quote_resp_t *)(task->receive_buf + HEADER_SIZE); |
| 159 | if (msg->selected_id_size != 0) { |
| 160 | error_report("QGS message selected ID was %d not 0", |
| 161 | msg->selected_id_size); |
| 162 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 163 | goto end; |
| 164 | } |
| 165 | |
| 166 | if ((task->payload_len - HEADER_SIZE - sizeof(qgs_msg_get_quote_resp_t)) != |
| 167 | msg->quote_size) { |
| 168 | error_report("QGS quote size %d should be %zu", |
| 169 | msg->quote_size, |
| 170 | (task->payload_len - sizeof(qgs_msg_get_quote_resp_t))); |
| 171 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 172 | goto end; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | if (task->receive_buf_received == task->payload_len) { |
| 177 | size_t strip = HEADER_SIZE + sizeof(qgs_msg_get_quote_resp_t); |
| 178 | memmove(task->receive_buf, |
| 179 | task->receive_buf + strip, |
| 180 | task->receive_buf_received - strip); |
| 181 | task->receive_buf_received -= strip; |
| 182 | task->status_code = TDX_VP_GET_QUOTE_SUCCESS; |
| 183 | goto end; |
| 184 | } |
| 185 | |
| 186 | return G_SOURCE_CONTINUE; |
| 187 | |
| 188 | end: |
| 189 | tdx_generate_quote_cleanup(task); |
| 190 | return G_SOURCE_REMOVE; |
| 191 | } |
| 192 | |
| 193 | static gboolean tdx_send_report(QIOChannel *ioc, GIOCondition condition, |
| 194 | gpointer opaque) |
| 195 | { |
| 196 | TdxGenerateQuoteTask *task = opaque; |
| 197 | Error *err = NULL; |
| 198 | int ret; |
| 199 | |
| 200 | ret = qio_channel_write(ioc, task->send_data + task->send_data_sent, |
| 201 | task->send_data_size - task->send_data_sent, &err); |
| 202 | if (ret < 0) { |
| 203 | if (ret == QIO_CHANNEL_ERR_BLOCK) { |
| 204 | ret = 0; |
| 205 | } else { |
| 206 | error_report_err(err); |
| 207 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 208 | tdx_generate_quote_cleanup(task); |
| 209 | goto end; |
| 210 | } |
| 211 | } |
| 212 | task->send_data_sent += ret; |
| 213 | |
| 214 | if (task->send_data_sent == task->send_data_size) { |
| 215 | task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_IN, |
| 216 | tdx_get_quote_read, task, NULL); |
| 217 | goto end; |
| 218 | } |
| 219 | |
| 220 | return G_SOURCE_CONTINUE; |
| 221 | |
| 222 | end: |
| 223 | return G_SOURCE_REMOVE; |
| 224 | } |
| 225 | |
| 226 | static void tdx_quote_generator_connected(QIOTask *qio_task, gpointer opaque) |
| 227 | { |
| 228 | TdxGenerateQuoteTask *task = opaque; |
| 229 | Error *err = NULL; |
| 230 | int ret; |
| 231 | |
| 232 | ret = qio_task_propagate_error(qio_task, &err); |
| 233 | if (ret) { |
| 234 | error_report_err(err); |
| 235 | task->status_code = TDX_VP_GET_QUOTE_QGS_UNAVAILABLE; |
| 236 | tdx_generate_quote_cleanup(task); |
| 237 | return; |
| 238 | } |
| 239 | |
| 240 | task->watch = qio_channel_add_watch(QIO_CHANNEL(task->sioc), G_IO_OUT, |
| 241 | tdx_send_report, task, NULL); |
| 242 | } |
| 243 | |
| 244 | #define TRANSACTION_TIMEOUT 30000 |
| 245 | |
| 246 | static void getquote_expired(void *opaque) |
| 247 | { |
| 248 | TdxGenerateQuoteTask *task = opaque; |
| 249 | |
| 250 | task->status_code = TDX_VP_GET_QUOTE_ERROR; |
| 251 | tdx_generate_quote_cleanup(task); |
| 252 | } |
| 253 | |
| 254 | static void setup_get_quote_timer(TdxGenerateQuoteTask *task) |
| 255 | { |
| 256 | int64_t time; |
| 257 | |
| 258 | timer_init_ms(&task->timer, QEMU_CLOCK_VIRTUAL, getquote_expired, task); |
| 259 | time = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL); |
| 260 | timer_mod(&task->timer, time + TRANSACTION_TIMEOUT); |
| 261 | } |
| 262 | |
| 263 | void tdx_generate_quote(TdxGenerateQuoteTask *task, |
| 264 | SocketAddress *qg_sock_addr) |
| 265 | { |
| 266 | QIOChannelSocket *sioc; |
| 267 | qgs_msg_get_quote_req_t msg; |
| 268 | |
| 269 | /* Prepare a QGS message prelude */ |
| 270 | msg.header.major_version = QGS_MSG_LIB_MAJOR_VER; |
| 271 | msg.header.minor_version = QGS_MSG_LIB_MINOR_VER; |
| 272 | msg.header.type = GET_QUOTE_REQ; |
| 273 | msg.header.size = sizeof(msg) + task->send_data_size; |
| 274 | msg.header.error_code = 0; |
| 275 | msg.report_size = task->send_data_size; |
| 276 | msg.id_list_size = 0; |
| 277 | |
| 278 | /* Make room to add the QGS message prelude */ |
| 279 | task->send_data = g_renew(char, |
| 280 | task->send_data, |
| 281 | task->send_data_size + sizeof(msg) + HEADER_SIZE); |
| 282 | memmove(task->send_data + sizeof(msg) + HEADER_SIZE, |
| 283 | task->send_data, |
| 284 | task->send_data_size); |
| 285 | memcpy(task->send_data + HEADER_SIZE, |
| 286 | &msg, |
| 287 | sizeof(msg)); |
| 288 | encode_header(task->send_data, HEADER_SIZE, task->send_data_size + sizeof(msg)); |
| 289 | task->send_data_size += sizeof(msg) + HEADER_SIZE; |
| 290 | |
| 291 | sioc = qio_channel_socket_new(); |
| 292 | task->sioc = sioc; |
| 293 | |
| 294 | setup_get_quote_timer(task); |
| 295 | |
| 296 | qio_channel_socket_connect_async(sioc, qg_sock_addr, |
| 297 | tdx_quote_generator_connected, task, |
| 298 | NULL, NULL); |
| 299 | } |