diff options
author | Ilya Dryomov <idryomov@gmail.com> | 2015-10-11 13:38:00 -0400 |
---|---|---|
committer | Ilya Dryomov <idryomov@gmail.com> | 2015-10-23 12:36:03 -0400 |
commit | 1f2c6651f69c14d0d3a9cfbda44ea101b02160ba (patch) | |
tree | e306645e6644fe420d3bd45388f5b4e33c1cac4c | |
parent | 7379047d5585187d1288486d4627873170d0005a (diff) |
rbd: don't leak parent_spec in rbd_dev_probe_parent()
Currently we leak parent_spec and trigger a "parent reference
underflow" warning if rbd_dev_create() in rbd_dev_probe_parent() fails.
The problem is we take the !parent out_err branch and that only drops
refcounts; parent_spec that would've been freed had we called
rbd_dev_unparent() remains and triggers rbd_warn() in
rbd_dev_parent_put() - at that point we have parent_spec != NULL and
parent_ref == 0, so counter ends up being -1 after the decrement.
Redo rbd_dev_probe_parent() to fix this.
Cc: stable@vger.kernel.org # 3.10+, needs backporting for < 4.2
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Reviewed-by: Alex Elder <elder@linaro.org>
-rw-r--r-- | drivers/block/rbd.c | 36 |
1 files changed, 16 insertions, 20 deletions
diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c index f5e49b639818..028db28cb8a0 100644 --- a/drivers/block/rbd.c +++ b/drivers/block/rbd.c | |||
@@ -5134,41 +5134,37 @@ out_err: | |||
5134 | static int rbd_dev_probe_parent(struct rbd_device *rbd_dev) | 5134 | static int rbd_dev_probe_parent(struct rbd_device *rbd_dev) |
5135 | { | 5135 | { |
5136 | struct rbd_device *parent = NULL; | 5136 | struct rbd_device *parent = NULL; |
5137 | struct rbd_spec *parent_spec; | ||
5138 | struct rbd_client *rbdc; | ||
5139 | int ret; | 5137 | int ret; |
5140 | 5138 | ||
5141 | if (!rbd_dev->parent_spec) | 5139 | if (!rbd_dev->parent_spec) |
5142 | return 0; | 5140 | return 0; |
5143 | /* | ||
5144 | * We need to pass a reference to the client and the parent | ||
5145 | * spec when creating the parent rbd_dev. Images related by | ||
5146 | * parent/child relationships always share both. | ||
5147 | */ | ||
5148 | parent_spec = rbd_spec_get(rbd_dev->parent_spec); | ||
5149 | rbdc = __rbd_get_client(rbd_dev->rbd_client); | ||
5150 | 5141 | ||
5151 | ret = -ENOMEM; | 5142 | parent = rbd_dev_create(rbd_dev->rbd_client, rbd_dev->parent_spec, |
5152 | parent = rbd_dev_create(rbdc, parent_spec, NULL); | 5143 | NULL); |
5153 | if (!parent) | 5144 | if (!parent) { |
5145 | ret = -ENOMEM; | ||
5154 | goto out_err; | 5146 | goto out_err; |
5147 | } | ||
5148 | |||
5149 | /* | ||
5150 | * Images related by parent/child relationships always share | ||
5151 | * rbd_client and spec/parent_spec, so bump their refcounts. | ||
5152 | */ | ||
5153 | __rbd_get_client(rbd_dev->rbd_client); | ||
5154 | rbd_spec_get(rbd_dev->parent_spec); | ||
5155 | 5155 | ||
5156 | ret = rbd_dev_image_probe(parent, false); | 5156 | ret = rbd_dev_image_probe(parent, false); |
5157 | if (ret < 0) | 5157 | if (ret < 0) |
5158 | goto out_err; | 5158 | goto out_err; |
5159 | |||
5159 | rbd_dev->parent = parent; | 5160 | rbd_dev->parent = parent; |
5160 | atomic_set(&rbd_dev->parent_ref, 1); | 5161 | atomic_set(&rbd_dev->parent_ref, 1); |
5161 | |||
5162 | return 0; | 5162 | return 0; |
5163 | |||
5163 | out_err: | 5164 | out_err: |
5164 | if (parent) { | 5165 | rbd_dev_unparent(rbd_dev); |
5165 | rbd_dev_unparent(rbd_dev); | 5166 | if (parent) |
5166 | rbd_dev_destroy(parent); | 5167 | rbd_dev_destroy(parent); |
5167 | } else { | ||
5168 | rbd_put_client(rbdc); | ||
5169 | rbd_spec_put(parent_spec); | ||
5170 | } | ||
5171 | |||
5172 | return ret; | 5168 | return ret; |
5173 | } | 5169 | } |
5174 | 5170 | ||