xref: /openbmc/qemu/target/i386/kvm/tdx-quote-generator.c (revision b92b39af4219df4250f121f64d215506909c7404)
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 
decode_header(const char * buf,size_t len)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 
encode_header(char * buf,size_t len,uint32_t size)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 
tdx_generate_quote_cleanup(TdxGenerateQuoteTask * task)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 
tdx_get_quote_read(QIOChannel * ioc,GIOCondition condition,gpointer opaque)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 
tdx_send_report(QIOChannel * ioc,GIOCondition condition,gpointer opaque)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 
tdx_quote_generator_connected(QIOTask * qio_task,gpointer opaque)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 
getquote_expired(void * opaque)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 
setup_get_quote_timer(TdxGenerateQuoteTask * task)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 
tdx_generate_quote(TdxGenerateQuoteTask * task,SocketAddress * qg_sock_addr)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