On Mon, Feb 06, 2012 at 03:34:00PM -0800, Vasu Dev wrote: > On Thu, 2012-02-02 at 16:03 -0500, Neil Horman wrote: > > fc_port->ema_list is being accessed in multiple parallel contexts with no > > locking around it leading to potential corruption. > > I don't see any possibility of ema_list corruption here and if there is I do, and I've tried to explain it here. It seems I've not been clear enough.
> any such possible case/s then we should fix such case/s at source > instead ensuring list sanity by any un-necessary additional locking as What do think I'm trying to do here? > that may hide real issue/s at source.This patch is based on code > observations and I've explanation why those cases are not issues here, > see more details below. > You're analysis below is incomplete. > > Whats more the fc_find_ema > > function was manually accessing ema_list.prev without checking for a > > list_empty > > condition. The ema_list could easily be emtpy if an transation came in to > > the > > foce stack with an UNKNOWN xid prior to our adding a ema anchor to the list > > (dev_add_pack gets called before said add occurs). > > Yes dev_add_pack is called before done with ema_list setup with lport The fact that dev_add_pack is called early isn't overly relevent, it was just meant to illustrate one way in which ema_list.perv could be accessed without any elements on the list, which is a corrupting action. There are others. > but added fcoe interface is not hooked up with lport until we are done > with complete lport config and that prevents getting to even lport which > holds ema_list head. So added packet handler fcoe_rcv() will drops all > frames untill lport/ema_list set at:- > > fcoe = container_of(ptype, struct fcoe_interface, > fcoe_packet_type); > lport = fcoe->ctlr.lp; > if (unlikely(!lport)) { > FCOE_NETDEV_DBG(netdev, "Cannot find hba structure"); > goto err2; > } > Ok, I'll try make this as clear as I can. Here are the concurrent paths as I see them: CPU0 ==== fcoe_rcv fcoe_recv_frame fc_exch_recv fc_find_ema list_for_each(lport->ema_list) CPU1 ==== fcoe_destroy_work fcoe_if_destroy fc_exch_mgr_free fc_exch_mgr_del list_del(lport->ema_list) CPU2 ==== fcoe_if_create fcoe_em_config fc_exch_mgr_add list_add(lport->ema_list) Notes: CPU0 derives the lport from the structure surrounding the packet_type struct, which isn't removed until after fcoe_if_destroy is called (via fcoe_interface_cleanup). CPU1 and CPU2 are serialized by the config_mutex. However, neither of them are serialized against CPU0 which runs in softirq context. And since fcoe_rcv determines the fcoe_ctlr and lport based on the struct surrounding the passed in packet_type, which isn't removed until the end of fcoe_destroy_work (via fcoe_interface_cleanup). Its entirely possible for the path in CPU0 to run concurrently with a remove operation in CPU1, causing a list traversal at the same time as a list_del (read, corruption). > BTW FCoE switch is not expected to send FCoE frame until lport logged > in but just in case safety check is already in place to drop fcoe frames > in that case. > The above scenario is on delete, which presumes that the lport is already logged in. > Also no possibility of list corruption during sysfs create/destroy as > all fcoe creation/destroys are serialized by global fcoe_config_mutex. > Correct, but I'm not talking about serialization of two list modifications, I'm talking about the serialization of a list traversal with a modification. > May be I'm missing something among lengthy side references after some > good review comments discussion points from Bhanu on initial patch, in > that case help me understand in what specific scenario this patch will > prevent a possible bug. > Please see above Thanks Neil > Thanks > Vasu > > > > > Fix it by adding an ema_lock to list accesses. Make it an rcu lock so we > > don't > > get Warnings when we try to tx a frame with a spinlock held > > > > Change notes: > > v2) Removed pool->lock and ex_lock changes that had incorectly crept into > > this commit > > > > Signed-off-by: Neil Horman <nhor...@tuxdriver.com> > > CC: kiran.pa...@intel.com > > CC: bprak...@broadcom.com > > CC: robert.w.l...@intel.com > > --- > > drivers/scsi/libfc/fc_exch.c | 82 > > ++++++++++++++++++++++++++++++++--------- > > include/scsi/libfc.h | 2 + > > 2 files changed, 66 insertions(+), 18 deletions(-) > > > > diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c > > index 630291f..3c03c87 100644 > > --- a/drivers/scsi/libfc/fc_exch.c > > +++ b/drivers/scsi/libfc/fc_exch.c > > @@ -130,6 +130,7 @@ struct fc_exch_mgr_anchor { > > struct list_head ema_list; > > struct fc_exch_mgr *mp; > > bool (*match)(struct fc_frame *); > > + struct rcu_head rcu; > > }; > > > > static void fc_exch_rrq(struct fc_exch *); > > @@ -801,9 +802,13 @@ static inline struct fc_exch *fc_exch_alloc(struct > > fc_lport *lport, > > { > > struct fc_exch_mgr_anchor *ema; > > > > - list_for_each_entry(ema, &lport->ema_list, ema_list) > > - if (!ema->match || ema->match(fp)) > > + rcu_read_lock(); > > + list_for_each_entry_rcu(ema, &lport->ema_list, ema_list) > > + if (!ema->match || ema->match(fp)) { > > + rcu_read_unlock(); > > return fc_exch_em_alloc(lport, ema->mp); > > + } > > + rcu_read_unlock(); > > return NULL; > > } > > > > @@ -1329,10 +1334,13 @@ static struct fc_seq *fc_seq_assign(struct fc_lport > > *lport, struct fc_frame *fp) > > WARN_ON(fr_seq(fp)); > > fr_seq(fp) = NULL; > > > > - list_for_each_entry(ema, &lport->ema_list, ema_list) > > + rcu_read_lock(); > > + list_for_each_entry_rcu(ema, &lport->ema_list, ema_list) > > if ((!ema->match || ema->match(fp)) && > > fc_seq_lookup_recip(lport, ema->mp, fp) == FC_RJT_NONE) > > break; > > + > > + rcu_read_unlock(); > > return fr_seq(fp); > > } > > > > @@ -1817,12 +1825,14 @@ void fc_exch_mgr_reset(struct fc_lport *lport, u32 > > sid, u32 did) > > struct fc_exch_mgr_anchor *ema; > > unsigned int cpu; > > > > - list_for_each_entry(ema, &lport->ema_list, ema_list) { > > + rcu_read_lock(); > > + list_for_each_entry_rcu(ema, &lport->ema_list, ema_list) { > > for_each_possible_cpu(cpu) > > fc_exch_pool_reset(lport, > > per_cpu_ptr(ema->mp->pool, cpu), > > sid, did); > > } > > + rcu_read_unlock(); > > } > > EXPORT_SYMBOL(fc_exch_mgr_reset); > > > > @@ -1836,11 +1846,17 @@ EXPORT_SYMBOL(fc_exch_mgr_reset); > > static struct fc_exch *fc_exch_lookup(struct fc_lport *lport, u32 xid) > > { > > struct fc_exch_mgr_anchor *ema; > > + struct fc_exch *exch = NULL; > > > > - list_for_each_entry(ema, &lport->ema_list, ema_list) > > - if (ema->mp->min_xid <= xid && xid <= ema->mp->max_xid) > > - return fc_exch_find(ema->mp, xid); > > - return NULL; > > + rcu_read_lock(); > > + list_for_each_entry_rcu(ema, &lport->ema_list, ema_list) { > > + if (ema->mp->min_xid <= xid && xid <= ema->mp->max_xid) { > > + exch = fc_exch_find(ema->mp, xid); > > + break; > > + } > > + } > > + rcu_read_unlock(); > > + return exch; > > } > > > > /** > > @@ -2166,6 +2182,7 @@ struct fc_exch_mgr_anchor *fc_exch_mgr_add(struct > > fc_lport *lport, > > bool (*match)(struct fc_frame *)) > > { > > struct fc_exch_mgr_anchor *ema; > > + unsigned long flags; > > > > ema = kmalloc(sizeof(*ema), GFP_ATOMIC); > > if (!ema) > > @@ -2174,8 +2191,10 @@ struct fc_exch_mgr_anchor *fc_exch_mgr_add(struct > > fc_lport *lport, > > ema->mp = mp; > > ema->match = match; > > /* add EM anchor to EM anchors list */ > > - list_add_tail(&ema->ema_list, &lport->ema_list); > > + spin_lock_irqsave(&lport->ema_lock, flags); > > + list_add_tail_rcu(&ema->ema_list, &lport->ema_list); > > kref_get(&mp->kref); > > + spin_unlock_irqrestore(&lport->ema_lock, flags); > > return ema; > > } > > EXPORT_SYMBOL(fc_exch_mgr_add); > > @@ -2193,16 +2212,26 @@ static void fc_exch_mgr_destroy(struct kref *kref) > > kfree(mp); > > } > > > > +static void del_ema_rcu_cb(struct rcu_head *head) > > +{ > > + struct fc_exch_mgr_anchor *ema; > > + > > + ema = container_of(head, struct fc_exch_mgr_anchor, rcu); > > + > > + kref_put(&ema->mp->kref, fc_exch_mgr_destroy); > > + kfree(ema); > > +} > > + > > /** > > * fc_exch_mgr_del() - Delete an EM from a local port's list > > * @ema: The exchange manager anchor identifying the EM to be deleted > > + * NOTE: Assumes that the ema_lock for the list this anchor is on is held > > */ > > void fc_exch_mgr_del(struct fc_exch_mgr_anchor *ema) > > { > > /* remove EM anchor from EM anchors list */ > > - list_del(&ema->ema_list); > > - kref_put(&ema->mp->kref, fc_exch_mgr_destroy); > > - kfree(ema); > > + list_del_rcu(&ema->ema_list); > > + call_rcu(&ema->rcu, del_ema_rcu_cb); > > } > > EXPORT_SYMBOL(fc_exch_mgr_del); > > > > @@ -2214,15 +2243,21 @@ EXPORT_SYMBOL(fc_exch_mgr_del); > > int fc_exch_mgr_list_clone(struct fc_lport *src, struct fc_lport *dst) > > { > > struct fc_exch_mgr_anchor *ema, *tmp; > > + unsigned long flags; > > > > + spin_lock_irqsave(&src->ema_lock, flags); > > list_for_each_entry(ema, &src->ema_list, ema_list) { > > if (!fc_exch_mgr_add(dst, ema->mp, ema->match)) > > goto err; > > } > > + spin_unlock_irqrestore(&src->ema_lock, flags); > > return 0; > > err: > > + spin_unlock_irqrestore(&src->ema_lock, flags); > > + spin_lock_irqsave(&dst->ema_lock, flags); > > list_for_each_entry_safe(ema, tmp, &dst->ema_list, ema_list) > > fc_exch_mgr_del(ema); > > + spin_unlock_irqrestore(&dst->ema_lock, flags); > > return -ENOMEM; > > } > > EXPORT_SYMBOL(fc_exch_mgr_list_clone); > > @@ -2322,10 +2357,13 @@ EXPORT_SYMBOL(fc_exch_mgr_alloc); > > void fc_exch_mgr_free(struct fc_lport *lport) > > { > > struct fc_exch_mgr_anchor *ema, *next; > > + unsigned long flags; > > > > flush_workqueue(fc_exch_workqueue); > > + spin_lock_irqsave(&lport->ema_lock, flags); > > list_for_each_entry_safe(ema, next, &lport->ema_list, ema_list) > > fc_exch_mgr_del(ema); > > + spin_unlock_irqrestore(&lport->ema_lock, flags); > > } > > EXPORT_SYMBOL(fc_exch_mgr_free); > > > > @@ -2340,24 +2378,32 @@ static struct fc_exch_mgr_anchor *fc_find_ema(u32 > > f_ctl, > > struct fc_lport *lport, > > struct fc_frame_header *fh) > > { > > - struct fc_exch_mgr_anchor *ema; > > u16 xid; > > + struct fc_exch_mgr_anchor *ema = NULL; > > > > + rcu_read_lock(); > > if (f_ctl & FC_FC_EX_CTX) > > xid = ntohs(fh->fh_ox_id); > > else { > > xid = ntohs(fh->fh_rx_id); > > - if (xid == FC_XID_UNKNOWN) > > - return list_entry(lport->ema_list.prev, > > + if (xid == FC_XID_UNKNOWN) { > > + > > + ema = list_empty(&lport->ema_list) ? > > + NULL : > > + list_entry(lport->ema_list.prev, > > typeof(*ema), ema_list); > > + goto out; > > + } > > } > > > > - list_for_each_entry(ema, &lport->ema_list, ema_list) { > > + list_for_each_entry_rcu(ema, &lport->ema_list, ema_list) { > > if ((xid >= ema->mp->min_xid) && > > (xid <= ema->mp->max_xid)) > > - return ema; > > + goto out; > > } > > - return NULL; > > +out: > > + rcu_read_unlock(); > > + return ema; > > } > > /** > > * fc_exch_recv() - Handler for received frames > > diff --git a/include/scsi/libfc.h b/include/scsi/libfc.h > > index 8f9dfba..5627d99 100644 > > --- a/include/scsi/libfc.h > > +++ b/include/scsi/libfc.h > > @@ -850,6 +850,7 @@ struct fc_lport { > > /* Associations */ > > struct Scsi_Host *host; > > struct list_head ema_list; > > + spinlock_t ema_lock; > > struct fc_rport_priv *dns_rdata; > > struct fc_rport_priv *ms_rdata; > > struct fc_rport_priv *ptp_rdata; > > @@ -1024,6 +1025,7 @@ libfc_host_alloc(struct scsi_host_template *sht, int > > priv_size) > > lport = shost_priv(shost); > > lport->host = shost; > > INIT_LIST_HEAD(&lport->ema_list); > > + spin_lock_init(&lport->ema_lock); > > INIT_LIST_HEAD(&lport->vports); > > return lport; > > } > > > _______________________________________________ devel mailing list devel@open-fcoe.org https://lists.open-fcoe.org/mailman/listinfo/devel