Commit 517c1d1b authored by Kurt Garloff's avatar Kurt Garloff Committed by Linus Torvalds

[PATCH] fix bio_uncopy_user() mem leak

  When using bounce buffers for SG_IO commands with unaligned buffers in
  blk_rq_map_user(), we should free the pages from blk_rq_unmap_user() which
  calls bio_uncopy_user() for the non-BIO_USER_MAPPED case.  That function
  failed to free the pages for write requests.

  So we leaked pages and you machine would go OOM.  Rebooting helped ;-)

  This bug was triggered by writing audio CDs (but not on data CDs), as the
  audio frames are not aligned well (2352 bytes), so the user pages don't just
  get mapped.

  Bug was reported by Mathias Homan and debugged by Chris Mason + me.  (Jens
  is away.)

From: Chris Mason <mason@suse.com>

  Fix the leak for real
Signed-off-by: default avatarKurt Garloff <garloff@suse.de>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent be3a4d39
...@@ -388,20 +388,17 @@ int bio_uncopy_user(struct bio *bio) ...@@ -388,20 +388,17 @@ int bio_uncopy_user(struct bio *bio)
struct bio_vec *bvec; struct bio_vec *bvec;
int i, ret = 0; int i, ret = 0;
if (bio_data_dir(bio) == READ) { char *uaddr = bio->bi_private;
char *uaddr = bio->bi_private;
__bio_for_each_segment(bvec, bio, i, 0) { __bio_for_each_segment(bvec, bio, i, 0) {
char *addr = page_address(bvec->bv_page); char *addr = page_address(bvec->bv_page);
if (bio_data_dir(bio) == READ && !ret &&
if (!ret && copy_to_user(uaddr, addr, bvec->bv_len)) copy_to_user(uaddr, addr, bvec->bv_len))
ret = -EFAULT; ret = -EFAULT;
__free_page(bvec->bv_page); __free_page(bvec->bv_page);
uaddr += bvec->bv_len; uaddr += bvec->bv_len;
}
} }
bio_put(bio); bio_put(bio);
return ret; return ret;
} }
...@@ -457,6 +454,7 @@ struct bio *bio_copy_user(request_queue_t *q, unsigned long uaddr, ...@@ -457,6 +454,7 @@ struct bio *bio_copy_user(request_queue_t *q, unsigned long uaddr,
*/ */
if (!ret) { if (!ret) {
if (!write_to_vm) { if (!write_to_vm) {
unsigned long p = uaddr;
bio->bi_rw |= (1 << BIO_RW); bio->bi_rw |= (1 << BIO_RW);
/* /*
* for a write, copy in data to kernel pages * for a write, copy in data to kernel pages
...@@ -465,8 +463,9 @@ struct bio *bio_copy_user(request_queue_t *q, unsigned long uaddr, ...@@ -465,8 +463,9 @@ struct bio *bio_copy_user(request_queue_t *q, unsigned long uaddr,
bio_for_each_segment(bvec, bio, i) { bio_for_each_segment(bvec, bio, i) {
char *addr = page_address(bvec->bv_page); char *addr = page_address(bvec->bv_page);
if (copy_from_user(addr, (char *) uaddr, bvec->bv_len)) if (copy_from_user(addr, (char *) p, bvec->bv_len))
goto cleanup; goto cleanup;
p += bvec->bv_len;
} }
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment