Patchwork Fix subtle integer overflow bug in memory API

login
register
mail settings
Submitter David Gibson
Date Sept. 14, 2011, 7:02 a.m.
Message ID <1315983769-8287-1-git-send-email-david@gibson.dropbear.id.au>
Download mbox | patch
Permalink /patch/114582/
State New
Headers show

Comments

David Gibson - Sept. 14, 2011, 7:02 a.m.
It is quite common to have a MemoryRegion with size of INT64_MAX.
When processing alias regions in render_memory_region() it's quite
easy to find a case where it will construct a temporary AddrRange with
a non-zero start, and size still of INT64_MAX.  When means attempting
to compute the end of such a range as start + size will result in
signed integer overflow.

This integer overflow means that addrrange_intersects() can
incorrectly report regions as not intersecting when they do.  For
example consider the case of address ranges {0x10000000000,
0x7fffffffffffffff} and {0x10010000000, 0x10000000} where the second
is in fact included completely in the first.

This patch rearranges addrrange_intersects() to avoid the integer
overflow, correcting this behaviour.

Signed-off-by: David Gibson <david@gibson.dropbear.id.au>
---
 memory.c |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
Avi Kivity - Sept. 14, 2011, 8:23 a.m.
On 09/14/2011 10:02 AM, David Gibson wrote:
> It is quite common to have a MemoryRegion with size of INT64_MAX.
> When processing alias regions in render_memory_region() it's quite
> easy to find a case where it will construct a temporary AddrRange with
> a non-zero start, and size still of INT64_MAX.  When means attempting
> to compute the end of such a range as start + size will result in
> signed integer overflow.
>
> This integer overflow means that addrrange_intersects() can
> incorrectly report regions as not intersecting when they do.  For
> example consider the case of address ranges {0x10000000000,
> 0x7fffffffffffffff} and {0x10010000000, 0x10000000} where the second
> is in fact included completely in the first.

Good catch, thanks for digging this out.

> This patch rearranges addrrange_intersects() to avoid the integer
> overflow, correcting this behaviour.

I expect that the bad behaviour can still be triggered, for example by 
pointing aliases towards the end of very large regions.  Not that I 
expect this to occur in practice.

I think we should move towards using __int128 internally.  Is there any 
relevant host which does not support __int128?

Meanwhile, applied to memory/core, and will request a pull shortly.
Avi Kivity - Sept. 14, 2011, 8:38 a.m.
On 09/14/2011 11:23 AM, Avi Kivity wrote:
>
> I think we should move towards using __int128 internally.  Is there 
> any relevant host which does not support __int128?

Crap, it's not even supported on i386.
David Gibson - Sept. 15, 2011, 2:34 a.m.
On Wed, Sep 14, 2011 at 11:23:25AM +0300, Avi Kivity wrote:
> On 09/14/2011 10:02 AM, David Gibson wrote:
> >It is quite common to have a MemoryRegion with size of INT64_MAX.
> >When processing alias regions in render_memory_region() it's quite
> >easy to find a case where it will construct a temporary AddrRange with
> >a non-zero start, and size still of INT64_MAX.  When means attempting
> >to compute the end of such a range as start + size will result in
> >signed integer overflow.
> >
> >This integer overflow means that addrrange_intersects() can
> >incorrectly report regions as not intersecting when they do.  For
> >example consider the case of address ranges {0x10000000000,
> >0x7fffffffffffffff} and {0x10010000000, 0x10000000} where the second
> >is in fact included completely in the first.
> 
> Good catch, thanks for digging this out.
> 
> >This patch rearranges addrrange_intersects() to avoid the integer
> >overflow, correcting this behaviour.
> 
> I expect that the bad behaviour can still be triggered, for example
> by pointing aliases towards the end of very large regions.  Not that
> I expect this to occur in practice.

Well.. I'm pretty sure that particular case can no longer be
triggered.  But there may be other integer overflow bugs in this code,
though I didn't spot them at a glance.

> I think we should move towards using __int128 internally.  Is there
> any relevant host which does not support __int128?

Hrm, using a barely supported integer type I'd never previously heard
of seems sub-optimal.  With appropriate care it ought to be possible
to do all the necessary calculations correctly in 64 bits, even if we
need a few explicit "if (a + b) < a" tests in some places.


> Meanwhile, applied to memory/core, and will request a pull shortly.
>
David Gibson - Sept. 15, 2011, 2:58 a.m.
On Thu, Sep 15, 2011 at 12:34:31PM +1000, David Gibson wrote:
> On Wed, Sep 14, 2011 at 11:23:25AM +0300, Avi Kivity wrote:
> > On 09/14/2011 10:02 AM, David Gibson wrote:
> > >It is quite common to have a MemoryRegion with size of INT64_MAX.
> > >When processing alias regions in render_memory_region() it's quite
> > >easy to find a case where it will construct a temporary AddrRange with
> > >a non-zero start, and size still of INT64_MAX.  When means attempting
> > >to compute the end of such a range as start + size will result in
> > >signed integer overflow.
> > >
> > >This integer overflow means that addrrange_intersects() can
> > >incorrectly report regions as not intersecting when they do.  For
> > >example consider the case of address ranges {0x10000000000,
> > >0x7fffffffffffffff} and {0x10010000000, 0x10000000} where the second
> > >is in fact included completely in the first.
> > 
> > Good catch, thanks for digging this out.
> > 
> > >This patch rearranges addrrange_intersects() to avoid the integer
> > >overflow, correcting this behaviour.
> > 
> > I expect that the bad behaviour can still be triggered, for example
> > by pointing aliases towards the end of very large regions.  Not that
> > I expect this to occur in practice.
> 
> Well.. I'm pretty sure that particular case can no longer be
> triggered.  But there may be other integer overflow bugs in this code,
> though I didn't spot them at a glance.

Actually, there definitely are.  addrrange_end() should probably be
redefined as the inclusive end, rather than the exclusive end, and
computed as:

	(start + size - 1) < start ? INT64_MAX : start + size - 1

Hrm, except that that doesn't handle zero size ranges.  And gets a bit
weird for INT64_MAX sized regions.

Hrm, yeah, these really should be changed to use unsigneds.
Avi Kivity - Sept. 15, 2011, 7:28 a.m.
On 09/15/2011 05:58 AM, David Gibson wrote:
> >  Well.. I'm pretty sure that particular case can no longer be
> >  triggered.  But there may be other integer overflow bugs in this code,
> >  though I didn't spot them at a glance.
>
> Actually, there definitely are.  addrrange_end() should probably be
> redefined as the inclusive end, rather than the exclusive end, and
> computed as:
>
> 	(start + size - 1)<  start ? INT64_MAX : start + size - 1
>
> Hrm, except that that doesn't handle zero size ranges.  And gets a bit
> weird for INT64_MAX sized regions.
>
> Hrm, yeah, these really should be changed to use unsigneds.
>

Unsigneds don't work because we sometimes subtract values and get into 
the negative area, due to aliases.

We're using values that are close to the end of the 63 or 64 bit range 
and playing with them.  Overflow is always close and we'll always be 
able to find a way to defeat the code (even if it doesn't occur in 
practice).  I'm tempted to declare that the memory API supports only 60 
bits of address space (and s/INT64_MAX/MEMORY_REGION_SIZE_MAX/), that 
gives us a few levels of wild offsetting before we get confused.
Paolo Bonzini - Sept. 15, 2011, 7:38 a.m.
On 09/15/2011 04:34 AM, David Gibson wrote:
> explicit "if (a + b)<  a" tests in some places.

Please wrap these in a macro, since the addition has to be done in an 
unsigned type.  Otherwise overflow is undefined.

Paolo
Avi Kivity - Sept. 15, 2011, 7:43 a.m.
On 09/15/2011 10:38 AM, Paolo Bonzini wrote:
> On 09/15/2011 04:34 AM, David Gibson wrote:
>> explicit "if (a + b)<  a" tests in some places.
>
> Please wrap these in a macro, since the addition has to be done in an 
> unsigned type.  Otherwise overflow is undefined.

It doesn't help if the overflow is defined but produces an incorrect result.

The fact is we need a 64+N bit datatype, where N is the nesting level of 
MemoryRegions (including aliases).  Each nesting level can overflow a 
bit.  Right now the only viable options seems to be #define 64 60, but 
I'll be happy to consider others.
David Gibson - Sept. 16, 2011, 3:16 a.m.
On Thu, Sep 15, 2011 at 10:28:42AM +0300, Avi Kivity wrote:
> On 09/15/2011 05:58 AM, David Gibson wrote:
> >>  Well.. I'm pretty sure that particular case can no longer be
> >>  triggered.  But there may be other integer overflow bugs in this code,
> >>  though I didn't spot them at a glance.
> >
> >Actually, there definitely are.  addrrange_end() should probably be
> >redefined as the inclusive end, rather than the exclusive end, and
> >computed as:
> >
> >	(start + size - 1)<  start ? INT64_MAX : start + size - 1
> >
> >Hrm, except that that doesn't handle zero size ranges.  And gets a bit
> >weird for INT64_MAX sized regions.
> >
> >Hrm, yeah, these really should be changed to use unsigneds.
> >
> 
> Unsigneds don't work because we sometimes subtract values and get
> into the negative area, due to aliases.

I realise that, but I'm fairly confident the order of calculations can
be adjusted to cope with those cases, just as we can cope with the
overflow cases with care.

Remember that because unsigned overflow is defined, and we can use
that in some cases.


> We're using values that are close to the end of the 63 or 64 bit
> range and playing with them.  Overflow is always close and we'll
> always be able to find a way to defeat the code (even if it doesn't
> occur in practice).  I'm tempted to declare that the memory API
> supports only 60 bits of address space (and
> s/INT64_MAX/MEMORY_REGION_SIZE_MAX/), that gives us a few levels of
> wild offsetting before we get confused.

There is something to be said for that.  Although many systems have
theoretical 64-bit addressing, on powerpc at least, the biggest
actually implemented physical address bus I'm aware of is under 50
bits.

Of course, there could be some highly sparse intermediate bus
addressing schemes which screw that up.

Patch

diff --git a/memory.c b/memory.c
index 57f0fa4..101b67c 100644
--- a/memory.c
+++ b/memory.c
@@ -55,8 +55,8 @@  static AddrRange addrrange_shift(AddrRange range, int64_t delta)
 
 static bool addrrange_intersects(AddrRange r1, AddrRange r2)
 {
-    return (r1.start >= r2.start && r1.start < r2.start + r2.size)
-        || (r2.start >= r1.start && r2.start < r1.start + r1.size);
+    return (r1.start >= r2.start && (r1.start - r2.start) < r2.size)
+        || (r2.start >= r1.start && (r2.start - r1.start) < r1.size);
 }
 
 static AddrRange addrrange_intersection(AddrRange r1, AddrRange r2)