Skip to content

Commit e48f129

Browse files
nhormanJames Bottomley
authored and
James Bottomley
committed
[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 <[email protected]> Reviewed-by: Karen Xie <[email protected]> Cc: [email protected] Signed-off-by: James Bottomley <[email protected]>
1 parent 3538a00 commit e48f129

File tree

5 files changed

+48
-18
lines changed

5 files changed

+48
-18
lines changed

drivers/infiniband/hw/cxgb3/iwch_cm.c

+5-5
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ void __free_ep(struct kref *kref)
287287
if (test_bit(RELEASE_RESOURCES, &ep->com.flags)) {
288288
cxgb3_remove_tid(ep->com.tdev, (void *)ep, ep->hwtid);
289289
dst_release(ep->dst);
290-
l2t_release(L2DATA(ep->com.tdev), ep->l2t);
290+
l2t_release(ep->com.tdev, ep->l2t);
291291
}
292292
kfree(ep);
293293
}
@@ -1178,7 +1178,7 @@ static int act_open_rpl(struct t3cdev *tdev, struct sk_buff *skb, void *ctx)
11781178
release_tid(ep->com.tdev, GET_TID(rpl), NULL);
11791179
cxgb3_free_atid(ep->com.tdev, ep->atid);
11801180
dst_release(ep->dst);
1181-
l2t_release(L2DATA(ep->com.tdev), ep->l2t);
1181+
l2t_release(ep->com.tdev, ep->l2t);
11821182
put_ep(&ep->com);
11831183
return CPL_RET_BUF_DONE;
11841184
}
@@ -1377,7 +1377,7 @@ static int pass_accept_req(struct t3cdev *tdev, struct sk_buff *skb, void *ctx)
13771377
if (!child_ep) {
13781378
printk(KERN_ERR MOD "%s - failed to allocate ep entry!\n",
13791379
__func__);
1380-
l2t_release(L2DATA(tdev), l2t);
1380+
l2t_release(tdev, l2t);
13811381
dst_release(dst);
13821382
goto reject;
13831383
}
@@ -1956,7 +1956,7 @@ int iwch_connect(struct iw_cm_id *cm_id, struct iw_cm_conn_param *conn_param)
19561956
if (!err)
19571957
goto out;
19581958

1959-
l2t_release(L2DATA(h->rdev.t3cdev_p), ep->l2t);
1959+
l2t_release(h->rdev.t3cdev_p, ep->l2t);
19601960
fail4:
19611961
dst_release(ep->dst);
19621962
fail3:
@@ -2127,7 +2127,7 @@ int iwch_ep_redirect(void *ctx, struct dst_entry *old, struct dst_entry *new,
21272127
PDBG("%s ep %p redirect to dst %p l2t %p\n", __func__, ep, new,
21282128
l2t);
21292129
dst_hold(new);
2130-
l2t_release(L2DATA(ep->com.tdev), ep->l2t);
2130+
l2t_release(ep->com.tdev, ep->l2t);
21312131
ep->l2t = l2t;
21322132
dst_release(old);
21332133
ep->dst = new;

drivers/net/cxgb3/cxgb3_offload.c

+18-5
Original file line numberDiff line numberDiff line change
@@ -1146,12 +1146,14 @@ static void cxgb_redirect(struct dst_entry *old, struct dst_entry *new)
11461146
if (te && te->ctx && te->client && te->client->redirect) {
11471147
update_tcb = te->client->redirect(te->ctx, old, new, e);
11481148
if (update_tcb) {
1149+
rcu_read_lock();
11491150
l2t_hold(L2DATA(tdev), e);
1151+
rcu_read_unlock();
11501152
set_l2t_ix(tdev, tid, e);
11511153
}
11521154
}
11531155
}
1154-
l2t_release(L2DATA(tdev), e);
1156+
l2t_release(tdev, e);
11551157
}
11561158

11571159
/*
@@ -1264,7 +1266,7 @@ int cxgb3_offload_activate(struct adapter *adapter)
12641266
goto out_free;
12651267

12661268
err = -ENOMEM;
1267-
L2DATA(dev) = t3_init_l2t(l2t_capacity);
1269+
RCU_INIT_POINTER(dev->l2opt, t3_init_l2t(l2t_capacity));
12681270
if (!L2DATA(dev))
12691271
goto out_free;
12701272

@@ -1298,25 +1300,36 @@ int cxgb3_offload_activate(struct adapter *adapter)
12981300

12991301
out_free_l2t:
13001302
t3_free_l2t(L2DATA(dev));
1301-
L2DATA(dev) = NULL;
1303+
rcu_assign_pointer(dev->l2opt, NULL);
13021304
out_free:
13031305
kfree(t);
13041306
return err;
13051307
}
13061308

1309+
static void clean_l2_data(struct rcu_head *head)
1310+
{
1311+
struct l2t_data *d = container_of(head, struct l2t_data, rcu_head);
1312+
t3_free_l2t(d);
1313+
}
1314+
1315+
13071316
void cxgb3_offload_deactivate(struct adapter *adapter)
13081317
{
13091318
struct t3cdev *tdev = &adapter->tdev;
13101319
struct t3c_data *t = T3C_DATA(tdev);
1320+
struct l2t_data *d;
13111321

13121322
remove_adapter(adapter);
13131323
if (list_empty(&adapter_list))
13141324
unregister_netevent_notifier(&nb);
13151325

13161326
free_tid_maps(&t->tid_maps);
13171327
T3C_DATA(tdev) = NULL;
1318-
t3_free_l2t(L2DATA(tdev));
1319-
L2DATA(tdev) = NULL;
1328+
rcu_read_lock();
1329+
d = L2DATA(tdev);
1330+
rcu_read_unlock();
1331+
rcu_assign_pointer(tdev->l2opt, NULL);
1332+
call_rcu(&d->rcu_head, clean_l2_data);
13201333
if (t->nofail_skb)
13211334
kfree_skb(t->nofail_skb);
13221335
kfree(t);

drivers/net/cxgb3/l2t.c

+12-3
Original file line numberDiff line numberDiff line change
@@ -300,14 +300,21 @@ static inline void reuse_entry(struct l2t_entry *e, struct neighbour *neigh)
300300
struct l2t_entry *t3_l2t_get(struct t3cdev *cdev, struct neighbour *neigh,
301301
struct net_device *dev)
302302
{
303-
struct l2t_entry *e;
304-
struct l2t_data *d = L2DATA(cdev);
303+
struct l2t_entry *e = NULL;
304+
struct l2t_data *d;
305+
int hash;
305306
u32 addr = *(u32 *) neigh->primary_key;
306307
int ifidx = neigh->dev->ifindex;
307-
int hash = arp_hash(addr, ifidx, d);
308308
struct port_info *p = netdev_priv(dev);
309309
int smt_idx = p->port_id;
310310

311+
rcu_read_lock();
312+
d = L2DATA(cdev);
313+
if (!d)
314+
goto done_rcu;
315+
316+
hash = arp_hash(addr, ifidx, d);
317+
311318
write_lock_bh(&d->lock);
312319
for (e = d->l2tab[hash].first; e; e = e->next)
313320
if (e->addr == addr && e->ifindex == ifidx &&
@@ -338,6 +345,8 @@ struct l2t_entry *t3_l2t_get(struct t3cdev *cdev, struct neighbour *neigh,
338345
}
339346
done:
340347
write_unlock_bh(&d->lock);
348+
done_rcu:
349+
rcu_read_unlock();
341350
return e;
342351
}
343352

drivers/net/cxgb3/l2t.h

+12-4
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ struct l2t_data {
7676
atomic_t nfree; /* number of free entries */
7777
rwlock_t lock;
7878
struct l2t_entry l2tab[0];
79+
struct rcu_head rcu_head; /* to handle rcu cleanup */
7980
};
8081

8182
typedef void (*arp_failure_handler_func)(struct t3cdev * dev,
@@ -99,7 +100,7 @@ static inline void set_arp_failure_handler(struct sk_buff *skb,
99100
/*
100101
* Getting to the L2 data from an offload device.
101102
*/
102-
#define L2DATA(dev) ((dev)->l2opt)
103+
#define L2DATA(cdev) (rcu_dereference((cdev)->l2opt))
103104

104105
#define W_TCB_L2T_IX 0
105106
#define S_TCB_L2T_IX 7
@@ -126,15 +127,22 @@ static inline int l2t_send(struct t3cdev *dev, struct sk_buff *skb,
126127
return t3_l2t_send_slow(dev, skb, e);
127128
}
128129

129-
static inline void l2t_release(struct l2t_data *d, struct l2t_entry *e)
130+
static inline void l2t_release(struct t3cdev *t, struct l2t_entry *e)
130131
{
131-
if (atomic_dec_and_test(&e->refcnt))
132+
struct l2t_data *d;
133+
134+
rcu_read_lock();
135+
d = L2DATA(t);
136+
137+
if (atomic_dec_and_test(&e->refcnt) && d)
132138
t3_l2e_free(d, e);
139+
140+
rcu_read_unlock();
133141
}
134142

135143
static inline void l2t_hold(struct l2t_data *d, struct l2t_entry *e)
136144
{
137-
if (atomic_add_return(1, &e->refcnt) == 1) /* 0 -> 1 transition */
145+
if (d && atomic_add_return(1, &e->refcnt) == 1) /* 0 -> 1 transition */
138146
atomic_dec(&d->nfree);
139147
}
140148

drivers/scsi/cxgbi/cxgb3i/cxgb3i.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ static void l2t_put(struct cxgbi_sock *csk)
913913
struct t3cdev *t3dev = (struct t3cdev *)csk->cdev->lldev;
914914

915915
if (csk->l2t) {
916-
l2t_release(L2DATA(t3dev), csk->l2t);
916+
l2t_release(t3dev, csk->l2t);
917917
csk->l2t = NULL;
918918
cxgbi_sock_put(csk);
919919
}

0 commit comments

Comments
 (0)