| From foo@baz Mon Feb 20 16:34:36 CET 2017 |
| From: Andrew Collins <acollins@cradlepoint.com> |
| Date: Mon, 3 Oct 2016 13:43:02 -0600 |
| Subject: [PATCH 081/760] net: Add netdev all_adj_list refcnt propagation to fix panic |
| |
| From: Andrew Collins <acollins@cradlepoint.com> |
| |
| |
| [ Upstream commit 93409033ae653f1c9a949202fb537ab095b2092f ] |
| |
| This is a respin of a patch to fix a relatively easily reproducible kernel |
| panic related to the all_adj_list handling for netdevs in recent kernels. |
| |
| The following sequence of commands will reproduce the issue: |
| |
| ip link add link eth0 name eth0.100 type vlan id 100 |
| ip link add link eth0 name eth0.200 type vlan id 200 |
| ip link add name testbr type bridge |
| ip link set eth0.100 master testbr |
| ip link set eth0.200 master testbr |
| ip link add link testbr mac0 type macvlan |
| ip link delete dev testbr |
| |
| This creates an upper/lower tree of (excuse the poor ASCII art): |
| |
| /---eth0.100-eth0 |
| mac0-testbr- |
| \---eth0.200-eth0 |
| |
| When testbr is deleted, the all_adj_lists are walked, and eth0 is deleted twice from |
| the mac0 list. Unfortunately, during setup in __netdev_upper_dev_link, only one |
| reference to eth0 is added, so this results in a panic. |
| |
| This change adds reference count propagation so things are handled properly. |
| |
| Matthias Schiffer reported a similar crash in batman-adv: |
| |
| https://github.com/freifunk-gluon/gluon/issues/680 |
| https://www.open-mesh.org/issues/247 |
| |
| which this patch also seems to resolve. |
| |
| Signed-off-by: Andrew Collins <acollins@cradlepoint.com> |
| Signed-off-by: David S. Miller <davem@davemloft.net> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| net/core/dev.c | 68 +++++++++++++++++++++++++++++++-------------------------- |
| 1 file changed, 37 insertions(+), 31 deletions(-) |
| |
| --- a/net/core/dev.c |
| +++ b/net/core/dev.c |
| @@ -4893,6 +4893,7 @@ static inline bool netdev_adjacent_is_ne |
| |
| static int __netdev_adjacent_dev_insert(struct net_device *dev, |
| struct net_device *adj_dev, |
| + u16 ref_nr, |
| struct list_head *dev_list, |
| void *private, bool master) |
| { |
| @@ -4902,7 +4903,7 @@ static int __netdev_adjacent_dev_insert( |
| adj = __netdev_find_adj(dev, adj_dev, dev_list); |
| |
| if (adj) { |
| - adj->ref_nr++; |
| + adj->ref_nr += ref_nr; |
| return 0; |
| } |
| |
| @@ -4912,7 +4913,7 @@ static int __netdev_adjacent_dev_insert( |
| |
| adj->dev = adj_dev; |
| adj->master = master; |
| - adj->ref_nr = 1; |
| + adj->ref_nr = ref_nr; |
| adj->private = private; |
| dev_hold(adj_dev); |
| |
| @@ -4951,6 +4952,7 @@ free_adj: |
| |
| static void __netdev_adjacent_dev_remove(struct net_device *dev, |
| struct net_device *adj_dev, |
| + u16 ref_nr, |
| struct list_head *dev_list) |
| { |
| struct netdev_adjacent *adj; |
| @@ -4963,10 +4965,10 @@ static void __netdev_adjacent_dev_remove |
| BUG(); |
| } |
| |
| - if (adj->ref_nr > 1) { |
| - pr_debug("%s to %s ref_nr-- = %d\n", dev->name, adj_dev->name, |
| - adj->ref_nr-1); |
| - adj->ref_nr--; |
| + if (adj->ref_nr > ref_nr) { |
| + pr_debug("%s to %s ref_nr-%d = %d\n", dev->name, adj_dev->name, |
| + ref_nr, adj->ref_nr-ref_nr); |
| + adj->ref_nr -= ref_nr; |
| return; |
| } |
| |
| @@ -4985,21 +4987,22 @@ static void __netdev_adjacent_dev_remove |
| |
| static int __netdev_adjacent_dev_link_lists(struct net_device *dev, |
| struct net_device *upper_dev, |
| + u16 ref_nr, |
| struct list_head *up_list, |
| struct list_head *down_list, |
| void *private, bool master) |
| { |
| int ret; |
| |
| - ret = __netdev_adjacent_dev_insert(dev, upper_dev, up_list, private, |
| - master); |
| + ret = __netdev_adjacent_dev_insert(dev, upper_dev, ref_nr, up_list, |
| + private, master); |
| if (ret) |
| return ret; |
| |
| - ret = __netdev_adjacent_dev_insert(upper_dev, dev, down_list, private, |
| - false); |
| + ret = __netdev_adjacent_dev_insert(upper_dev, dev, ref_nr, down_list, |
| + private, false); |
| if (ret) { |
| - __netdev_adjacent_dev_remove(dev, upper_dev, up_list); |
| + __netdev_adjacent_dev_remove(dev, upper_dev, ref_nr, up_list); |
| return ret; |
| } |
| |
| @@ -5007,9 +5010,10 @@ static int __netdev_adjacent_dev_link_li |
| } |
| |
| static int __netdev_adjacent_dev_link(struct net_device *dev, |
| - struct net_device *upper_dev) |
| + struct net_device *upper_dev, |
| + u16 ref_nr) |
| { |
| - return __netdev_adjacent_dev_link_lists(dev, upper_dev, |
| + return __netdev_adjacent_dev_link_lists(dev, upper_dev, ref_nr, |
| &dev->all_adj_list.upper, |
| &upper_dev->all_adj_list.lower, |
| NULL, false); |
| @@ -5017,17 +5021,19 @@ static int __netdev_adjacent_dev_link(st |
| |
| static void __netdev_adjacent_dev_unlink_lists(struct net_device *dev, |
| struct net_device *upper_dev, |
| + u16 ref_nr, |
| struct list_head *up_list, |
| struct list_head *down_list) |
| { |
| - __netdev_adjacent_dev_remove(dev, upper_dev, up_list); |
| - __netdev_adjacent_dev_remove(upper_dev, dev, down_list); |
| + __netdev_adjacent_dev_remove(dev, upper_dev, ref_nr, up_list); |
| + __netdev_adjacent_dev_remove(upper_dev, dev, ref_nr, down_list); |
| } |
| |
| static void __netdev_adjacent_dev_unlink(struct net_device *dev, |
| - struct net_device *upper_dev) |
| + struct net_device *upper_dev, |
| + u16 ref_nr) |
| { |
| - __netdev_adjacent_dev_unlink_lists(dev, upper_dev, |
| + __netdev_adjacent_dev_unlink_lists(dev, upper_dev, ref_nr, |
| &dev->all_adj_list.upper, |
| &upper_dev->all_adj_list.lower); |
| } |
| @@ -5036,17 +5042,17 @@ static int __netdev_adjacent_dev_link_ne |
| struct net_device *upper_dev, |
| void *private, bool master) |
| { |
| - int ret = __netdev_adjacent_dev_link(dev, upper_dev); |
| + int ret = __netdev_adjacent_dev_link(dev, upper_dev, 1); |
| |
| if (ret) |
| return ret; |
| |
| - ret = __netdev_adjacent_dev_link_lists(dev, upper_dev, |
| + ret = __netdev_adjacent_dev_link_lists(dev, upper_dev, 1, |
| &dev->adj_list.upper, |
| &upper_dev->adj_list.lower, |
| private, master); |
| if (ret) { |
| - __netdev_adjacent_dev_unlink(dev, upper_dev); |
| + __netdev_adjacent_dev_unlink(dev, upper_dev, 1); |
| return ret; |
| } |
| |
| @@ -5056,8 +5062,8 @@ static int __netdev_adjacent_dev_link_ne |
| static void __netdev_adjacent_dev_unlink_neighbour(struct net_device *dev, |
| struct net_device *upper_dev) |
| { |
| - __netdev_adjacent_dev_unlink(dev, upper_dev); |
| - __netdev_adjacent_dev_unlink_lists(dev, upper_dev, |
| + __netdev_adjacent_dev_unlink(dev, upper_dev, 1); |
| + __netdev_adjacent_dev_unlink_lists(dev, upper_dev, 1, |
| &dev->adj_list.upper, |
| &upper_dev->adj_list.lower); |
| } |
| @@ -5098,7 +5104,7 @@ static int __netdev_upper_dev_link(struc |
| list_for_each_entry(j, &upper_dev->all_adj_list.upper, list) { |
| pr_debug("Interlinking %s with %s, non-neighbour\n", |
| i->dev->name, j->dev->name); |
| - ret = __netdev_adjacent_dev_link(i->dev, j->dev); |
| + ret = __netdev_adjacent_dev_link(i->dev, j->dev, i->ref_nr); |
| if (ret) |
| goto rollback_mesh; |
| } |
| @@ -5108,7 +5114,7 @@ static int __netdev_upper_dev_link(struc |
| list_for_each_entry(i, &upper_dev->all_adj_list.upper, list) { |
| pr_debug("linking %s's upper device %s with %s\n", |
| upper_dev->name, i->dev->name, dev->name); |
| - ret = __netdev_adjacent_dev_link(dev, i->dev); |
| + ret = __netdev_adjacent_dev_link(dev, i->dev, i->ref_nr); |
| if (ret) |
| goto rollback_upper_mesh; |
| } |
| @@ -5117,7 +5123,7 @@ static int __netdev_upper_dev_link(struc |
| list_for_each_entry(i, &dev->all_adj_list.lower, list) { |
| pr_debug("linking %s's lower device %s with %s\n", dev->name, |
| i->dev->name, upper_dev->name); |
| - ret = __netdev_adjacent_dev_link(i->dev, upper_dev); |
| + ret = __netdev_adjacent_dev_link(i->dev, upper_dev, i->ref_nr); |
| if (ret) |
| goto rollback_lower_mesh; |
| } |
| @@ -5130,7 +5136,7 @@ rollback_lower_mesh: |
| list_for_each_entry(i, &dev->all_adj_list.lower, list) { |
| if (i == to_i) |
| break; |
| - __netdev_adjacent_dev_unlink(i->dev, upper_dev); |
| + __netdev_adjacent_dev_unlink(i->dev, upper_dev, i->ref_nr); |
| } |
| |
| i = NULL; |
| @@ -5140,7 +5146,7 @@ rollback_upper_mesh: |
| list_for_each_entry(i, &upper_dev->all_adj_list.upper, list) { |
| if (i == to_i) |
| break; |
| - __netdev_adjacent_dev_unlink(dev, i->dev); |
| + __netdev_adjacent_dev_unlink(dev, i->dev, i->ref_nr); |
| } |
| |
| i = j = NULL; |
| @@ -5152,7 +5158,7 @@ rollback_mesh: |
| list_for_each_entry(j, &upper_dev->all_adj_list.upper, list) { |
| if (i == to_i && j == to_j) |
| break; |
| - __netdev_adjacent_dev_unlink(i->dev, j->dev); |
| + __netdev_adjacent_dev_unlink(i->dev, j->dev, i->ref_nr); |
| } |
| if (i == to_i) |
| break; |
| @@ -5228,16 +5234,16 @@ void netdev_upper_dev_unlink(struct net_ |
| */ |
| list_for_each_entry(i, &dev->all_adj_list.lower, list) |
| list_for_each_entry(j, &upper_dev->all_adj_list.upper, list) |
| - __netdev_adjacent_dev_unlink(i->dev, j->dev); |
| + __netdev_adjacent_dev_unlink(i->dev, j->dev, i->ref_nr); |
| |
| /* remove also the devices itself from lower/upper device |
| * list |
| */ |
| list_for_each_entry(i, &dev->all_adj_list.lower, list) |
| - __netdev_adjacent_dev_unlink(i->dev, upper_dev); |
| + __netdev_adjacent_dev_unlink(i->dev, upper_dev, i->ref_nr); |
| |
| list_for_each_entry(i, &upper_dev->all_adj_list.upper, list) |
| - __netdev_adjacent_dev_unlink(dev, i->dev); |
| + __netdev_adjacent_dev_unlink(dev, i->dev, i->ref_nr); |
| |
| call_netdevice_notifiers(NETDEV_CHANGEUPPER, dev); |
| } |