master
c 625 lines 18.7 KB
Raw
1 /*
2 * vmlaunchupdate device fwcfg test.
3 *
4 * Copyright (c) 2026 Red Hat, Inc.
5 *
6 * Author:
7 * Ani Sinha <anisinha@redhat.com>
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11
12 #include "qemu/osdep.h"
13 #include "libqos/libqos-pc.h"
14 #include "libqtest.h"
15 #include "standard-headers/linux/qemu_fw_cfg.h"
16 #include "libqos/fw_cfg.h"
17 #include "qemu/bswap.h"
18 #include "hw/misc/vmlaunchupdate.h"
19
20 #define WAIT_SEC 10
21 static bool debug;
22 static bool trace;
23 static bool confidential;
24
25 static void test_vm_launch_update_capability(void)
26 {
27 QFWCFG *fw_cfg;
28 QTestState *s;
29 VMLaunchUpdate launch_update;
30 size_t filesize;
31 uint64_t capabilities;
32
33 if (!qtest_has_device("vm-launch-update")) {
34 g_test_skip("Device vm-launch-update is not available");
35 return;
36 }
37
38 s = qtest_init("-device vm-launch-update");
39 fw_cfg = pc_fw_cfg_init(s);
40
41 filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
42 &launch_update, sizeof(launch_update));
43 g_assert_cmpint(filesize, ==, sizeof(launch_update));
44 capabilities = le64_to_cpu(launch_update.capabilities);
45 g_assert_cmpint(capabilities, ==, VM_LAUNCHUPDATE_FORMAT_IGVM);
46 pc_fw_cfg_uninit(fw_cfg);
47 qtest_quit(s);
48 }
49
50
51 static void test_vm_launch_update_disable(void)
52 {
53 QFWCFG *fw_cfg;
54 QOSState *qs;
55 VMLaunchUpdate launch_update;
56 uint64_t control;
57 size_t filesize;
58
59 if (!qtest_has_device("vm-launch-update")) {
60 g_test_skip("Device vm-launch-update is not available");
61 return;
62 }
63
64 /* use default accelerator */
65 qs = qtest_pc_boot("-device vm-launch-update");
66
67 fw_cfg = pc_fw_cfg_init(qs->qts);
68
69 filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
70 &launch_update, sizeof(launch_update));
71 g_assert_cmpint(filesize, ==, sizeof(launch_update));
72 control = le64_to_cpu(launch_update.control);
73 g_assert_cmpint(VM_LAUNCHUPDATE_CTL_DISABLE & control, ==, 0);
74
75 /* disable the device */
76 memset(&launch_update, 0, sizeof(launch_update));
77 launch_update.control |= VM_LAUNCHUPDATE_CTL_DISABLE;
78
79 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
80 &launch_update, sizeof(launch_update));
81 g_assert_cmpint(filesize, ==, sizeof(launch_update));
82
83 /* try to clear the dsable flag */
84 memset(&launch_update, 0, sizeof(launch_update));
85
86 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
87 &launch_update, sizeof(launch_update));
88 g_assert_cmpint(filesize, ==, sizeof(launch_update));
89
90 /* check if the device is still disabled */
91 filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
92 &launch_update, sizeof(launch_update));
93 g_assert_cmpint(filesize, ==, sizeof(launch_update));
94 control = le64_to_cpu(launch_update.control);
95 g_assert_cmpint(VM_LAUNCHUPDATE_CTL_DISABLE & control, ==, 1);
96
97 pc_fw_cfg_uninit(fw_cfg);
98 qtest_shutdown(qs);
99 }
100
101 static void check_error(void)
102 {
103 QFWCFG *fw_cfg;
104 QOSState *qs;
105 VMLaunchUpdate launch_update;
106 uint16_t status;
107 size_t filesize;
108
109 if (!qtest_has_device("vm-launch-update")) {
110 g_test_skip("Device vm-launch-update is not available");
111 return;
112 }
113
114 /* guest not started with IGVM and with default accelerator */
115 qs = qtest_pc_boot("-device vm-launch-update");
116
117 fw_cfg = pc_fw_cfg_init(qs->qts);
118
119 memset(&launch_update, 0, sizeof(launch_update));
120 launch_update.fw_image_size = 50;
121 launch_update.fw_image_addr = cpu_to_le64(0xdeadbeef);
122 launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
123
124 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
125 &launch_update, sizeof(launch_update));
126 g_assert_cmpint(filesize, ==, sizeof(launch_update));
127
128 memset(&launch_update, 0, sizeof(launch_update));
129 filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
130 &launch_update, sizeof(launch_update));
131 g_assert_cmpint(filesize, ==, sizeof(launch_update));
132 status = le64_to_cpu(launch_update.status);
133 /* should fail with NOT_IGVM_INIT */
134 g_assert_cmpint(status, ==, VM_LAUNCHUPDATE_NOT_IGVM_INIT);
135
136 memset(&launch_update, 0, sizeof(launch_update));
137 launch_update.fw_image_size = 50;
138 launch_update.fw_image_addr = cpu_to_le64(0xdeadbeef);
139 /* control set to 0, not VM_LAUNCHUPDATE_FORMAT_IGVM */
140
141 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
142 &launch_update, sizeof(launch_update));
143 g_assert_cmpint(filesize, ==, sizeof(launch_update));
144
145 memset(&launch_update, 0, sizeof(launch_update));
146 filesize = qfw_cfg_get_file(fw_cfg, FILE_VMLAUNCHUPDATE,
147 &launch_update, sizeof(launch_update));
148 g_assert_cmpint(filesize, ==, sizeof(launch_update));
149 status = le64_to_cpu(launch_update.status);
150 /* should fail with LOAD_FAIL since it was not IGVM format */
151 g_assert_cmpint(status, ==, VM_LAUNCHUPDATE_LOAD_FAIL);
152 }
153
154 static int64_t get_image_size(const char *filename)
155 {
156 int fd;
157 int64_t size;
158 fd = open(filename, O_RDONLY | O_BINARY);
159 g_assert_true(fd > 0);
160 size = lseek(fd, 0, SEEK_END);
161 close(fd);
162 return size;
163 }
164
165 static ssize_t load_image(const char *igvm_f, void **addr, size_t *size)
166 {
167 ssize_t actsize = 0, l = 0;
168 int f_igvm_f;
169 size_t l_size;
170
171 f_igvm_f = open(igvm_f, O_RDONLY | O_BINARY);
172 g_assert_true(f_igvm_f);
173 l_size = get_image_size(igvm_f);
174 g_assert_true(l_size > 0);
175 *addr = g_malloc0(l_size);
176 g_assert_true(*addr);
177
178 while (l < l_size) {
179 actsize = read(f_igvm_f, *addr + l, 1);
180 if (actsize < 0) {
181 break;
182 }
183 l += actsize;
184 }
185
186 close(f_igvm_f);
187 *size = l_size;
188 return actsize < 0 ? -1 : l;
189 }
190
191 static guint32 match_string(char *serial_f, const char *exp_out)
192 {
193 GError *error = NULL;
194 g_autofree gchar *f_contents = NULL;
195 g_autofree GRegex *regex = NULL;
196 g_autofree GMatchInfo *match_info = NULL;
197 gsize len;
198 guint32 count = 0;
199 gboolean ret;
200
201 ret = g_file_get_contents(serial_f, &f_contents, &len, &error);
202 g_assert(ret);
203 g_assert_no_error(error);
204
205 regex = g_regex_new(exp_out, G_REGEX_CASELESS, 0, &error);
206 g_assert_no_error(error);
207
208 ret = g_regex_match_full(regex, f_contents, -1, 0, 0, &match_info, &error);
209 g_assert_no_error(error);
210
211 while (g_match_info_matches(match_info)) {
212 gchar *word = g_match_info_fetch(match_info, 0);
213 g_free(word);
214 g_match_info_next(match_info, &error);
215 count++;
216 }
217 g_regex_unref(regex);
218 return count;
219 }
220
221 static int wait_for_match(char *serial_f,
222 const char *exp_out, int64_t timeout_s,
223 guint32 count)
224 {
225 time_t start, delta;
226 int ret = -1;
227
228 start = time(NULL);
229 while (1) {
230 if (match_string(serial_f, exp_out) == count) {
231 ret = 0;
232 break;
233 }
234
235 delta = time(NULL) - start;
236 if (delta >= timeout_s) {
237 fprintf(stderr, "timed out waiting to read serial output\n");
238 break;
239 }
240
241 /* wait 20 ms before trying again */
242 if (false) {
243 fprintf(stderr,
244 "sleeping 20 ms before checking serial output again.\n");
245 }
246 g_usleep(20000);
247 }
248 return ret;
249 }
250
251 static void set_test_params(const char **igvm_f, const char **igvm_init,
252 const char **snp, const char **cgs)
253 {
254 if (confidential) {
255 *snp = "-object \'{\"qom-type\":\"sev-snp-guest\",\"id\":\"lsec0\","
256 "\"cbitpos\":51,\"reduced-phys-bits\":1,\"policy\":196608}\'";
257 *cgs = "confidential-guest-support=lsec0";
258 /*
259 * The following two IGVM files can be built from the source
260 * present in https://gitlab.com/anisinha/virt-firmware-rs .
261 * Typing 'make' from the top of this repository will build the
262 * IGVM files for both confidential and
263 * non-confidential tests. The IGVM files for the non-coco
264 * case has been checked-in into the QEMU repository for
265 * convenience and easy CI pipeline testing.
266 */
267 *igvm_f = "tests/data/igvm/snptest.igvm"; /* prints 'hello world' */
268 *igvm_init = "tests/data/igvm/snptest-nohello.igvm";
269 } else {
270 *igvm_f = "tests/data/igvm/hello.igvm";
271 *igvm_init = "tests/data/igvm/qemuinit.igvm";
272 *snp = "";
273 *cgs = "";
274 }
275
276 return;
277 }
278
279 static void set_expected_out(const char **exp_out, const char **exp_out2,
280 const char **exp_out3)
281 {
282 *exp_out = "Hello world!";
283 *exp_out2 = "Test succeeded!";
284 *exp_out3 = "boot process complete with initial igvm";
285
286 return;
287 }
288
289 static QOSState *set_qemu_args(const char *cgs, const char *tp, char *serialf,
290 const char *igvm_init, const char *snp)
291 {
292 QOSState *qs;
293
294 if (tp) {
295 qs = qtest_pc_boot("-machine q35,igvm-cfg=igvm0,%s -m 1G -accel kvm "
296 "-device vm-launch-update %s "
297 "-chardev file,id=serial0,path=%s "
298 "-serial chardev:serial0 "
299 "-object igvm-cfg,id=igvm0,file=%s %s",
300 cgs, tp, serialf, igvm_init, snp);
301 } else {
302 qs = qtest_pc_boot("-machine q35,igvm-cfg=igvm0,%s -m 1G -accel kvm "
303 "-device vm-launch-update "
304 "-chardev file,id=serial0,path=%s "
305 "-serial chardev:serial0 "
306 "-object igvm-cfg,id=igvm0,file=%s %s",
307 cgs, serialf, igvm_init, snp);
308 }
309
310 return qs;
311 }
312
313 static void test_load_igvm(void)
314 {
315 const char *igvm_f;
316 const char *igvm_init;
317 int ser_fd;
318 g_autofree void *igvm_blob = NULL;
319 g_autofree char *serialtmp = NULL;
320 const char *exp_out;
321 const char *exp_out2;
322 const char *exp_out3;
323 const char *tracepoints = "--trace memory_region_finalize "
324 "--trace qigvm_cleanup_memory -D /tmp/qemu-debug.log ";
325 const char *snp, *cgs;
326 uint64_t gaddr;
327 size_t igvm_sz;
328 size_t filesize;
329 QFWCFG *fw_cfg;
330 QOSState *qs;
331 VMLaunchUpdate launch_update;
332
333 if (!trace) {
334 tracepoints = "";
335 }
336
337 if (!qtest_has_machine("q35")) {
338 g_test_skip("q35 machine not available");
339 return;
340 }
341
342 if (!qtest_has_accel("kvm")) {
343 g_test_skip("No KVM accelerator available");
344 return;
345 }
346
347 if (!qtest_has_device("vm-launch-update")) {
348 g_test_skip("Device vm-launch-update is not available");
349 return;
350 }
351
352 set_test_params(&igvm_f, &igvm_init, &snp, &cgs);
353 set_expected_out(&exp_out, &exp_out2, &exp_out3);
354
355 if (!g_file_test(igvm_f, G_FILE_TEST_EXISTS) ||
356 !g_file_test(igvm_init, G_FILE_TEST_EXISTS)) {
357 g_test_skip("igvm file bundle(s) does not exist!");
358 return;
359 }
360
361 ser_fd = g_file_open_tmp("launchupdate-qtest-serial-sXXXXXX",
362 &serialtmp, NULL);
363 g_assert_true(ser_fd != -1);
364
365 if (debug) {
366 fprintf(stderr, "serial console file is %s\n", serialtmp);
367 }
368
369 qs = set_qemu_args(cgs, tracepoints, serialtmp, igvm_init, snp);
370
371 fw_cfg = pc_fw_cfg_init(qs->qts);
372
373 if (debug) {
374 fprintf(stderr, "target endianness: %s\n",
375 qtest_big_endian(qs->qts) ? "big" : "little");
376 }
377
378 /* exp_out3 should be printed once from initial boot */
379 g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 1) == 0);
380
381 if (debug) {
382 fprintf(stderr, "initially booted with host igvm\n");
383 }
384
385 g_assert_true(load_image(igvm_f, &igvm_blob, &igvm_sz) == igvm_sz);
386
387 /* create a data buffer in guest memory */
388 gaddr = guest_alloc(&qs->alloc, igvm_sz);
389
390 if (debug) {
391 fprintf(stderr, "guest paddr: %"PRIx64 " igvm size: %lu\n",
392 gaddr, igvm_sz);
393 }
394
395 if (debug) {
396 fprintf(stderr, "writing igvm file into the guest memory\n");
397 }
398
399 qtest_bufwrite(qs->qts, gaddr, igvm_blob, igvm_sz);
400
401 if (debug) {
402 fprintf(stderr,
403 "tell hypervisor where igvm is loaded in guest memory\n");
404 }
405
406 /* now tell hypervisor where we loaded the bios */
407 memset(&launch_update, 0, sizeof(launch_update));
408 launch_update.fw_image_size = cpu_to_le64(igvm_sz);
409 launch_update.fw_image_addr = cpu_to_le64(gaddr);
410 launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
411
412 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
413 &launch_update, sizeof(launch_update));
414 g_assert_cmpint(filesize, ==, sizeof(launch_update));
415
416 if (debug) {
417 fprintf(stderr, "resetting the virtual machine now\n");
418 }
419
420 qtest_system_reset(qs->qts);
421
422 /* expected string should be printed on the console */
423 g_assert_true(wait_for_match(serialtmp, exp_out, WAIT_SEC, 1) == 0);
424 g_assert_true(wait_for_match(serialtmp, exp_out2, WAIT_SEC, 1) == 0);
425
426 if (debug) {
427 fprintf(stderr, "hello world found on console\n");
428 }
429
430 /* check if VM_LAUNCHUPDATE_CTL_HOST_IGVM function works */
431
432 /* set only VM_LAUNCHUPDATE_CTL_HOST_IGVM control without IGVM bundle */
433 memset(&launch_update, 0, sizeof(launch_update));
434 launch_update.control |= VM_LAUNCHUPDATE_CTL_HOST_IGVM;
435
436 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
437 &launch_update, sizeof(launch_update));
438 g_assert_cmpint(filesize, ==, sizeof(launch_update));
439
440 /* now reset the guest */
441 if (debug) {
442 fprintf(stderr,
443 "resetting again in order to restore host provided IGVM\n");
444 }
445 qtest_system_reset(qs->qts);
446
447 /*
448 * exp_out3 should be printed twice, once from initial boot,
449 * once from restoring host igvm.
450 */
451 g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 2) == 0);
452
453 if (debug) {
454 fprintf(stderr, "booted with host igvm again\n");
455 }
456
457 close(ser_fd);
458 guest_free(&qs->alloc, gaddr);
459 pc_fw_cfg_uninit(fw_cfg);
460 /* qtest_quit() kils QEMU, first by sending SIGTERM, then SIGKILL */
461 qtest_quit(qs->qts);
462 }
463
464 static void test_set_ctrl_once_and_reset_to_host_igvm(void)
465 {
466 const char *igvm_f;
467 const char *igvm_init;
468 int ser_fd;
469 g_autofree void *igvm_blob = NULL;
470 g_autofree char *serialtmp = NULL;
471 const char *exp_out;
472 const char *exp_out2;
473 const char *exp_out3;
474 const char *snp, *cgs;
475 uint64_t gaddr;
476 size_t igvm_sz;
477 size_t filesize;
478 QFWCFG *fw_cfg;
479 QOSState *qs;
480 VMLaunchUpdate launch_update;
481
482 if (!qtest_has_machine("q35")) {
483 g_test_skip("q35 machine not available");
484 return;
485 }
486
487 if (!qtest_has_accel("kvm")) {
488 g_test_skip("No KVM accelerator available");
489 return;
490 }
491
492 if (!qtest_has_device("vm-launch-update")) {
493 g_test_skip("Device vm-launch-update is not available");
494 return;
495 }
496
497 set_test_params(&igvm_f, &igvm_init, &snp, &cgs);
498 set_expected_out(&exp_out, &exp_out2, &exp_out3);
499
500 if (!g_file_test(igvm_f, G_FILE_TEST_EXISTS) ||
501 !g_file_test(igvm_init, G_FILE_TEST_EXISTS)) {
502 g_test_skip("igvm file bundle(s) does not exist!");
503 return;
504 }
505
506 ser_fd = g_file_open_tmp("launchupdate-qtest-serial-sXXXXXX",
507 &serialtmp, NULL);
508 g_assert_true(ser_fd != -1);
509
510 if (debug) {
511 fprintf(stderr, "serial console file is %s\n", serialtmp);
512 }
513
514 qs = set_qemu_args(cgs, NULL, serialtmp, igvm_init, snp);
515
516 fw_cfg = pc_fw_cfg_init(qs->qts);
517
518 g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 1) == 0);
519
520 if (debug) {
521 fprintf(stderr, "initially booted with host igvm\n");
522 }
523
524 g_assert_true(load_image(igvm_f, &igvm_blob, &igvm_sz) == igvm_sz);
525
526 /* create a data buffer in guest memory */
527 gaddr = guest_alloc(&qs->alloc, igvm_sz);
528
529 if (debug) {
530 fprintf(stderr, "guest paddr: %"PRIx64 " igvm size: %lu\n",
531 gaddr, igvm_sz);
532 }
533
534 if (debug) {
535 fprintf(stderr, "writing igvm file into the guest memory\n");
536 }
537
538 qtest_bufwrite(qs->qts, gaddr, igvm_blob, igvm_sz);
539
540 if (debug) {
541 fprintf(stderr,
542 "tell hypervisor where igvm is loaded in guest memory\n");
543 }
544
545 /* now tell hypervisor where we loaded the bios */
546 memset(&launch_update, 0, sizeof(launch_update));
547 launch_update.fw_image_size = cpu_to_le64(igvm_sz);
548 launch_update.fw_image_addr = cpu_to_le64(gaddr);
549
550 /* set both host ctrl and format_igvm ctrl once */
551 launch_update.control |= VM_LAUNCHUPDATE_FORMAT_IGVM;
552 launch_update.control |= VM_LAUNCHUPDATE_CTL_HOST_IGVM;
553
554 filesize = qfw_cfg_write_file(fw_cfg, qs, FILE_VMLAUNCHUPDATE,
555 &launch_update, sizeof(launch_update));
556 g_assert_cmpint(filesize, ==, sizeof(launch_update));
557
558 if (debug) {
559 fprintf(stderr, "resetting the virtual machine. This should load "
560 "user provided igvm.\n");
561 }
562
563 qtest_system_reset(qs->qts);
564
565 /* expected string should be printed on the console */
566 g_assert_true(wait_for_match(serialtmp, exp_out, WAIT_SEC, 1) == 0);
567 g_assert_true(wait_for_match(serialtmp, exp_out2, WAIT_SEC, 1) == 0);
568
569 if (debug) {
570 fprintf(stderr, "hello world found on console\n");
571 fprintf(stderr, "Now resetting again in order to reset to host igvm\n");
572 }
573
574 qtest_system_reset(qs->qts);
575
576 /*
577 * exp_out3 should be printed twice, once from initial boot,
578 * once from restoring host igvm.
579 */
580 g_assert_true(wait_for_match(serialtmp, exp_out3, WAIT_SEC, 2) == 0);
581
582 if (debug) {
583 fprintf(stderr, "booted with host igvm\n");
584 }
585
586 close(ser_fd);
587 guest_free(&qs->alloc, gaddr);
588 pc_fw_cfg_uninit(fw_cfg);
589 /* qtest_quit() kils QEMU, first by sending SIGTERM, then SIGKILL */
590 qtest_quit(qs->qts);
591 }
592
593 int main(int argc, char **argv)
594 {
595 const char *arch = qtest_get_arch();
596
597 g_test_init(&argc, &argv, NULL);
598
599 if (strcmp(arch, "x86_64")) {
600 g_test_skip("vmlaunchupdate tests are only available on x86_64\n");
601 return 0;
602 }
603
604 g_test_add_func("/vm-launch-update/cap", test_vm_launch_update_capability);
605 g_test_add_func("/vm-launch-update/disabled",
606 test_vm_launch_update_disable);
607
608 g_test_add_func("/vm-launch-update/errorcheck", check_error);
609 g_test_add_func("/vm-launch-update/load_igvm",
610 test_load_igvm);
611 g_test_add_func("/vm-launch-update/ctrl_set_once",
612 test_set_ctrl_once_and_reset_to_host_igvm);
613
614 if (getenv("LAUNCHUPDATE_DEBUG")) {
615 debug = true;
616 }
617 if (getenv("LAUNCHUPDATE_TRACE")) {
618 trace = true;
619 }
620 if (getenv("COCO")) {
621 confidential = true;
622 }
623
624 return g_test_run();
625 }