bridge: fix use-after-free bug on bridge member free
[project/netifd.git] / bridge.c
index c96dcc7bd0ab2c02fbfb2c19a527aef4ed501127..eebd8e9df98b10c3bf2be31c24f0ca21578d4a7f 100644 (file)
--- a/bridge.c
+++ b/bridge.c
@@ -122,6 +122,11 @@ struct bridge_member {
        char name[];
 };
 
+struct bridge_vlan_hotplug_port {
+       struct list_head list;
+       struct bridge_vlan_port port;
+};
+
 static void
 bridge_reset_primary(struct bridge_state *bst)
 {
@@ -153,6 +158,7 @@ bridge_reset_primary(struct bridge_state *bst)
 static struct bridge_vlan_port *
 bridge_find_vlan_member_port(struct bridge_member *bm, struct bridge_vlan *vlan)
 {
+       struct bridge_vlan_hotplug_port *port;
        const char *ifname = bm->dev.dev->ifname;
        int i;
 
@@ -163,6 +169,13 @@ bridge_find_vlan_member_port(struct bridge_member *bm, struct bridge_vlan *vlan)
                return &vlan->ports[i];
        }
 
+       list_for_each_entry(port, &vlan->hotplug_ports, list) {
+               if (strcmp(port->port.ifname, ifname) != 0)
+                       continue;
+
+               return &port->port;
+       }
+
        return NULL;
 }
 
@@ -205,7 +218,7 @@ bridge_set_member_vlan(struct bridge_member *bm, struct bridge_vlan *vlan, bool
 }
 
 static void
-brigde_set_local_vlan(struct bridge_state *bst, struct bridge_vlan *vlan, bool add)
+bridge_set_local_vlan(struct bridge_state *bst, struct bridge_vlan *vlan, bool add)
 {
        if (!vlan->local && add)
                return;
@@ -219,7 +232,7 @@ bridge_set_local_vlans(struct bridge_state *bst, bool add)
        struct bridge_vlan *vlan;
 
        vlist_for_each_element(&bst->dev.vlans, vlan, node)
-               brigde_set_local_vlan(bst, vlan, add);
+               bridge_set_local_vlan(bst, vlan, add);
 }
 
 static struct bridge_vlan *
@@ -251,7 +264,7 @@ bridge_set_vlan_state(struct bridge_state *bst, struct bridge_vlan *vlan, bool a
        struct bridge_member *bm;
        struct bridge_vlan *vlan2;
 
-       brigde_set_local_vlan(bst, vlan, add);
+       bridge_set_local_vlan(bst, vlan, add);
 
        vlist_for_each_element(&bst->members, bm, node) {
                struct bridge_vlan_port *port;
@@ -415,9 +428,27 @@ bridge_remove_member(struct bridge_member *bm)
 static void
 bridge_free_member(struct bridge_member *bm)
 {
+       struct bridge_state *bst = bm->bst;
        struct device *dev = bm->dev.dev;
+       const char *ifname = dev->ifname;
+       struct bridge_vlan *vlan;
 
        bridge_remove_member(bm);
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node) {
+               struct bridge_vlan_hotplug_port *port, *tmp;
+
+               list_for_each_entry_safe(port, tmp, &vlan->hotplug_ports, list) {
+                       if (strcmp(port->port.ifname, ifname) != 0)
+                               continue;
+
+                       list_del(&port->list);
+                       free(port);
+               }
+       }
+
+       device_lock();
+
        device_remove_user(&bm->dev);
 
        /*
@@ -432,6 +463,8 @@ bridge_free_member(struct bridge_member *bm)
                device_set_present(dev, true);
        }
 
+       device_unlock();
+
        free(bm);
 }
 
@@ -616,11 +649,66 @@ bridge_add_member(struct bridge_state *bst, const char *name)
        bridge_create_member(bst, name, dev, false);
 }
 
+static void
+bridge_hotplug_create_member_vlans(struct bridge_state *bst, struct blob_attr *vlans, const char *ifname)
+{
+       struct bridge_vlan *vlan;
+       struct blob_attr *cur;
+       int rem;
+
+       if (!vlans)
+               return;
+
+       blobmsg_for_each_attr(cur, vlans, rem) {
+               struct bridge_vlan_hotplug_port *port;
+               uint16_t flags = BRVLAN_F_UNTAGGED;
+               char *name_buf;
+               unsigned int vid;
+               char *end;
+
+               if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING)
+                       continue;
+
+               vid = strtoul(blobmsg_get_string(cur), &end, 0);
+               if (!vid || vid > 4095)
+                       continue;
+
+               vlan = vlist_find(&bst->dev.vlans, &vid, vlan, node);
+               if (!vlan)
+                       continue;
+
+               if (end && *end) {
+                       if (*end != ':')
+                               continue;
+
+                       for (end++; *end; end++) {
+                               switch (*end) {
+                               case 't':
+                                       flags &= ~BRVLAN_F_UNTAGGED;
+                                       break;
+                               case '*':
+                                       flags |= BRVLAN_F_PVID;
+                                       break;
+                               }
+                       }
+               }
+
+               port = calloc_a(sizeof(*port), &name_buf, strlen(ifname) + 1);
+               if (!port)
+                       continue;
+
+               port->port.flags = flags;
+               port->port.ifname = strcpy(name_buf, ifname);
+               list_add_tail(&port->list, &vlan->hotplug_ports);
+       }
+}
+
 static int
-bridge_hotplug_add(struct device *dev, struct device *member)
+bridge_hotplug_add(struct device *dev, struct device *member, struct blob_attr *vlan)
 {
        struct bridge_state *bst = container_of(dev, struct bridge_state, dev);
 
+       bridge_hotplug_create_member_vlans(bst, vlan, member->ifname);
        bridge_create_member(bst, member->ifname, member, true);
 
        return 0;
@@ -665,15 +753,54 @@ bridge_free(struct device *dev)
 
        bst = container_of(dev, struct bridge_state, dev);
        vlist_flush_all(&bst->members);
+       vlist_flush_all(&dev->vlans);
        free(bst->config_data);
        free(bst);
 }
 
+static void
+bridge_dump_port(struct blob_buf *b, struct bridge_vlan_port *port)
+{
+       bool tagged = !(port->flags & BRVLAN_F_UNTAGGED);
+       bool pvid = (port->flags & BRVLAN_F_PVID);
+
+       blobmsg_printf(b, "%s%s%s%s\n", port->ifname,
+               tagged || pvid ? ":" : "",
+               tagged ? "t" : "",
+               pvid ? "*" : "");
+}
+
+static void
+bridge_dump_vlan(struct blob_buf *b, struct bridge_vlan *vlan)
+{
+       struct bridge_vlan_hotplug_port *port;
+       void *c, *p;
+       int i;
+
+       c = blobmsg_open_table(b, NULL);
+
+       blobmsg_add_u32(b, "id", vlan->vid);
+       blobmsg_add_u8(b, "local", vlan->local);
+
+       p = blobmsg_open_array(b, "ports");
+
+       for (i = 0; i < vlan->n_ports; i++)
+           bridge_dump_port(b, &vlan->ports[i]);
+
+       list_for_each_entry(port, &vlan->hotplug_ports, list)
+               bridge_dump_port(b, &port->port);
+
+       blobmsg_close_array(b, p);
+
+       blobmsg_close_table(b, c);
+}
+
 static void
 bridge_dump_info(struct device *dev, struct blob_buf *b)
 {
        struct bridge_state *bst;
        struct bridge_member *bm;
+       struct bridge_vlan *vlan;
        void *list;
 
        bst = container_of(dev, struct bridge_state, dev);
@@ -689,6 +816,16 @@ bridge_dump_info(struct device *dev, struct blob_buf *b)
        }
 
        blobmsg_close_array(b, list);
+
+       if (avl_is_empty(&dev->vlans.avl))
+               return;
+
+       list = blobmsg_open_array(b, "bridge-vlans");
+
+       vlist_for_each_element(&bst->dev.vlans, vlan, node)
+               bridge_dump_vlan(b, vlan);
+
+       blobmsg_close_array(b, list);
 }
 
 static void
@@ -898,6 +1035,20 @@ bridge_vlan_equal(struct bridge_vlan *v1, struct bridge_vlan *v2)
        return true;
 }
 
+static void
+bridge_vlan_free(struct bridge_vlan *vlan)
+{
+       struct bridge_vlan_hotplug_port *port, *tmp;
+
+       if (!vlan)
+               return;
+
+       list_for_each_entry_safe(port, tmp, &vlan->hotplug_ports, list)
+               free(port);
+
+       free(vlan);
+}
+
 static void
 bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new,
                   struct vlist_node *node_old)
@@ -913,19 +1064,24 @@ bridge_vlan_update(struct vlist_tree *tree, struct vlist_node *node_new,
        if (node_new)
                vlan_new = container_of(node_new, struct bridge_vlan, node);
 
-       if (node_new && node_old && bridge_vlan_equal(vlan_old, vlan_new))
+       if (node_new && node_old && bridge_vlan_equal(vlan_old, vlan_new)) {
+               list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports);
                goto out;
+       }
 
        if (node_old)
                bridge_set_vlan_state(bst, vlan_old, false);
 
+       if (node_old && node_new)
+               list_splice_init(&vlan_old->hotplug_ports, &vlan_new->hotplug_ports);
+
        if (node_new)
                bridge_set_vlan_state(bst, vlan_new, true);
 
        bst->dev.config_pending = true;
 
 out:
-       free(vlan_old);
+       bridge_vlan_free(vlan_old);
 }
 
 static struct device *