diff options
author | Michael S. Tsirkin <mst@redhat.com> | 2017-10-13 09:11:48 -0400 |
---|---|---|
committer | Michael S. Tsirkin <mst@redhat.com> | 2017-11-14 16:57:38 -0500 |
commit | c7cdff0e864713a089d7cb3a2b1136ba9a54881a (patch) | |
tree | 10844edfefa9cf4b619bbbd5722404acb858eefd | |
parent | bebc6082da0a9f5d47a1ea2edc099bf671058bd4 (diff) |
virtio_balloon: fix deadlock on OOM
fill_balloon doing memory allocations under balloon_lock
can cause a deadlock when leak_balloon is called from
virtballoon_oom_notify and tries to take same lock.
To fix, split page allocation and enqueue and do allocations outside the lock.
Here's a detailed analysis of the deadlock by Tetsuo Handa:
In leak_balloon(), mutex_lock(&vb->balloon_lock) is called in order to
serialize against fill_balloon(). But in fill_balloon(),
alloc_page(GFP_HIGHUSER[_MOVABLE] | __GFP_NOMEMALLOC | __GFP_NORETRY) is
called with vb->balloon_lock mutex held. Since GFP_HIGHUSER[_MOVABLE]
implies __GFP_DIRECT_RECLAIM | __GFP_IO | __GFP_FS, despite __GFP_NORETRY
is specified, this allocation attempt might indirectly depend on somebody
else's __GFP_DIRECT_RECLAIM memory allocation. And such indirect
__GFP_DIRECT_RECLAIM memory allocation might call leak_balloon() via
virtballoon_oom_notify() via blocking_notifier_call_chain() callback via
out_of_memory() when it reached __alloc_pages_may_oom() and held oom_lock
mutex. Since vb->balloon_lock mutex is already held by fill_balloon(), it
will cause OOM lockup.
Thread1 Thread2
fill_balloon()
takes a balloon_lock
balloon_page_enqueue()
alloc_page(GFP_HIGHUSER_MOVABLE)
direct reclaim (__GFP_FS context) takes a fs lock
waits for that fs lock alloc_page(GFP_NOFS)
__alloc_pages_may_oom()
takes the oom_lock
out_of_memory()
blocking_notifier_call_chain()
leak_balloon()
tries to take that balloon_lock and deadlocks
Reported-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
Cc: Michal Hocko <mhocko@suse.com>
Cc: Wei Wang <wei.w.wang@intel.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
-rw-r--r-- | drivers/virtio/virtio_balloon.c | 24 | ||||
-rw-r--r-- | include/linux/balloon_compaction.h | 35 | ||||
-rw-r--r-- | mm/balloon_compaction.c | 28 |
3 files changed, 74 insertions, 13 deletions
diff --git a/drivers/virtio/virtio_balloon.c b/drivers/virtio/virtio_balloon.c index f0b3a0b9d42f..7960746f7597 100644 --- a/drivers/virtio/virtio_balloon.c +++ b/drivers/virtio/virtio_balloon.c | |||
@@ -143,16 +143,17 @@ static void set_page_pfns(struct virtio_balloon *vb, | |||
143 | 143 | ||
144 | static unsigned fill_balloon(struct virtio_balloon *vb, size_t num) | 144 | static unsigned fill_balloon(struct virtio_balloon *vb, size_t num) |
145 | { | 145 | { |
146 | struct balloon_dev_info *vb_dev_info = &vb->vb_dev_info; | ||
147 | unsigned num_allocated_pages; | 146 | unsigned num_allocated_pages; |
147 | unsigned num_pfns; | ||
148 | struct page *page; | ||
149 | LIST_HEAD(pages); | ||
148 | 150 | ||
149 | /* We can only do one array worth at a time. */ | 151 | /* We can only do one array worth at a time. */ |
150 | num = min(num, ARRAY_SIZE(vb->pfns)); | 152 | num = min(num, ARRAY_SIZE(vb->pfns)); |
151 | 153 | ||
152 | mutex_lock(&vb->balloon_lock); | 154 | for (num_pfns = 0; num_pfns < num; |
153 | for (vb->num_pfns = 0; vb->num_pfns < num; | 155 | num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE) { |
154 | vb->num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE) { | 156 | struct page *page = balloon_page_alloc(); |
155 | struct page *page = balloon_page_enqueue(vb_dev_info); | ||
156 | 157 | ||
157 | if (!page) { | 158 | if (!page) { |
158 | dev_info_ratelimited(&vb->vdev->dev, | 159 | dev_info_ratelimited(&vb->vdev->dev, |
@@ -162,6 +163,19 @@ static unsigned fill_balloon(struct virtio_balloon *vb, size_t num) | |||
162 | msleep(200); | 163 | msleep(200); |
163 | break; | 164 | break; |
164 | } | 165 | } |
166 | |||
167 | balloon_page_push(&pages, page); | ||
168 | } | ||
169 | |||
170 | mutex_lock(&vb->balloon_lock); | ||
171 | |||
172 | vb->num_pfns = 0; | ||
173 | |||
174 | while ((page = balloon_page_pop(&pages))) { | ||
175 | balloon_page_enqueue(&vb->vb_dev_info, page); | ||
176 | |||
177 | vb->num_pfns += VIRTIO_BALLOON_PAGES_PER_PAGE; | ||
178 | |||
165 | set_page_pfns(vb, vb->pfns + vb->num_pfns, page); | 179 | set_page_pfns(vb, vb->pfns + vb->num_pfns, page); |
166 | vb->num_pages += VIRTIO_BALLOON_PAGES_PER_PAGE; | 180 | vb->num_pages += VIRTIO_BALLOON_PAGES_PER_PAGE; |
167 | if (!virtio_has_feature(vb->vdev, | 181 | if (!virtio_has_feature(vb->vdev, |
diff --git a/include/linux/balloon_compaction.h b/include/linux/balloon_compaction.h index fbbe6da40fed..53051f3d8f25 100644 --- a/include/linux/balloon_compaction.h +++ b/include/linux/balloon_compaction.h | |||
@@ -50,6 +50,7 @@ | |||
50 | #include <linux/gfp.h> | 50 | #include <linux/gfp.h> |
51 | #include <linux/err.h> | 51 | #include <linux/err.h> |
52 | #include <linux/fs.h> | 52 | #include <linux/fs.h> |
53 | #include <linux/list.h> | ||
53 | 54 | ||
54 | /* | 55 | /* |
55 | * Balloon device information descriptor. | 56 | * Balloon device information descriptor. |
@@ -67,7 +68,9 @@ struct balloon_dev_info { | |||
67 | struct inode *inode; | 68 | struct inode *inode; |
68 | }; | 69 | }; |
69 | 70 | ||
70 | extern struct page *balloon_page_enqueue(struct balloon_dev_info *b_dev_info); | 71 | extern struct page *balloon_page_alloc(void); |
72 | extern void balloon_page_enqueue(struct balloon_dev_info *b_dev_info, | ||
73 | struct page *page); | ||
71 | extern struct page *balloon_page_dequeue(struct balloon_dev_info *b_dev_info); | 74 | extern struct page *balloon_page_dequeue(struct balloon_dev_info *b_dev_info); |
72 | 75 | ||
73 | static inline void balloon_devinfo_init(struct balloon_dev_info *balloon) | 76 | static inline void balloon_devinfo_init(struct balloon_dev_info *balloon) |
@@ -193,4 +196,34 @@ static inline gfp_t balloon_mapping_gfp_mask(void) | |||
193 | } | 196 | } |
194 | 197 | ||
195 | #endif /* CONFIG_BALLOON_COMPACTION */ | 198 | #endif /* CONFIG_BALLOON_COMPACTION */ |
199 | |||
200 | /* | ||
201 | * balloon_page_push - insert a page into a page list. | ||
202 | * @head : pointer to list | ||
203 | * @page : page to be added | ||
204 | * | ||
205 | * Caller must ensure the page is private and protect the list. | ||
206 | */ | ||
207 | static inline void balloon_page_push(struct list_head *pages, struct page *page) | ||
208 | { | ||
209 | list_add(&page->lru, pages); | ||
210 | } | ||
211 | |||
212 | /* | ||
213 | * balloon_page_pop - remove a page from a page list. | ||
214 | * @head : pointer to list | ||
215 | * @page : page to be added | ||
216 | * | ||
217 | * Caller must ensure the page is private and protect the list. | ||
218 | */ | ||
219 | static inline struct page *balloon_page_pop(struct list_head *pages) | ||
220 | { | ||
221 | struct page *page = list_first_entry_or_null(pages, struct page, lru); | ||
222 | |||
223 | if (!page) | ||
224 | return NULL; | ||
225 | |||
226 | list_del(&page->lru); | ||
227 | return page; | ||
228 | } | ||
196 | #endif /* _LINUX_BALLOON_COMPACTION_H */ | 229 | #endif /* _LINUX_BALLOON_COMPACTION_H */ |
diff --git a/mm/balloon_compaction.c b/mm/balloon_compaction.c index 68d28924ba79..ef858d547e2d 100644 --- a/mm/balloon_compaction.c +++ b/mm/balloon_compaction.c | |||
@@ -11,22 +11,37 @@ | |||
11 | #include <linux/balloon_compaction.h> | 11 | #include <linux/balloon_compaction.h> |
12 | 12 | ||
13 | /* | 13 | /* |
14 | * balloon_page_alloc - allocates a new page for insertion into the balloon | ||
15 | * page list. | ||
16 | * | ||
17 | * Driver must call it to properly allocate a new enlisted balloon page. | ||
18 | * Driver must call balloon_page_enqueue before definitively removing it from | ||
19 | * the guest system. This function returns the page address for the recently | ||
20 | * allocated page or NULL in the case we fail to allocate a new page this turn. | ||
21 | */ | ||
22 | struct page *balloon_page_alloc(void) | ||
23 | { | ||
24 | struct page *page = alloc_page(balloon_mapping_gfp_mask() | | ||
25 | __GFP_NOMEMALLOC | __GFP_NORETRY); | ||
26 | return page; | ||
27 | } | ||
28 | EXPORT_SYMBOL_GPL(balloon_page_alloc); | ||
29 | |||
30 | /* | ||
14 | * balloon_page_enqueue - allocates a new page and inserts it into the balloon | 31 | * balloon_page_enqueue - allocates a new page and inserts it into the balloon |
15 | * page list. | 32 | * page list. |
16 | * @b_dev_info: balloon device descriptor where we will insert a new page to | 33 | * @b_dev_info: balloon device descriptor where we will insert a new page to |
34 | * @page: new page to enqueue - allocated using balloon_page_alloc. | ||
17 | * | 35 | * |
18 | * Driver must call it to properly allocate a new enlisted balloon page | 36 | * Driver must call it to properly enqueue a new allocated balloon page |
19 | * before definitively removing it from the guest system. | 37 | * before definitively removing it from the guest system. |
20 | * This function returns the page address for the recently enqueued page or | 38 | * This function returns the page address for the recently enqueued page or |
21 | * NULL in the case we fail to allocate a new page this turn. | 39 | * NULL in the case we fail to allocate a new page this turn. |
22 | */ | 40 | */ |
23 | struct page *balloon_page_enqueue(struct balloon_dev_info *b_dev_info) | 41 | void balloon_page_enqueue(struct balloon_dev_info *b_dev_info, |
42 | struct page *page) | ||
24 | { | 43 | { |
25 | unsigned long flags; | 44 | unsigned long flags; |
26 | struct page *page = alloc_page(balloon_mapping_gfp_mask() | | ||
27 | __GFP_NOMEMALLOC | __GFP_NORETRY); | ||
28 | if (!page) | ||
29 | return NULL; | ||
30 | 45 | ||
31 | /* | 46 | /* |
32 | * Block others from accessing the 'page' when we get around to | 47 | * Block others from accessing the 'page' when we get around to |
@@ -39,7 +54,6 @@ struct page *balloon_page_enqueue(struct balloon_dev_info *b_dev_info) | |||
39 | __count_vm_event(BALLOON_INFLATE); | 54 | __count_vm_event(BALLOON_INFLATE); |
40 | spin_unlock_irqrestore(&b_dev_info->pages_lock, flags); | 55 | spin_unlock_irqrestore(&b_dev_info->pages_lock, flags); |
41 | unlock_page(page); | 56 | unlock_page(page); |
42 | return page; | ||
43 | } | 57 | } |
44 | EXPORT_SYMBOL_GPL(balloon_page_enqueue); | 58 | EXPORT_SYMBOL_GPL(balloon_page_enqueue); |
45 | 59 | ||