Make page_find() return 0 for too-large addresses
PATCH 1/1 Make page_find() return 0 for too-large addresses
On some cases, such as under KVM, tb_invalidate_phys_page_range() may be called for large addresses, when qemu is configured to more than 4GB of RAM.
On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without CONFIG_USER_ONLY.
Below is a sample backtrace of this happening, under KVM. 'start' on tb_invalidate_phys_page_range() is 0x132b1c812, beyond 2^32, making page_find() return a bogus pointer.
#0 tb_invalidate_phys_page_range (start=5145479186, end=5145479246, is_cpu_write_access=0) at /usr/src/debug/kvm-74/qemu/exec.c:877
#1 0x000000000049cd28 in cpu_physical_memory_rw (addr=5682350098, buf=0x7389dd10 "RT", len=60, is_write=1) at /usr/src/debug/kvm-74/qemu/exec.c:2818
#2 0x0000000000428f54 in rtl8139_do_receive (opaque=0x1f472240, buf=<value optimized out>, size=60, do_interrupt=1) at ../cpu-all.h:929
#3 0x000000000040717b in qemu_send_packet (vc1=0x1e54f480, buf=0x7389ddc0 "RT", size=42) at /usr/src/debug/kvm-74/qemu/vl.c:4146
#4 0x0000000000480990 in slirp_input (pkt=0x1f4a9010 "������RT", pkt_len=42) at slirp/slirp.c:597
#5 0x000000000040717b in qemu_send_packet (vc1=0x1f475d70, buf=0x1f4a9010 "������RT", size=42) at /usr/src/debug/kvm-74/qemu/vl.c:4146
#6 0x00000000004295bd in rtl8139_io_writeb (opaque=0x1f472240, addr=<value optimized out>, val=<value optimized out>) at /usr/src/debug/kvm-74/qemu/hw/rtl8139.c:2299
#7 0x000000000049cb62 in cpu_physical_memory_rw (addr=4060090585, buf=0x2aaadcf11028 <Address 0x2aaadcf11028 out of bounds>, len=1, is_write=1)
at /usr/src/debug/kvm-74/qemu/exec.c:2807
#8 0x00000000004eebf8 in kvm_mmio_write (opaque=<value optimized out>, addr=5145479186, data=0x132b1c84e <Address 0x132b1c84e out of bounds>, len=707232)
at /usr/src/debug/kvm-74/qemu/qemu-kvm.c:689
#9 0x0000000000515646 in handle_mmio (kvm=0x1e54e040, kvm_run=0x2aaadcf11000) at libkvm.c:849
#10 0x0000000000515b57 in kvm_run (kvm=0x1e54e040, vcpu=0) at libkvm.c:975The following patch makes page_find() safe for >32-bit addresses, by just returning 0 if the address is too large.
The translation block handling code on exec.c still doesn't support addresses >32-bit if CONFIG_USER_ONLY is not defined (KVM qemu currently crashes immediately when using -no-kvm with more than 4 GB of memory), but this is another issue.
---
exec.c +4 -1
@@ -317,8 +317,11 @@ static inline PageDesc *page_find_alloc(static inline PageDesc *page_find (target_ulong index) {PageDesc *p; +target_ulong l1_index = (index >>L2_BITS ); +if (l1_index >L1_SIZE ) +return 0 ; - p = l1_map[index >>L2_BITS ]; + p = l1_map[l1_index];if (!p)return 0 ;return p + (index & (L2_SIZE -1 ));
Signature · 1 line
Eduardo
Quoted 8 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.
Did you have kqemu enabled in the build? l1_map should be sufficiently large when you have kqemu disabled.
Regards,
Anthony Liguori
Quoted 5 lines · Anthony Liguori
Quoted 4 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.
Quoted 7 lines
Quoted 4 lines
On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.Did you have kqemu enabled in the build?
l1_mapshould be sufficiently large when you have kqemu disabled.
KVM uses './configure --disable-kqemu', but exec.c has this:
#if defined(CONFIG_USER_ONLY) && defined(TARGET_VIRT_ADDR_SPACE_BITS) /* XXX: this is a temporary hack for alpha target.
* In the future, this is to be replaced by a multi-level table
* to actually be able to handle the complete 64 bits address space.
*/
#define L1_BITS (TARGET_VIRT_ADDR_SPACE_BITS - L2_BITS - TARGET_PAGE_BITS) #else
#define L1_BITS (32 - L2_BITS - TARGET_PAGE_BITS)
#endif
And CONFIG_USER_ONLY is not defined, making l1_map work only for 32-bit addresses.
BTW, I've just noticed page_find_alloc() has this:
#if TARGET_LONG_BITS > 32
/* Host memory outside guest VM. For 32-bit targets we have already
excluded high addresses. */
if (index > ((target_ulong)L2_SIZE * L1_SIZE))
return NULL;#endif
So, we can just use a similar check on page_find().
Side note: the check on the kvm git tree looks broken: it is checking for (L2_SIZE * L1_SIZE * TARGET_PAGE_SIZE) instead.
Signature · 1 line
Eduardo
Quoted 10 lines · Eduardo Habkost
Quoted 9 lines · Anthony Liguori
Quoted 8 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.
<snip>
Quoted 11 lines
BTW, I've just noticed
page_find_alloc()has this:#if TARGET_LONG_BITS > 32 /* Host memory outside guest VM. For 32-bit targets we have already excluded high addresses. */ if (index > ((target_ulong)L2_SIZE * L1_SIZE)) return NULL;#endif
So, we can just use a similar check on
page_find().
New patch, reusing the range check from page_find_alloc() on page_find(). Untested.
---
exec.c +16 -6
@@ -279,17 +279,24 @@ static void page_init(void)#endif } -static inline PageDesc *page_find_alloc (target_ulong index) +static inline PageDesc **page_l1_map (target_ulong index) { -PageDesc **lp, *p; -#if TARGET_LONG_BITS >32 /* Host memory outside guest VM. For 32-bit targets we have already excluded high addresses. */ if (index > ((target_ulong )L2_SIZE *L1_SIZE ))return NULL ;#endif - lp = &l1_map[index >>L2_BITS ]; +return &l1_map[index >>L2_BITS ]; +} + +static inline PageDesc *page_find_alloc (target_ulong index) +{ +PageDesc **lp, *p; + lp =page_l1_map (index); +if (!lp) +return NULL ; + p = *lp;if (!p) {/* allocate if not found */
@@ -316,9 +323,12 @@ static inline PageDesc *page_find_alloc(static inline PageDesc *page_find (target_ulong index) { -PageDesc *p; +PageDesc **lp, *p; + lp =page_l1_map (index); +if (!lp) +return NULL ; - p = l1_map[index >>L2_BITS ]; + p = *lp;if (!p)return 0 ;return p + (index & (L2_SIZE -1 ));
Signature · 1 line
Eduardo
Quoted 10 lines · Eduardo Habkost
Quoted 8 lines · Eduardo Habkost
Quoted 6 lines · Anthony Liguori
Quoted 4 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.
Quoted 7 lines
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines
On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.<snip>
So... are you building with kqemu enabled? If so, TARGET_PHYS_ADDR_SPACE_BITS will be 32. So I don't think this sort of work-around is correct. A better solution would be to validate ram_size against TARGET_PHYS_ADDR_SPACE_BITS.
To put it another way, if you have kqemu enabled (at build time), you cannot use > ~4GB of memory for the guest.
Regards,
Anthony Liguori
Quoted 11 lines · Anthony Liguori
Quoted 10 lines · Eduardo Habkost
Quoted 8 lines · Eduardo Habkost
Quoted 6 lines · Anthony Liguori
Quoted 4 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.
Quoted 8 lines
Quoted 6 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.<snip>
So... are you building with kqemu enabled?
No, as I've stated in the reply I've sent before the patch.
Quoted 4 lines
If so,
TARGET_PHYS_ADDR_SPACE_BITSwill be 32. So I don't think this sort of work-around is correct. A better solution would be to validateram_sizeagainstTARGET_PHYS_ADDR_SPACE_BITS.
This is a good idea (maybe it is already checked?), but it is not the case I am dealing with.
Quoted 3 lines
To put it another way, if you have kqemu enabled (at build time), you cannot use > ~4GB of memory for the guest.
Right.
Signature · 1 line
Eduardo
Quoted 18 lines · Anthony Liguori
Quoted 10 lines · Eduardo Habkost
Quoted 8 lines · Eduardo Habkost
Quoted 6 lines · Anthony Liguori
Quoted 4 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.Quoted 6 lines
Quoted 4 lines
Quoted 4 lines
Quoted 4 lines
On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.<snip>
Quoted 7 lines
So... are you building with kqemu enabled? If so,
TARGET_PHYS_ADDR_SPACE_BITSwill be 32. So I don't think this sort of work-around is correct. A better solution would be to validateram_sizeagainstTARGET_PHYS_ADDR_SPACE_BITS.To put it another way, if you have kqemu enabled (at build time), you cannot use > ~4GB of memory for the guest.
I can be very wrong here, but page_find() does not seem to limit our ability to run guests if > 4GB, as it is only concerned about the code translation layer. Differently from phys_page_find(), that does manage physical memory, and even presented problems about it in the past.
Quoted 5 lines
Regards,
Anthony Liguori
Quoted 26 lines · Eduardo Habkost
Quoted 13 lines · Eduardo Habkost
Quoted 11 lines · Anthony Liguori
Quoted 9 lines · Eduardo Habkost
On some cases, such as under KVM,
tb_invalidate_phys_page_range()may be called for large addresses, when qemu is configured to more than 4GB of RAM.On these cases, qemu was crashing because it was using an index too large for l1_map[], that supports only 32-bit addresses when compiling without
CONFIG_USER_ONLY.<snip>
Quoted 9 lines
BTW, I've just noticed
page_find_alloc()has this:#if TARGET_LONG_BITS > 32 /* Host memory outside guest VM. For 32-bit targets we have already excluded high addresses. */ if (index > ((target_ulong)L2_SIZE * L1_SIZE)) return NULL;#endif
Quoted 6 lines
Quoted 2 lines
So, we can just use a similar check on
page_find().New patch, reusing the range check from
page_find_alloc()onpage_find(). Untested.
Have you tested this patch yet? I like to avoid being the first one to test something when it's not my code :-)
Regards,
Anthony Liguori
Quoted 53 lines
Signed-off-by Eduardo Habkost <[email protected]>---
Index:qemu/exec.c
===================================================================exec.c +16
-6@@ -279,17 +279,24 @@ static void page_init(void)#endif } -static inline PageDesc *page_find_alloc (target_ulong index) +static inline PageDesc **page_l1_map (target_ulong index) { -PageDesc **lp, *p; -#if TARGET_LONG_BITS >32 /* Host memory outside guest VM. For 32-bit targets we have already excluded high addresses. */ if (index > ((target_ulong )L2_SIZE *L1_SIZE ))return NULL ;#endif - lp = &l1_map[index >>L2_BITS ]; +return &l1_map[index >>L2_BITS ]; +} + +static inline PageDesc *page_find_alloc (target_ulong index) +{ +PageDesc **lp, *p; + lp =page_l1_map (index); +if (!lp) +return NULL ; + p = *lp;if (!p) {/* allocate if not found */ @@ -316,9 +323,12 @@ static inline PageDesc *page_find_alloc(static inline PageDesc *page_find (target_ulong index) { -PageDesc *p; +PageDesc **lp, *p; + lp =page_l1_map (index); +if (!lp) +return NULL ; - p = l1_map[index >>L2_BITS ]; + p = *lp;if (!p)return 0 ;return p + (index & (L2_SIZE -1 ));
Quoted 1 line · Anthony Liguori
Eduardo Habkost wrote:
<snip>
Quoted 7 lines
Quoted 4 lines
New patch, reusing the range check from
page_find_alloc()onpage_find(). Untested.Have you tested this patch yet? I like to avoid being the first one to test something when it's not my code :-)
I've tested it now, both with KVM enabled and with KVM disabled. My machine didn't explode yet, so it seems to be fine. :)
Quoted 57 lines
Regards,
Anthony Liguori
Quoted 50 lines
Signed-off-by Eduardo Habkost <[email protected]>---
Index:qemu/exec.c
===================================================================exec.c +16
-4@@ -279,17 +279,24 @@ static void page_init(void)#endif } -static inline PageDesc *page_find_alloc(target_ulong index) +static inline PageDesc **page_l1_map(target_ulong index) { -PageDesc **lp, *p; -#if TARGET_LONG_BITS >32 /* Host memory outside guest VM. For 32-bit targets we have already excluded high addresses. */ if (index > ((target_ulong )L2_SIZE *L1_SIZE ))return NULL ;#endif - lp = &l1_map[index >>L2_BITS ]; +return &l1_map[index >>L2_BITS ]; +} + +static inline PageDesc *page_find_alloc (target_ulong index) +{ +PageDesc **lp, *p; + lp =page_l1_map (index); +if (!lp) +return NULL ; + p = *lp;if (!p) {/* allocate if not found */ @@ -316,9 +323,12 @@ static inline PageDesc *page_find_alloc(static inline PageDesc *page_find (target_ulong index) { -PageDesc *p; +PageDesc **lp, *p; + lp =page_l1_map (index); +if (!lp) +return NULL ; - p = l1_map[index >>L2_BITS ]; + p = *lp;if (!p)return 0 ;return p + (index & (L2_SIZE -1 ));
Signature · 1 line
Eduardo
Anthony Liguori
AppliedQuoted 10 lines · Eduardo Habkost
Quoted 2 lines · Anthony Liguori
Eduardo Habkost wrote:
<snip>
Quoted 4 lines
Quoted 4 lines
New patch, reusing the range check from
page_find_alloc()onpage_find(). Untested.
Quoted 6 lines
Quoted 2 lines
Have you tested this patch yet? I like to avoid being the first one to test something when it's not my code :-)
I've tested it now, both with KVM enabled and with KVM disabled. My machine didn't explode yet, so it seems to be fine. :)
Applied. Thanks.
Regards,
Anthony Liguori