aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Hoppenbrouwers <david@salt-inc.org>2021-08-27 17:23:25 +0200
committerAlistair Francis <alistair.francis@wdc.com>2021-09-01 11:59:12 +1000
commit4dc06bb8167fb18b8eb7e40762a94dcc36101047 (patch)
tree77bcca69ad88f3771f7b9886fcc4e2e7a6533cc3
parent33fcedfac8af376afad478f029cebb9ddb09f74a (diff)
downloadqemu-4dc06bb8167fb18b8eb7e40762a94dcc36101047.zip
qemu-4dc06bb8167fb18b8eb7e40762a94dcc36101047.tar.gz
qemu-4dc06bb8167fb18b8eb7e40762a94dcc36101047.tar.bz2
hw/intc/sifive_clint: Fix muldiv64 overflow in sifive_clint_write_timecmp()
`muldiv64` would overflow in cases where the final 96-bit value does not fit in a `uint64_t`. This would result in small values that cause an interrupt to be triggered much sooner than intended. The overflow can be detected in most cases by checking if the new value is smaller than the previous value. If the final result is larger than `diff` it is either correct or it doesn't matter as it is effectively infinite anyways. `next` is an `uint64_t` value, but `timer_mod` takes an `int64_t`. This resulted in high values such as `UINT64_MAX` being converted to `-1`, which caused an immediate timer interrupt. By limiting `next` to `INT64_MAX` no overflow will happen while the timer will still be effectively set to "infinitely" far in the future. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/493 Signed-off-by: David Hoppenbrouwers <david@salt-inc.org> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Message-id: 20210827152324.5201-1-david@salt-inc.org Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
-rw-r--r--hw/intc/sifive_clint.c25
1 files changed, 23 insertions, 2 deletions
diff --git a/hw/intc/sifive_clint.c b/hw/intc/sifive_clint.c
index 0f41e5e..99c870c 100644
--- a/hw/intc/sifive_clint.c
+++ b/hw/intc/sifive_clint.c
@@ -59,8 +59,29 @@ static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value,
riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(0));
diff = cpu->env.timecmp - rtc_r;
/* back to ns (note args switched in muldiv64) */
- next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
- muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
+ uint64_t ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
+
+ /*
+ * check if ns_diff overflowed and check if the addition would potentially
+ * overflow
+ */
+ if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
+ ns_diff > INT64_MAX) {
+ next = INT64_MAX;
+ } else {
+ /*
+ * as it is very unlikely qemu_clock_get_ns will return a value
+ * greater than INT64_MAX, no additional check is needed for an
+ * unsigned integer overflow.
+ */
+ next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
+ /*
+ * if ns_diff is INT64_MAX next may still be outside the range
+ * of a signed integer.
+ */
+ next = MIN(next, INT64_MAX);
+ }
+
timer_mod(cpu->env.timer, next);
}