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 "qapi/qmp/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 68 static char *test_image; 69 70 #define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask)) 71 #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0) 72 73 static uint8_t base = 0x70; 74 75 enum { 76 CMOS_FLOPPY = 0x10, 77 }; 78 79 static void floppy_send(uint8_t byte) 80 { 81 uint8_t msr; 82 83 msr = inb(FLOPPY_BASE + reg_msr); 84 assert_bit_set(msr, RQM); 85 assert_bit_clear(msr, DIO); 86 87 outb(FLOPPY_BASE + reg_fifo, byte); 88 } 89 90 static uint8_t floppy_recv(void) 91 { 92 uint8_t msr; 93 94 msr = inb(FLOPPY_BASE + reg_msr); 95 assert_bit_set(msr, RQM | DIO); 96 97 return inb(FLOPPY_BASE + reg_fifo); 98 } 99 100 /* pcn: Present Cylinder Number */ 101 static void ack_irq(uint8_t *pcn) 102 { 103 uint8_t ret; 104 105 g_assert(get_irq(FLOPPY_IRQ)); 106 floppy_send(CMD_SENSE_INT); 107 floppy_recv(); 108 109 ret = floppy_recv(); 110 if (pcn != NULL) { 111 *pcn = ret; 112 } 113 114 g_assert(!get_irq(FLOPPY_IRQ)); 115 } 116 117 static uint8_t send_read_command(uint8_t cmd) 118 { 119 uint8_t drive = 0; 120 uint8_t head = 0; 121 uint8_t cyl = 0; 122 uint8_t sect_addr = 1; 123 uint8_t sect_size = 2; 124 uint8_t eot = 1; 125 uint8_t gap = 0x1b; 126 uint8_t gpl = 0xff; 127 128 uint8_t msr = 0; 129 uint8_t st0; 130 131 uint8_t ret = 0; 132 133 floppy_send(cmd); 134 floppy_send(head << 2 | drive); 135 g_assert(!get_irq(FLOPPY_IRQ)); 136 floppy_send(cyl); 137 floppy_send(head); 138 floppy_send(sect_addr); 139 floppy_send(sect_size); 140 floppy_send(eot); 141 floppy_send(gap); 142 floppy_send(gpl); 143 144 uint8_t i = 0; 145 uint8_t n = 2; 146 for (; i < n; i++) { 147 msr = inb(FLOPPY_BASE + reg_msr); 148 if (msr == 0xd0) { 149 break; 150 } 151 sleep(1); 152 } 153 154 if (i >= n) { 155 return 1; 156 } 157 158 st0 = floppy_recv(); 159 if (st0 != 0x40) { 160 ret = 1; 161 } 162 163 floppy_recv(); 164 floppy_recv(); 165 floppy_recv(); 166 floppy_recv(); 167 floppy_recv(); 168 floppy_recv(); 169 170 return ret; 171 } 172 173 static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0) 174 { 175 uint8_t drive = 0; 176 uint8_t head = 0; 177 uint8_t cyl = 0; 178 uint8_t sect_addr = 1; 179 uint8_t sect_size = 2; 180 uint8_t eot = nb_sect; 181 uint8_t gap = 0x1b; 182 uint8_t gpl = 0xff; 183 184 uint8_t msr = 0; 185 uint8_t st0; 186 187 uint8_t ret = 0; 188 189 floppy_send(CMD_READ); 190 floppy_send(head << 2 | drive); 191 g_assert(!get_irq(FLOPPY_IRQ)); 192 floppy_send(cyl); 193 floppy_send(head); 194 floppy_send(sect_addr); 195 floppy_send(sect_size); 196 floppy_send(eot); 197 floppy_send(gap); 198 floppy_send(gpl); 199 200 uint16_t i = 0; 201 uint8_t n = 2; 202 for (; i < n; i++) { 203 msr = inb(FLOPPY_BASE + reg_msr); 204 if (msr == (BUSY | NONDMA | DIO | RQM)) { 205 break; 206 } 207 sleep(1); 208 } 209 210 if (i >= n) { 211 return 1; 212 } 213 214 /* Non-DMA mode */ 215 for (i = 0; i < 512 * 2 * nb_sect; i++) { 216 msr = inb(FLOPPY_BASE + reg_msr); 217 assert_bit_set(msr, BUSY | RQM | DIO); 218 inb(FLOPPY_BASE + reg_fifo); 219 } 220 221 msr = inb(FLOPPY_BASE + reg_msr); 222 assert_bit_set(msr, BUSY | RQM | DIO); 223 g_assert(get_irq(FLOPPY_IRQ)); 224 225 st0 = floppy_recv(); 226 if (st0 != expected_st0) { 227 ret = 1; 228 } 229 230 floppy_recv(); 231 floppy_recv(); 232 floppy_recv(); 233 floppy_recv(); 234 floppy_recv(); 235 g_assert(get_irq(FLOPPY_IRQ)); 236 floppy_recv(); 237 238 /* Check that we're back in command phase */ 239 msr = inb(FLOPPY_BASE + reg_msr); 240 assert_bit_clear(msr, BUSY | DIO); 241 assert_bit_set(msr, RQM); 242 g_assert(!get_irq(FLOPPY_IRQ)); 243 244 return ret; 245 } 246 247 static void send_seek(int cyl) 248 { 249 int drive = 0; 250 int head = 0; 251 252 floppy_send(CMD_SEEK); 253 floppy_send(head << 2 | drive); 254 g_assert(!get_irq(FLOPPY_IRQ)); 255 floppy_send(cyl); 256 ack_irq(NULL); 257 } 258 259 static uint8_t cmos_read(uint8_t reg) 260 { 261 outb(base + 0, reg); 262 return inb(base + 1); 263 } 264 265 static void test_cmos(void) 266 { 267 uint8_t cmos; 268 269 cmos = cmos_read(CMOS_FLOPPY); 270 g_assert(cmos == 0x40 || cmos == 0x50); 271 } 272 273 static void test_no_media_on_start(void) 274 { 275 uint8_t dir; 276 277 /* Media changed bit must be set all time after start if there is 278 * no media in drive. */ 279 dir = inb(FLOPPY_BASE + reg_dir); 280 assert_bit_set(dir, DSKCHG); 281 dir = inb(FLOPPY_BASE + reg_dir); 282 assert_bit_set(dir, DSKCHG); 283 send_seek(1); 284 dir = inb(FLOPPY_BASE + reg_dir); 285 assert_bit_set(dir, DSKCHG); 286 dir = inb(FLOPPY_BASE + reg_dir); 287 assert_bit_set(dir, DSKCHG); 288 } 289 290 static void test_read_without_media(void) 291 { 292 uint8_t ret; 293 294 ret = send_read_command(CMD_READ); 295 g_assert(ret == 0); 296 } 297 298 static void test_media_insert(void) 299 { 300 uint8_t dir; 301 302 /* Insert media in drive. DSKCHK should not be reset until a step pulse 303 * is sent. */ 304 qtest_qmp_assert_success(global_qtest, 305 "{'execute':'blockdev-change-medium', 'arguments':{" 306 " 'id':'floppy0', 'filename': %s, 'format': 'raw' }}", 307 test_image); 308 309 dir = inb(FLOPPY_BASE + reg_dir); 310 assert_bit_set(dir, DSKCHG); 311 dir = inb(FLOPPY_BASE + reg_dir); 312 assert_bit_set(dir, DSKCHG); 313 314 send_seek(0); 315 dir = inb(FLOPPY_BASE + reg_dir); 316 assert_bit_set(dir, DSKCHG); 317 dir = inb(FLOPPY_BASE + reg_dir); 318 assert_bit_set(dir, DSKCHG); 319 320 /* Step to next track should clear DSKCHG bit. */ 321 send_seek(1); 322 dir = inb(FLOPPY_BASE + reg_dir); 323 assert_bit_clear(dir, DSKCHG); 324 dir = inb(FLOPPY_BASE + reg_dir); 325 assert_bit_clear(dir, DSKCHG); 326 } 327 328 static void test_media_change(void) 329 { 330 uint8_t dir; 331 332 test_media_insert(); 333 334 /* Eject the floppy and check that DSKCHG is set. Reading it out doesn't 335 * reset the bit. */ 336 qtest_qmp_assert_success(global_qtest, 337 "{'execute':'eject', 'arguments':{" 338 " 'id':'floppy0' }}"); 339 340 dir = inb(FLOPPY_BASE + reg_dir); 341 assert_bit_set(dir, DSKCHG); 342 dir = inb(FLOPPY_BASE + reg_dir); 343 assert_bit_set(dir, DSKCHG); 344 345 send_seek(0); 346 dir = inb(FLOPPY_BASE + reg_dir); 347 assert_bit_set(dir, DSKCHG); 348 dir = inb(FLOPPY_BASE + reg_dir); 349 assert_bit_set(dir, DSKCHG); 350 351 send_seek(1); 352 dir = inb(FLOPPY_BASE + reg_dir); 353 assert_bit_set(dir, DSKCHG); 354 dir = inb(FLOPPY_BASE + reg_dir); 355 assert_bit_set(dir, DSKCHG); 356 } 357 358 static void test_sense_interrupt(void) 359 { 360 int drive = 0; 361 int head = 0; 362 int cyl = 0; 363 int ret = 0; 364 365 floppy_send(CMD_SENSE_INT); 366 ret = floppy_recv(); 367 g_assert(ret == 0x80); 368 369 floppy_send(CMD_SEEK); 370 floppy_send(head << 2 | drive); 371 g_assert(!get_irq(FLOPPY_IRQ)); 372 floppy_send(cyl); 373 374 floppy_send(CMD_SENSE_INT); 375 ret = floppy_recv(); 376 g_assert(ret == 0x20); 377 floppy_recv(); 378 } 379 380 static void test_relative_seek(void) 381 { 382 uint8_t drive = 0; 383 uint8_t head = 0; 384 uint8_t cyl = 1; 385 uint8_t pcn; 386 387 /* Send seek to track 0 */ 388 send_seek(0); 389 390 /* Send relative seek to increase track by 1 */ 391 floppy_send(CMD_RELATIVE_SEEK_IN); 392 floppy_send(head << 2 | drive); 393 g_assert(!get_irq(FLOPPY_IRQ)); 394 floppy_send(cyl); 395 396 ack_irq(&pcn); 397 g_assert(pcn == 1); 398 399 /* Send relative seek to decrease track by 1 */ 400 floppy_send(CMD_RELATIVE_SEEK_OUT); 401 floppy_send(head << 2 | drive); 402 g_assert(!get_irq(FLOPPY_IRQ)); 403 floppy_send(cyl); 404 405 ack_irq(&pcn); 406 g_assert(pcn == 0); 407 } 408 409 static void test_read_id(void) 410 { 411 uint8_t drive = 0; 412 uint8_t head = 0; 413 uint8_t cyl; 414 uint8_t st0; 415 uint8_t msr; 416 417 /* Seek to track 0 and check with READ ID */ 418 send_seek(0); 419 420 floppy_send(CMD_READ_ID); 421 g_assert(!get_irq(FLOPPY_IRQ)); 422 floppy_send(head << 2 | drive); 423 424 msr = inb(FLOPPY_BASE + reg_msr); 425 if (!get_irq(FLOPPY_IRQ)) { 426 assert_bit_set(msr, BUSY); 427 assert_bit_clear(msr, RQM); 428 } 429 430 while (!get_irq(FLOPPY_IRQ)) { 431 /* qemu involves a timer with READ ID... */ 432 clock_step(1000000000LL / 50); 433 } 434 435 msr = inb(FLOPPY_BASE + reg_msr); 436 assert_bit_set(msr, BUSY | RQM | DIO); 437 438 st0 = floppy_recv(); 439 floppy_recv(); 440 floppy_recv(); 441 cyl = floppy_recv(); 442 head = floppy_recv(); 443 floppy_recv(); 444 g_assert(get_irq(FLOPPY_IRQ)); 445 floppy_recv(); 446 g_assert(!get_irq(FLOPPY_IRQ)); 447 448 g_assert_cmpint(cyl, ==, 0); 449 g_assert_cmpint(head, ==, 0); 450 g_assert_cmpint(st0, ==, head << 2); 451 452 /* Seek to track 8 on head 1 and check with READ ID */ 453 head = 1; 454 cyl = 8; 455 456 floppy_send(CMD_SEEK); 457 floppy_send(head << 2 | drive); 458 g_assert(!get_irq(FLOPPY_IRQ)); 459 floppy_send(cyl); 460 g_assert(get_irq(FLOPPY_IRQ)); 461 ack_irq(NULL); 462 463 floppy_send(CMD_READ_ID); 464 g_assert(!get_irq(FLOPPY_IRQ)); 465 floppy_send(head << 2 | drive); 466 467 msr = inb(FLOPPY_BASE + reg_msr); 468 if (!get_irq(FLOPPY_IRQ)) { 469 assert_bit_set(msr, BUSY); 470 assert_bit_clear(msr, RQM); 471 } 472 473 while (!get_irq(FLOPPY_IRQ)) { 474 /* qemu involves a timer with READ ID... */ 475 clock_step(1000000000LL / 50); 476 } 477 478 msr = inb(FLOPPY_BASE + reg_msr); 479 assert_bit_set(msr, BUSY | RQM | DIO); 480 481 st0 = floppy_recv(); 482 floppy_recv(); 483 floppy_recv(); 484 cyl = floppy_recv(); 485 head = floppy_recv(); 486 floppy_recv(); 487 g_assert(get_irq(FLOPPY_IRQ)); 488 floppy_recv(); 489 g_assert(!get_irq(FLOPPY_IRQ)); 490 491 g_assert_cmpint(cyl, ==, 8); 492 g_assert_cmpint(head, ==, 1); 493 g_assert_cmpint(st0, ==, head << 2); 494 } 495 496 static void test_read_no_dma_1(void) 497 { 498 uint8_t ret; 499 500 outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08); 501 send_seek(0); 502 ret = send_read_no_dma_command(1, 0x04); 503 g_assert(ret == 0); 504 } 505 506 static void test_read_no_dma_18(void) 507 { 508 uint8_t ret; 509 510 outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08); 511 send_seek(0); 512 ret = send_read_no_dma_command(18, 0x04); 513 g_assert(ret == 0); 514 } 515 516 static void test_read_no_dma_19(void) 517 { 518 uint8_t ret; 519 520 outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08); 521 send_seek(0); 522 ret = send_read_no_dma_command(19, 0x20); 523 g_assert(ret == 0); 524 } 525 526 static void test_verify(void) 527 { 528 uint8_t ret; 529 530 ret = send_read_command(CMD_VERIFY); 531 g_assert(ret == 0); 532 } 533 534 /* success if no crash or abort */ 535 static void fuzz_registers(void) 536 { 537 unsigned int i; 538 539 for (i = 0; i < 1000; i++) { 540 uint8_t reg, val; 541 542 reg = (uint8_t)g_test_rand_int_range(0, 8); 543 val = (uint8_t)g_test_rand_int_range(0, 256); 544 545 outb(FLOPPY_BASE + reg, val); 546 inb(FLOPPY_BASE + reg); 547 } 548 } 549 550 static bool qtest_check_clang_sanitizer(void) 551 { 552 #ifdef QEMU_SANITIZE_ADDRESS 553 return true; 554 #else 555 g_test_skip("QEMU not configured using --enable-sanitizers"); 556 return false; 557 #endif 558 } 559 static void test_cve_2021_20196(void) 560 { 561 QTestState *s; 562 563 if (!qtest_check_clang_sanitizer()) { 564 return; 565 } 566 567 s = qtest_initf("-nographic -m 32M -nodefaults " DRIVE_FLOPPY_BLANK); 568 569 qtest_outw(s, 0x3f4, 0x0500); 570 qtest_outb(s, 0x3f5, 0x00); 571 qtest_outb(s, 0x3f5, 0x00); 572 qtest_outw(s, 0x3f4, 0x0000); 573 qtest_outb(s, 0x3f5, 0x00); 574 qtest_outw(s, 0x3f1, 0x0400); 575 qtest_outw(s, 0x3f4, 0x0000); 576 qtest_outw(s, 0x3f4, 0x0000); 577 qtest_outb(s, 0x3f5, 0x00); 578 qtest_outb(s, 0x3f5, 0x01); 579 qtest_outw(s, 0x3f1, 0x0500); 580 qtest_outb(s, 0x3f5, 0x00); 581 qtest_quit(s); 582 } 583 584 static void test_cve_2021_3507(void) 585 { 586 QTestState *s; 587 588 s = qtest_initf("-nographic -m 32M -nodefaults " 589 "-drive file=%s,format=raw,if=floppy,snapshot=on", 590 test_image); 591 qtest_outl(s, 0x9, 0x0a0206); 592 qtest_outw(s, 0x3f4, 0x1600); 593 qtest_outw(s, 0x3f4, 0x0000); 594 qtest_outw(s, 0x3f4, 0x0000); 595 qtest_outw(s, 0x3f4, 0x0000); 596 qtest_outw(s, 0x3f4, 0x0200); 597 qtest_outw(s, 0x3f4, 0x0200); 598 qtest_outw(s, 0x3f4, 0x0000); 599 qtest_outw(s, 0x3f4, 0x0000); 600 qtest_outw(s, 0x3f4, 0x0000); 601 qtest_quit(s); 602 } 603 604 int main(int argc, char **argv) 605 { 606 int fd; 607 int ret; 608 609 /* Create a temporary raw image */ 610 fd = g_file_open_tmp("qtest.XXXXXX", &test_image, NULL); 611 g_assert(fd >= 0); 612 ret = ftruncate(fd, TEST_IMAGE_SIZE); 613 g_assert(ret == 0); 614 close(fd); 615 616 /* Run the tests */ 617 g_test_init(&argc, &argv, NULL); 618 619 qtest_start("-machine pc -device floppy,id=floppy0"); 620 qtest_irq_intercept_in(global_qtest, "ioapic"); 621 qtest_add_func("/fdc/cmos", test_cmos); 622 qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start); 623 qtest_add_func("/fdc/read_without_media", test_read_without_media); 624 qtest_add_func("/fdc/media_change", test_media_change); 625 qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt); 626 qtest_add_func("/fdc/relative_seek", test_relative_seek); 627 qtest_add_func("/fdc/read_id", test_read_id); 628 qtest_add_func("/fdc/verify", test_verify); 629 qtest_add_func("/fdc/media_insert", test_media_insert); 630 qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1); 631 qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18); 632 qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19); 633 qtest_add_func("/fdc/fuzz-registers", fuzz_registers); 634 qtest_add_func("/fdc/fuzz/cve_2021_20196", test_cve_2021_20196); 635 qtest_add_func("/fdc/fuzz/cve_2021_3507", test_cve_2021_3507); 636 637 ret = g_test_run(); 638 639 /* Cleanup */ 640 qtest_end(); 641 unlink(test_image); 642 g_free(test_image); 643 644 return ret; 645 } 646