Skip to content

Commit

Permalink
llc: Fix races between llc2 handler use and (un)registration
Browse files Browse the repository at this point in the history
When registering the handlers, any state they rely on must be
completely initialised first.  When unregistering, we must wait until
they are definitely no longer running.  llc_rcv() must also avoid
reading the handler pointers again after checking for NULL.

Signed-off-by: Ben Hutchings <[email protected]>
Signed-off-by: David S. Miller <[email protected]>
  • Loading branch information
bwhacks authored and davem330 committed Aug 14, 2012
1 parent f4f8720 commit aadf31d
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 5 deletions.
21 changes: 17 additions & 4 deletions net/llc/llc_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ static void (*llc_type_handlers[2])(struct llc_sap *sap,
void llc_add_pack(int type, void (*handler)(struct llc_sap *sap,
struct sk_buff *skb))
{
smp_wmb(); /* ensure initialisation is complete before it's called */
if (type == LLC_DEST_SAP || type == LLC_DEST_CONN)
llc_type_handlers[type - 1] = handler;
}
Expand All @@ -50,11 +51,19 @@ void llc_remove_pack(int type)
{
if (type == LLC_DEST_SAP || type == LLC_DEST_CONN)
llc_type_handlers[type - 1] = NULL;
synchronize_net();
}

void llc_set_station_handler(void (*handler)(struct sk_buff *skb))
{
/* Ensure initialisation is complete before it's called */
if (handler)
smp_wmb();

llc_station_handler = handler;

if (!handler)
synchronize_net();
}

/**
Expand Down Expand Up @@ -150,6 +159,8 @@ int llc_rcv(struct sk_buff *skb, struct net_device *dev,
int dest;
int (*rcv)(struct sk_buff *, struct net_device *,
struct packet_type *, struct net_device *);
void (*sta_handler)(struct sk_buff *skb);
void (*sap_handler)(struct llc_sap *sap, struct sk_buff *skb);

if (!net_eq(dev_net(dev), &init_net))
goto drop;
Expand Down Expand Up @@ -182,7 +193,8 @@ int llc_rcv(struct sk_buff *skb, struct net_device *dev,
*/
rcv = rcu_dereference(sap->rcv_func);
dest = llc_pdu_type(skb);
if (unlikely(!dest || !llc_type_handlers[dest - 1])) {
sap_handler = dest ? ACCESS_ONCE(llc_type_handlers[dest - 1]) : NULL;
if (unlikely(!sap_handler)) {
if (rcv)
rcv(skb, dev, pt, orig_dev);
else
Expand All @@ -193,7 +205,7 @@ int llc_rcv(struct sk_buff *skb, struct net_device *dev,
if (cskb)
rcv(cskb, dev, pt, orig_dev);
}
llc_type_handlers[dest - 1](sap, skb);
sap_handler(sap, skb);
}
llc_sap_put(sap);
out:
Expand All @@ -202,9 +214,10 @@ int llc_rcv(struct sk_buff *skb, struct net_device *dev,
kfree_skb(skb);
goto out;
handle_station:
if (!llc_station_handler)
sta_handler = ACCESS_ONCE(llc_station_handler);
if (!sta_handler)
goto drop;
llc_station_handler(skb);
sta_handler(skb);
goto out;
}

Expand Down
2 changes: 1 addition & 1 deletion net/llc/llc_station.c
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,9 @@ void __init llc_station_init(void)
(unsigned long)&llc_main_station);
llc_main_station.ack_timer.expires = jiffies +
sysctl_llc_station_ack_timeout;
llc_set_station_handler(llc_station_rcv);
llc_main_station.maximum_retry = 1;
llc_main_station.state = LLC_STATION_STATE_UP;
llc_set_station_handler(llc_station_rcv);
}

void llc_station_exit(void)
Expand Down

0 comments on commit aadf31d

Please sign in to comment.