master
c 701 lines 16.6 KB
Raw
1 /*
2 * Floppy test cases.
3 *
4 * Copyright (c) 2012 Kevin Wolf <kwolf@redhat.com>
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a copy
7 * of this software and associated documentation files (the "Software"), to deal
8 * in the Software without restriction, including without limitation the rights
9 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 * copies of the Software, and to permit persons to whom the Software is
11 * furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 * THE SOFTWARE.
23 */
24
25 #include "qemu/osdep.h"
26
27
28 #include "libqtest-single.h"
29 #include "qobject/qdict.h"
30
31 #define DRIVE_FLOPPY_BLANK \
32 "-drive if=floppy,file=null-co://,file.read-zeroes=on,format=raw,size=1440k"
33
34 #define TEST_IMAGE_SIZE 1440 * 1024
35
36 #define FLOPPY_BASE 0x3f0
37 #define FLOPPY_IRQ 6
38
39 enum {
40 reg_sra = 0x0,
41 reg_srb = 0x1,
42 reg_dor = 0x2,
43 reg_msr = 0x4,
44 reg_dsr = 0x4,
45 reg_fifo = 0x5,
46 reg_dir = 0x7,
47 };
48
49 enum {
50 CMD_SENSE_INT = 0x08,
51 CMD_READ_ID = 0x0a,
52 CMD_SEEK = 0x0f,
53 CMD_VERIFY = 0x16,
54 CMD_READ = 0xe6,
55 CMD_RELATIVE_SEEK_OUT = 0x8f,
56 CMD_RELATIVE_SEEK_IN = 0xcf,
57 };
58
59 enum {
60 BUSY = 0x10,
61 NONDMA = 0x20,
62 RQM = 0x80,
63 DIO = 0x40,
64
65 DSKCHG = 0x80,
66 };
67 enum {
68 ST0_IC_MASK = 0xc0, /* interrupt code */
69 ST0_IC_ABNTERM = 0x40, /* abnormal termination */
70
71 ST1_MA = 0x01, /* missing address mark */
72 };
73
74 static char *test_image;
75
76 #define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
77 #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
78
79 static uint8_t base = 0x70;
80
81 enum {
82 CMOS_FLOPPY = 0x10,
83 };
84
85 static void floppy_send(uint8_t byte)
86 {
87 uint8_t msr;
88
89 msr = inb(FLOPPY_BASE + reg_msr);
90 assert_bit_set(msr, RQM);
91 assert_bit_clear(msr, DIO);
92
93 outb(FLOPPY_BASE + reg_fifo, byte);
94 }
95
96 static uint8_t floppy_recv(void)
97 {
98 uint8_t msr;
99
100 msr = inb(FLOPPY_BASE + reg_msr);
101 assert_bit_set(msr, RQM | DIO);
102
103 return inb(FLOPPY_BASE + reg_fifo);
104 }
105
106 /* pcn: Present Cylinder Number */
107 static void ack_irq(uint8_t *pcn)
108 {
109 uint8_t ret;
110
111 g_assert(get_irq(FLOPPY_IRQ));
112 floppy_send(CMD_SENSE_INT);
113 floppy_recv();
114
115 ret = floppy_recv();
116 if (pcn != NULL) {
117 *pcn = ret;
118 }
119
120 g_assert(!get_irq(FLOPPY_IRQ));
121 }
122
123 static uint8_t send_read_command(uint8_t cmd)
124 {
125 uint8_t drive = 0;
126 uint8_t head = 0;
127 uint8_t cyl = 0;
128 uint8_t sect_addr = 1;
129 uint8_t sect_size = 2;
130 uint8_t eot = 1;
131 uint8_t gap = 0x1b;
132 uint8_t gpl = 0xff;
133
134 uint8_t msr = 0;
135 uint8_t st0;
136
137 uint8_t ret = 0;
138
139 floppy_send(cmd);
140 floppy_send(head << 2 | drive);
141 g_assert(!get_irq(FLOPPY_IRQ));
142 floppy_send(cyl);
143 floppy_send(head);
144 floppy_send(sect_addr);
145 floppy_send(sect_size);
146 floppy_send(eot);
147 floppy_send(gap);
148 floppy_send(gpl);
149
150 uint8_t i = 0;
151 uint8_t n = 2;
152 for (; i < n; i++) {
153 msr = inb(FLOPPY_BASE + reg_msr);
154 if (msr == 0xd0) {
155 break;
156 }
157 sleep(1);
158 }
159
160 if (i >= n) {
161 return 1;
162 }
163
164 st0 = floppy_recv();
165 if (st0 != 0x40) {
166 ret = 1;
167 }
168
169 floppy_recv();
170 floppy_recv();
171 floppy_recv();
172 floppy_recv();
173 floppy_recv();
174 floppy_recv();
175
176 return ret;
177 }
178
179 static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
180 {
181 uint8_t drive = 0;
182 uint8_t head = 0;
183 uint8_t cyl = 0;
184 uint8_t sect_addr = 1;
185 uint8_t sect_size = 2;
186 uint8_t eot = nb_sect;
187 uint8_t gap = 0x1b;
188 uint8_t gpl = 0xff;
189
190 uint8_t msr = 0;
191 uint8_t st0;
192
193 uint8_t ret = 0;
194
195 floppy_send(CMD_READ);
196 floppy_send(head << 2 | drive);
197 g_assert(!get_irq(FLOPPY_IRQ));
198 floppy_send(cyl);
199 floppy_send(head);
200 floppy_send(sect_addr);
201 floppy_send(sect_size);
202 floppy_send(eot);
203 floppy_send(gap);
204 floppy_send(gpl);
205
206 uint16_t i = 0;
207 uint8_t n = 2;
208 for (; i < n; i++) {
209 msr = inb(FLOPPY_BASE + reg_msr);
210 if (msr == (BUSY | NONDMA | DIO | RQM)) {
211 break;
212 }
213 sleep(1);
214 }
215
216 if (i >= n) {
217 return 1;
218 }
219
220 /* Non-DMA mode */
221 for (i = 0; i < 512 * 2 * nb_sect; i++) {
222 msr = inb(FLOPPY_BASE + reg_msr);
223 assert_bit_set(msr, BUSY | RQM | DIO);
224 inb(FLOPPY_BASE + reg_fifo);
225 }
226
227 msr = inb(FLOPPY_BASE + reg_msr);
228 assert_bit_set(msr, BUSY | RQM | DIO);
229 g_assert(get_irq(FLOPPY_IRQ));
230
231 st0 = floppy_recv();
232 if (st0 != expected_st0) {
233 ret = 1;
234 }
235
236 floppy_recv();
237 floppy_recv();
238 floppy_recv();
239 floppy_recv();
240 floppy_recv();
241 g_assert(get_irq(FLOPPY_IRQ));
242 floppy_recv();
243
244 /* Check that we're back in command phase */
245 msr = inb(FLOPPY_BASE + reg_msr);
246 assert_bit_clear(msr, BUSY | DIO);
247 assert_bit_set(msr, RQM);
248 g_assert(!get_irq(FLOPPY_IRQ));
249
250 return ret;
251 }
252
253 static void send_seek(int cyl)
254 {
255 int drive = 0;
256 int head = 0;
257
258 floppy_send(CMD_SEEK);
259 floppy_send(head << 2 | drive);
260 g_assert(!get_irq(FLOPPY_IRQ));
261 floppy_send(cyl);
262 ack_irq(NULL);
263 }
264
265 static uint8_t cmos_read(uint8_t reg)
266 {
267 outb(base + 0, reg);
268 return inb(base + 1);
269 }
270
271 static void test_cmos(void)
272 {
273 uint8_t cmos;
274
275 cmos = cmos_read(CMOS_FLOPPY);
276 g_assert(cmos == 0x40 || cmos == 0x50);
277 }
278
279 static void media_insert(void)
280 {
281 qtest_qmp_assert_success(global_qtest,
282 "{'execute':'blockdev-change-medium', 'arguments':{"
283 " 'id':'floppy0', 'filename': %s, 'format': 'raw' }}",
284 test_image);
285 }
286
287 static void media_eject(void)
288 {
289 qtest_qmp_assert_success(global_qtest,
290 "{'execute':'eject', 'arguments':{"
291 " 'id':'floppy0' }}");
292 }
293
294 static void test_no_media_on_start(void)
295 {
296 uint8_t dir;
297
298 /* Media changed bit must be set all time after start if there is
299 * no media in drive. */
300 dir = inb(FLOPPY_BASE + reg_dir);
301 assert_bit_set(dir, DSKCHG);
302 dir = inb(FLOPPY_BASE + reg_dir);
303 assert_bit_set(dir, DSKCHG);
304 send_seek(1);
305 dir = inb(FLOPPY_BASE + reg_dir);
306 assert_bit_set(dir, DSKCHG);
307 dir = inb(FLOPPY_BASE + reg_dir);
308 assert_bit_set(dir, DSKCHG);
309 }
310
311 static void test_read_without_media(void)
312 {
313 uint8_t ret;
314
315 ret = send_read_command(CMD_READ);
316 g_assert(ret == 0);
317 }
318
319 static void test_media_insert(void)
320 {
321 uint8_t dir;
322
323 /* Insert media in drive. DSKCHK should not be reset until a step pulse
324 * is sent. */
325 media_insert();
326
327 dir = inb(FLOPPY_BASE + reg_dir);
328 assert_bit_set(dir, DSKCHG);
329 dir = inb(FLOPPY_BASE + reg_dir);
330 assert_bit_set(dir, DSKCHG);
331
332 send_seek(0);
333 dir = inb(FLOPPY_BASE + reg_dir);
334 assert_bit_set(dir, DSKCHG);
335 dir = inb(FLOPPY_BASE + reg_dir);
336 assert_bit_set(dir, DSKCHG);
337
338 /* Step to next track should clear DSKCHG bit. */
339 send_seek(1);
340 dir = inb(FLOPPY_BASE + reg_dir);
341 assert_bit_clear(dir, DSKCHG);
342 dir = inb(FLOPPY_BASE + reg_dir);
343 assert_bit_clear(dir, DSKCHG);
344 }
345
346 static void test_media_change(void)
347 {
348 uint8_t dir;
349
350 test_media_insert();
351
352 /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
353 * reset the bit. */
354 media_eject();
355
356 dir = inb(FLOPPY_BASE + reg_dir);
357 assert_bit_set(dir, DSKCHG);
358 dir = inb(FLOPPY_BASE + reg_dir);
359 assert_bit_set(dir, DSKCHG);
360
361 send_seek(0);
362 dir = inb(FLOPPY_BASE + reg_dir);
363 assert_bit_set(dir, DSKCHG);
364 dir = inb(FLOPPY_BASE + reg_dir);
365 assert_bit_set(dir, DSKCHG);
366
367 send_seek(1);
368 dir = inb(FLOPPY_BASE + reg_dir);
369 assert_bit_set(dir, DSKCHG);
370 dir = inb(FLOPPY_BASE + reg_dir);
371 assert_bit_set(dir, DSKCHG);
372 }
373
374 static void test_sense_interrupt(void)
375 {
376 int drive = 0;
377 int head = 0;
378 int cyl = 0;
379 int ret = 0;
380
381 floppy_send(CMD_SENSE_INT);
382 ret = floppy_recv();
383 g_assert(ret == 0x80);
384
385 floppy_send(CMD_SEEK);
386 floppy_send(head << 2 | drive);
387 g_assert(!get_irq(FLOPPY_IRQ));
388 floppy_send(cyl);
389
390 floppy_send(CMD_SENSE_INT);
391 ret = floppy_recv();
392 g_assert(ret == 0x20);
393 floppy_recv();
394 }
395
396 static void test_relative_seek(void)
397 {
398 uint8_t drive = 0;
399 uint8_t head = 0;
400 uint8_t cyl = 1;
401 uint8_t pcn;
402
403 /* Send seek to track 0 */
404 send_seek(0);
405
406 /* Send relative seek to increase track by 1 */
407 floppy_send(CMD_RELATIVE_SEEK_IN);
408 floppy_send(head << 2 | drive);
409 g_assert(!get_irq(FLOPPY_IRQ));
410 floppy_send(cyl);
411
412 ack_irq(&pcn);
413 g_assert(pcn == 1);
414
415 /* Send relative seek to decrease track by 1 */
416 floppy_send(CMD_RELATIVE_SEEK_OUT);
417 floppy_send(head << 2 | drive);
418 g_assert(!get_irq(FLOPPY_IRQ));
419 floppy_send(cyl);
420
421 ack_irq(&pcn);
422 g_assert(pcn == 0);
423 }
424
425 static void test_read_id(void)
426 {
427 uint8_t drive = 0;
428 uint8_t head = 0;
429 uint8_t cyl;
430 uint8_t st0;
431 uint8_t msr;
432
433 /* READ ID reads an address mark, so it needs a medium in the drive. */
434 media_insert();
435
436 /* Seek to track 0 and check with READ ID */
437 send_seek(0);
438
439 floppy_send(CMD_READ_ID);
440 g_assert(!get_irq(FLOPPY_IRQ));
441 floppy_send(head << 2 | drive);
442
443 msr = inb(FLOPPY_BASE + reg_msr);
444 if (!get_irq(FLOPPY_IRQ)) {
445 assert_bit_set(msr, BUSY);
446 assert_bit_clear(msr, RQM);
447 }
448
449 while (!get_irq(FLOPPY_IRQ)) {
450 /* qemu involves a timer with READ ID... */
451 clock_step(1000000000LL / 50);
452 }
453
454 msr = inb(FLOPPY_BASE + reg_msr);
455 assert_bit_set(msr, BUSY | RQM | DIO);
456
457 st0 = floppy_recv();
458 floppy_recv();
459 floppy_recv();
460 cyl = floppy_recv();
461 head = floppy_recv();
462 floppy_recv();
463 g_assert(get_irq(FLOPPY_IRQ));
464 floppy_recv();
465 g_assert(!get_irq(FLOPPY_IRQ));
466
467 g_assert_cmpint(cyl, ==, 0);
468 g_assert_cmpint(head, ==, 0);
469 g_assert_cmpint(st0, ==, head << 2);
470
471 /* Seek to track 8 on head 1 and check with READ ID */
472 head = 1;
473 cyl = 8;
474
475 floppy_send(CMD_SEEK);
476 floppy_send(head << 2 | drive);
477 g_assert(!get_irq(FLOPPY_IRQ));
478 floppy_send(cyl);
479 g_assert(get_irq(FLOPPY_IRQ));
480 ack_irq(NULL);
481
482 floppy_send(CMD_READ_ID);
483 g_assert(!get_irq(FLOPPY_IRQ));
484 floppy_send(head << 2 | drive);
485
486 msr = inb(FLOPPY_BASE + reg_msr);
487 if (!get_irq(FLOPPY_IRQ)) {
488 assert_bit_set(msr, BUSY);
489 assert_bit_clear(msr, RQM);
490 }
491
492 while (!get_irq(FLOPPY_IRQ)) {
493 /* qemu involves a timer with READ ID... */
494 clock_step(1000000000LL / 50);
495 }
496
497 msr = inb(FLOPPY_BASE + reg_msr);
498 assert_bit_set(msr, BUSY | RQM | DIO);
499
500 st0 = floppy_recv();
501 floppy_recv();
502 floppy_recv();
503 cyl = floppy_recv();
504 head = floppy_recv();
505 floppy_recv();
506 g_assert(get_irq(FLOPPY_IRQ));
507 floppy_recv();
508 g_assert(!get_irq(FLOPPY_IRQ));
509
510 g_assert_cmpint(cyl, ==, 8);
511 g_assert_cmpint(head, ==, 1);
512 g_assert_cmpint(st0, ==, head << 2);
513
514 /* Leave the drive empty, the way the machine starts up. */
515 media_eject();
516 }
517
518 /*
519 * An empty drive spins no diskette, so READ ID finds no address mark and must
520 * terminate abnormally. Reporting success (with a made-up sector ID) would
521 * tell the guest that a medium is still present after it has been ejected.
522 */
523 static void test_read_id_no_media(void)
524 {
525 uint8_t drive = 0;
526 uint8_t head = 0;
527 uint8_t st0, st1;
528
529 floppy_send(CMD_READ_ID);
530 g_assert(!get_irq(FLOPPY_IRQ));
531 floppy_send(head << 2 | drive);
532
533 while (!get_irq(FLOPPY_IRQ)) {
534 clock_step(1000000000LL / 50);
535 }
536
537 st0 = floppy_recv();
538 st1 = floppy_recv();
539 floppy_recv(); /* ST2 */
540 floppy_recv(); /* cylinder */
541 floppy_recv(); /* head */
542 floppy_recv(); /* sector */
543 g_assert(get_irq(FLOPPY_IRQ));
544 floppy_recv(); /* sector size */
545 g_assert(!get_irq(FLOPPY_IRQ));
546
547 g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
548 g_assert_cmpint(st1 & ST1_MA, ==, ST1_MA);
549 }
550
551 static void test_read_no_dma_1(void)
552 {
553 uint8_t ret;
554
555 outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
556 send_seek(0);
557 ret = send_read_no_dma_command(1, 0x04);
558 g_assert(ret == 0);
559 }
560
561 static void test_read_no_dma_18(void)
562 {
563 uint8_t ret;
564
565 outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
566 send_seek(0);
567 ret = send_read_no_dma_command(18, 0x04);
568 g_assert(ret == 0);
569 }
570
571 static void test_read_no_dma_19(void)
572 {
573 uint8_t ret;
574
575 outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
576 send_seek(0);
577 ret = send_read_no_dma_command(19, 0x20);
578 g_assert(ret == 0);
579 }
580
581 static void test_verify(void)
582 {
583 uint8_t ret;
584
585 ret = send_read_command(CMD_VERIFY);
586 g_assert(ret == 0);
587 }
588
589 /* success if no crash or abort */
590 static void fuzz_registers(void)
591 {
592 unsigned int i;
593
594 for (i = 0; i < 1000; i++) {
595 uint8_t reg, val;
596
597 reg = (uint8_t)g_test_rand_int_range(0, 8);
598 val = (uint8_t)g_test_rand_int_range(0, 256);
599
600 outb(FLOPPY_BASE + reg, val);
601 inb(FLOPPY_BASE + reg);
602 }
603 }
604
605 static bool qtest_check_clang_sanitizer(void)
606 {
607 #ifdef QEMU_SANITIZE_ADDRESS
608 return true;
609 #else
610 g_test_skip("QEMU not configured using --enable-asan");
611 return false;
612 #endif
613 }
614 static void test_cve_2021_20196(void)
615 {
616 QTestState *s;
617
618 if (!qtest_check_clang_sanitizer()) {
619 return;
620 }
621
622 s = qtest_initf("-nographic -m 32M -nodefaults " DRIVE_FLOPPY_BLANK);
623
624 qtest_outw(s, 0x3f4, 0x0500);
625 qtest_outb(s, 0x3f5, 0x00);
626 qtest_outb(s, 0x3f5, 0x00);
627 qtest_outw(s, 0x3f4, 0x0000);
628 qtest_outb(s, 0x3f5, 0x00);
629 qtest_outw(s, 0x3f1, 0x0400);
630 qtest_outw(s, 0x3f4, 0x0000);
631 qtest_outw(s, 0x3f4, 0x0000);
632 qtest_outb(s, 0x3f5, 0x00);
633 qtest_outb(s, 0x3f5, 0x01);
634 qtest_outw(s, 0x3f1, 0x0500);
635 qtest_outb(s, 0x3f5, 0x00);
636 qtest_quit(s);
637 }
638
639 static void test_cve_2021_3507(void)
640 {
641 QTestState *s;
642
643 s = qtest_initf("-nographic -m 32M -nodefaults "
644 "-drive file=%s,format=raw,if=floppy,snapshot=on",
645 test_image);
646 qtest_outl(s, 0x9, 0x0a0206);
647 qtest_outw(s, 0x3f4, 0x1600);
648 qtest_outw(s, 0x3f4, 0x0000);
649 qtest_outw(s, 0x3f4, 0x0000);
650 qtest_outw(s, 0x3f4, 0x0000);
651 qtest_outw(s, 0x3f4, 0x0200);
652 qtest_outw(s, 0x3f4, 0x0200);
653 qtest_outw(s, 0x3f4, 0x0000);
654 qtest_outw(s, 0x3f4, 0x0000);
655 qtest_outw(s, 0x3f4, 0x0000);
656 qtest_quit(s);
657 }
658
659 int main(int argc, char **argv)
660 {
661 int fd;
662 int ret;
663
664 /* Create a temporary raw image */
665 fd = g_file_open_tmp("qtest.XXXXXX", &test_image, NULL);
666 g_assert(fd >= 0);
667 ret = ftruncate(fd, TEST_IMAGE_SIZE);
668 g_assert(ret == 0);
669 close(fd);
670
671 /* Run the tests */
672 g_test_init(&argc, &argv, NULL);
673
674 qtest_start("-machine pc -device floppy,id=floppy0");
675 qtest_irq_intercept_in(global_qtest, "ioapic");
676 qtest_add_func("/fdc/cmos", test_cmos);
677 qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start);
678 qtest_add_func("/fdc/read_without_media", test_read_without_media);
679 qtest_add_func("/fdc/media_change", test_media_change);
680 qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
681 qtest_add_func("/fdc/relative_seek", test_relative_seek);
682 qtest_add_func("/fdc/read_id", test_read_id);
683 qtest_add_func("/fdc/read_id_no_media", test_read_id_no_media);
684 qtest_add_func("/fdc/verify", test_verify);
685 qtest_add_func("/fdc/media_insert", test_media_insert);
686 qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
687 qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
688 qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
689 qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
690 qtest_add_func("/fdc/fuzz/cve_2021_20196", test_cve_2021_20196);
691 qtest_add_func("/fdc/fuzz/cve_2021_3507", test_cve_2021_3507);
692
693 ret = g_test_run();
694
695 /* Cleanup */
696 qtest_end();
697 unlink(test_image);
698 g_free(test_image);
699
700 return ret;
701 }