mirror of
https://github.com/torvalds/linux.git
synced 2024-11-10 22:21:40 +00:00
[SCSI] cxgb3i: convert cdev->l2opt to use rcu to prevent NULL dereference
This oops was reported recently: d:mon> e cpu 0xd: Vector: 300 (Data Access) at [c0000000fd4c7120] pc: d00000000076f194: .t3_l2t_get+0x44/0x524 [cxgb3] lr: d000000000b02108: .init_act_open+0x150/0x3d4 [cxgb3i] sp: c0000000fd4c73a0 msr: 8000000000009032 dar: 0 dsisr: 40000000 current = 0xc0000000fd640d40 paca = 0xc00000000054ff80 pid = 5085, comm = iscsid d:mon> t [c0000000fd4c7450] d000000000b02108 .init_act_open+0x150/0x3d4 [cxgb3i] [c0000000fd4c7500] d000000000e45378 .cxgbi_ep_connect+0x784/0x8e8 [libcxgbi] [c0000000fd4c7650] d000000000db33f0 .iscsi_if_rx+0x71c/0xb18 [scsi_transport_iscsi2] [c0000000fd4c7740] c000000000370c9c .netlink_data_ready+0x40/0xa4 [c0000000fd4c77c0] c00000000036f010 .netlink_sendskb+0x4c/0x9c [c0000000fd4c7850] c000000000370c18 .netlink_sendmsg+0x358/0x39c [c0000000fd4c7950] c00000000033be24 .sock_sendmsg+0x114/0x1b8 [c0000000fd4c7b50] c00000000033d208 .sys_sendmsg+0x218/0x2ac [c0000000fd4c7d70] c00000000033f55c .sys_socketcall+0x228/0x27c [c0000000fd4c7e30] c0000000000086a4 syscall_exit+0x0/0x40 --- Exception: c01 (System Call) at 00000080da560cfc The root cause was an EEH error, which sent us down the offload_close path in the cxgb3 driver, which in turn sets cdev->l2opt to NULL, without regard for upper layer driver (like the cxgbi drivers) which might have execution contexts in the middle of its use. The result is the oops above, when t3_l2t_get attempts to dereference L2DATA(cdev)->nentries in arp_hash right after the EEH error handler sets it to NULL. The fix is to prevent the setting of the NULL pointer until after there are no further users of it. The t3cdev->l2opt pointer is now converted to be an rcu pointer and the L2DATA macro is now called under the protection of the rcu_read_lock(). When the EEH error path: t3_adapter_error->offload_close->cxgb3_offload_deactivate Is exectured, setting of that l2opt pointer to NULL, is now gated on an rcu quiescence point, preventing, allowing L2DATA callers to safely check for a NULL pointer without concern that the underlying data will be freeded before the pointer is dereferenced. This has been tested by the reporter and shown to fix the reproted oops [nhorman: fix up unitinialised variable reported by Dan Carpenter] Signed-off-by: Neil Horman <nhorman@tuxdriver.com> Reviewed-by: Karen Xie <kxie@chelsio.com> Cc: stable@kernel.org Signed-off-by: James Bottomley <JBottomley@Parallels.com>
This commit is contained in:
parent
3538a001ea
commit
e48f129c2f
@ -287,7 +287,7 @@ void __free_ep(struct kref *kref)
|
|||||||
if (test_bit(RELEASE_RESOURCES, &ep->com.flags)) {
|
if (test_bit(RELEASE_RESOURCES, &ep->com.flags)) {
|
||||||
cxgb3_remove_tid(ep->com.tdev, (void *)ep, ep->hwtid);
|
cxgb3_remove_tid(ep->com.tdev, (void *)ep, ep->hwtid);
|
||||||
dst_release(ep->dst);
|
dst_release(ep->dst);
|
||||||
l2t_release(L2DATA(ep->com.tdev), ep->l2t);
|
l2t_release(ep->com.tdev, ep->l2t);
|
||||||
}
|
}
|
||||||
kfree(ep);
|
kfree(ep);
|
||||||
}
|
}
|
||||||
@ -1178,7 +1178,7 @@ static int act_open_rpl(struct t3cdev *tdev, struct sk_buff *skb, void *ctx)
|
|||||||
release_tid(ep->com.tdev, GET_TID(rpl), NULL);
|
release_tid(ep->com.tdev, GET_TID(rpl), NULL);
|
||||||
cxgb3_free_atid(ep->com.tdev, ep->atid);
|
cxgb3_free_atid(ep->com.tdev, ep->atid);
|
||||||
dst_release(ep->dst);
|
dst_release(ep->dst);
|
||||||
l2t_release(L2DATA(ep->com.tdev), ep->l2t);
|
l2t_release(ep->com.tdev, ep->l2t);
|
||||||
put_ep(&ep->com);
|
put_ep(&ep->com);
|
||||||
return CPL_RET_BUF_DONE;
|
return CPL_RET_BUF_DONE;
|
||||||
}
|
}
|
||||||
@ -1377,7 +1377,7 @@ static int pass_accept_req(struct t3cdev *tdev, struct sk_buff *skb, void *ctx)
|
|||||||
if (!child_ep) {
|
if (!child_ep) {
|
||||||
printk(KERN_ERR MOD "%s - failed to allocate ep entry!\n",
|
printk(KERN_ERR MOD "%s - failed to allocate ep entry!\n",
|
||||||
__func__);
|
__func__);
|
||||||
l2t_release(L2DATA(tdev), l2t);
|
l2t_release(tdev, l2t);
|
||||||
dst_release(dst);
|
dst_release(dst);
|
||||||
goto reject;
|
goto reject;
|
||||||
}
|
}
|
||||||
@ -1956,7 +1956,7 @@ int iwch_connect(struct iw_cm_id *cm_id, struct iw_cm_conn_param *conn_param)
|
|||||||
if (!err)
|
if (!err)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
l2t_release(L2DATA(h->rdev.t3cdev_p), ep->l2t);
|
l2t_release(h->rdev.t3cdev_p, ep->l2t);
|
||||||
fail4:
|
fail4:
|
||||||
dst_release(ep->dst);
|
dst_release(ep->dst);
|
||||||
fail3:
|
fail3:
|
||||||
@ -2127,7 +2127,7 @@ int iwch_ep_redirect(void *ctx, struct dst_entry *old, struct dst_entry *new,
|
|||||||
PDBG("%s ep %p redirect to dst %p l2t %p\n", __func__, ep, new,
|
PDBG("%s ep %p redirect to dst %p l2t %p\n", __func__, ep, new,
|
||||||
l2t);
|
l2t);
|
||||||
dst_hold(new);
|
dst_hold(new);
|
||||||
l2t_release(L2DATA(ep->com.tdev), ep->l2t);
|
l2t_release(ep->com.tdev, ep->l2t);
|
||||||
ep->l2t = l2t;
|
ep->l2t = l2t;
|
||||||
dst_release(old);
|
dst_release(old);
|
||||||
ep->dst = new;
|
ep->dst = new;
|
||||||
|
@ -1146,12 +1146,14 @@ static void cxgb_redirect(struct dst_entry *old, struct dst_entry *new)
|
|||||||
if (te && te->ctx && te->client && te->client->redirect) {
|
if (te && te->ctx && te->client && te->client->redirect) {
|
||||||
update_tcb = te->client->redirect(te->ctx, old, new, e);
|
update_tcb = te->client->redirect(te->ctx, old, new, e);
|
||||||
if (update_tcb) {
|
if (update_tcb) {
|
||||||
|
rcu_read_lock();
|
||||||
l2t_hold(L2DATA(tdev), e);
|
l2t_hold(L2DATA(tdev), e);
|
||||||
|
rcu_read_unlock();
|
||||||
set_l2t_ix(tdev, tid, e);
|
set_l2t_ix(tdev, tid, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l2t_release(L2DATA(tdev), e);
|
l2t_release(tdev, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -1264,7 +1266,7 @@ int cxgb3_offload_activate(struct adapter *adapter)
|
|||||||
goto out_free;
|
goto out_free;
|
||||||
|
|
||||||
err = -ENOMEM;
|
err = -ENOMEM;
|
||||||
L2DATA(dev) = t3_init_l2t(l2t_capacity);
|
RCU_INIT_POINTER(dev->l2opt, t3_init_l2t(l2t_capacity));
|
||||||
if (!L2DATA(dev))
|
if (!L2DATA(dev))
|
||||||
goto out_free;
|
goto out_free;
|
||||||
|
|
||||||
@ -1298,16 +1300,24 @@ int cxgb3_offload_activate(struct adapter *adapter)
|
|||||||
|
|
||||||
out_free_l2t:
|
out_free_l2t:
|
||||||
t3_free_l2t(L2DATA(dev));
|
t3_free_l2t(L2DATA(dev));
|
||||||
L2DATA(dev) = NULL;
|
rcu_assign_pointer(dev->l2opt, NULL);
|
||||||
out_free:
|
out_free:
|
||||||
kfree(t);
|
kfree(t);
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void clean_l2_data(struct rcu_head *head)
|
||||||
|
{
|
||||||
|
struct l2t_data *d = container_of(head, struct l2t_data, rcu_head);
|
||||||
|
t3_free_l2t(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void cxgb3_offload_deactivate(struct adapter *adapter)
|
void cxgb3_offload_deactivate(struct adapter *adapter)
|
||||||
{
|
{
|
||||||
struct t3cdev *tdev = &adapter->tdev;
|
struct t3cdev *tdev = &adapter->tdev;
|
||||||
struct t3c_data *t = T3C_DATA(tdev);
|
struct t3c_data *t = T3C_DATA(tdev);
|
||||||
|
struct l2t_data *d;
|
||||||
|
|
||||||
remove_adapter(adapter);
|
remove_adapter(adapter);
|
||||||
if (list_empty(&adapter_list))
|
if (list_empty(&adapter_list))
|
||||||
@ -1315,8 +1325,11 @@ void cxgb3_offload_deactivate(struct adapter *adapter)
|
|||||||
|
|
||||||
free_tid_maps(&t->tid_maps);
|
free_tid_maps(&t->tid_maps);
|
||||||
T3C_DATA(tdev) = NULL;
|
T3C_DATA(tdev) = NULL;
|
||||||
t3_free_l2t(L2DATA(tdev));
|
rcu_read_lock();
|
||||||
L2DATA(tdev) = NULL;
|
d = L2DATA(tdev);
|
||||||
|
rcu_read_unlock();
|
||||||
|
rcu_assign_pointer(tdev->l2opt, NULL);
|
||||||
|
call_rcu(&d->rcu_head, clean_l2_data);
|
||||||
if (t->nofail_skb)
|
if (t->nofail_skb)
|
||||||
kfree_skb(t->nofail_skb);
|
kfree_skb(t->nofail_skb);
|
||||||
kfree(t);
|
kfree(t);
|
||||||
|
@ -300,14 +300,21 @@ static inline void reuse_entry(struct l2t_entry *e, struct neighbour *neigh)
|
|||||||
struct l2t_entry *t3_l2t_get(struct t3cdev *cdev, struct neighbour *neigh,
|
struct l2t_entry *t3_l2t_get(struct t3cdev *cdev, struct neighbour *neigh,
|
||||||
struct net_device *dev)
|
struct net_device *dev)
|
||||||
{
|
{
|
||||||
struct l2t_entry *e;
|
struct l2t_entry *e = NULL;
|
||||||
struct l2t_data *d = L2DATA(cdev);
|
struct l2t_data *d;
|
||||||
|
int hash;
|
||||||
u32 addr = *(u32 *) neigh->primary_key;
|
u32 addr = *(u32 *) neigh->primary_key;
|
||||||
int ifidx = neigh->dev->ifindex;
|
int ifidx = neigh->dev->ifindex;
|
||||||
int hash = arp_hash(addr, ifidx, d);
|
|
||||||
struct port_info *p = netdev_priv(dev);
|
struct port_info *p = netdev_priv(dev);
|
||||||
int smt_idx = p->port_id;
|
int smt_idx = p->port_id;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
d = L2DATA(cdev);
|
||||||
|
if (!d)
|
||||||
|
goto done_rcu;
|
||||||
|
|
||||||
|
hash = arp_hash(addr, ifidx, d);
|
||||||
|
|
||||||
write_lock_bh(&d->lock);
|
write_lock_bh(&d->lock);
|
||||||
for (e = d->l2tab[hash].first; e; e = e->next)
|
for (e = d->l2tab[hash].first; e; e = e->next)
|
||||||
if (e->addr == addr && e->ifindex == ifidx &&
|
if (e->addr == addr && e->ifindex == ifidx &&
|
||||||
@ -338,6 +345,8 @@ struct l2t_entry *t3_l2t_get(struct t3cdev *cdev, struct neighbour *neigh,
|
|||||||
}
|
}
|
||||||
done:
|
done:
|
||||||
write_unlock_bh(&d->lock);
|
write_unlock_bh(&d->lock);
|
||||||
|
done_rcu:
|
||||||
|
rcu_read_unlock();
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +76,7 @@ struct l2t_data {
|
|||||||
atomic_t nfree; /* number of free entries */
|
atomic_t nfree; /* number of free entries */
|
||||||
rwlock_t lock;
|
rwlock_t lock;
|
||||||
struct l2t_entry l2tab[0];
|
struct l2t_entry l2tab[0];
|
||||||
|
struct rcu_head rcu_head; /* to handle rcu cleanup */
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef void (*arp_failure_handler_func)(struct t3cdev * dev,
|
typedef void (*arp_failure_handler_func)(struct t3cdev * dev,
|
||||||
@ -99,7 +100,7 @@ static inline void set_arp_failure_handler(struct sk_buff *skb,
|
|||||||
/*
|
/*
|
||||||
* Getting to the L2 data from an offload device.
|
* Getting to the L2 data from an offload device.
|
||||||
*/
|
*/
|
||||||
#define L2DATA(dev) ((dev)->l2opt)
|
#define L2DATA(cdev) (rcu_dereference((cdev)->l2opt))
|
||||||
|
|
||||||
#define W_TCB_L2T_IX 0
|
#define W_TCB_L2T_IX 0
|
||||||
#define S_TCB_L2T_IX 7
|
#define S_TCB_L2T_IX 7
|
||||||
@ -126,15 +127,22 @@ static inline int l2t_send(struct t3cdev *dev, struct sk_buff *skb,
|
|||||||
return t3_l2t_send_slow(dev, skb, e);
|
return t3_l2t_send_slow(dev, skb, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void l2t_release(struct l2t_data *d, struct l2t_entry *e)
|
static inline void l2t_release(struct t3cdev *t, struct l2t_entry *e)
|
||||||
{
|
{
|
||||||
if (atomic_dec_and_test(&e->refcnt))
|
struct l2t_data *d;
|
||||||
|
|
||||||
|
rcu_read_lock();
|
||||||
|
d = L2DATA(t);
|
||||||
|
|
||||||
|
if (atomic_dec_and_test(&e->refcnt) && d)
|
||||||
t3_l2e_free(d, e);
|
t3_l2e_free(d, e);
|
||||||
|
|
||||||
|
rcu_read_unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void l2t_hold(struct l2t_data *d, struct l2t_entry *e)
|
static inline void l2t_hold(struct l2t_data *d, struct l2t_entry *e)
|
||||||
{
|
{
|
||||||
if (atomic_add_return(1, &e->refcnt) == 1) /* 0 -> 1 transition */
|
if (d && atomic_add_return(1, &e->refcnt) == 1) /* 0 -> 1 transition */
|
||||||
atomic_dec(&d->nfree);
|
atomic_dec(&d->nfree);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -913,7 +913,7 @@ static void l2t_put(struct cxgbi_sock *csk)
|
|||||||
struct t3cdev *t3dev = (struct t3cdev *)csk->cdev->lldev;
|
struct t3cdev *t3dev = (struct t3cdev *)csk->cdev->lldev;
|
||||||
|
|
||||||
if (csk->l2t) {
|
if (csk->l2t) {
|
||||||
l2t_release(L2DATA(t3dev), csk->l2t);
|
l2t_release(t3dev, csk->l2t);
|
||||||
csk->l2t = NULL;
|
csk->l2t = NULL;
|
||||||
cxgbi_sock_put(csk);
|
cxgbi_sock_put(csk);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user