Skip to content

Commit

Permalink
virtio_balloon: fix handling of PAGE_SIZE != 4k
Browse files Browse the repository at this point in the history
As reported by David Gibson, current code handles PAGE_SIZE != 4k
completely wrong which can lead to guest memory corruption errors:

- page_to_balloon_pfn is wrong: e.g. on system with 64K page size
 it gives the same pfn value for 16 different pages.

- we also need to convert back to linux pfns when we free.

- for each linux page we need to tell host about multiple balloon
  pages, but code only adds one pfn to the array.

This patch fixes all that, tested with a 64k ppc64 kernel.

Reported-by: David Gibson <david@gibson.dropbear.id.au>
Tested-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
  • Loading branch information
Michael S. Tsirkin committed Apr 15, 2012
1 parent 1a87228 commit 3ccc937
Showing 1 changed file with 41 additions and 10 deletions.
51 changes: 41 additions & 10 deletions drivers/virtio/virtio_balloon.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@
#include <linux/slab.h>
#include <linux/module.h>

/*
* Balloon device works in 4K page units. So each page is pointed to by
* multiple balloon pages. All memory counters in this driver are in balloon
* page units.
*/
#define VIRTIO_BALLOON_PAGES_PER_PAGE (PAGE_SIZE >> VIRTIO_BALLOON_PFN_SHIFT)

struct virtio_balloon
{
struct virtio_device *vdev;
Expand All @@ -42,8 +49,13 @@ struct virtio_balloon
/* Waiting for host to ack the pages we released. */
struct completion acked;

/* The pages we've told the Host we're not using. */
/* Number of balloon pages we've told the Host we're not using. */
unsigned int num_pages;
/*
* The pages we've told the Host we're not using.
* Each page on this list adds VIRTIO_BALLOON_PAGES_PER_PAGE
* to num_pages above.
*/
struct list_head pages;

/* The array of pfns we tell the Host about. */
Expand All @@ -66,7 +78,13 @@ static u32 page_to_balloon_pfn(struct page *page)

BUILD_BUG_ON(PAGE_SHIFT < VIRTIO_BALLOON_PFN_SHIFT);
/* Convert pfn from Linux page size to balloon page size. */
return pfn >> (PAGE_SHIFT - VIRTIO_BALLOON_PFN_SHIFT);
return pfn * VIRTIO_BALLOON_PAGES_PER_PAGE;
}

static struct page *balloon_pfn_to_page(u32 pfn)
{
BUG_ON(pfn % VIRTIO_BALLOON_PAGES_PER_PAGE);
return pfn_to_page(pfn / VIRTIO_BALLOON_PAGES_PER_PAGE);
}

static void balloon_ack(struct virtqueue *vq)
Expand Down Expand Up @@ -96,12 +114,23 @@ static void tell_host(struct virtio_balloon *vb, struct virtqueue *vq)
wait_for_completion(&vb->acked);
}

static void set_page_pfns(u32 pfns[], struct page *page)
{
unsigned int i;

/* Set balloon pfns pointing at this page.
* Note that the first pfn points at start of the page. */
for (i = 0; i < VIRTIO_BALLOON_PAGES_PER_PAGE; i++)
pfns[i] = page_to_balloon_pfn(page) + i;
}

static void fill_balloon(struct virtio_balloon *vb, size_t num)
{
/* We can only do one array worth at a time. */
num = min(num, ARRAY_SIZE(vb->pfns));

for (vb->num_pfns = 0; vb->num_pfns < num; vb->num_pfns++) {
for (vb->num_pfns = 0; vb->num_pfns < num;
vb->num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE) {
struct page *page = alloc_page(GFP_HIGHUSER | __GFP_NORETRY |
__GFP_NOMEMALLOC | __GFP_NOWARN);
if (!page) {
Expand All @@ -113,9 +142,9 @@ static void fill_balloon(struct virtio_balloon *vb, size_t num)
msleep(200);
break;
}
vb->pfns[vb->num_pfns] = page_to_balloon_pfn(page);
set_page_pfns(vb->pfns + vb->num_pfns, page);
vb->num_pages += VIRTIO_BALLOON_PAGES_PER_PAGE;
totalram_pages--;
vb->num_pages++;
list_add(&page->lru, &vb->pages);
}

Expand All @@ -130,8 +159,9 @@ static void release_pages_by_pfn(const u32 pfns[], unsigned int num)
{
unsigned int i;

for (i = 0; i < num; i++) {
__free_page(pfn_to_page(pfns[i]));
/* Find pfns pointing at start of each page, get pages and free them. */
for (i = 0; i < num; i += VIRTIO_BALLOON_PAGES_PER_PAGE) {
__free_page(balloon_pfn_to_page(pfns[i]));
totalram_pages++;
}
}
Expand All @@ -143,11 +173,12 @@ static void leak_balloon(struct virtio_balloon *vb, size_t num)
/* We can only do one array worth at a time. */
num = min(num, ARRAY_SIZE(vb->pfns));

for (vb->num_pfns = 0; vb->num_pfns < num; vb->num_pfns++) {
for (vb->num_pfns = 0; vb->num_pfns < num;
vb->num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE) {
page = list_first_entry(&vb->pages, struct page, lru);
list_del(&page->lru);
vb->pfns[vb->num_pfns] = page_to_balloon_pfn(page);
vb->num_pages--;
set_page_pfns(vb->pfns + vb->num_pfns, page);
vb->num_pages -= VIRTIO_BALLOON_PAGES_PER_PAGE;
}

/*
Expand Down

0 comments on commit 3ccc937

Please sign in to comment.