/*- * Copyright (c) 2007 Michael Taylor, Apprion * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * $Id: ieee80211_linux.c 2829 2007-11-05 20:43:50Z mtaylor $ */ #ifndef EXPORT_SYMTAB #define EXPORT_SYMTAB #endif /* * IEEE 802.11 support (Linux-specific code) */ #ifndef AUTOCONF_INCLUDED #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX for ARPHRD_* */ #include #include "if_media.h" #include "if_ethersubr.h" #include #include #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,17) #include #endif #undef alloc_skb #undef dev_alloc_skb #undef dev_kfree_skb #undef dev_kfree_skb_any #undef dev_kfree_skb_irq #undef dev_queue_xmit #undef kfree_skb #undef kfree_skb_fast #undef netif_rx #undef pskb_copy #undef skb_clone #undef skb_copy #undef skb_copy_expand #undef skb_get #undef skb_realloc_headroom #undef skb_share_check #undef skb_unshare #undef vlan_hwaccel_rx atomic_t skb_total_counter = ATOMIC_INIT(0); EXPORT_SYMBOL(skb_total_counter); #ifdef IEEE80211_DEBUG_REFCNT atomic_t skb_refs_counter = ATOMIC_INIT(0); EXPORT_SYMBOL(skb_refs_counter); #endif /******************************************************************************* * Debug Helpers ******************************************************************************/ static void skb_print_message( int show_counter, const struct sk_buff *skb, const char *func, int line, const char *message, ...) { va_list args; char skb_count[32] = { '\0' }; char expanded_message[1024] = { '\0' }; if (show_counter) { #ifdef IEEE80211_DEBUG_REFCNT snprintf(skb_count, sizeof(skb_count), "[#SKB=%05d #REF=%05d] ", atomic_read(&skb_total_counter), atomic_read(&skb_refs_counter)); #else snprintf(skb_count, sizeof(skb_count), "[#SKB=%05d] ", atomic_read(&skb_total_counter)); #endif } va_start(args, message); vsnprintf(expanded_message, sizeof(expanded_message), message, args); printk(KERN_DEBUG "%s: %s%s:%d %s\n", ((skb != NULL) ? DEV_NAME(skb->dev) : "none"), skb_count, func, line, expanded_message); va_end(args); #ifdef IEEE80211_DEBUG_REFCNT dump_stack(); #endif } #ifdef IEEE80211_DEBUG_REFCNT static void print_skb_refchange_message( const struct sk_buff *skb, int users_adjustment, const char *func, int line); static void print_skb_trackchange_message( const struct sk_buff *skb, int users_adjustment, const char *func, int line, char *message); /* Called automatically when an SKB reaches zero users, * reporting any leaked node references. */ #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST static void skb_destructor(struct sk_buff *skb); #endif static void get_skb_description(char *dst, int dst_size, const char *label, const struct sk_buff *skb, int users_adjustment); static struct sk_buff * clean_clone_or_copy(struct sk_buff *skb); static struct sk_buff * track_skb(struct sk_buff *skb, int users_adjustment, const char *func, int line); static struct sk_buff * untrack_skb(struct sk_buff *skb, int users_adjustment, const char *func, int line); #define UNREF_USE_KFREE_SKB 0 #define UNREF_USE_DEV_KFREE_SKB_ANY 1 #define UNREF_USE_DEV_KFREE_SKB 2 #define UNREF_USE_DEV_KFREE_SKB_IRQ 3 /* Assumes SKB is not yet freed at the time of the call and shows the new users * count as (users - 1). */ static void unref_skb(struct sk_buff *skb, int type, const char *func, int line); /* Assumes SKB reference counter has already been updated and reports count as * atomic_read(&skb->users). */ static struct sk_buff * ref_skb(struct sk_buff *skb, const char *func, int line); #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST /* Destructor for reporting node reference leaks */ static void skb_destructor(struct sk_buff *skb) { /* Report any node reference leaks - caused by kernel net device queue * dropping buffer, rather than passing it to the driver. */ if (SKB_NI(skb) != NULL) { printk(KERN_ERR "%s:%d - ERROR: non-NULL node pointer in %p, %p<" MAC_FMT ">! " "Leak Detected!\n", __func__, __LINE__, skb, SKB_NI(skb), MAC_ADDR(SKB_NI(skb)->ni_macaddr)); dump_stack(); } if (SKB_CB(skb)->next_destructor != NULL) { SKB_CB(skb)->next_destructor(skb); } } EXPORT_SYMBOL(skb_destructor); #endif /* #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST */ static void get_skb_description(char *dst, int dst_size, const char *label, const struct sk_buff *skb, int users_adjustment) { dst[0] = '\0'; if (NULL != skb) { int adj_users = atomic_read(&skb->users) + users_adjustment; if (SKB_NI(skb) != NULL) { snprintf(dst, dst_size, " [%s%s%p,users=%d,node=%p<" MAC_FMT ">,aid=%d%s%s]", label, (label != NULL ? ": " : ""), skb, adj_users, SKB_NI(skb), MAC_ADDR(SKB_NI(skb)->ni_macaddr), SKB_NI(skb)->ni_associd, ((adj_users < 0) ? " ** CORRUPTED **" : ""), ((adj_users == 0) ? " ** RELEASED **" : "") ); } else { snprintf(dst, dst_size, " [%s%s%p,users=%d,node=NULL,aid=N/A%s%s]", label, (label != NULL ? ": " : ""), skb, adj_users, ((adj_users < 0) ? " ** CORRUPTED **" : ""), ((adj_users == 0) ? " ** RELEASED **" : "") ); } dst[dst_size-1] = '\0'; } } static void print_skb_refchange_message( const struct sk_buff *skb, int users_adjustment, const char *func, int line) { char skb_desc[128] = { '\0' }; if (0 == (ath_debug_global & GLOBAL_DEBUG_SKB_REF)) return; get_skb_description(skb_desc, sizeof(skb_desc), "skb", skb, users_adjustment); skb_print_message(0 /* no global count */, skb, func, line, skb_desc); } static void print_skb_trackchange_message( const struct sk_buff *skb, int users_adjustment, const char *func, int line, char *message) { char skb_desc[128] = { '\0' }; if (0 == (ath_debug_global & GLOBAL_DEBUG_SKB)) return; get_skb_description(skb_desc, sizeof(skb_desc), "skb", skb, users_adjustment); skb_print_message(1 /* show global count */, skb, func, line, "%s%s", skb_desc, message); } static struct sk_buff * clean_clone_or_copy(struct sk_buff *skb) { if (skb != NULL) M_FLAG_CLR(skb, M_SKB_TRACKED); return skb; } static struct sk_buff * track_skb(struct sk_buff *skb, int users_adjustment, const char *func, int line) { if (NULL == skb) { skb_print_message(0 /* show_counter */, skb, func2, line2, "ERROR: NULL skb received. Skipping."); return NULL; } if (M_FLAG_GET(skb, M_SKB_TRACKED)) { skb_print_message(0 /* show_counter */, skb, func2, line2, "ERROR: Already tracked skb received. Skipping."); dump_stack(); return skb; } if (skb_shared(skb)) { skb_print_message(0 /* show_counter */, skb, func2, line2, "ERROR: Shared skb received. References leaked??"); dump_stack(); } atomic_inc(&skb_total_counter); atomic_inc(&skb_refs_counter); M_FLAG_SET(skb, M_SKB_TRACKED); print_skb_trackchange_message(skb, users_adjustment, func2, line2, " is now ** TRACKED **"); #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST /* Install our debug destructor, chaining to the original... */ if (skb->destructor != skb_destructor) { SKB_CB(skb)->next_destructor = skb->destructor; skb->destructor = skb_destructor; } #endif /* #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST */ return skb; } static struct sk_buff * untrack_skb(struct sk_buff *skb, int users_adjustment, const char *func, int line) { if (NULL == skb) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: NULL skb received. No changes made."); return NULL; } if (!M_FLAG_GET(skb, M_SKB_TRACKED)) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: Untracked skb received. No changes made."); return skb; } if (skb_shared(skb)) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: Shared skb received. References leaked??"); } atomic_dec(&skb_total_counter); atomic_dec(&skb_refs_counter); M_FLAG_CLR(skb, M_SKB_TRACKED); #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST /* Uninstall our debug destructor, restoring any original... */ if (skb->destructor == skb_destructor) { skb->destructor = SKB_CB(skb)->next_destructor; SKB_CB(skb)->next_destructor = NULL; } #endif /* #ifdef IEEE80211_DEBUG_REFCNT_SKBDEST */ print_skb_trackchange_message(skb, users_adjustment, func, line, " is now ** UNTRACKED **"); return skb; } #define UNREF_USE_KFREE_SKB 0 #define UNREF_USE_DEV_KFREE_SKB_ANY 1 #define UNREF_USE_DEV_KFREE_SKB 2 #define UNREF_USE_DEV_KFREE_SKB_IRQ 3 /* Assumes SKB is not yet freed at the time of the call and shows the new users * count as (users - 1). */ static void unref_skb(struct sk_buff *skb, int type, const char *func, int line) { if (NULL == skb) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: NULL skb received."); dump_stack(); return; } if (!M_FLAG_GET(skb, M_SKB_TRACKED)) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: Untracked skb received. Probable duplicate free error!"); dump_stack(); return; } /* If free is unacceptable for current user count, report the error. */ if (atomic_read(&skb->users) < 1) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: free an skb with %d users", atomic_read(&skb->users)); dump_stack(); return; } if (skb_shared(skb)) { atomic_dec(&skb_refs_counter); print_skb_refchange_message(skb, -1, func2, line2); } else { if (SKB_NI(skb) != NULL) { printk(KERN_ERR "%s:%d - ERROR: non-NULL node pointer in %p, %p<" MAC_FMT ">! " "Driver Leak Detected!\n", __func__, __LINE__, skb, SKB_NI(skb), MAC_ADDR(SKB_NI(skb)->ni_macaddr)); dump_stack(); /* Allow the leak and let programmer fix it, but do not * report it again in the destructor. */ SKB_NI(skb) = NULL; } untrack_skb(skb, -1, func, line); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) if ((in_irq() || irqs_disabled()) && (type == UNREF_USE_KFREE_SKB || type == UNREF_USE_DEV_KFREE_SKB)) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: free an skb in interrupt context using a non-" "safe form of skb free function."); type = UNREF_USE_DEV_KFREE_SKB_ANY; dump_stack(); } #endif switch (type) { case UNREF_USE_DEV_KFREE_SKB_ANY: dev_kfree_skb_any(skb); break; case UNREF_USE_DEV_KFREE_SKB_IRQ: dev_kfree_skb_irq(skb); break; case UNREF_USE_DEV_KFREE_SKB: /* NOTE: dev_kfree_skb is a macro pointing to kfree_skb, so * fallthrough... */ case UNREF_USE_KFREE_SKB: /* fallthrough */ default: kfree_skb(skb); break; } } /* Assumes SKB reference counter has already been updated and reports count as * atomic_read(&skb->users). */ static struct sk_buff * ref_skb(struct sk_buff *skb, const char *func, int line) { if (NULL == skb) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: NULL skb received. No changes made."); dump_stack(); return NULL; } if (!M_FLAG_GET(skb, M_SKB_TRACKED)) { skb_print_message(0 /* show_counter */, skb, func, line, "ERROR: Untracked skb received. Probable use after free! " "No changes made."); dump_stack(); return skb; } print_skb_refchange_message(skb, 0, func, line); return skb; } #endif /* #ifdef IEEE80211_DEBUG_REFCNT */ /******************************************************************************* * Public API ******************************************************************************/ /* ieee80211_dev_kfree_skb will release one reference from SKB. * If SKB refcount is going to zero: * - Free the node reference and set it to null. * - Break the linked list, clearing next skb's prev pointer if possible. */ void ieee80211_dev_kfree_skb(struct sk_buff **pskb) { struct sk_buff *skb; /* Do not fail on null, as we are going to use this in cleanup code. */ if (!pskb || !(skb = *pskb)) return; /* Release the SKB references, for fragments of chain that are * unshared... starting at skb passed in. */ if (skb->prev == NULL) { if (skb->next != NULL) skb->next->prev = NULL; skb->next = NULL; } if (SKB_NI(skb) != NULL) ieee80211_unref_node(&SKB_NI(skb)); #ifdef IEEE80211_DEBUG_REFCNT unref_skb(skb, UNREF_USE_DEV_KFREE_SKB_ANY, __func__, __LINE__); #else dev_kfree_skb_any(skb); #endif *pskb = NULL; } /* ieee80211_dev_kfree_skb_list will invoke ieee80211_dev_kfree_skb on each node in * a list of skbs, starting with the first. */ void ieee80211_dev_kfree_skb_list(struct sk_buff **pskb) { struct sk_buff *skb, *tskb; /* Do not fail on null, as we are going to use this in cleanup code */ if (!pskb || !(skb = *pskb)) return; while (skb) { tskb = skb->next; ieee80211_dev_kfree_skb(&skb); skb = tskb; } *pskb = NULL; } struct sk_buff * ieee80211_dev_alloc_skb(int size) { struct sk_buff *skb = dev_alloc_skb(size); if (skb == NULL) { skb_print_message( 0 /* show_counter */, NULL /* skb */, __func__, __LINE__, "sk_buff allocation of size %u failed", size); return NULL; } #ifdef IEEE80211_DEBUG_REFCNT return track_skb(skb, 0, __func__, __LINE__); #else return skb; #endif } void ieee80211_skb_track(struct sk_buff *skb) { #ifdef IEEE80211_DEBUG_REFCNT track_skb(skb, 0 /* users_adjustment */, __func__, __LINE__); #else /* Just a dumb counter, in no-debug builds */ atomic_inc(&skb_total_counter); #endif /* #ifdef IEEE80211_DEBUG_REFCNT */ } void ieee80211_skb_untrack(struct sk_buff *skb) { /* Just a dumb counter, in no-debug builds */ atomic_dec(&skb_total_counter); } #ifdef IEEE80211_DEBUG_REFCNT int ieee80211_skb_counter(void) { return atomic_read(&skb_total_counter); } int ieee80211_skb_references(void) { return atomic_read(&skb_refs_counter); } #endif /* #ifdef IEEE80211_DEBUG_REFCNT */ /******************************************************************************* * skbuff leak/refcount debugging Replacement Functions * PUT last in order to avoid conflicts with use of original functions in * inline functions above this point. ******************************************************************************/ #ifdef IEEE80211_DEBUG_REFCNT int vlan_hwaccel_rx_debug(struct sk_buff *skb, struct vlan_group *grp, unsigned short vlan_tag, const char *func, int line) { return vlan_hwaccel_rx( untrack_skb(skb, 0, __func__, __LINE__), grp, vlan_tag); } int netif_rx_debug(struct sk_buff *skb, const char *func, int line) { return netif_rx(untrack_skb(skb, 0, __func__, __LINE__)); } struct sk_buff *alloc_skb_debug(unsigned int length, gfp_t gfp_mask, const char *func, int line) { return track_skb(alloc_skb(length, gfp_mask), 0 /* users_adjustment */, __func__, __LINE__); } struct sk_buff *dev_alloc_skb_debug(unsigned int length, const char *func, int line) { return track_skb(dev_alloc_skb(length), 0 /* users_adjustment */, __func__, __LINE__); } struct sk_buff *skb_clone_debug(struct sk_buff *skb, gfp_t pri, const char *func, int line) { return track_skb( clean_clone_or_copy(skb_clone(skb, pri)), 0 /* users_adjustment */, __func__, __LINE__); } struct sk_buff *skb_copy_debug(struct sk_buff *skb, gfp_t pri, const char *func, int line) { return track_skb( clean_clone_or_copy(skb_copy(skb, pri)), 0 /* users_adjustment */, __func__, __LINE__); } struct sk_buff *skb_get_debug(struct sk_buff *skb, const char *func, int line) { return ref_skb(skb_get(skb), __func__, __LINE__); } struct sk_buff *skb_realloc_headroom_debug(struct sk_buff *skb, unsigned int headroom, const char *func, int line) { /* skb_realloc_headroom ALWAYS returns a copy or a clone, refcount of * new one is always zero and refcount of original is not touched. */ return track_skb( clean_clone_or_copy( skb_realloc_headroom(skb, headroom)), 0 /* users_adjustment */, __func__, __LINE__); } struct sk_buff *pskb_copy_debug(struct sk_buff *skb, gfp_t pri, const char *func, int line) { return track_skb( clean_clone_or_copy(pskb_copy(skb, pri)), 0 /* users_adjustment */, __func__, __LINE__); } int dev_queue_xmit_debug(struct sk_buff *skb, const char *func, int line) { return dev_queue_xmit(untrack_skb(skb, 0, __func__, __LINE__)); } struct sk_buff *skb_share_check_debug(struct sk_buff *skb, gfp_t pri, const char *func, int line) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) might_sleep_if(pri & __GFP_WAIT); #endif if (skb_shared(skb)) { struct sk_buff *nskb = track_skb( clean_clone_or_copy(skb_clone(skb, pri)), 0, __func__, __LINE__); unref_skb(skb, UNREF_USE_DEV_KFREE_SKB_ANY, __func__, __LINE__); skb = nskb; } return skb; } void kfree_skb_fast_debug(struct sk_buff *skb, const char *func, int line) { /* NOT so fast... */ unref_skb(skb, UNREF_USE_DEV_KFREE_SKB_ANY, func, line, __func__, __LINE__); } struct sk_buff *skb_unshare_debug(struct sk_buff *skb, gfp_t pri, const char *func, int line) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) might_sleep_if(pri & __GFP_WAIT); #endif if (skb_cloned(skb)) { struct sk_buff *nskb = track_skb( clean_clone_or_copy(skb_copy(skb, pri)), 0, __func__, __LINE__); unref_skb(skb, UNREF_USE_DEV_KFREE_SKB_ANY, __func__, __LINE__); skb = nskb; } return skb; } struct sk_buff *skb_copy_expand_debug(const struct sk_buff *skb, int newheadroom, int newtailroom, gfp_t gfp_mask, const char *func, int line) { return track_skb( clean_clone_or_copy( skb_copy_expand(skb, newheadroom, newtailroom, gfp_mask)), 0 /* users_adjustment */, __func__, __LINE__); } EXPORT_SYMBOL(vlan_hwaccel_rx_debug); EXPORT_SYMBOL(netif_rx_debug); EXPORT_SYMBOL(alloc_skb_debug); EXPORT_SYMBOL(dev_alloc_skb_debug); EXPORT_SYMBOL(skb_clone_debug); EXPORT_SYMBOL(skb_copy_debug); EXPORT_SYMBOL(skb_get_debug); EXPORT_SYMBOL(skb_realloc_headroom_debug); EXPORT_SYMBOL(pskb_copy_debug); EXPORT_SYMBOL(dev_queue_xmit_debug); EXPORT_SYMBOL(skb_share_check_debug); EXPORT_SYMBOL(kfree_skb_fast_debug); EXPORT_SYMBOL(skb_unshare_debug); EXPORT_SYMBOL(skb_copy_expand_debug); EXPORT_SYMBOL(ieee80211_skb_counter); EXPORT_SYMBOL(ieee80211_skb_references); #endif /* #ifdef IEEE80211_DEBUG_REFCNT */ EXPORT_SYMBOL(ieee80211_dev_alloc_skb); EXPORT_SYMBOL(ieee80211_skb_untrack); EXPORT_SYMBOL(ieee80211_dev_kfree_skb_list); EXPORT_SYMBOL(ieee80211_dev_kfree_skb); EXPORT_SYMBOL(ieee80211_skb_track);