diff options
author | Johannes Berg <johannes.berg@intel.com> | 2012-11-28 19:25:20 -0500 |
---|---|---|
committer | Johannes Berg <johannes.berg@intel.com> | 2012-11-30 07:42:20 -0500 |
commit | 9caf03640279e64d0ba36539b42daa1b43a49486 (patch) | |
tree | cb094a4a577f61421d1b402e16f0e68f151d5726 /net/wireless/scan.c | |
parent | b9a9ada14aab17f08c1d9735601f1097cdcfc6de (diff) |
cfg80211: fix BSS struct IE access races
When a BSS struct is updated, the IEs are currently
overwritten or freed. This can lead to races if some
other CPU is accessing the BSS struct and using the
IEs concurrently.
Fix this by always allocating the IEs in a new struct
that holds the data and length and protecting access
to this new struct with RCU.
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
Diffstat (limited to 'net/wireless/scan.c')
-rw-r--r-- | net/wireless/scan.c | 409 |
1 files changed, 200 insertions, 209 deletions
diff --git a/net/wireless/scan.c b/net/wireless/scan.c index 834e0d153fbe..01592d7d4789 100644 --- a/net/wireless/scan.c +++ b/net/wireless/scan.c | |||
@@ -23,6 +23,7 @@ | |||
23 | 23 | ||
24 | static void bss_release(struct kref *ref) | 24 | static void bss_release(struct kref *ref) |
25 | { | 25 | { |
26 | struct cfg80211_bss_ies *ies; | ||
26 | struct cfg80211_internal_bss *bss; | 27 | struct cfg80211_internal_bss *bss; |
27 | 28 | ||
28 | bss = container_of(ref, struct cfg80211_internal_bss, ref); | 29 | bss = container_of(ref, struct cfg80211_internal_bss, ref); |
@@ -33,10 +34,12 @@ static void bss_release(struct kref *ref) | |||
33 | if (bss->pub.free_priv) | 34 | if (bss->pub.free_priv) |
34 | bss->pub.free_priv(&bss->pub); | 35 | bss->pub.free_priv(&bss->pub); |
35 | 36 | ||
36 | if (bss->beacon_ies_allocated) | 37 | ies = (void *)rcu_access_pointer(bss->pub.beacon_ies); |
37 | kfree(bss->pub.beacon_ies); | 38 | if (ies) |
38 | if (bss->proberesp_ies_allocated) | 39 | kfree_rcu(ies, rcu_head); |
39 | kfree(bss->pub.proberesp_ies); | 40 | ies = (void *)rcu_access_pointer(bss->pub.proberesp_ies); |
41 | if (ies) | ||
42 | kfree_rcu(ies, rcu_head); | ||
40 | 43 | ||
41 | kfree(bss); | 44 | kfree(bss); |
42 | } | 45 | } |
@@ -288,7 +291,7 @@ const u8 *cfg80211_find_vendor_ie(unsigned int oui, u8 oui_type, | |||
288 | } | 291 | } |
289 | EXPORT_SYMBOL(cfg80211_find_vendor_ie); | 292 | EXPORT_SYMBOL(cfg80211_find_vendor_ie); |
290 | 293 | ||
291 | static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2) | 294 | static int cmp_ies(u8 num, const u8 *ies1, int len1, const u8 *ies2, int len2) |
292 | { | 295 | { |
293 | const u8 *ie1 = cfg80211_find_ie(num, ies1, len1); | 296 | const u8 *ie1 = cfg80211_find_ie(num, ies1, len1); |
294 | const u8 *ie2 = cfg80211_find_ie(num, ies2, len2); | 297 | const u8 *ie2 = cfg80211_find_ie(num, ies2, len2); |
@@ -311,6 +314,7 @@ static int cmp_ies(u8 num, u8 *ies1, size_t len1, u8 *ies2, size_t len2) | |||
311 | static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, | 314 | static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, |
312 | const u8 *ssid, size_t ssid_len) | 315 | const u8 *ssid, size_t ssid_len) |
313 | { | 316 | { |
317 | const struct cfg80211_bss_ies *ies; | ||
314 | const u8 *ssidie; | 318 | const u8 *ssidie; |
315 | 319 | ||
316 | if (bssid && !ether_addr_equal(a->bssid, bssid)) | 320 | if (bssid && !ether_addr_equal(a->bssid, bssid)) |
@@ -319,9 +323,10 @@ static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, | |||
319 | if (!ssid) | 323 | if (!ssid) |
320 | return true; | 324 | return true; |
321 | 325 | ||
322 | ssidie = cfg80211_find_ie(WLAN_EID_SSID, | 326 | ies = rcu_access_pointer(a->ies); |
323 | a->information_elements, | 327 | if (!ies) |
324 | a->len_information_elements); | 328 | return false; |
329 | ssidie = cfg80211_find_ie(WLAN_EID_SSID, ies->data, ies->len); | ||
325 | if (!ssidie) | 330 | if (!ssidie) |
326 | return false; | 331 | return false; |
327 | if (ssidie[1] != ssid_len) | 332 | if (ssidie[1] != ssid_len) |
@@ -331,20 +336,21 @@ static bool is_bss(struct cfg80211_bss *a, const u8 *bssid, | |||
331 | 336 | ||
332 | static bool is_mesh_bss(struct cfg80211_bss *a) | 337 | static bool is_mesh_bss(struct cfg80211_bss *a) |
333 | { | 338 | { |
339 | const struct cfg80211_bss_ies *ies; | ||
334 | const u8 *ie; | 340 | const u8 *ie; |
335 | 341 | ||
336 | if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability)) | 342 | if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability)) |
337 | return false; | 343 | return false; |
338 | 344 | ||
339 | ie = cfg80211_find_ie(WLAN_EID_MESH_ID, | 345 | ies = rcu_access_pointer(a->ies); |
340 | a->information_elements, | 346 | if (!ies) |
341 | a->len_information_elements); | 347 | return false; |
348 | |||
349 | ie = cfg80211_find_ie(WLAN_EID_MESH_ID, ies->data, ies->len); | ||
342 | if (!ie) | 350 | if (!ie) |
343 | return false; | 351 | return false; |
344 | 352 | ||
345 | ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, | 353 | ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, ies->data, ies->len); |
346 | a->information_elements, | ||
347 | a->len_information_elements); | ||
348 | if (!ie) | 354 | if (!ie) |
349 | return false; | 355 | return false; |
350 | 356 | ||
@@ -355,14 +361,17 @@ static bool is_mesh(struct cfg80211_bss *a, | |||
355 | const u8 *meshid, size_t meshidlen, | 361 | const u8 *meshid, size_t meshidlen, |
356 | const u8 *meshcfg) | 362 | const u8 *meshcfg) |
357 | { | 363 | { |
364 | const struct cfg80211_bss_ies *ies; | ||
358 | const u8 *ie; | 365 | const u8 *ie; |
359 | 366 | ||
360 | if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability)) | 367 | if (!WLAN_CAPABILITY_IS_STA_BSS(a->capability)) |
361 | return false; | 368 | return false; |
362 | 369 | ||
363 | ie = cfg80211_find_ie(WLAN_EID_MESH_ID, | 370 | ies = rcu_access_pointer(a->ies); |
364 | a->information_elements, | 371 | if (!ies) |
365 | a->len_information_elements); | 372 | return false; |
373 | |||
374 | ie = cfg80211_find_ie(WLAN_EID_MESH_ID, ies->data, ies->len); | ||
366 | if (!ie) | 375 | if (!ie) |
367 | return false; | 376 | return false; |
368 | if (ie[1] != meshidlen) | 377 | if (ie[1] != meshidlen) |
@@ -370,9 +379,7 @@ static bool is_mesh(struct cfg80211_bss *a, | |||
370 | if (memcmp(ie + 2, meshid, meshidlen)) | 379 | if (memcmp(ie + 2, meshid, meshidlen)) |
371 | return false; | 380 | return false; |
372 | 381 | ||
373 | ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, | 382 | ie = cfg80211_find_ie(WLAN_EID_MESH_CONFIG, ies->data, ies->len); |
374 | a->information_elements, | ||
375 | a->len_information_elements); | ||
376 | if (!ie) | 383 | if (!ie) |
377 | return false; | 384 | return false; |
378 | if (ie[1] != sizeof(struct ieee80211_meshconf_ie)) | 385 | if (ie[1] != sizeof(struct ieee80211_meshconf_ie)) |
@@ -389,24 +396,28 @@ static bool is_mesh(struct cfg80211_bss *a, | |||
389 | 396 | ||
390 | static int cmp_bss_core(struct cfg80211_bss *a, struct cfg80211_bss *b) | 397 | static int cmp_bss_core(struct cfg80211_bss *a, struct cfg80211_bss *b) |
391 | { | 398 | { |
399 | const struct cfg80211_bss_ies *a_ies, *b_ies; | ||
392 | int r; | 400 | int r; |
393 | 401 | ||
394 | if (a->channel != b->channel) | 402 | if (a->channel != b->channel) |
395 | return b->channel->center_freq - a->channel->center_freq; | 403 | return b->channel->center_freq - a->channel->center_freq; |
396 | 404 | ||
397 | if (is_mesh_bss(a) && is_mesh_bss(b)) { | 405 | if (is_mesh_bss(a) && is_mesh_bss(b)) { |
406 | a_ies = rcu_access_pointer(a->ies); | ||
407 | if (!a_ies) | ||
408 | return -1; | ||
409 | b_ies = rcu_access_pointer(b->ies); | ||
410 | if (!b_ies) | ||
411 | return 1; | ||
412 | |||
398 | r = cmp_ies(WLAN_EID_MESH_ID, | 413 | r = cmp_ies(WLAN_EID_MESH_ID, |
399 | a->information_elements, | 414 | a_ies->data, a_ies->len, |
400 | a->len_information_elements, | 415 | b_ies->data, b_ies->len); |
401 | b->information_elements, | ||
402 | b->len_information_elements); | ||
403 | if (r) | 416 | if (r) |
404 | return r; | 417 | return r; |
405 | return cmp_ies(WLAN_EID_MESH_CONFIG, | 418 | return cmp_ies(WLAN_EID_MESH_CONFIG, |
406 | a->information_elements, | 419 | a_ies->data, a_ies->len, |
407 | a->len_information_elements, | 420 | b_ies->data, b_ies->len); |
408 | b->information_elements, | ||
409 | b->len_information_elements); | ||
410 | } | 421 | } |
411 | 422 | ||
412 | /* | 423 | /* |
@@ -419,21 +430,28 @@ static int cmp_bss_core(struct cfg80211_bss *a, struct cfg80211_bss *b) | |||
419 | static int cmp_bss(struct cfg80211_bss *a, | 430 | static int cmp_bss(struct cfg80211_bss *a, |
420 | struct cfg80211_bss *b) | 431 | struct cfg80211_bss *b) |
421 | { | 432 | { |
433 | const struct cfg80211_bss_ies *a_ies, *b_ies; | ||
422 | int r; | 434 | int r; |
423 | 435 | ||
424 | r = cmp_bss_core(a, b); | 436 | r = cmp_bss_core(a, b); |
425 | if (r) | 437 | if (r) |
426 | return r; | 438 | return r; |
427 | 439 | ||
440 | a_ies = rcu_access_pointer(a->ies); | ||
441 | if (!a_ies) | ||
442 | return -1; | ||
443 | b_ies = rcu_access_pointer(b->ies); | ||
444 | if (!b_ies) | ||
445 | return 1; | ||
446 | |||
428 | return cmp_ies(WLAN_EID_SSID, | 447 | return cmp_ies(WLAN_EID_SSID, |
429 | a->information_elements, | 448 | a_ies->data, a_ies->len, |
430 | a->len_information_elements, | 449 | b_ies->data, b_ies->len); |
431 | b->information_elements, | ||
432 | b->len_information_elements); | ||
433 | } | 450 | } |
434 | 451 | ||
435 | static int cmp_hidden_bss(struct cfg80211_bss *a, struct cfg80211_bss *b) | 452 | static int cmp_hidden_bss(struct cfg80211_bss *a, struct cfg80211_bss *b) |
436 | { | 453 | { |
454 | const struct cfg80211_bss_ies *a_ies, *b_ies; | ||
437 | const u8 *ie1; | 455 | const u8 *ie1; |
438 | const u8 *ie2; | 456 | const u8 *ie2; |
439 | int i; | 457 | int i; |
@@ -443,12 +461,15 @@ static int cmp_hidden_bss(struct cfg80211_bss *a, struct cfg80211_bss *b) | |||
443 | if (r) | 461 | if (r) |
444 | return r; | 462 | return r; |
445 | 463 | ||
446 | ie1 = cfg80211_find_ie(WLAN_EID_SSID, | 464 | a_ies = rcu_access_pointer(a->ies); |
447 | a->information_elements, | 465 | if (!a_ies) |
448 | a->len_information_elements); | 466 | return -1; |
449 | ie2 = cfg80211_find_ie(WLAN_EID_SSID, | 467 | b_ies = rcu_access_pointer(b->ies); |
450 | b->information_elements, | 468 | if (!b_ies) |
451 | b->len_information_elements); | 469 | return 1; |
470 | |||
471 | ie1 = cfg80211_find_ie(WLAN_EID_SSID, a_ies->data, a_ies->len); | ||
472 | ie2 = cfg80211_find_ie(WLAN_EID_SSID, b_ies->data, b_ies->len); | ||
452 | 473 | ||
453 | /* | 474 | /* |
454 | * Key comparator must use same algorithm in any rb-tree | 475 | * Key comparator must use same algorithm in any rb-tree |
@@ -633,126 +654,84 @@ static void | |||
633 | copy_hidden_ies(struct cfg80211_internal_bss *res, | 654 | copy_hidden_ies(struct cfg80211_internal_bss *res, |
634 | struct cfg80211_internal_bss *hidden) | 655 | struct cfg80211_internal_bss *hidden) |
635 | { | 656 | { |
636 | if (unlikely(res->pub.beacon_ies)) | 657 | const struct cfg80211_bss_ies *ies; |
637 | return; | 658 | |
638 | if (WARN_ON(!hidden->pub.beacon_ies)) | 659 | if (rcu_access_pointer(res->pub.beacon_ies)) |
639 | return; | 660 | return; |
640 | 661 | ||
641 | res->pub.beacon_ies = kmalloc(hidden->pub.len_beacon_ies, GFP_ATOMIC); | 662 | ies = rcu_access_pointer(hidden->pub.beacon_ies); |
642 | if (unlikely(!res->pub.beacon_ies)) | 663 | if (WARN_ON(!ies)) |
643 | return; | 664 | return; |
644 | 665 | ||
645 | res->beacon_ies_allocated = true; | 666 | ies = kmemdup(ies, sizeof(*ies) + ies->len, GFP_ATOMIC); |
646 | res->pub.len_beacon_ies = hidden->pub.len_beacon_ies; | 667 | if (unlikely(!ies)) |
647 | memcpy(res->pub.beacon_ies, hidden->pub.beacon_ies, | 668 | return; |
648 | res->pub.len_beacon_ies); | 669 | rcu_assign_pointer(res->pub.beacon_ies, ies); |
649 | } | 670 | } |
650 | 671 | ||
651 | static struct cfg80211_internal_bss * | 672 | static struct cfg80211_internal_bss * |
652 | cfg80211_bss_update(struct cfg80211_registered_device *dev, | 673 | cfg80211_bss_update(struct cfg80211_registered_device *dev, |
653 | struct cfg80211_internal_bss *res) | 674 | struct cfg80211_internal_bss *tmp) |
654 | { | 675 | { |
655 | struct cfg80211_internal_bss *found = NULL; | 676 | struct cfg80211_internal_bss *found = NULL; |
656 | 677 | ||
657 | /* | 678 | if (WARN_ON(!tmp->pub.channel)) |
658 | * The reference to "res" is donated to this function. | ||
659 | */ | ||
660 | |||
661 | if (WARN_ON(!res->pub.channel)) { | ||
662 | kref_put(&res->ref, bss_release); | ||
663 | return NULL; | 679 | return NULL; |
664 | } | ||
665 | 680 | ||
666 | res->ts = jiffies; | 681 | tmp->ts = jiffies; |
667 | 682 | ||
668 | spin_lock_bh(&dev->bss_lock); | 683 | spin_lock_bh(&dev->bss_lock); |
669 | 684 | ||
670 | found = rb_find_bss(dev, res); | 685 | if (WARN_ON(!rcu_access_pointer(tmp->pub.ies))) { |
686 | spin_unlock_bh(&dev->bss_lock); | ||
687 | return NULL; | ||
688 | } | ||
689 | |||
690 | found = rb_find_bss(dev, tmp); | ||
671 | 691 | ||
672 | if (found) { | 692 | if (found) { |
673 | found->pub.beacon_interval = res->pub.beacon_interval; | 693 | found->pub.beacon_interval = tmp->pub.beacon_interval; |
674 | found->pub.tsf = res->pub.tsf; | 694 | found->pub.tsf = tmp->pub.tsf; |
675 | found->pub.signal = res->pub.signal; | 695 | found->pub.signal = tmp->pub.signal; |
676 | found->pub.capability = res->pub.capability; | 696 | found->pub.capability = tmp->pub.capability; |
677 | found->ts = res->ts; | 697 | found->ts = tmp->ts; |
678 | 698 | ||
679 | /* Update IEs */ | 699 | /* Update IEs */ |
680 | if (res->pub.proberesp_ies) { | 700 | if (rcu_access_pointer(tmp->pub.proberesp_ies)) { |
681 | size_t used = dev->wiphy.bss_priv_size + sizeof(*res); | 701 | const struct cfg80211_bss_ies *old; |
682 | size_t ielen = res->pub.len_proberesp_ies; | 702 | |
683 | 703 | old = rcu_access_pointer(found->pub.proberesp_ies); | |
684 | if (found->pub.proberesp_ies && | ||
685 | !found->proberesp_ies_allocated && | ||
686 | ksize(found) >= used + ielen) { | ||
687 | memcpy(found->pub.proberesp_ies, | ||
688 | res->pub.proberesp_ies, ielen); | ||
689 | found->pub.len_proberesp_ies = ielen; | ||
690 | } else { | ||
691 | u8 *ies = found->pub.proberesp_ies; | ||
692 | |||
693 | if (found->proberesp_ies_allocated) | ||
694 | ies = krealloc(ies, ielen, GFP_ATOMIC); | ||
695 | else | ||
696 | ies = kmalloc(ielen, GFP_ATOMIC); | ||
697 | |||
698 | if (ies) { | ||
699 | memcpy(ies, res->pub.proberesp_ies, | ||
700 | ielen); | ||
701 | found->proberesp_ies_allocated = true; | ||
702 | found->pub.proberesp_ies = ies; | ||
703 | found->pub.len_proberesp_ies = ielen; | ||
704 | } | ||
705 | } | ||
706 | 704 | ||
705 | rcu_assign_pointer(found->pub.proberesp_ies, | ||
706 | tmp->pub.proberesp_ies); | ||
707 | /* Override possible earlier Beacon frame IEs */ | 707 | /* Override possible earlier Beacon frame IEs */ |
708 | found->pub.information_elements = | 708 | rcu_assign_pointer(found->pub.ies, |
709 | found->pub.proberesp_ies; | 709 | tmp->pub.proberesp_ies); |
710 | found->pub.len_information_elements = | 710 | if (old) |
711 | found->pub.len_proberesp_ies; | 711 | kfree_rcu((struct cfg80211_bss_ies *)old, |
712 | } | 712 | rcu_head); |
713 | } else if (rcu_access_pointer(tmp->pub.beacon_ies)) { | ||
714 | const struct cfg80211_bss_ies *old, *ies; | ||
713 | 715 | ||
714 | if (res->pub.beacon_ies) { | 716 | old = rcu_access_pointer(found->pub.beacon_ies); |
715 | size_t used = dev->wiphy.bss_priv_size + sizeof(*res); | 717 | ies = rcu_access_pointer(found->pub.ies); |
716 | size_t ielen = res->pub.len_beacon_ies; | 718 | |
717 | bool information_elements_is_beacon_ies = | 719 | rcu_assign_pointer(found->pub.beacon_ies, |
718 | (found->pub.information_elements == | 720 | tmp->pub.beacon_ies); |
719 | found->pub.beacon_ies); | ||
720 | |||
721 | if (found->pub.beacon_ies && | ||
722 | !found->beacon_ies_allocated && | ||
723 | ksize(found) >= used + ielen) { | ||
724 | memcpy(found->pub.beacon_ies, | ||
725 | res->pub.beacon_ies, ielen); | ||
726 | found->pub.len_beacon_ies = ielen; | ||
727 | } else { | ||
728 | u8 *ies = found->pub.beacon_ies; | ||
729 | |||
730 | if (found->beacon_ies_allocated) | ||
731 | ies = krealloc(ies, ielen, GFP_ATOMIC); | ||
732 | else | ||
733 | ies = kmalloc(ielen, GFP_ATOMIC); | ||
734 | |||
735 | if (ies) { | ||
736 | memcpy(ies, res->pub.beacon_ies, | ||
737 | ielen); | ||
738 | found->beacon_ies_allocated = true; | ||
739 | found->pub.beacon_ies = ies; | ||
740 | found->pub.len_beacon_ies = ielen; | ||
741 | } | ||
742 | } | ||
743 | 721 | ||
744 | /* Override IEs if they were from a beacon before */ | 722 | /* Override IEs if they were from a beacon before */ |
745 | if (information_elements_is_beacon_ies) { | 723 | if (old == ies) |
746 | found->pub.information_elements = | 724 | rcu_assign_pointer(found->pub.ies, |
747 | found->pub.beacon_ies; | 725 | tmp->pub.beacon_ies); |
748 | found->pub.len_information_elements = | ||
749 | found->pub.len_beacon_ies; | ||
750 | } | ||
751 | } | ||
752 | 726 | ||
753 | kref_put(&res->ref, bss_release); | 727 | if (old) |
728 | kfree_rcu((struct cfg80211_bss_ies *)old, | ||
729 | rcu_head); | ||
730 | } | ||
754 | } else { | 731 | } else { |
732 | struct cfg80211_internal_bss *new; | ||
755 | struct cfg80211_internal_bss *hidden; | 733 | struct cfg80211_internal_bss *hidden; |
734 | struct cfg80211_bss_ies *ies; | ||
756 | 735 | ||
757 | /* First check if the beacon is a probe response from | 736 | /* First check if the beacon is a probe response from |
758 | * a hidden bss. If so, copy beacon ies (with nullified | 737 | * a hidden bss. If so, copy beacon ies (with nullified |
@@ -763,14 +742,32 @@ cfg80211_bss_update(struct cfg80211_registered_device *dev, | |||
763 | /* TODO: The code is not trying to update existing probe | 742 | /* TODO: The code is not trying to update existing probe |
764 | * response bss entries when beacon ies are | 743 | * response bss entries when beacon ies are |
765 | * getting changed. */ | 744 | * getting changed. */ |
766 | hidden = rb_find_hidden_bss(dev, res); | 745 | hidden = rb_find_hidden_bss(dev, tmp); |
767 | if (hidden) | 746 | if (hidden) |
768 | copy_hidden_ies(res, hidden); | 747 | copy_hidden_ies(tmp, hidden); |
769 | 748 | ||
770 | /* this "consumes" the reference */ | 749 | /* |
771 | list_add_tail(&res->list, &dev->bss_list); | 750 | * create a copy -- the "res" variable that is passed in |
772 | rb_insert_bss(dev, res); | 751 | * is allocated on the stack since it's not needed in the |
773 | found = res; | 752 | * more common case of an update |
753 | */ | ||
754 | new = kzalloc(sizeof(*new) + dev->wiphy.bss_priv_size, | ||
755 | GFP_ATOMIC); | ||
756 | if (!new) { | ||
757 | ies = (void *)rcu_dereference(tmp->pub.beacon_ies); | ||
758 | if (ies) | ||
759 | kfree_rcu(ies, rcu_head); | ||
760 | ies = (void *)rcu_dereference(tmp->pub.proberesp_ies); | ||
761 | if (ies) | ||
762 | kfree_rcu(ies, rcu_head); | ||
763 | spin_unlock_bh(&dev->bss_lock); | ||
764 | return NULL; | ||
765 | } | ||
766 | memcpy(new, tmp, sizeof(*new)); | ||
767 | kref_init(&new->ref); | ||
768 | list_add_tail(&new->list, &dev->bss_list); | ||
769 | rb_insert_bss(dev, new); | ||
770 | found = new; | ||
774 | } | 771 | } |
775 | 772 | ||
776 | dev->bss_generation++; | 773 | dev->bss_generation++; |
@@ -819,14 +816,12 @@ cfg80211_inform_bss(struct wiphy *wiphy, | |||
819 | u16 beacon_interval, const u8 *ie, size_t ielen, | 816 | u16 beacon_interval, const u8 *ie, size_t ielen, |
820 | s32 signal, gfp_t gfp) | 817 | s32 signal, gfp_t gfp) |
821 | { | 818 | { |
822 | struct cfg80211_internal_bss *res; | 819 | struct cfg80211_bss_ies *ies; |
823 | size_t privsz; | 820 | struct cfg80211_internal_bss tmp = {}, *res; |
824 | 821 | ||
825 | if (WARN_ON(!wiphy)) | 822 | if (WARN_ON(!wiphy)) |
826 | return NULL; | 823 | return NULL; |
827 | 824 | ||
828 | privsz = wiphy->bss_priv_size; | ||
829 | |||
830 | if (WARN_ON(wiphy->signal_type == CFG80211_SIGNAL_TYPE_UNSPEC && | 825 | if (WARN_ON(wiphy->signal_type == CFG80211_SIGNAL_TYPE_UNSPEC && |
831 | (signal < 0 || signal > 100))) | 826 | (signal < 0 || signal > 100))) |
832 | return NULL; | 827 | return NULL; |
@@ -835,36 +830,33 @@ cfg80211_inform_bss(struct wiphy *wiphy, | |||
835 | if (!channel) | 830 | if (!channel) |
836 | return NULL; | 831 | return NULL; |
837 | 832 | ||
838 | res = kzalloc(sizeof(*res) + privsz + ielen, gfp); | 833 | memcpy(tmp.pub.bssid, bssid, ETH_ALEN); |
839 | if (!res) | 834 | tmp.pub.channel = channel; |
840 | return NULL; | 835 | tmp.pub.signal = signal; |
841 | 836 | tmp.pub.tsf = tsf; | |
842 | memcpy(res->pub.bssid, bssid, ETH_ALEN); | 837 | tmp.pub.beacon_interval = beacon_interval; |
843 | res->pub.channel = channel; | 838 | tmp.pub.capability = capability; |
844 | res->pub.signal = signal; | ||
845 | res->pub.tsf = tsf; | ||
846 | res->pub.beacon_interval = beacon_interval; | ||
847 | res->pub.capability = capability; | ||
848 | /* | 839 | /* |
849 | * Since we do not know here whether the IEs are from a Beacon or Probe | 840 | * Since we do not know here whether the IEs are from a Beacon or Probe |
850 | * Response frame, we need to pick one of the options and only use it | 841 | * Response frame, we need to pick one of the options and only use it |
851 | * with the driver that does not provide the full Beacon/Probe Response | 842 | * with the driver that does not provide the full Beacon/Probe Response |
852 | * frame. Use Beacon frame pointer to avoid indicating that this should | 843 | * frame. Use Beacon frame pointer to avoid indicating that this should |
853 | * override the information_elements pointer should we have received an | 844 | * override the iies pointer should we have received an earlier |
854 | * earlier indication of Probe Response data. | 845 | * indication of Probe Response data. |
855 | * | 846 | * |
856 | * The initial buffer for the IEs is allocated with the BSS entry and | 847 | * The initial buffer for the IEs is allocated with the BSS entry and |
857 | * is located after the private area. | 848 | * is located after the private area. |
858 | */ | 849 | */ |
859 | res->pub.beacon_ies = (u8 *)res + sizeof(*res) + privsz; | 850 | ies = kmalloc(sizeof(*ies) + ielen, gfp); |
860 | memcpy(res->pub.beacon_ies, ie, ielen); | 851 | if (!ies) |
861 | res->pub.len_beacon_ies = ielen; | 852 | return NULL; |
862 | res->pub.information_elements = res->pub.beacon_ies; | 853 | ies->len = ielen; |
863 | res->pub.len_information_elements = res->pub.len_beacon_ies; | 854 | memcpy(ies->data, ie, ielen); |
864 | 855 | ||
865 | kref_init(&res->ref); | 856 | rcu_assign_pointer(tmp.pub.beacon_ies, ies); |
857 | rcu_assign_pointer(tmp.pub.ies, ies); | ||
866 | 858 | ||
867 | res = cfg80211_bss_update(wiphy_to_dev(wiphy), res); | 859 | res = cfg80211_bss_update(wiphy_to_dev(wiphy), &tmp); |
868 | if (!res) | 860 | if (!res) |
869 | return NULL; | 861 | return NULL; |
870 | 862 | ||
@@ -883,10 +875,10 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy, | |||
883 | struct ieee80211_mgmt *mgmt, size_t len, | 875 | struct ieee80211_mgmt *mgmt, size_t len, |
884 | s32 signal, gfp_t gfp) | 876 | s32 signal, gfp_t gfp) |
885 | { | 877 | { |
886 | struct cfg80211_internal_bss *res; | 878 | struct cfg80211_internal_bss tmp = {}, *res; |
879 | struct cfg80211_bss_ies *ies; | ||
887 | size_t ielen = len - offsetof(struct ieee80211_mgmt, | 880 | size_t ielen = len - offsetof(struct ieee80211_mgmt, |
888 | u.probe_resp.variable); | 881 | u.probe_resp.variable); |
889 | size_t privsz; | ||
890 | 882 | ||
891 | BUILD_BUG_ON(offsetof(struct ieee80211_mgmt, u.probe_resp.variable) != | 883 | BUILD_BUG_ON(offsetof(struct ieee80211_mgmt, u.probe_resp.variable) != |
892 | offsetof(struct ieee80211_mgmt, u.beacon.variable)); | 884 | offsetof(struct ieee80211_mgmt, u.beacon.variable)); |
@@ -906,45 +898,31 @@ cfg80211_inform_bss_frame(struct wiphy *wiphy, | |||
906 | if (WARN_ON(len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable))) | 898 | if (WARN_ON(len < offsetof(struct ieee80211_mgmt, u.probe_resp.variable))) |
907 | return NULL; | 899 | return NULL; |
908 | 900 | ||
909 | privsz = wiphy->bss_priv_size; | ||
910 | |||
911 | channel = cfg80211_get_bss_channel(wiphy, mgmt->u.beacon.variable, | 901 | channel = cfg80211_get_bss_channel(wiphy, mgmt->u.beacon.variable, |
912 | ielen, channel); | 902 | ielen, channel); |
913 | if (!channel) | 903 | if (!channel) |
914 | return NULL; | 904 | return NULL; |
915 | 905 | ||
916 | res = kzalloc(sizeof(*res) + privsz + ielen, gfp); | 906 | ies = kmalloc(sizeof(*ies) + ielen, gfp); |
917 | if (!res) | 907 | if (!ies) |
918 | return NULL; | 908 | return NULL; |
909 | ies->len = ielen; | ||
910 | memcpy(ies->data, mgmt->u.probe_resp.variable, ielen); | ||
919 | 911 | ||
920 | memcpy(res->pub.bssid, mgmt->bssid, ETH_ALEN); | 912 | if (ieee80211_is_probe_resp(mgmt->frame_control)) |
921 | res->pub.channel = channel; | 913 | rcu_assign_pointer(tmp.pub.proberesp_ies, ies); |
922 | res->pub.signal = signal; | 914 | else |
923 | res->pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); | 915 | rcu_assign_pointer(tmp.pub.beacon_ies, ies); |
924 | res->pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); | 916 | rcu_assign_pointer(tmp.pub.ies, ies); |
925 | res->pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); | 917 | |
926 | /* | 918 | memcpy(tmp.pub.bssid, mgmt->bssid, ETH_ALEN); |
927 | * The initial buffer for the IEs is allocated with the BSS entry and | 919 | tmp.pub.channel = channel; |
928 | * is located after the private area. | 920 | tmp.pub.signal = signal; |
929 | */ | 921 | tmp.pub.tsf = le64_to_cpu(mgmt->u.probe_resp.timestamp); |
930 | if (ieee80211_is_probe_resp(mgmt->frame_control)) { | 922 | tmp.pub.beacon_interval = le16_to_cpu(mgmt->u.probe_resp.beacon_int); |
931 | res->pub.proberesp_ies = (u8 *) res + sizeof(*res) + privsz; | 923 | tmp.pub.capability = le16_to_cpu(mgmt->u.probe_resp.capab_info); |
932 | memcpy(res->pub.proberesp_ies, mgmt->u.probe_resp.variable, | 924 | |
933 | ielen); | 925 | res = cfg80211_bss_update(wiphy_to_dev(wiphy), &tmp); |
934 | res->pub.len_proberesp_ies = ielen; | ||
935 | res->pub.information_elements = res->pub.proberesp_ies; | ||
936 | res->pub.len_information_elements = res->pub.len_proberesp_ies; | ||
937 | } else { | ||
938 | res->pub.beacon_ies = (u8 *) res + sizeof(*res) + privsz; | ||
939 | memcpy(res->pub.beacon_ies, mgmt->u.beacon.variable, ielen); | ||
940 | res->pub.len_beacon_ies = ielen; | ||
941 | res->pub.information_elements = res->pub.beacon_ies; | ||
942 | res->pub.len_information_elements = res->pub.len_beacon_ies; | ||
943 | } | ||
944 | |||
945 | kref_init(&res->ref); | ||
946 | |||
947 | res = cfg80211_bss_update(wiphy_to_dev(wiphy), res); | ||
948 | if (!res) | 926 | if (!res) |
949 | return NULL; | 927 | return NULL; |
950 | 928 | ||
@@ -1136,22 +1114,21 @@ int cfg80211_wext_siwscan(struct net_device *dev, | |||
1136 | EXPORT_SYMBOL_GPL(cfg80211_wext_siwscan); | 1114 | EXPORT_SYMBOL_GPL(cfg80211_wext_siwscan); |
1137 | 1115 | ||
1138 | static void ieee80211_scan_add_ies(struct iw_request_info *info, | 1116 | static void ieee80211_scan_add_ies(struct iw_request_info *info, |
1139 | struct cfg80211_bss *bss, | 1117 | const struct cfg80211_bss_ies *ies, |
1140 | char **current_ev, char *end_buf) | 1118 | char **current_ev, char *end_buf) |
1141 | { | 1119 | { |
1142 | u8 *pos, *end, *next; | 1120 | const u8 *pos, *end, *next; |
1143 | struct iw_event iwe; | 1121 | struct iw_event iwe; |
1144 | 1122 | ||
1145 | if (!bss->information_elements || | 1123 | if (!ies) |
1146 | !bss->len_information_elements) | ||
1147 | return; | 1124 | return; |
1148 | 1125 | ||
1149 | /* | 1126 | /* |
1150 | * If needed, fragment the IEs buffer (at IE boundaries) into short | 1127 | * If needed, fragment the IEs buffer (at IE boundaries) into short |
1151 | * enough fragments to fit into IW_GENERIC_IE_MAX octet messages. | 1128 | * enough fragments to fit into IW_GENERIC_IE_MAX octet messages. |
1152 | */ | 1129 | */ |
1153 | pos = bss->information_elements; | 1130 | pos = ies->data; |
1154 | end = pos + bss->len_information_elements; | 1131 | end = pos + ies->len; |
1155 | 1132 | ||
1156 | while (end - pos > IW_GENERIC_IE_MAX) { | 1133 | while (end - pos > IW_GENERIC_IE_MAX) { |
1157 | next = pos + 2 + pos[1]; | 1134 | next = pos + 2 + pos[1]; |
@@ -1162,7 +1139,8 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info, | |||
1162 | iwe.cmd = IWEVGENIE; | 1139 | iwe.cmd = IWEVGENIE; |
1163 | iwe.u.data.length = next - pos; | 1140 | iwe.u.data.length = next - pos; |
1164 | *current_ev = iwe_stream_add_point(info, *current_ev, | 1141 | *current_ev = iwe_stream_add_point(info, *current_ev, |
1165 | end_buf, &iwe, pos); | 1142 | end_buf, &iwe, |
1143 | (void *)pos); | ||
1166 | 1144 | ||
1167 | pos = next; | 1145 | pos = next; |
1168 | } | 1146 | } |
@@ -1172,7 +1150,8 @@ static void ieee80211_scan_add_ies(struct iw_request_info *info, | |||
1172 | iwe.cmd = IWEVGENIE; | 1150 | iwe.cmd = IWEVGENIE; |
1173 | iwe.u.data.length = end - pos; | 1151 | iwe.u.data.length = end - pos; |
1174 | *current_ev = iwe_stream_add_point(info, *current_ev, | 1152 | *current_ev = iwe_stream_add_point(info, *current_ev, |
1175 | end_buf, &iwe, pos); | 1153 | end_buf, &iwe, |
1154 | (void *)pos); | ||
1176 | } | 1155 | } |
1177 | } | 1156 | } |
1178 | 1157 | ||
@@ -1191,10 +1170,11 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, | |||
1191 | struct cfg80211_internal_bss *bss, char *current_ev, | 1170 | struct cfg80211_internal_bss *bss, char *current_ev, |
1192 | char *end_buf) | 1171 | char *end_buf) |
1193 | { | 1172 | { |
1173 | const struct cfg80211_bss_ies *ies; | ||
1194 | struct iw_event iwe; | 1174 | struct iw_event iwe; |
1175 | const u8 *ie; | ||
1195 | u8 *buf, *cfg, *p; | 1176 | u8 *buf, *cfg, *p; |
1196 | u8 *ie = bss->pub.information_elements; | 1177 | int rem, i, sig; |
1197 | int rem = bss->pub.len_information_elements, i, sig; | ||
1198 | bool ismesh = false; | 1178 | bool ismesh = false; |
1199 | 1179 | ||
1200 | memset(&iwe, 0, sizeof(iwe)); | 1180 | memset(&iwe, 0, sizeof(iwe)); |
@@ -1259,7 +1239,17 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, | |||
1259 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | 1239 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, |
1260 | &iwe, ""); | 1240 | &iwe, ""); |
1261 | 1241 | ||
1262 | while (rem >= 2) { | 1242 | rcu_read_lock(); |
1243 | ies = rcu_dereference(bss->pub.ies); | ||
1244 | if (ies) { | ||
1245 | rem = ies->len; | ||
1246 | ie = ies->data; | ||
1247 | } else { | ||
1248 | rem = 0; | ||
1249 | ie = NULL; | ||
1250 | } | ||
1251 | |||
1252 | while (ies && rem >= 2) { | ||
1263 | /* invalid data */ | 1253 | /* invalid data */ |
1264 | if (ie[1] > rem - 2) | 1254 | if (ie[1] > rem - 2) |
1265 | break; | 1255 | break; |
@@ -1271,7 +1261,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, | |||
1271 | iwe.u.data.length = ie[1]; | 1261 | iwe.u.data.length = ie[1]; |
1272 | iwe.u.data.flags = 1; | 1262 | iwe.u.data.flags = 1; |
1273 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | 1263 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, |
1274 | &iwe, ie + 2); | 1264 | &iwe, (u8 *)ie + 2); |
1275 | break; | 1265 | break; |
1276 | case WLAN_EID_MESH_ID: | 1266 | case WLAN_EID_MESH_ID: |
1277 | memset(&iwe, 0, sizeof(iwe)); | 1267 | memset(&iwe, 0, sizeof(iwe)); |
@@ -1279,7 +1269,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, | |||
1279 | iwe.u.data.length = ie[1]; | 1269 | iwe.u.data.length = ie[1]; |
1280 | iwe.u.data.flags = 1; | 1270 | iwe.u.data.flags = 1; |
1281 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, | 1271 | current_ev = iwe_stream_add_point(info, current_ev, end_buf, |
1282 | &iwe, ie + 2); | 1272 | &iwe, (u8 *)ie + 2); |
1283 | break; | 1273 | break; |
1284 | case WLAN_EID_MESH_CONFIG: | 1274 | case WLAN_EID_MESH_CONFIG: |
1285 | ismesh = true; | 1275 | ismesh = true; |
@@ -1288,7 +1278,7 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, | |||
1288 | buf = kmalloc(50, GFP_ATOMIC); | 1278 | buf = kmalloc(50, GFP_ATOMIC); |
1289 | if (!buf) | 1279 | if (!buf) |
1290 | break; | 1280 | break; |
1291 | cfg = ie + 2; | 1281 | cfg = (u8 *)ie + 2; |
1292 | memset(&iwe, 0, sizeof(iwe)); | 1282 | memset(&iwe, 0, sizeof(iwe)); |
1293 | iwe.cmd = IWEVCUSTOM; | 1283 | iwe.cmd = IWEVCUSTOM; |
1294 | sprintf(buf, "Mesh Network Path Selection Protocol ID: " | 1284 | sprintf(buf, "Mesh Network Path Selection Protocol ID: " |
@@ -1386,7 +1376,8 @@ ieee80211_bss(struct wiphy *wiphy, struct iw_request_info *info, | |||
1386 | kfree(buf); | 1376 | kfree(buf); |
1387 | } | 1377 | } |
1388 | 1378 | ||
1389 | ieee80211_scan_add_ies(info, &bss->pub, ¤t_ev, end_buf); | 1379 | ieee80211_scan_add_ies(info, ies, ¤t_ev, end_buf); |
1380 | rcu_read_unlock(); | ||
1390 | 1381 | ||
1391 | return current_ev; | 1382 | return current_ev; |
1392 | } | 1383 | } |