qemu/tests/qtest/fdc-test.c
Richard Henderson cb771ac1f5 meson: Split --enable-sanitizers to --enable-{asan, ubsan}
We do not always want both address and undefined behavior
sanitizers running at the same time.

For the gitlab custom-runners, drop to only --enable-ubsan.
These jobs are not run by default, but as will be obvious in the
next patch, we don't run ASan on x86 either, and it seems wrong
to hold aarch64 and s390x to a different standard.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Message-ID: <20240813095216.306555-2-richard.henderson@linaro.org>
Signed-off-by: Thomas Huth <thuth@redhat.com>
2024-09-11 09:49:11 +02:00

646 lines
15 KiB
C

/*
* Floppy test cases.
*
* Copyright (c) 2012 Kevin Wolf <kwolf@redhat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "qemu/osdep.h"
#include "libqtest-single.h"
#include "qapi/qmp/qdict.h"
#define DRIVE_FLOPPY_BLANK \
"-drive if=floppy,file=null-co://,file.read-zeroes=on,format=raw,size=1440k"
#define TEST_IMAGE_SIZE 1440 * 1024
#define FLOPPY_BASE 0x3f0
#define FLOPPY_IRQ 6
enum {
reg_sra = 0x0,
reg_srb = 0x1,
reg_dor = 0x2,
reg_msr = 0x4,
reg_dsr = 0x4,
reg_fifo = 0x5,
reg_dir = 0x7,
};
enum {
CMD_SENSE_INT = 0x08,
CMD_READ_ID = 0x0a,
CMD_SEEK = 0x0f,
CMD_VERIFY = 0x16,
CMD_READ = 0xe6,
CMD_RELATIVE_SEEK_OUT = 0x8f,
CMD_RELATIVE_SEEK_IN = 0xcf,
};
enum {
BUSY = 0x10,
NONDMA = 0x20,
RQM = 0x80,
DIO = 0x40,
DSKCHG = 0x80,
};
static char *test_image;
#define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, (mask))
#define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
static uint8_t base = 0x70;
enum {
CMOS_FLOPPY = 0x10,
};
static void floppy_send(uint8_t byte)
{
uint8_t msr;
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_set(msr, RQM);
assert_bit_clear(msr, DIO);
outb(FLOPPY_BASE + reg_fifo, byte);
}
static uint8_t floppy_recv(void)
{
uint8_t msr;
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_set(msr, RQM | DIO);
return inb(FLOPPY_BASE + reg_fifo);
}
/* pcn: Present Cylinder Number */
static void ack_irq(uint8_t *pcn)
{
uint8_t ret;
g_assert(get_irq(FLOPPY_IRQ));
floppy_send(CMD_SENSE_INT);
floppy_recv();
ret = floppy_recv();
if (pcn != NULL) {
*pcn = ret;
}
g_assert(!get_irq(FLOPPY_IRQ));
}
static uint8_t send_read_command(uint8_t cmd)
{
uint8_t drive = 0;
uint8_t head = 0;
uint8_t cyl = 0;
uint8_t sect_addr = 1;
uint8_t sect_size = 2;
uint8_t eot = 1;
uint8_t gap = 0x1b;
uint8_t gpl = 0xff;
uint8_t msr = 0;
uint8_t st0;
uint8_t ret = 0;
floppy_send(cmd);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
floppy_send(head);
floppy_send(sect_addr);
floppy_send(sect_size);
floppy_send(eot);
floppy_send(gap);
floppy_send(gpl);
uint8_t i = 0;
uint8_t n = 2;
for (; i < n; i++) {
msr = inb(FLOPPY_BASE + reg_msr);
if (msr == 0xd0) {
break;
}
sleep(1);
}
if (i >= n) {
return 1;
}
st0 = floppy_recv();
if (st0 != 0x40) {
ret = 1;
}
floppy_recv();
floppy_recv();
floppy_recv();
floppy_recv();
floppy_recv();
floppy_recv();
return ret;
}
static uint8_t send_read_no_dma_command(int nb_sect, uint8_t expected_st0)
{
uint8_t drive = 0;
uint8_t head = 0;
uint8_t cyl = 0;
uint8_t sect_addr = 1;
uint8_t sect_size = 2;
uint8_t eot = nb_sect;
uint8_t gap = 0x1b;
uint8_t gpl = 0xff;
uint8_t msr = 0;
uint8_t st0;
uint8_t ret = 0;
floppy_send(CMD_READ);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
floppy_send(head);
floppy_send(sect_addr);
floppy_send(sect_size);
floppy_send(eot);
floppy_send(gap);
floppy_send(gpl);
uint16_t i = 0;
uint8_t n = 2;
for (; i < n; i++) {
msr = inb(FLOPPY_BASE + reg_msr);
if (msr == (BUSY | NONDMA | DIO | RQM)) {
break;
}
sleep(1);
}
if (i >= n) {
return 1;
}
/* Non-DMA mode */
for (i = 0; i < 512 * 2 * nb_sect; i++) {
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_set(msr, BUSY | RQM | DIO);
inb(FLOPPY_BASE + reg_fifo);
}
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_set(msr, BUSY | RQM | DIO);
g_assert(get_irq(FLOPPY_IRQ));
st0 = floppy_recv();
if (st0 != expected_st0) {
ret = 1;
}
floppy_recv();
floppy_recv();
floppy_recv();
floppy_recv();
floppy_recv();
g_assert(get_irq(FLOPPY_IRQ));
floppy_recv();
/* Check that we're back in command phase */
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_clear(msr, BUSY | DIO);
assert_bit_set(msr, RQM);
g_assert(!get_irq(FLOPPY_IRQ));
return ret;
}
static void send_seek(int cyl)
{
int drive = 0;
int head = 0;
floppy_send(CMD_SEEK);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
ack_irq(NULL);
}
static uint8_t cmos_read(uint8_t reg)
{
outb(base + 0, reg);
return inb(base + 1);
}
static void test_cmos(void)
{
uint8_t cmos;
cmos = cmos_read(CMOS_FLOPPY);
g_assert(cmos == 0x40 || cmos == 0x50);
}
static void test_no_media_on_start(void)
{
uint8_t dir;
/* Media changed bit must be set all time after start if there is
* no media in drive. */
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
send_seek(1);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
}
static void test_read_without_media(void)
{
uint8_t ret;
ret = send_read_command(CMD_READ);
g_assert(ret == 0);
}
static void test_media_insert(void)
{
uint8_t dir;
/* Insert media in drive. DSKCHK should not be reset until a step pulse
* is sent. */
qtest_qmp_assert_success(global_qtest,
"{'execute':'blockdev-change-medium', 'arguments':{"
" 'id':'floppy0', 'filename': %s, 'format': 'raw' }}",
test_image);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
send_seek(0);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
/* Step to next track should clear DSKCHG bit. */
send_seek(1);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_clear(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_clear(dir, DSKCHG);
}
static void test_media_change(void)
{
uint8_t dir;
test_media_insert();
/* Eject the floppy and check that DSKCHG is set. Reading it out doesn't
* reset the bit. */
qtest_qmp_assert_success(global_qtest,
"{'execute':'eject', 'arguments':{"
" 'id':'floppy0' }}");
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
send_seek(0);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
send_seek(1);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
dir = inb(FLOPPY_BASE + reg_dir);
assert_bit_set(dir, DSKCHG);
}
static void test_sense_interrupt(void)
{
int drive = 0;
int head = 0;
int cyl = 0;
int ret = 0;
floppy_send(CMD_SENSE_INT);
ret = floppy_recv();
g_assert(ret == 0x80);
floppy_send(CMD_SEEK);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
floppy_send(CMD_SENSE_INT);
ret = floppy_recv();
g_assert(ret == 0x20);
floppy_recv();
}
static void test_relative_seek(void)
{
uint8_t drive = 0;
uint8_t head = 0;
uint8_t cyl = 1;
uint8_t pcn;
/* Send seek to track 0 */
send_seek(0);
/* Send relative seek to increase track by 1 */
floppy_send(CMD_RELATIVE_SEEK_IN);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
ack_irq(&pcn);
g_assert(pcn == 1);
/* Send relative seek to decrease track by 1 */
floppy_send(CMD_RELATIVE_SEEK_OUT);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
ack_irq(&pcn);
g_assert(pcn == 0);
}
static void test_read_id(void)
{
uint8_t drive = 0;
uint8_t head = 0;
uint8_t cyl;
uint8_t st0;
uint8_t msr;
/* Seek to track 0 and check with READ ID */
send_seek(0);
floppy_send(CMD_READ_ID);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(head << 2 | drive);
msr = inb(FLOPPY_BASE + reg_msr);
if (!get_irq(FLOPPY_IRQ)) {
assert_bit_set(msr, BUSY);
assert_bit_clear(msr, RQM);
}
while (!get_irq(FLOPPY_IRQ)) {
/* qemu involves a timer with READ ID... */
clock_step(1000000000LL / 50);
}
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_set(msr, BUSY | RQM | DIO);
st0 = floppy_recv();
floppy_recv();
floppy_recv();
cyl = floppy_recv();
head = floppy_recv();
floppy_recv();
g_assert(get_irq(FLOPPY_IRQ));
floppy_recv();
g_assert(!get_irq(FLOPPY_IRQ));
g_assert_cmpint(cyl, ==, 0);
g_assert_cmpint(head, ==, 0);
g_assert_cmpint(st0, ==, head << 2);
/* Seek to track 8 on head 1 and check with READ ID */
head = 1;
cyl = 8;
floppy_send(CMD_SEEK);
floppy_send(head << 2 | drive);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(cyl);
g_assert(get_irq(FLOPPY_IRQ));
ack_irq(NULL);
floppy_send(CMD_READ_ID);
g_assert(!get_irq(FLOPPY_IRQ));
floppy_send(head << 2 | drive);
msr = inb(FLOPPY_BASE + reg_msr);
if (!get_irq(FLOPPY_IRQ)) {
assert_bit_set(msr, BUSY);
assert_bit_clear(msr, RQM);
}
while (!get_irq(FLOPPY_IRQ)) {
/* qemu involves a timer with READ ID... */
clock_step(1000000000LL / 50);
}
msr = inb(FLOPPY_BASE + reg_msr);
assert_bit_set(msr, BUSY | RQM | DIO);
st0 = floppy_recv();
floppy_recv();
floppy_recv();
cyl = floppy_recv();
head = floppy_recv();
floppy_recv();
g_assert(get_irq(FLOPPY_IRQ));
floppy_recv();
g_assert(!get_irq(FLOPPY_IRQ));
g_assert_cmpint(cyl, ==, 8);
g_assert_cmpint(head, ==, 1);
g_assert_cmpint(st0, ==, head << 2);
}
static void test_read_no_dma_1(void)
{
uint8_t ret;
outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
send_seek(0);
ret = send_read_no_dma_command(1, 0x04);
g_assert(ret == 0);
}
static void test_read_no_dma_18(void)
{
uint8_t ret;
outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
send_seek(0);
ret = send_read_no_dma_command(18, 0x04);
g_assert(ret == 0);
}
static void test_read_no_dma_19(void)
{
uint8_t ret;
outb(FLOPPY_BASE + reg_dor, inb(FLOPPY_BASE + reg_dor) & ~0x08);
send_seek(0);
ret = send_read_no_dma_command(19, 0x20);
g_assert(ret == 0);
}
static void test_verify(void)
{
uint8_t ret;
ret = send_read_command(CMD_VERIFY);
g_assert(ret == 0);
}
/* success if no crash or abort */
static void fuzz_registers(void)
{
unsigned int i;
for (i = 0; i < 1000; i++) {
uint8_t reg, val;
reg = (uint8_t)g_test_rand_int_range(0, 8);
val = (uint8_t)g_test_rand_int_range(0, 256);
outb(FLOPPY_BASE + reg, val);
inb(FLOPPY_BASE + reg);
}
}
static bool qtest_check_clang_sanitizer(void)
{
#ifdef QEMU_SANITIZE_ADDRESS
return true;
#else
g_test_skip("QEMU not configured using --enable-asan");
return false;
#endif
}
static void test_cve_2021_20196(void)
{
QTestState *s;
if (!qtest_check_clang_sanitizer()) {
return;
}
s = qtest_initf("-nographic -m 32M -nodefaults " DRIVE_FLOPPY_BLANK);
qtest_outw(s, 0x3f4, 0x0500);
qtest_outb(s, 0x3f5, 0x00);
qtest_outb(s, 0x3f5, 0x00);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outb(s, 0x3f5, 0x00);
qtest_outw(s, 0x3f1, 0x0400);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outb(s, 0x3f5, 0x00);
qtest_outb(s, 0x3f5, 0x01);
qtest_outw(s, 0x3f1, 0x0500);
qtest_outb(s, 0x3f5, 0x00);
qtest_quit(s);
}
static void test_cve_2021_3507(void)
{
QTestState *s;
s = qtest_initf("-nographic -m 32M -nodefaults "
"-drive file=%s,format=raw,if=floppy,snapshot=on",
test_image);
qtest_outl(s, 0x9, 0x0a0206);
qtest_outw(s, 0x3f4, 0x1600);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outw(s, 0x3f4, 0x0200);
qtest_outw(s, 0x3f4, 0x0200);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outw(s, 0x3f4, 0x0000);
qtest_outw(s, 0x3f4, 0x0000);
qtest_quit(s);
}
int main(int argc, char **argv)
{
int fd;
int ret;
/* Create a temporary raw image */
fd = g_file_open_tmp("qtest.XXXXXX", &test_image, NULL);
g_assert(fd >= 0);
ret = ftruncate(fd, TEST_IMAGE_SIZE);
g_assert(ret == 0);
close(fd);
/* Run the tests */
g_test_init(&argc, &argv, NULL);
qtest_start("-machine pc -device floppy,id=floppy0");
qtest_irq_intercept_in(global_qtest, "ioapic");
qtest_add_func("/fdc/cmos", test_cmos);
qtest_add_func("/fdc/no_media_on_start", test_no_media_on_start);
qtest_add_func("/fdc/read_without_media", test_read_without_media);
qtest_add_func("/fdc/media_change", test_media_change);
qtest_add_func("/fdc/sense_interrupt", test_sense_interrupt);
qtest_add_func("/fdc/relative_seek", test_relative_seek);
qtest_add_func("/fdc/read_id", test_read_id);
qtest_add_func("/fdc/verify", test_verify);
qtest_add_func("/fdc/media_insert", test_media_insert);
qtest_add_func("/fdc/read_no_dma_1", test_read_no_dma_1);
qtest_add_func("/fdc/read_no_dma_18", test_read_no_dma_18);
qtest_add_func("/fdc/read_no_dma_19", test_read_no_dma_19);
qtest_add_func("/fdc/fuzz-registers", fuzz_registers);
qtest_add_func("/fdc/fuzz/cve_2021_20196", test_cve_2021_20196);
qtest_add_func("/fdc/fuzz/cve_2021_3507", test_cve_2021_3507);
ret = g_test_run();
/* Cleanup */
qtest_end();
unlink(test_image);
g_free(test_image);
return ret;
}