master
c 299 lines 9.58 KB
Raw
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 }