Commit df6d02300f7c2fbd0fbe626d819c8e5237d72c62
Committed by
John W. Linville
1 parent
7acc7c683a
Exists in
master
and in
7 other branches
wext: fix potential private ioctl memory content leak
When a driver doesn't fill the entire buffer, old heap contents may remain, and if it also doesn't update the length properly, this old heap content will be copied back to userspace. It is very unlikely that this happens in any of the drivers using private ioctls since it would show up as junk being reported by iwpriv, but it seems better to be safe here, so use kzalloc. Reported-by: Jeff Mahoney <jeffm@suse.com> Cc: stable@kernel.org Signed-off-by: Johannes Berg <johannes.berg@intel.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
Showing 1 changed file with 1 additions and 1 deletions Inline Diff
net/wireless/wext-priv.c
1 | /* | 1 | /* |
2 | * This file implement the Wireless Extensions priv API. | 2 | * This file implement the Wireless Extensions priv API. |
3 | * | 3 | * |
4 | * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com> | 4 | * Authors : Jean Tourrilhes - HPL - <jt@hpl.hp.com> |
5 | * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. | 5 | * Copyright (c) 1997-2007 Jean Tourrilhes, All Rights Reserved. |
6 | * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> | 6 | * Copyright 2009 Johannes Berg <johannes@sipsolutions.net> |
7 | * | 7 | * |
8 | * (As all part of the Linux kernel, this file is GPL) | 8 | * (As all part of the Linux kernel, this file is GPL) |
9 | */ | 9 | */ |
10 | #include <linux/slab.h> | 10 | #include <linux/slab.h> |
11 | #include <linux/wireless.h> | 11 | #include <linux/wireless.h> |
12 | #include <linux/netdevice.h> | 12 | #include <linux/netdevice.h> |
13 | #include <net/iw_handler.h> | 13 | #include <net/iw_handler.h> |
14 | #include <net/wext.h> | 14 | #include <net/wext.h> |
15 | 15 | ||
16 | int iw_handler_get_private(struct net_device * dev, | 16 | int iw_handler_get_private(struct net_device * dev, |
17 | struct iw_request_info * info, | 17 | struct iw_request_info * info, |
18 | union iwreq_data * wrqu, | 18 | union iwreq_data * wrqu, |
19 | char * extra) | 19 | char * extra) |
20 | { | 20 | { |
21 | /* Check if the driver has something to export */ | 21 | /* Check if the driver has something to export */ |
22 | if ((dev->wireless_handlers->num_private_args == 0) || | 22 | if ((dev->wireless_handlers->num_private_args == 0) || |
23 | (dev->wireless_handlers->private_args == NULL)) | 23 | (dev->wireless_handlers->private_args == NULL)) |
24 | return -EOPNOTSUPP; | 24 | return -EOPNOTSUPP; |
25 | 25 | ||
26 | /* Check if there is enough buffer up there */ | 26 | /* Check if there is enough buffer up there */ |
27 | if (wrqu->data.length < dev->wireless_handlers->num_private_args) { | 27 | if (wrqu->data.length < dev->wireless_handlers->num_private_args) { |
28 | /* User space can't know in advance how large the buffer | 28 | /* User space can't know in advance how large the buffer |
29 | * needs to be. Give it a hint, so that we can support | 29 | * needs to be. Give it a hint, so that we can support |
30 | * any size buffer we want somewhat efficiently... */ | 30 | * any size buffer we want somewhat efficiently... */ |
31 | wrqu->data.length = dev->wireless_handlers->num_private_args; | 31 | wrqu->data.length = dev->wireless_handlers->num_private_args; |
32 | return -E2BIG; | 32 | return -E2BIG; |
33 | } | 33 | } |
34 | 34 | ||
35 | /* Set the number of available ioctls. */ | 35 | /* Set the number of available ioctls. */ |
36 | wrqu->data.length = dev->wireless_handlers->num_private_args; | 36 | wrqu->data.length = dev->wireless_handlers->num_private_args; |
37 | 37 | ||
38 | /* Copy structure to the user buffer. */ | 38 | /* Copy structure to the user buffer. */ |
39 | memcpy(extra, dev->wireless_handlers->private_args, | 39 | memcpy(extra, dev->wireless_handlers->private_args, |
40 | sizeof(struct iw_priv_args) * wrqu->data.length); | 40 | sizeof(struct iw_priv_args) * wrqu->data.length); |
41 | 41 | ||
42 | return 0; | 42 | return 0; |
43 | } | 43 | } |
44 | 44 | ||
45 | /* Size (in bytes) of the various private data types */ | 45 | /* Size (in bytes) of the various private data types */ |
46 | static const char iw_priv_type_size[] = { | 46 | static const char iw_priv_type_size[] = { |
47 | 0, /* IW_PRIV_TYPE_NONE */ | 47 | 0, /* IW_PRIV_TYPE_NONE */ |
48 | 1, /* IW_PRIV_TYPE_BYTE */ | 48 | 1, /* IW_PRIV_TYPE_BYTE */ |
49 | 1, /* IW_PRIV_TYPE_CHAR */ | 49 | 1, /* IW_PRIV_TYPE_CHAR */ |
50 | 0, /* Not defined */ | 50 | 0, /* Not defined */ |
51 | sizeof(__u32), /* IW_PRIV_TYPE_INT */ | 51 | sizeof(__u32), /* IW_PRIV_TYPE_INT */ |
52 | sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ | 52 | sizeof(struct iw_freq), /* IW_PRIV_TYPE_FLOAT */ |
53 | sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ | 53 | sizeof(struct sockaddr), /* IW_PRIV_TYPE_ADDR */ |
54 | 0, /* Not defined */ | 54 | 0, /* Not defined */ |
55 | }; | 55 | }; |
56 | 56 | ||
57 | static int get_priv_size(__u16 args) | 57 | static int get_priv_size(__u16 args) |
58 | { | 58 | { |
59 | int num = args & IW_PRIV_SIZE_MASK; | 59 | int num = args & IW_PRIV_SIZE_MASK; |
60 | int type = (args & IW_PRIV_TYPE_MASK) >> 12; | 60 | int type = (args & IW_PRIV_TYPE_MASK) >> 12; |
61 | 61 | ||
62 | return num * iw_priv_type_size[type]; | 62 | return num * iw_priv_type_size[type]; |
63 | } | 63 | } |
64 | 64 | ||
65 | static int adjust_priv_size(__u16 args, struct iw_point *iwp) | 65 | static int adjust_priv_size(__u16 args, struct iw_point *iwp) |
66 | { | 66 | { |
67 | int num = iwp->length; | 67 | int num = iwp->length; |
68 | int max = args & IW_PRIV_SIZE_MASK; | 68 | int max = args & IW_PRIV_SIZE_MASK; |
69 | int type = (args & IW_PRIV_TYPE_MASK) >> 12; | 69 | int type = (args & IW_PRIV_TYPE_MASK) >> 12; |
70 | 70 | ||
71 | /* Make sure the driver doesn't goof up */ | 71 | /* Make sure the driver doesn't goof up */ |
72 | if (max < num) | 72 | if (max < num) |
73 | num = max; | 73 | num = max; |
74 | 74 | ||
75 | return num * iw_priv_type_size[type]; | 75 | return num * iw_priv_type_size[type]; |
76 | } | 76 | } |
77 | 77 | ||
78 | /* | 78 | /* |
79 | * Wrapper to call a private Wireless Extension handler. | 79 | * Wrapper to call a private Wireless Extension handler. |
80 | * We do various checks and also take care of moving data between | 80 | * We do various checks and also take care of moving data between |
81 | * user space and kernel space. | 81 | * user space and kernel space. |
82 | * It's not as nice and slimline as the standard wrapper. The cause | 82 | * It's not as nice and slimline as the standard wrapper. The cause |
83 | * is struct iw_priv_args, which was not really designed for the | 83 | * is struct iw_priv_args, which was not really designed for the |
84 | * job we are going here. | 84 | * job we are going here. |
85 | * | 85 | * |
86 | * IMPORTANT : This function prevent to set and get data on the same | 86 | * IMPORTANT : This function prevent to set and get data on the same |
87 | * IOCTL and enforce the SET/GET convention. Not doing it would be | 87 | * IOCTL and enforce the SET/GET convention. Not doing it would be |
88 | * far too hairy... | 88 | * far too hairy... |
89 | * If you need to set and get data at the same time, please don't use | 89 | * If you need to set and get data at the same time, please don't use |
90 | * a iw_handler but process it in your ioctl handler (i.e. use the | 90 | * a iw_handler but process it in your ioctl handler (i.e. use the |
91 | * old driver API). | 91 | * old driver API). |
92 | */ | 92 | */ |
93 | static int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd, | 93 | static int get_priv_descr_and_size(struct net_device *dev, unsigned int cmd, |
94 | const struct iw_priv_args **descrp) | 94 | const struct iw_priv_args **descrp) |
95 | { | 95 | { |
96 | const struct iw_priv_args *descr; | 96 | const struct iw_priv_args *descr; |
97 | int i, extra_size; | 97 | int i, extra_size; |
98 | 98 | ||
99 | descr = NULL; | 99 | descr = NULL; |
100 | for (i = 0; i < dev->wireless_handlers->num_private_args; i++) { | 100 | for (i = 0; i < dev->wireless_handlers->num_private_args; i++) { |
101 | if (cmd == dev->wireless_handlers->private_args[i].cmd) { | 101 | if (cmd == dev->wireless_handlers->private_args[i].cmd) { |
102 | descr = &dev->wireless_handlers->private_args[i]; | 102 | descr = &dev->wireless_handlers->private_args[i]; |
103 | break; | 103 | break; |
104 | } | 104 | } |
105 | } | 105 | } |
106 | 106 | ||
107 | extra_size = 0; | 107 | extra_size = 0; |
108 | if (descr) { | 108 | if (descr) { |
109 | if (IW_IS_SET(cmd)) { | 109 | if (IW_IS_SET(cmd)) { |
110 | int offset = 0; /* For sub-ioctls */ | 110 | int offset = 0; /* For sub-ioctls */ |
111 | /* Check for sub-ioctl handler */ | 111 | /* Check for sub-ioctl handler */ |
112 | if (descr->name[0] == '\0') | 112 | if (descr->name[0] == '\0') |
113 | /* Reserve one int for sub-ioctl index */ | 113 | /* Reserve one int for sub-ioctl index */ |
114 | offset = sizeof(__u32); | 114 | offset = sizeof(__u32); |
115 | 115 | ||
116 | /* Size of set arguments */ | 116 | /* Size of set arguments */ |
117 | extra_size = get_priv_size(descr->set_args); | 117 | extra_size = get_priv_size(descr->set_args); |
118 | 118 | ||
119 | /* Does it fits in iwr ? */ | 119 | /* Does it fits in iwr ? */ |
120 | if ((descr->set_args & IW_PRIV_SIZE_FIXED) && | 120 | if ((descr->set_args & IW_PRIV_SIZE_FIXED) && |
121 | ((extra_size + offset) <= IFNAMSIZ)) | 121 | ((extra_size + offset) <= IFNAMSIZ)) |
122 | extra_size = 0; | 122 | extra_size = 0; |
123 | } else { | 123 | } else { |
124 | /* Size of get arguments */ | 124 | /* Size of get arguments */ |
125 | extra_size = get_priv_size(descr->get_args); | 125 | extra_size = get_priv_size(descr->get_args); |
126 | 126 | ||
127 | /* Does it fits in iwr ? */ | 127 | /* Does it fits in iwr ? */ |
128 | if ((descr->get_args & IW_PRIV_SIZE_FIXED) && | 128 | if ((descr->get_args & IW_PRIV_SIZE_FIXED) && |
129 | (extra_size <= IFNAMSIZ)) | 129 | (extra_size <= IFNAMSIZ)) |
130 | extra_size = 0; | 130 | extra_size = 0; |
131 | } | 131 | } |
132 | } | 132 | } |
133 | *descrp = descr; | 133 | *descrp = descr; |
134 | return extra_size; | 134 | return extra_size; |
135 | } | 135 | } |
136 | 136 | ||
137 | static int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd, | 137 | static int ioctl_private_iw_point(struct iw_point *iwp, unsigned int cmd, |
138 | const struct iw_priv_args *descr, | 138 | const struct iw_priv_args *descr, |
139 | iw_handler handler, struct net_device *dev, | 139 | iw_handler handler, struct net_device *dev, |
140 | struct iw_request_info *info, int extra_size) | 140 | struct iw_request_info *info, int extra_size) |
141 | { | 141 | { |
142 | char *extra; | 142 | char *extra; |
143 | int err; | 143 | int err; |
144 | 144 | ||
145 | /* Check what user space is giving us */ | 145 | /* Check what user space is giving us */ |
146 | if (IW_IS_SET(cmd)) { | 146 | if (IW_IS_SET(cmd)) { |
147 | if (!iwp->pointer && iwp->length != 0) | 147 | if (!iwp->pointer && iwp->length != 0) |
148 | return -EFAULT; | 148 | return -EFAULT; |
149 | 149 | ||
150 | if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK)) | 150 | if (iwp->length > (descr->set_args & IW_PRIV_SIZE_MASK)) |
151 | return -E2BIG; | 151 | return -E2BIG; |
152 | } else if (!iwp->pointer) | 152 | } else if (!iwp->pointer) |
153 | return -EFAULT; | 153 | return -EFAULT; |
154 | 154 | ||
155 | extra = kmalloc(extra_size, GFP_KERNEL); | 155 | extra = kzalloc(extra_size, GFP_KERNEL); |
156 | if (!extra) | 156 | if (!extra) |
157 | return -ENOMEM; | 157 | return -ENOMEM; |
158 | 158 | ||
159 | /* If it is a SET, get all the extra data in here */ | 159 | /* If it is a SET, get all the extra data in here */ |
160 | if (IW_IS_SET(cmd) && (iwp->length != 0)) { | 160 | if (IW_IS_SET(cmd) && (iwp->length != 0)) { |
161 | if (copy_from_user(extra, iwp->pointer, extra_size)) { | 161 | if (copy_from_user(extra, iwp->pointer, extra_size)) { |
162 | err = -EFAULT; | 162 | err = -EFAULT; |
163 | goto out; | 163 | goto out; |
164 | } | 164 | } |
165 | } | 165 | } |
166 | 166 | ||
167 | /* Call the handler */ | 167 | /* Call the handler */ |
168 | err = handler(dev, info, (union iwreq_data *) iwp, extra); | 168 | err = handler(dev, info, (union iwreq_data *) iwp, extra); |
169 | 169 | ||
170 | /* If we have something to return to the user */ | 170 | /* If we have something to return to the user */ |
171 | if (!err && IW_IS_GET(cmd)) { | 171 | if (!err && IW_IS_GET(cmd)) { |
172 | /* Adjust for the actual length if it's variable, | 172 | /* Adjust for the actual length if it's variable, |
173 | * avoid leaking kernel bits outside. | 173 | * avoid leaking kernel bits outside. |
174 | */ | 174 | */ |
175 | if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) | 175 | if (!(descr->get_args & IW_PRIV_SIZE_FIXED)) |
176 | extra_size = adjust_priv_size(descr->get_args, iwp); | 176 | extra_size = adjust_priv_size(descr->get_args, iwp); |
177 | 177 | ||
178 | if (copy_to_user(iwp->pointer, extra, extra_size)) | 178 | if (copy_to_user(iwp->pointer, extra, extra_size)) |
179 | err = -EFAULT; | 179 | err = -EFAULT; |
180 | } | 180 | } |
181 | 181 | ||
182 | out: | 182 | out: |
183 | kfree(extra); | 183 | kfree(extra); |
184 | return err; | 184 | return err; |
185 | } | 185 | } |
186 | 186 | ||
187 | int ioctl_private_call(struct net_device *dev, struct iwreq *iwr, | 187 | int ioctl_private_call(struct net_device *dev, struct iwreq *iwr, |
188 | unsigned int cmd, struct iw_request_info *info, | 188 | unsigned int cmd, struct iw_request_info *info, |
189 | iw_handler handler) | 189 | iw_handler handler) |
190 | { | 190 | { |
191 | int extra_size = 0, ret = -EINVAL; | 191 | int extra_size = 0, ret = -EINVAL; |
192 | const struct iw_priv_args *descr; | 192 | const struct iw_priv_args *descr; |
193 | 193 | ||
194 | extra_size = get_priv_descr_and_size(dev, cmd, &descr); | 194 | extra_size = get_priv_descr_and_size(dev, cmd, &descr); |
195 | 195 | ||
196 | /* Check if we have a pointer to user space data or not. */ | 196 | /* Check if we have a pointer to user space data or not. */ |
197 | if (extra_size == 0) { | 197 | if (extra_size == 0) { |
198 | /* No extra arguments. Trivial to handle */ | 198 | /* No extra arguments. Trivial to handle */ |
199 | ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); | 199 | ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); |
200 | } else { | 200 | } else { |
201 | ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr, | 201 | ret = ioctl_private_iw_point(&iwr->u.data, cmd, descr, |
202 | handler, dev, info, extra_size); | 202 | handler, dev, info, extra_size); |
203 | } | 203 | } |
204 | 204 | ||
205 | /* Call commit handler if needed and defined */ | 205 | /* Call commit handler if needed and defined */ |
206 | if (ret == -EIWCOMMIT) | 206 | if (ret == -EIWCOMMIT) |
207 | ret = call_commit_handler(dev); | 207 | ret = call_commit_handler(dev); |
208 | 208 | ||
209 | return ret; | 209 | return ret; |
210 | } | 210 | } |
211 | 211 | ||
212 | #ifdef CONFIG_COMPAT | 212 | #ifdef CONFIG_COMPAT |
213 | int compat_private_call(struct net_device *dev, struct iwreq *iwr, | 213 | int compat_private_call(struct net_device *dev, struct iwreq *iwr, |
214 | unsigned int cmd, struct iw_request_info *info, | 214 | unsigned int cmd, struct iw_request_info *info, |
215 | iw_handler handler) | 215 | iw_handler handler) |
216 | { | 216 | { |
217 | const struct iw_priv_args *descr; | 217 | const struct iw_priv_args *descr; |
218 | int ret, extra_size; | 218 | int ret, extra_size; |
219 | 219 | ||
220 | extra_size = get_priv_descr_and_size(dev, cmd, &descr); | 220 | extra_size = get_priv_descr_and_size(dev, cmd, &descr); |
221 | 221 | ||
222 | /* Check if we have a pointer to user space data or not. */ | 222 | /* Check if we have a pointer to user space data or not. */ |
223 | if (extra_size == 0) { | 223 | if (extra_size == 0) { |
224 | /* No extra arguments. Trivial to handle */ | 224 | /* No extra arguments. Trivial to handle */ |
225 | ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); | 225 | ret = handler(dev, info, &(iwr->u), (char *) &(iwr->u)); |
226 | } else { | 226 | } else { |
227 | struct compat_iw_point *iwp_compat; | 227 | struct compat_iw_point *iwp_compat; |
228 | struct iw_point iwp; | 228 | struct iw_point iwp; |
229 | 229 | ||
230 | iwp_compat = (struct compat_iw_point *) &iwr->u.data; | 230 | iwp_compat = (struct compat_iw_point *) &iwr->u.data; |
231 | iwp.pointer = compat_ptr(iwp_compat->pointer); | 231 | iwp.pointer = compat_ptr(iwp_compat->pointer); |
232 | iwp.length = iwp_compat->length; | 232 | iwp.length = iwp_compat->length; |
233 | iwp.flags = iwp_compat->flags; | 233 | iwp.flags = iwp_compat->flags; |
234 | 234 | ||
235 | ret = ioctl_private_iw_point(&iwp, cmd, descr, | 235 | ret = ioctl_private_iw_point(&iwp, cmd, descr, |
236 | handler, dev, info, extra_size); | 236 | handler, dev, info, extra_size); |
237 | 237 | ||
238 | iwp_compat->pointer = ptr_to_compat(iwp.pointer); | 238 | iwp_compat->pointer = ptr_to_compat(iwp.pointer); |
239 | iwp_compat->length = iwp.length; | 239 | iwp_compat->length = iwp.length; |
240 | iwp_compat->flags = iwp.flags; | 240 | iwp_compat->flags = iwp.flags; |
241 | } | 241 | } |
242 | 242 | ||
243 | /* Call commit handler if needed and defined */ | 243 | /* Call commit handler if needed and defined */ |
244 | if (ret == -EIWCOMMIT) | 244 | if (ret == -EIWCOMMIT) |
245 | ret = call_commit_handler(dev); | 245 | ret = call_commit_handler(dev); |
246 | 246 | ||
247 | return ret; | 247 | return ret; |
248 | } | 248 | } |
249 | #endif | 249 | #endif |
250 | 250 |