diff options
author | Davidlohr Bueso <davidlohr@hp.com> | 2014-04-03 17:47:27 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-04-03 19:20:59 -0400 |
commit | 7b24d8616be33616efd41ff67d3c76362c60ca84 (patch) | |
tree | 6bc0558e36c54f66f73e33a4891e9e5ead924104 /mm/hugetlb.c | |
parent | 1406ec9ba6c65cb69e9243bff07ca3f51e2525e0 (diff) |
mm, hugetlb: fix race in region tracking
There is a race condition if we map a same file on different processes.
Region tracking is protected by mmap_sem and hugetlb_instantiation_mutex.
When we do mmap, we don't grab a hugetlb_instantiation_mutex, but only
mmap_sem (exclusively). This doesn't prevent other tasks from modifying
the region structure, so it can be modified by two processes
concurrently.
To solve this, introduce a spinlock to resv_map and make region
manipulation function grab it before they do actual work.
[davidlohr@hp.com: updated changelog]
Signed-off-by: Davidlohr Bueso <davidlohr@hp.com>
Signed-off-by: Joonsoo Kim <iamjoonsoo.kim@lge.com>
Suggested-by: Joonsoo Kim <iamjoonsoo.kim@lge.com>
Acked-by: David Gibson <david@gibson.dropbear.id.au>
Cc: David Gibson <david@gibson.dropbear.id.au>
Cc: Naoya Horiguchi <n-horiguchi@ah.jp.nec.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'mm/hugetlb.c')
-rw-r--r-- | mm/hugetlb.c | 58 |
1 files changed, 38 insertions, 20 deletions
diff --git a/mm/hugetlb.c b/mm/hugetlb.c index 454b311373c5..5a2515a774b5 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c | |||
@@ -135,15 +135,8 @@ static inline struct hugepage_subpool *subpool_vma(struct vm_area_struct *vma) | |||
135 | * Region tracking -- allows tracking of reservations and instantiated pages | 135 | * Region tracking -- allows tracking of reservations and instantiated pages |
136 | * across the pages in a mapping. | 136 | * across the pages in a mapping. |
137 | * | 137 | * |
138 | * The region data structures are protected by a combination of the mmap_sem | 138 | * The region data structures are embedded into a resv_map and |
139 | * and the hugetlb_instantiation_mutex. To access or modify a region the caller | 139 | * protected by a resv_map's lock |
140 | * must either hold the mmap_sem for write, or the mmap_sem for read and | ||
141 | * the hugetlb_instantiation_mutex: | ||
142 | * | ||
143 | * down_write(&mm->mmap_sem); | ||
144 | * or | ||
145 | * down_read(&mm->mmap_sem); | ||
146 | * mutex_lock(&hugetlb_instantiation_mutex); | ||
147 | */ | 140 | */ |
148 | struct file_region { | 141 | struct file_region { |
149 | struct list_head link; | 142 | struct list_head link; |
@@ -156,6 +149,7 @@ static long region_add(struct resv_map *resv, long f, long t) | |||
156 | struct list_head *head = &resv->regions; | 149 | struct list_head *head = &resv->regions; |
157 | struct file_region *rg, *nrg, *trg; | 150 | struct file_region *rg, *nrg, *trg; |
158 | 151 | ||
152 | spin_lock(&resv->lock); | ||
159 | /* Locate the region we are either in or before. */ | 153 | /* Locate the region we are either in or before. */ |
160 | list_for_each_entry(rg, head, link) | 154 | list_for_each_entry(rg, head, link) |
161 | if (f <= rg->to) | 155 | if (f <= rg->to) |
@@ -185,15 +179,18 @@ static long region_add(struct resv_map *resv, long f, long t) | |||
185 | } | 179 | } |
186 | nrg->from = f; | 180 | nrg->from = f; |
187 | nrg->to = t; | 181 | nrg->to = t; |
182 | spin_unlock(&resv->lock); | ||
188 | return 0; | 183 | return 0; |
189 | } | 184 | } |
190 | 185 | ||
191 | static long region_chg(struct resv_map *resv, long f, long t) | 186 | static long region_chg(struct resv_map *resv, long f, long t) |
192 | { | 187 | { |
193 | struct list_head *head = &resv->regions; | 188 | struct list_head *head = &resv->regions; |
194 | struct file_region *rg, *nrg; | 189 | struct file_region *rg, *nrg = NULL; |
195 | long chg = 0; | 190 | long chg = 0; |
196 | 191 | ||
192 | retry: | ||
193 | spin_lock(&resv->lock); | ||
197 | /* Locate the region we are before or in. */ | 194 | /* Locate the region we are before or in. */ |
198 | list_for_each_entry(rg, head, link) | 195 | list_for_each_entry(rg, head, link) |
199 | if (f <= rg->to) | 196 | if (f <= rg->to) |
@@ -203,15 +200,21 @@ static long region_chg(struct resv_map *resv, long f, long t) | |||
203 | * Subtle, allocate a new region at the position but make it zero | 200 | * Subtle, allocate a new region at the position but make it zero |
204 | * size such that we can guarantee to record the reservation. */ | 201 | * size such that we can guarantee to record the reservation. */ |
205 | if (&rg->link == head || t < rg->from) { | 202 | if (&rg->link == head || t < rg->from) { |
206 | nrg = kmalloc(sizeof(*nrg), GFP_KERNEL); | 203 | if (!nrg) { |
207 | if (!nrg) | 204 | spin_unlock(&resv->lock); |
208 | return -ENOMEM; | 205 | nrg = kmalloc(sizeof(*nrg), GFP_KERNEL); |
209 | nrg->from = f; | 206 | if (!nrg) |
210 | nrg->to = f; | 207 | return -ENOMEM; |
211 | INIT_LIST_HEAD(&nrg->link); | 208 | |
212 | list_add(&nrg->link, rg->link.prev); | 209 | nrg->from = f; |
210 | nrg->to = f; | ||
211 | INIT_LIST_HEAD(&nrg->link); | ||
212 | goto retry; | ||
213 | } | ||
213 | 214 | ||
214 | return t - f; | 215 | list_add(&nrg->link, rg->link.prev); |
216 | chg = t - f; | ||
217 | goto out_nrg; | ||
215 | } | 218 | } |
216 | 219 | ||
217 | /* Round our left edge to the current segment if it encloses us. */ | 220 | /* Round our left edge to the current segment if it encloses us. */ |
@@ -224,7 +227,7 @@ static long region_chg(struct resv_map *resv, long f, long t) | |||
224 | if (&rg->link == head) | 227 | if (&rg->link == head) |
225 | break; | 228 | break; |
226 | if (rg->from > t) | 229 | if (rg->from > t) |
227 | return chg; | 230 | goto out; |
228 | 231 | ||
229 | /* We overlap with this area, if it extends further than | 232 | /* We overlap with this area, if it extends further than |
230 | * us then we must extend ourselves. Account for its | 233 | * us then we must extend ourselves. Account for its |
@@ -235,6 +238,14 @@ static long region_chg(struct resv_map *resv, long f, long t) | |||
235 | } | 238 | } |
236 | chg -= rg->to - rg->from; | 239 | chg -= rg->to - rg->from; |
237 | } | 240 | } |
241 | |||
242 | out: | ||
243 | spin_unlock(&resv->lock); | ||
244 | /* We already know we raced and no longer need the new region */ | ||
245 | kfree(nrg); | ||
246 | return chg; | ||
247 | out_nrg: | ||
248 | spin_unlock(&resv->lock); | ||
238 | return chg; | 249 | return chg; |
239 | } | 250 | } |
240 | 251 | ||
@@ -244,12 +255,13 @@ static long region_truncate(struct resv_map *resv, long end) | |||
244 | struct file_region *rg, *trg; | 255 | struct file_region *rg, *trg; |
245 | long chg = 0; | 256 | long chg = 0; |
246 | 257 | ||
258 | spin_lock(&resv->lock); | ||
247 | /* Locate the region we are either in or before. */ | 259 | /* Locate the region we are either in or before. */ |
248 | list_for_each_entry(rg, head, link) | 260 | list_for_each_entry(rg, head, link) |
249 | if (end <= rg->to) | 261 | if (end <= rg->to) |
250 | break; | 262 | break; |
251 | if (&rg->link == head) | 263 | if (&rg->link == head) |
252 | return 0; | 264 | goto out; |
253 | 265 | ||
254 | /* If we are in the middle of a region then adjust it. */ | 266 | /* If we are in the middle of a region then adjust it. */ |
255 | if (end > rg->from) { | 267 | if (end > rg->from) { |
@@ -266,6 +278,9 @@ static long region_truncate(struct resv_map *resv, long end) | |||
266 | list_del(&rg->link); | 278 | list_del(&rg->link); |
267 | kfree(rg); | 279 | kfree(rg); |
268 | } | 280 | } |
281 | |||
282 | out: | ||
283 | spin_unlock(&resv->lock); | ||
269 | return chg; | 284 | return chg; |
270 | } | 285 | } |
271 | 286 | ||
@@ -275,6 +290,7 @@ static long region_count(struct resv_map *resv, long f, long t) | |||
275 | struct file_region *rg; | 290 | struct file_region *rg; |
276 | long chg = 0; | 291 | long chg = 0; |
277 | 292 | ||
293 | spin_lock(&resv->lock); | ||
278 | /* Locate each segment we overlap with, and count that overlap. */ | 294 | /* Locate each segment we overlap with, and count that overlap. */ |
279 | list_for_each_entry(rg, head, link) { | 295 | list_for_each_entry(rg, head, link) { |
280 | long seg_from; | 296 | long seg_from; |
@@ -290,6 +306,7 @@ static long region_count(struct resv_map *resv, long f, long t) | |||
290 | 306 | ||
291 | chg += seg_to - seg_from; | 307 | chg += seg_to - seg_from; |
292 | } | 308 | } |
309 | spin_unlock(&resv->lock); | ||
293 | 310 | ||
294 | return chg; | 311 | return chg; |
295 | } | 312 | } |
@@ -387,6 +404,7 @@ struct resv_map *resv_map_alloc(void) | |||
387 | return NULL; | 404 | return NULL; |
388 | 405 | ||
389 | kref_init(&resv_map->refs); | 406 | kref_init(&resv_map->refs); |
407 | spin_lock_init(&resv_map->lock); | ||
390 | INIT_LIST_HEAD(&resv_map->regions); | 408 | INIT_LIST_HEAD(&resv_map->regions); |
391 | 409 | ||
392 | return resv_map; | 410 | return resv_map; |