sfc: protect filter table against use-after-free
authorEdward Cree <ecree@solarflare.com>
Wed, 20 May 2015 10:10:03 +0000 (11:10 +0100)
committerDavid S. Miller <davem@davemloft.net>
Thu, 21 May 2015 22:43:53 +0000 (18:43 -0400)
If MCDI timeouts are encountered during efx_ef10_filter_table_remove(),
an FLR will be queued, but efx->filter_state will still be kfree()d.
The queued FLR will then call efx_ef10_filter_table_restore(), which
will try to use efx->filter_state. This previously caused a panic.
This patch adds an rwsem to protect the existence of efx->filter_state,
separately from the spinlock protecting its contents.  Users which can
race against efx_ef10_filter_table_remove() should down_read this rwsem.

Signed-off-by: Shradha Shah <sshah@solarflare.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/sfc/ef10.c
drivers/net/ethernet/sfc/efx.c
drivers/net/ethernet/sfc/efx.h
drivers/net/ethernet/sfc/ethtool.c
drivers/net/ethernet/sfc/net_driver.h

index 78d3236..9e2e8e1 100644 (file)
@@ -3314,6 +3314,9 @@ fail:
        return rc;
 }
 
+/* Caller must hold efx->filter_sem for read if race against
+ * efx_ef10_filter_table_remove() is possible
+ */
 static void efx_ef10_filter_table_restore(struct efx_nic *efx)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
@@ -3323,9 +3326,14 @@ static void efx_ef10_filter_table_restore(struct efx_nic *efx)
        bool failed = false;
        int rc;
 
+       WARN_ON(!rwsem_is_locked(&efx->filter_sem));
+
        if (!nic_data->must_restore_filters)
                return;
 
+       if (!table)
+               return;
+
        spin_lock_bh(&efx->filter_lock);
 
        for (filter_idx = 0; filter_idx < HUNT_FILTER_TBL_ROWS; filter_idx++) {
@@ -3361,6 +3369,7 @@ static void efx_ef10_filter_table_restore(struct efx_nic *efx)
                nic_data->must_restore_filters = false;
 }
 
+/* Caller must hold efx->filter_sem for write */
 static void efx_ef10_filter_table_remove(struct efx_nic *efx)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
@@ -3369,6 +3378,10 @@ static void efx_ef10_filter_table_remove(struct efx_nic *efx)
        unsigned int filter_idx;
        int rc;
 
+       efx->filter_state = NULL;
+       if (!table)
+               return;
+
        for (filter_idx = 0; filter_idx < HUNT_FILTER_TBL_ROWS; filter_idx++) {
                spec = efx_ef10_filter_entry_spec(table, filter_idx);
                if (!spec)
@@ -3394,6 +3407,9 @@ static void efx_ef10_filter_table_remove(struct efx_nic *efx)
        kfree(table);
 }
 
+/* Caller must hold efx->filter_sem for read if race against
+ * efx_ef10_filter_table_remove() is possible
+ */
 static void efx_ef10_filter_sync_rx_mode(struct efx_nic *efx)
 {
        struct efx_ef10_filter_table *table = efx->filter_state;
@@ -3408,6 +3424,9 @@ static void efx_ef10_filter_sync_rx_mode(struct efx_nic *efx)
        if (!efx_dev_registered(efx))
                return;
 
+       if (!table)
+               return;
+
        /* Mark old filters that may need to be removed */
        spin_lock_bh(&efx->filter_lock);
        n = table->dev_uc_count < 0 ? 1 : table->dev_uc_count;
Simple merge
Simple merge
Simple merge
Simple merge