[Qemu-devel] [PATCH] hw/riscv/sifive_clint.c: avoid integer overflow in timecmp write

Fabien Chouteau posted 1 patch 5 years, 2 months ago
Test docker-clang@ubuntu passed
Test docker-mingw@fedora passed
Test asan passed
Test checkpatch passed
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20190130185242.12490-1-chouteau@adacore.com
Maintainers: Bastian Koppelmann <kbastian@mail.uni-paderborn.de>, Palmer Dabbelt <palmer@sifive.com>, Sagar Karandikar <sagark@eecs.berkeley.edu>, Michael Clark <mjc@sifive.com>, Alistair Francis <Alistair.Francis@wdc.com>
hw/riscv/sifive_clint.c | 31 ++++++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 5 deletions(-)
[Qemu-devel] [PATCH] hw/riscv/sifive_clint.c: avoid integer overflow in timecmp write
Posted by Fabien Chouteau 5 years, 2 months ago
Writing a high value in timecmp leads to an integer overflow. This patch
modifies the code to detect such case, and use the maximum integer value
as the next trigger for the timer.

Signed-off-by: Fabien Chouteau <chouteau@adacore.com>
---
 hw/riscv/sifive_clint.c | 31 ++++++++++++++++++++++++++-----
 1 file changed, 26 insertions(+), 5 deletions(-)

diff --git a/hw/riscv/sifive_clint.c b/hw/riscv/sifive_clint.c
index d4c159e937..1ca1f8c75e 100644
--- a/hw/riscv/sifive_clint.c
+++ b/hw/riscv/sifive_clint.c
@@ -38,8 +38,10 @@ static uint64_t cpu_riscv_read_rtc(void)
  */
 static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value)
 {
-    uint64_t next;
     uint64_t diff;
+    uint64_t lapse_ns;
+    uint64_t clock_ns;
+    int64_t  next_ns;
 
     uint64_t rtc_r = cpu_riscv_read_rtc();
 
@@ -54,10 +56,29 @@ static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value)
     /* otherwise, set up the future timer interrupt */
     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, SIFIVE_CLINT_TIMEBASE_FREQ);
-    timer_mod(cpu->env.timer, next);
+
+    /*
+     * How many nanoseconds until the next trigger (note args switched in
+     * muldiv64)
+     */
+    lapse_ns = muldiv64(diff,
+                        NANOSECONDS_PER_SECOND,
+                        SIFIVE_CLINT_TIMEBASE_FREQ);
+
+    /* Current time in nanoseconds */
+    clock_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+
+    if ((G_MAXINT64 - clock_ns) <= lapse_ns) {
+        /*
+         * clock + lapse would overflow on 64bit. The highest 64bit value is
+         * used as the next trigger time.
+         */
+        next_ns = G_MAXINT64;
+    } else {
+        next_ns = clock_ns + lapse_ns;
+    }
+
+    timer_mod(cpu->env.timer, next_ns);
 }
 
 /*
-- 
2.17.1


Re: [Qemu-devel] [PATCH] hw/riscv/sifive_clint.c: avoid integer overflow in timecmp write
Posted by Alistair Francis 5 years, 2 months ago
On Wed, Jan 30, 2019 at 10:53 AM Fabien Chouteau <chouteau@adacore.com> wrote:
>
> Writing a high value in timecmp leads to an integer overflow. This patch
> modifies the code to detect such case, and use the maximum integer value
> as the next trigger for the timer.
>
> Signed-off-by: Fabien Chouteau <chouteau@adacore.com>
> ---
>  hw/riscv/sifive_clint.c | 31 ++++++++++++++++++++++++++-----
>  1 file changed, 26 insertions(+), 5 deletions(-)
>
> diff --git a/hw/riscv/sifive_clint.c b/hw/riscv/sifive_clint.c
> index d4c159e937..1ca1f8c75e 100644
> --- a/hw/riscv/sifive_clint.c
> +++ b/hw/riscv/sifive_clint.c
> @@ -38,8 +38,10 @@ static uint64_t cpu_riscv_read_rtc(void)
>   */
>  static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value)
>  {
> -    uint64_t next;
>      uint64_t diff;
> +    uint64_t lapse_ns;
> +    uint64_t clock_ns;
> +    int64_t  next_ns;
>
>      uint64_t rtc_r = cpu_riscv_read_rtc();
>
> @@ -54,10 +56,29 @@ static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value)
>      /* otherwise, set up the future timer interrupt */
>      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, SIFIVE_CLINT_TIMEBASE_FREQ);
> -    timer_mod(cpu->env.timer, next);
> +
> +    /*
> +     * How many nanoseconds until the next trigger (note args switched in
> +     * muldiv64)
> +     */
> +    lapse_ns = muldiv64(diff,
> +                        NANOSECONDS_PER_SECOND,
> +                        SIFIVE_CLINT_TIMEBASE_FREQ);
> +
> +    /* Current time in nanoseconds */
> +    clock_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> +
> +    if ((G_MAXINT64 - clock_ns) <= lapse_ns) {
> +        /*
> +         * clock + lapse would overflow on 64bit. The highest 64bit value is
> +         * used as the next trigger time.
> +         */
> +        next_ns = G_MAXINT64;
> +    } else {
> +        next_ns = clock_ns + lapse_ns;
> +    }

Can you describe what this fixes?

Won't an overflow be ok as we then just wrap around anyway? I guess
there is a problem if we want a value so large that we wrap around
past our current time though.

Alistair

> +
> +    timer_mod(cpu->env.timer, next_ns);
>  }
>
>  /*
> --
> 2.17.1
>
>

Re: [Qemu-devel] [PATCH] hw/riscv/sifive_clint.c: avoid integer overflow in timecmp write
Posted by Fabien Chouteau 5 years, 2 months ago
Hello Alistair,

On 07/02/2019 01:42, Alistair Francis wrote:> 
> Can you describe what this fixes?
> 

I encountered this problem when I tried to write 0xffffffffffffffff in timecmp.

With the integer overflow in QEMU, writing this value means that the QEMU timer
will be set in the past.

> Won't an overflow be ok as we then just wrap around anyway? I guess
> there is a problem if we want a value so large that we wrap around
> past our current time though.
> 

The overflow was in the computation of the value `next_ns`. It is used to set
the QEMU timer:

timer_mod(cpu->env.timer, next_ns);

A negative `next_ns` -because of the overflow- means that the timer
triggers immediately instead of far in the future.

Regards,

Re: [Qemu-devel] [PATCH] hw/riscv/sifive_clint.c: avoid integer overflow in timecmp write
Posted by Alistair Francis 5 years, 2 months ago
On Thu, Feb 7, 2019 at 2:08 AM Fabien Chouteau <chouteau@adacore.com> wrote:
>
> Hello Alistair,
>
> On 07/02/2019 01:42, Alistair Francis wrote:>
> > Can you describe what this fixes?
> >
>
> I encountered this problem when I tried to write 0xffffffffffffffff in timecmp.
>
> With the integer overflow in QEMU, writing this value means that the QEMU timer
> will be set in the past.
>
> > Won't an overflow be ok as we then just wrap around anyway? I guess
> > there is a problem if we want a value so large that we wrap around
> > past our current time though.
> >
>
> The overflow was in the computation of the value `next_ns`. It is used to set
> the QEMU timer:
>
> timer_mod(cpu->env.timer, next_ns);
>
> A negative `next_ns` -because of the overflow- means that the timer
> triggers immediately instead of far in the future.

Ah you are right here. The expired time of the timer will be set to
zero (it looks like QEMU ensures it can't be negative) but then it
detects that as being in the past and will trigger the timer event as
timer_expired_ns() will return true.

Reviewed-by: Alistair Francis <alistair.francis@wdc.com>

Alistair

>
> Regards,

Re: [Qemu-devel] [PATCH] hw/riscv/sifive_clint.c: avoid integer overflow in timecmp write
Posted by Palmer Dabbelt 5 years, 2 months ago
On Fri, 08 Feb 2019 10:41:17 PST (-0800), alistair23@gmail.com wrote:
> On Thu, Feb 7, 2019 at 2:08 AM Fabien Chouteau <chouteau@adacore.com> wrote:
>>
>> Hello Alistair,
>>
>> On 07/02/2019 01:42, Alistair Francis wrote:>
>> > Can you describe what this fixes?
>> >
>>
>> I encountered this problem when I tried to write 0xffffffffffffffff in timecmp.
>>
>> With the integer overflow in QEMU, writing this value means that the QEMU timer
>> will be set in the past.
>>
>> > Won't an overflow be ok as we then just wrap around anyway? I guess
>> > there is a problem if we want a value so large that we wrap around
>> > past our current time though.
>> >
>>
>> The overflow was in the computation of the value `next_ns`. It is used to set
>> the QEMU timer:
>>
>> timer_mod(cpu->env.timer, next_ns);
>>
>> A negative `next_ns` -because of the overflow- means that the timer
>> triggers immediately instead of far in the future.
>
> Ah you are right here. The expired time of the timer will be set to
> zero (it looks like QEMU ensures it can't be negative) but then it
> detects that as being in the past and will trigger the timer event as
> timer_expired_ns() will return true.
>
> Reviewed-by: Alistair Francis <alistair.francis@wdc.com>

Thanks.  I'll target this for the next PR.