On Mon, Aug 24, 2026 at 04:24:53PM +0100, Alex Bennée wrote:
> Add a test case for #3800 where we check that the last sector is
> properly bounded by the physical media that is inserted.
> 
> Link: https://gitlab.com/qemu-project/qemu/-/issues/3800
> AI-used-for: initial draft of test

Wasn't the change in AI policy still pending a new patch
proposal from Paolo ? 

> Signed-off-by: Alex Bennée <[email protected]>
> ---
>  tests/qtest/fdc-test.c | 246 
> ++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 241 insertions(+), 5 deletions(-)
> 
> diff --git a/tests/qtest/fdc-test.c b/tests/qtest/fdc-test.c
> index 1e1dd8659d0..3f2642fe671 100644
> --- a/tests/qtest/fdc-test.c
> +++ b/tests/qtest/fdc-test.c
> @@ -31,7 +31,8 @@
>  #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 TEST_IMAGE_1440KB (1440 * 1024)
> +#define TEST_IMAGE_720KB (720 * 1024)
>  
>  #define FLOPPY_BASE 0x3f0
>  #define FLOPPY_IRQ 6
> @@ -49,8 +50,11 @@ enum {
>  enum {
>      CMD_SENSE_INT           = 0x08,
>      CMD_READ_ID             = 0x0a,
> +    CMD_FORMAT_TRACK        = 0x4d,
>      CMD_SEEK                = 0x0f,
>      CMD_VERIFY              = 0x16,
> +    CMD_SAVE                = 0x2e,
> +    CMD_RESTORE             = 0x4e,
>      CMD_READ                = 0xe6,
>      CMD_RELATIVE_SEEK_OUT   = 0x8f,
>      CMD_RELATIVE_SEEK_IN    = 0xcf,
> @@ -69,9 +73,11 @@ enum {
>      ST0_IC_ABNTERM = 0x40,  /* abnormal termination */
>  
>      ST1_MA       = 0x01,    /* missing address mark */
> +    ST1_EC       = 0x80,    /* end of cylinder / sector past last_sect */
>  };
>  
>  static char *test_image;
> +static char *test_image_720k;
>  
>  #define assert_bit_set(data, mask) g_assert_cmphex((data) & (mask), ==, 
> (mask))
>  #define assert_bit_clear(data, mask) g_assert_cmphex((data) & (mask), ==, 0)
> @@ -276,12 +282,17 @@ static void test_cmos(void)
>      g_assert(cmos == 0x40 || cmos == 0x50);
>  }
>  
> -static void media_insert(void)
> +static void media_insert_path(const char *path)
>  {
>      qtest_qmp_assert_success(global_qtest,
>                               "{'execute':'blockdev-change-medium', 
> 'arguments':{"
>                               " 'id':'floppy0', 'filename': %s, 'format': 
> 'raw' }}",
> -                             test_image);
> +                             path);
> +}
> +
> +static void media_insert(void)
> +{
> +    media_insert_path(test_image);
>  }
>  
>  static void media_eject(void)
> @@ -586,6 +597,222 @@ static void test_verify(void)
>      g_assert(ret == 0);
>  }
>  
> +/*
> + * Query cur_drv->last_sect using the SAVE command (CMD_SAVE, 0x2e).
> + * Byte 8 of the 15 result bytes returned by CMD_SAVE holds last_sect.
> + */
> +static uint8_t get_lastsect(void)
> +{
> +    uint8_t res[15];
> +    int i;
> +
> +    floppy_send(CMD_SAVE);
> +    for (i = 0; i < 15; i++) {
> +        res[i] = floppy_recv();
> +    }
> +    return res[8];
> +}
> +
> +/*
> + * Attempt to set cur_drv->last_sect directly using the RESTORE command
> + * (CMD_RESTORE, 0x4e).
> + * While the 82078 datasheet describes RESTORE for restoring a previously
> + * saved state, a guest can issue raw RESTORE commands with arbitrary
> + * parameters without having issued SAVE. Parameter byte 9 is used by the
> + * controller to restore cur_drv->last_sect.
> + */
> +static void fake_lastsect(uint8_t last_sect)
> +{
> +    floppy_send(CMD_RESTORE);
> +    floppy_send(0); /* fifo[1] */
> +    floppy_send(0); /* fifo[2] */
> +    floppy_send(0); /* fifo[3]: drv0 track */
> +    floppy_send(0); /* fifo[4]: drv1 track */
> +    floppy_send(0); /* fifo[5]: drv2 track */
> +    floppy_send(0); /* fifo[6]: drv3 track */
> +    floppy_send(0); /* fifo[7]: timer0 */
> +    floppy_send(0); /* fifo[8]: timer1 */
> +    floppy_send(last_sect); /* fifo[9]: last_sect */
> +    floppy_send(0); /* fifo[10]: lock/perpendicular */
> +    floppy_send(0); /* fifo[11]: config */
> +    floppy_send(0); /* fifo[12]: precomp_trk */
> +    floppy_send(0); /* fifo[13]: pwrd */
> +    floppy_send(0); /* fifo[14] */
> +    floppy_send(0); /* fifo[15] */
> +    floppy_send(0); /* fifo[16] */
> +    floppy_send(0); /* fifo[17] */
> +}
> +
> +static void send_format_track(uint8_t drive, uint8_t head, uint8_t last_sect,
> +                              uint8_t *st0_out, uint8_t *st1_out)
> +{
> +    uint8_t st0, st1;
> +
> +    floppy_send(CMD_FORMAT_TRACK);
> +    floppy_send((head << 2) | drive);
> +    floppy_send(2);          /* 512 bytes per sector */
> +    floppy_send(last_sect);  /* sectors per track */
> +    floppy_send(0x1b);       /* GAP length */
> +    floppy_send(0x00);       /* filler byte */
> +
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    st0 = floppy_recv();
> +    st1 = floppy_recv();
> +    floppy_recv(); /* st2 */
> +    floppy_recv(); /* track */
> +    floppy_recv(); /* head */
> +    floppy_recv(); /* sect */
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    floppy_recv(); /* sz */
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +
> +    if (st0_out) {
> +        *st0_out = st0;
> +    }
> +    if (st1_out) {
> +        *st1_out = st1;
> +    }
> +}
> +
> +/*
> + * Test that guest cannot set last_sect beyond the probed media size
> + * via RESTORE or FORMAT TRACK commands (gitlab issue #3800).
> + */
> +static void test_last_sect_bounds(void)
> +{
> +    uint8_t st0, st1;
> +
> +    /* Start with 1.44 MB media inserted (last_sect = 18) */
> +    media_insert();
> +    send_seek(1);
> +    send_seek(0);
> +
> +    /* Valid last_sect values (<= 18) should succeed */
> +    fake_lastsect(18);
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(get_lastsect(), ==, 18);
> +
> +    fake_lastsect(9);
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(get_lastsect(), ==, 9);
> +
> +    /* Restoring to the default 18 */
> +    fake_lastsect(18);
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(get_lastsect(), ==, 18);
> +
> +    /* Invalid last_sect value (> 18) must fail */
> +    fake_lastsect(19);
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    st0 = floppy_recv();
> +    st1 = floppy_recv();
> +    floppy_recv(); /* st2 */
> +    floppy_recv(); /* track */
> +    floppy_recv(); /* head */
> +    floppy_recv(); /* sect */
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    floppy_recv(); /* sz */
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    /* Verify last_sect was not changed to 19 */
> +    g_assert_cmpint(get_lastsect(), ==, 18);
> +
> +    /* FORMAT TRACK with valid last_sect (18) should succeed */
> +    send_format_track(0, 0, 18, &st0, &st1);
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, 0);
> +    g_assert_cmpint(st1, ==, 0);
> +
> +    /* FORMAT TRACK with invalid last_sect (19) must fail */
> +    send_format_track(0, 0, 19, &st0, &st1);
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    /* Change media to 720 kB floppy (last_sect = 9) */
> +    media_eject();
> +    media_insert_path(test_image_720k);
> +    send_seek(1);
> +    send_seek(0);
> +
> +    /* Probed geometry now has last_sect = 9 */
> +    fake_lastsect(9);
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(get_lastsect(), ==, 9);
> +
> +    /* Values exceeding 9 (e.g. 10 or 18) must now fail */
> +    fake_lastsect(10);
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    st0 = floppy_recv();
> +    st1 = floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    floppy_recv();
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    fake_lastsect(18);
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    st0 = floppy_recv();
> +    st1 = floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    floppy_recv();
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    /* FORMAT TRACK on 720 kB floppy */
> +    send_format_track(0, 0, 9, &st0, &st1);
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, 0);
> +    g_assert_cmpint(st1, ==, 0);
> +
> +    send_format_track(0, 0, 10, &st0, &st1);
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    send_format_track(0, 0, 18, &st0, &st1);
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    /*
> +     * Change back to 1.44 MB floppy and verify last_sect = 18 is allowed
> +     * again.
> +     */
> +    media_eject();
> +    media_insert();
> +    send_seek(1);
> +    send_seek(0);
> +
> +    fake_lastsect(18);
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(get_lastsect(), ==, 18);
> +
> +    fake_lastsect(19);
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    st0 = floppy_recv();
> +    st1 = floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    floppy_recv();
> +    g_assert(get_irq(FLOPPY_IRQ));
> +    floppy_recv();
> +    g_assert(!get_irq(FLOPPY_IRQ));
> +    g_assert_cmpint(st0 & ST0_IC_MASK, ==, ST0_IC_ABNTERM);
> +    g_assert_cmpint(st1 & ST1_EC, ==, ST1_EC);
> +
> +    /* Leave drive empty */
> +    media_eject();
> +}
> +
>  /* success if no crash or abort */
>  static void fuzz_registers(void)
>  {
> @@ -661,10 +888,16 @@ int main(int argc, char **argv)
>      int fd;
>      int ret;
>  
> -    /* Create a temporary raw image */
> +    /* Create temporary raw images */
>      fd = g_file_open_tmp("qtest.XXXXXX", &test_image, NULL);
>      g_assert(fd >= 0);
> -    ret = ftruncate(fd, TEST_IMAGE_SIZE);
> +    ret = ftruncate(fd, TEST_IMAGE_1440KB);
> +    g_assert(ret == 0);
> +    close(fd);
> +
> +    fd = g_file_open_tmp("qtest720.XXXXXX", &test_image_720k, NULL);
> +    g_assert(fd >= 0);
> +    ret = ftruncate(fd, TEST_IMAGE_720KB);
>      g_assert(ret == 0);
>      close(fd);
>  
> @@ -686,6 +919,7 @@ int main(int argc, char **argv)
>      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/last_sect_bounds", test_last_sect_bounds);
>      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);
> @@ -696,6 +930,8 @@ int main(int argc, char **argv)
>      qtest_end();
>      unlink(test_image);
>      g_free(test_image);
> +    unlink(test_image_720k);
> +    g_free(test_image_720k);
>  
>      return ret;
>  }
> 
> -- 
> 2.47.3
> 
> 

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|


Reply via email to