We reference the gw_nodes as curr_gw for each bat_priv. They are tracked
inside a rcu protected list gw_list which may free the memory after it
gets removed from the list. Nevertheless it could still be referenced by
curr_gw and access to it would result in a kernel oops.

Signed-off-by: Sven Eckelmann <[email protected]>
---
 batman-adv/gateway_client.c |   22 +++++++++++++++++++---
 1 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/batman-adv/gateway_client.c b/batman-adv/gateway_client.c
index 16f0757..1cad4f8 100644
--- a/batman-adv/gateway_client.c
+++ b/batman-adv/gateway_client.c
@@ -51,13 +51,18 @@ void *gw_get_selected(struct bat_priv *bat_priv)
 
 void gw_deselect(struct bat_priv *bat_priv)
 {
+       struct gw_node *gw_node = bat_priv->curr_gw;
+
        bat_priv->curr_gw = NULL;
+
+       if (gw_node)
+               gw_node_put(gw_node);
 }
 
 void gw_election(struct bat_priv *bat_priv)
 {
        struct hlist_node *node;
-       struct gw_node *gw_node, *curr_gw_tmp = NULL;
+       struct gw_node *gw_node, *curr_gw_tmp = NULL, *old_gw_node = NULL;
        uint8_t max_tq = 0;
        uint32_t max_gw_factor = 0, tmp_gw_factor = 0;
        int down, up;
@@ -131,7 +136,6 @@ void gw_election(struct bat_priv *bat_priv)
                if (tmp_gw_factor > max_gw_factor)
                        max_gw_factor = tmp_gw_factor;
        }
-       rcu_read_unlock();
 
        if (bat_priv->curr_gw != curr_gw_tmp) {
                if ((bat_priv->curr_gw) && (!curr_gw_tmp))
@@ -153,8 +157,17 @@ void gw_election(struct bat_priv *bat_priv)
                                curr_gw_tmp->orig_node->gw_flags,
                                curr_gw_tmp->orig_node->router->tq_avg);
 
+               old_gw_node = bat_priv->curr_gw;
+               if (curr_gw_tmp)
+                       gw_node_hold(curr_gw_tmp);
+
                bat_priv->curr_gw = curr_gw_tmp;
        }
+
+       rcu_read_unlock();
+
+       if (old_gw_node)
+               gw_node_put(old_gw_node);
 }
 
 void gw_check_election(struct bat_priv *bat_priv, struct orig_node *orig_node)
@@ -258,8 +271,11 @@ void gw_node_update(struct bat_priv *bat_priv,
                                "Gateway %pM removed from gateway list\n",
                                orig_node->orig);
 
-                       if (gw_node == bat_priv->curr_gw)
+                       if (gw_node == bat_priv->curr_gw) {
+                               rcu_read_unlock();
                                gw_deselect(bat_priv);
+                               return;
+                       }
                }
 
                rcu_read_unlock();
-- 
1.7.2.3

Reply via email to