]>
Commit | Line | Data |
---|---|---|
b75945de GKH |
1 | From 1f2c6651f69c14d0d3a9cfbda44ea101b02160ba Mon Sep 17 00:00:00 2001 |
2 | From: Ilya Dryomov <idryomov@gmail.com> | |
3 | Date: Sun, 11 Oct 2015 19:38:00 +0200 | |
4 | Subject: rbd: don't leak parent_spec in rbd_dev_probe_parent() | |
5 | ||
6 | From: Ilya Dryomov <idryomov@gmail.com> | |
7 | ||
8 | commit 1f2c6651f69c14d0d3a9cfbda44ea101b02160ba upstream. | |
9 | ||
10 | Currently we leak parent_spec and trigger a "parent reference | |
11 | underflow" warning if rbd_dev_create() in rbd_dev_probe_parent() fails. | |
12 | The problem is we take the !parent out_err branch and that only drops | |
13 | refcounts; parent_spec that would've been freed had we called | |
14 | rbd_dev_unparent() remains and triggers rbd_warn() in | |
15 | rbd_dev_parent_put() - at that point we have parent_spec != NULL and | |
16 | parent_ref == 0, so counter ends up being -1 after the decrement. | |
17 | ||
18 | Redo rbd_dev_probe_parent() to fix this. | |
19 | ||
20 | Signed-off-by: Ilya Dryomov <idryomov@gmail.com> | |
21 | [idryomov@gmail.com: backport to < 4.2: rbd_dev->opts] | |
22 | Reviewed-by: Alex Elder <elder@linaro.org> | |
23 | Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> | |
24 | ||
25 | --- | |
26 | drivers/block/rbd.c | 35 +++++++++++++++-------------------- | |
27 | 1 file changed, 15 insertions(+), 20 deletions(-) | |
28 | ||
29 | --- a/drivers/block/rbd.c | |
30 | +++ b/drivers/block/rbd.c | |
31 | @@ -4825,41 +4825,36 @@ out_err: | |
32 | static int rbd_dev_probe_parent(struct rbd_device *rbd_dev) | |
33 | { | |
34 | struct rbd_device *parent = NULL; | |
35 | - struct rbd_spec *parent_spec; | |
36 | - struct rbd_client *rbdc; | |
37 | int ret; | |
38 | ||
39 | if (!rbd_dev->parent_spec) | |
40 | return 0; | |
41 | - /* | |
42 | - * We need to pass a reference to the client and the parent | |
43 | - * spec when creating the parent rbd_dev. Images related by | |
44 | - * parent/child relationships always share both. | |
45 | - */ | |
46 | - parent_spec = rbd_spec_get(rbd_dev->parent_spec); | |
47 | - rbdc = __rbd_get_client(rbd_dev->rbd_client); | |
48 | ||
49 | - ret = -ENOMEM; | |
50 | - parent = rbd_dev_create(rbdc, parent_spec); | |
51 | - if (!parent) | |
52 | + parent = rbd_dev_create(rbd_dev->rbd_client, rbd_dev->parent_spec); | |
53 | + if (!parent) { | |
54 | + ret = -ENOMEM; | |
55 | goto out_err; | |
56 | + } | |
57 | + | |
58 | + /* | |
59 | + * Images related by parent/child relationships always share | |
60 | + * rbd_client and spec/parent_spec, so bump their refcounts. | |
61 | + */ | |
62 | + __rbd_get_client(rbd_dev->rbd_client); | |
63 | + rbd_spec_get(rbd_dev->parent_spec); | |
64 | ||
65 | ret = rbd_dev_image_probe(parent, false); | |
66 | if (ret < 0) | |
67 | goto out_err; | |
68 | + | |
69 | rbd_dev->parent = parent; | |
70 | atomic_set(&rbd_dev->parent_ref, 1); | |
71 | - | |
72 | return 0; | |
73 | + | |
74 | out_err: | |
75 | - if (parent) { | |
76 | - rbd_dev_unparent(rbd_dev); | |
77 | + rbd_dev_unparent(rbd_dev); | |
78 | + if (parent) | |
79 | rbd_dev_destroy(parent); | |
80 | - } else { | |
81 | - rbd_put_client(rbdc); | |
82 | - rbd_spec_put(parent_spec); | |
83 | - } | |
84 | - | |
85 | return ret; | |
86 | } | |
87 |