batman-adv: Make bat_priv->primary_if an rcu protected pointer

The rcu protected macros rcu_dereference() and rcu_assign_pointer()
for the bat_priv->primary_if need to be used, as well as spin/rcu locking.

Otherwise we might end up using a primary_if pointer pointing to already
freed memory.

Signed-off-by: Marek Lindner <lindner_marek@yahoo.de>
Signed-off-by: Sven Eckelmann <sven@narfation.org>
diff --git a/net/batman-adv/hard-interface.c b/net/batman-adv/hard-interface.c
index b3058e4..3e888f1 100644
--- a/net/batman-adv/hard-interface.c
+++ b/net/batman-adv/hard-interface.c
@@ -110,47 +110,60 @@
 	return hard_iface;
 }
 
-static void update_primary_addr(struct bat_priv *bat_priv)
+static void primary_if_update_addr(struct bat_priv *bat_priv)
 {
 	struct vis_packet *vis_packet;
+	struct hard_iface *primary_if;
+
+	primary_if = primary_if_get_selected(bat_priv);
+	if (!primary_if)
+		goto out;
 
 	vis_packet = (struct vis_packet *)
 				bat_priv->my_vis_info->skb_packet->data;
-	memcpy(vis_packet->vis_orig,
-	       bat_priv->primary_if->net_dev->dev_addr, ETH_ALEN);
+	memcpy(vis_packet->vis_orig, primary_if->net_dev->dev_addr, ETH_ALEN);
 	memcpy(vis_packet->sender_orig,
-	       bat_priv->primary_if->net_dev->dev_addr, ETH_ALEN);
+	       primary_if->net_dev->dev_addr, ETH_ALEN);
+
+out:
+	if (primary_if)
+		hardif_free_ref(primary_if);
 }
 
-static void set_primary_if(struct bat_priv *bat_priv,
-			   struct hard_iface *hard_iface)
+static void primary_if_select(struct bat_priv *bat_priv,
+			      struct hard_iface *new_hard_iface)
 {
+	struct hard_iface *curr_hard_iface;
 	struct batman_packet *batman_packet;
-	struct hard_iface *old_if;
 
-	if (hard_iface && !atomic_inc_not_zero(&hard_iface->refcount))
-		hard_iface = NULL;
+	spin_lock_bh(&hardif_list_lock);
 
-	old_if = bat_priv->primary_if;
-	bat_priv->primary_if = hard_iface;
+	if (new_hard_iface && !atomic_inc_not_zero(&new_hard_iface->refcount))
+		new_hard_iface = NULL;
 
-	if (old_if)
-		hardif_free_ref(old_if);
+	curr_hard_iface = bat_priv->primary_if;
+	rcu_assign_pointer(bat_priv->primary_if, new_hard_iface);
 
-	if (!bat_priv->primary_if)
-		return;
+	if (curr_hard_iface)
+		hardif_free_ref(curr_hard_iface);
 
-	batman_packet = (struct batman_packet *)(hard_iface->packet_buff);
+	if (!new_hard_iface)
+		goto out;
+
+	batman_packet = (struct batman_packet *)(new_hard_iface->packet_buff);
 	batman_packet->flags = PRIMARIES_FIRST_HOP;
 	batman_packet->ttl = TTL;
 
-	update_primary_addr(bat_priv);
+	primary_if_update_addr(bat_priv);
 
 	/***
 	 * hacky trick to make sure that we send the HNA information via
 	 * our new primary interface
 	 */
 	atomic_set(&bat_priv->hna_local_changed, 1);
+
+out:
+	spin_unlock_bh(&hardif_list_lock);
 }
 
 static bool hardif_is_iface_up(struct hard_iface *hard_iface)
@@ -236,9 +249,10 @@
 static void hardif_activate_interface(struct hard_iface *hard_iface)
 {
 	struct bat_priv *bat_priv;
+	struct hard_iface *primary_if = NULL;
 
 	if (hard_iface->if_status != IF_INACTIVE)
-		return;
+		goto out;
 
 	bat_priv = netdev_priv(hard_iface->soft_iface);
 
@@ -249,14 +263,18 @@
 	 * the first active interface becomes our primary interface or
 	 * the next active interface after the old primay interface was removed
 	 */
-	if (!bat_priv->primary_if)
-		set_primary_if(bat_priv, hard_iface);
+	primary_if = primary_if_get_selected(bat_priv);
+	if (!primary_if)
+		primary_if_select(bat_priv, hard_iface);
 
 	bat_info(hard_iface->soft_iface, "Interface activated: %s\n",
 		 hard_iface->net_dev->name);
 
 	update_min_mtu(hard_iface->soft_iface);
-	return;
+
+out:
+	if (primary_if)
+		hardif_free_ref(primary_if);
 }
 
 static void hardif_deactivate_interface(struct hard_iface *hard_iface)
@@ -386,12 +404,13 @@
 void hardif_disable_interface(struct hard_iface *hard_iface)
 {
 	struct bat_priv *bat_priv = netdev_priv(hard_iface->soft_iface);
+	struct hard_iface *primary_if = NULL;
 
 	if (hard_iface->if_status == IF_ACTIVE)
 		hardif_deactivate_interface(hard_iface);
 
 	if (hard_iface->if_status != IF_INACTIVE)
-		return;
+		goto out;
 
 	bat_info(hard_iface->soft_iface, "Removing interface: %s\n",
 		 hard_iface->net_dev->name);
@@ -400,11 +419,12 @@
 	bat_priv->num_ifaces--;
 	orig_hash_del_if(hard_iface, bat_priv->num_ifaces);
 
-	if (hard_iface == bat_priv->primary_if) {
+	primary_if = primary_if_get_selected(bat_priv);
+	if (hard_iface == primary_if) {
 		struct hard_iface *new_if;
 
 		new_if = hardif_get_active(hard_iface->soft_iface);
-		set_primary_if(bat_priv, new_if);
+		primary_if_select(bat_priv, new_if);
 
 		if (new_if)
 			hardif_free_ref(new_if);
@@ -425,6 +445,10 @@
 
 	hard_iface->soft_iface = NULL;
 	hardif_free_ref(hard_iface);
+
+out:
+	if (primary_if)
+		hardif_free_ref(primary_if);
 }
 
 static struct hard_iface *hardif_add_interface(struct net_device *net_dev)
@@ -514,6 +538,7 @@
 {
 	struct net_device *net_dev = (struct net_device *)ptr;
 	struct hard_iface *hard_iface = hardif_get_by_netdev(net_dev);
+	struct hard_iface *primary_if = NULL;
 	struct bat_priv *bat_priv;
 
 	if (!hard_iface && event == NETDEV_REGISTER)
@@ -549,8 +574,12 @@
 		update_mac_addresses(hard_iface);
 
 		bat_priv = netdev_priv(hard_iface->soft_iface);
-		if (hard_iface == bat_priv->primary_if)
-			update_primary_addr(bat_priv);
+		primary_if = primary_if_get_selected(bat_priv);
+		if (!primary_if)
+			goto hardif_put;
+
+		if (hard_iface == primary_if)
+			primary_if_update_addr(bat_priv);
 		break;
 	default:
 		break;
@@ -559,6 +588,8 @@
 hardif_put:
 	hardif_free_ref(hard_iface);
 out:
+	if (primary_if)
+		hardif_free_ref(primary_if);
 	return NOTIFY_DONE;
 }