diff options
author | Johannes Berg <johannes@sipsolutions.net> | 2009-03-24 04:35:46 -0400 |
---|---|---|
committer | John W. Linville <linville@tuxdriver.com> | 2009-03-27 20:13:20 -0400 |
commit | 4bbf4d56583dd52c429d88f43cb614bdbe5deea6 (patch) | |
tree | 7a3f902a08820342254e0d67607fe870b02620b3 | |
parent | 3832c287f11ba001bbe48e9be8c59cb9f71f6b43 (diff) |
cfg80211: fix locking in nl80211_set_wiphy
Luis reports that there's a circular locking dependency;
this is because cfg80211_dev_rename() will acquire the
cfg80211_mutex while the device mutex is held, while
this normally is done the other way around. The solution
is to open-code the device-getting in nl80211_set_wiphy
and require holding the mutex around cfg80211_dev_rename
rather than acquiring it within.
Also fix a bug -- rtnl locking is expected by drivers so
we need to provide it.
Reported-by: Luis R. Rodriguez <lrodriguez@atheros.com>
Signed-off-by: Johannes Berg <johannes@sipsolutions.net>
Signed-off-by: John W. Linville <linville@tuxdriver.com>
-rw-r--r-- | net/wireless/core.c | 30 | ||||
-rw-r--r-- | net/wireless/core.h | 3 | ||||
-rw-r--r-- | net/wireless/nl80211.c | 28 |
3 files changed, 33 insertions, 28 deletions
diff --git a/net/wireless/core.c b/net/wireless/core.c index 17fe39049740..d1f556535f6d 100644 --- a/net/wireless/core.c +++ b/net/wireless/core.c | |||
@@ -87,7 +87,7 @@ struct wiphy *wiphy_idx_to_wiphy(int wiphy_idx) | |||
87 | } | 87 | } |
88 | 88 | ||
89 | /* requires cfg80211_mutex to be held! */ | 89 | /* requires cfg80211_mutex to be held! */ |
90 | static struct cfg80211_registered_device * | 90 | struct cfg80211_registered_device * |
91 | __cfg80211_drv_from_info(struct genl_info *info) | 91 | __cfg80211_drv_from_info(struct genl_info *info) |
92 | { | 92 | { |
93 | int ifindex; | 93 | int ifindex; |
@@ -176,13 +176,14 @@ void cfg80211_put_dev(struct cfg80211_registered_device *drv) | |||
176 | mutex_unlock(&drv->mtx); | 176 | mutex_unlock(&drv->mtx); |
177 | } | 177 | } |
178 | 178 | ||
179 | /* requires cfg80211_mutex to be held */ | ||
179 | int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, | 180 | int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, |
180 | char *newname) | 181 | char *newname) |
181 | { | 182 | { |
182 | struct cfg80211_registered_device *drv; | 183 | struct cfg80211_registered_device *drv; |
183 | int wiphy_idx, taken = -1, result, digits; | 184 | int wiphy_idx, taken = -1, result, digits; |
184 | 185 | ||
185 | mutex_lock(&cfg80211_mutex); | 186 | assert_cfg80211_lock(); |
186 | 187 | ||
187 | /* prohibit calling the thing phy%d when %d is not its number */ | 188 | /* prohibit calling the thing phy%d when %d is not its number */ |
188 | sscanf(newname, PHY_NAME "%d%n", &wiphy_idx, &taken); | 189 | sscanf(newname, PHY_NAME "%d%n", &wiphy_idx, &taken); |
@@ -195,30 +196,23 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, | |||
195 | * deny the name if it is phy<idx> where <idx> is printed | 196 | * deny the name if it is phy<idx> where <idx> is printed |
196 | * without leading zeroes. taken == strlen(newname) here | 197 | * without leading zeroes. taken == strlen(newname) here |
197 | */ | 198 | */ |
198 | result = -EINVAL; | ||
199 | if (taken == strlen(PHY_NAME) + digits) | 199 | if (taken == strlen(PHY_NAME) + digits) |
200 | goto out_unlock; | 200 | return -EINVAL; |
201 | } | 201 | } |
202 | 202 | ||
203 | 203 | ||
204 | /* Ignore nop renames */ | 204 | /* Ignore nop renames */ |
205 | result = 0; | ||
206 | if (strcmp(newname, dev_name(&rdev->wiphy.dev)) == 0) | 205 | if (strcmp(newname, dev_name(&rdev->wiphy.dev)) == 0) |
207 | goto out_unlock; | 206 | return 0; |
208 | 207 | ||
209 | /* Ensure another device does not already have this name. */ | 208 | /* Ensure another device does not already have this name. */ |
210 | list_for_each_entry(drv, &cfg80211_drv_list, list) { | 209 | list_for_each_entry(drv, &cfg80211_drv_list, list) |
211 | result = -EINVAL; | ||
212 | if (strcmp(newname, dev_name(&drv->wiphy.dev)) == 0) | 210 | if (strcmp(newname, dev_name(&drv->wiphy.dev)) == 0) |
213 | goto out_unlock; | 211 | return -EINVAL; |
214 | } | ||
215 | 212 | ||
216 | /* this will only check for collisions in sysfs | ||
217 | * which is not even always compiled in. | ||
218 | */ | ||
219 | result = device_rename(&rdev->wiphy.dev, newname); | 213 | result = device_rename(&rdev->wiphy.dev, newname); |
220 | if (result) | 214 | if (result) |
221 | goto out_unlock; | 215 | return result; |
222 | 216 | ||
223 | if (rdev->wiphy.debugfsdir && | 217 | if (rdev->wiphy.debugfsdir && |
224 | !debugfs_rename(rdev->wiphy.debugfsdir->d_parent, | 218 | !debugfs_rename(rdev->wiphy.debugfsdir->d_parent, |
@@ -228,13 +222,9 @@ int cfg80211_dev_rename(struct cfg80211_registered_device *rdev, | |||
228 | printk(KERN_ERR "cfg80211: failed to rename debugfs dir to %s!\n", | 222 | printk(KERN_ERR "cfg80211: failed to rename debugfs dir to %s!\n", |
229 | newname); | 223 | newname); |
230 | 224 | ||
231 | result = 0; | 225 | nl80211_notify_dev_rename(rdev); |
232 | out_unlock: | ||
233 | mutex_unlock(&cfg80211_mutex); | ||
234 | if (result == 0) | ||
235 | nl80211_notify_dev_rename(rdev); | ||
236 | 226 | ||
237 | return result; | 227 | return 0; |
238 | } | 228 | } |
239 | 229 | ||
240 | /* exported functions */ | 230 | /* exported functions */ |
diff --git a/net/wireless/core.h b/net/wireless/core.h index 97a6fd8b2b03..d43daa236ef9 100644 --- a/net/wireless/core.h +++ b/net/wireless/core.h | |||
@@ -99,6 +99,9 @@ struct cfg80211_internal_bss { | |||
99 | struct cfg80211_registered_device *cfg80211_drv_by_wiphy_idx(int wiphy_idx); | 99 | struct cfg80211_registered_device *cfg80211_drv_by_wiphy_idx(int wiphy_idx); |
100 | int get_wiphy_idx(struct wiphy *wiphy); | 100 | int get_wiphy_idx(struct wiphy *wiphy); |
101 | 101 | ||
102 | struct cfg80211_registered_device * | ||
103 | __cfg80211_drv_from_info(struct genl_info *info); | ||
104 | |||
102 | /* | 105 | /* |
103 | * This function returns a pointer to the driver | 106 | * This function returns a pointer to the driver |
104 | * that the genl_info item that is passed refers to. | 107 | * that the genl_info item that is passed refers to. |
diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 8808431bd581..353e1a4ece83 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c | |||
@@ -366,16 +366,26 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) | |||
366 | int result = 0, rem_txq_params = 0; | 366 | int result = 0, rem_txq_params = 0; |
367 | struct nlattr *nl_txq_params; | 367 | struct nlattr *nl_txq_params; |
368 | 368 | ||
369 | rdev = cfg80211_get_dev_from_info(info); | 369 | rtnl_lock(); |
370 | if (IS_ERR(rdev)) | 370 | |
371 | return PTR_ERR(rdev); | 371 | mutex_lock(&cfg80211_mutex); |
372 | |||
373 | rdev = __cfg80211_drv_from_info(info); | ||
374 | if (IS_ERR(rdev)) { | ||
375 | result = PTR_ERR(rdev); | ||
376 | goto unlock; | ||
377 | } | ||
372 | 378 | ||
373 | if (info->attrs[NL80211_ATTR_WIPHY_NAME]) { | 379 | mutex_lock(&rdev->mtx); |
380 | |||
381 | if (info->attrs[NL80211_ATTR_WIPHY_NAME]) | ||
374 | result = cfg80211_dev_rename( | 382 | result = cfg80211_dev_rename( |
375 | rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME])); | 383 | rdev, nla_data(info->attrs[NL80211_ATTR_WIPHY_NAME])); |
376 | if (result) | 384 | |
377 | goto bad_res; | 385 | mutex_unlock(&cfg80211_mutex); |
378 | } | 386 | |
387 | if (result) | ||
388 | goto bad_res; | ||
379 | 389 | ||
380 | if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) { | 390 | if (info->attrs[NL80211_ATTR_WIPHY_TXQ_PARAMS]) { |
381 | struct ieee80211_txq_params txq_params; | 391 | struct ieee80211_txq_params txq_params; |
@@ -471,7 +481,9 @@ static int nl80211_set_wiphy(struct sk_buff *skb, struct genl_info *info) | |||
471 | 481 | ||
472 | 482 | ||
473 | bad_res: | 483 | bad_res: |
474 | cfg80211_put_dev(rdev); | 484 | mutex_unlock(&rdev->mtx); |
485 | unlock: | ||
486 | rtnl_unlock(); | ||
475 | return result; | 487 | return result; |
476 | } | 488 | } |
477 | 489 | ||