aboutsummaryrefslogtreecommitdiffstats
path: root/net/hsr
diff options
context:
space:
mode:
authorArvid Brodin <arvid.brodin@alten.se>2015-02-27 15:26:03 -0500
committerDavid S. Miller <davem@davemloft.net>2015-03-01 13:40:23 -0500
commit56b08fdcf637955d3023d769afd6cdabc526ba22 (patch)
tree943467a3675031846c69f02325c4bb933320dd09 /net/hsr
parent187d67858bafd16334d150b0fa7cff1f5814c6fb (diff)
net/hsr: Fix NULL pointer dereference and refcnt bugs when deleting a HSR interface.
To repeat: $ sudo ip link del hsr0 BUG: unable to handle kernel NULL pointer dereference at 0000000000000018 IP: [<ffffffff8187f495>] hsr_del_port+0x15/0xa0 etc... Bug description: As part of the hsr master device destruction, hsr_del_port() is called for each of the hsr ports. At each such call, the master device is updated regarding features and mtu. When the master device is freed before the slave interfaces, master will be NULL in hsr_del_port(), which led to a NULL pointer dereference. Additionally, dev_put() was called on the master device itself in hsr_del_port(), causing a refcnt error. A third bug in the same code path was that the rtnl lock was not taken before hsr_del_port() was called as part of hsr_dev_destroy(). The reporter (Nicolas Dichtel) also said: "hsr_netdev_notify() supposes that the port will always be available when the notification is for an hsr interface. It's wrong. For example, netdev_wait_allrefs() may resend NETDEV_UNREGISTER.". As a precaution against this, a check for port == NULL was added in hsr_dev_notify(). Reported-by: Nicolas Dichtel <nicolas.dichtel@6wind.com> Fixes: 51f3c605318b056a ("net/hsr: Move slave init to hsr_slave.c.") Signed-off-by: Arvid Brodin <arvid.brodin@alten.se> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/hsr')
-rw-r--r--net/hsr/hsr_device.c3
-rw-r--r--net/hsr/hsr_main.c4
-rw-r--r--net/hsr/hsr_slave.c10
3 files changed, 14 insertions, 3 deletions
diff --git a/net/hsr/hsr_device.c b/net/hsr/hsr_device.c
index a138d75751df..44d27469ae55 100644
--- a/net/hsr/hsr_device.c
+++ b/net/hsr/hsr_device.c
@@ -359,8 +359,11 @@ static void hsr_dev_destroy(struct net_device *hsr_dev)
359 struct hsr_port *port; 359 struct hsr_port *port;
360 360
361 hsr = netdev_priv(hsr_dev); 361 hsr = netdev_priv(hsr_dev);
362
363 rtnl_lock();
362 hsr_for_each_port(hsr, port) 364 hsr_for_each_port(hsr, port)
363 hsr_del_port(port); 365 hsr_del_port(port);
366 rtnl_unlock();
364 367
365 del_timer_sync(&hsr->prune_timer); 368 del_timer_sync(&hsr->prune_timer);
366 del_timer_sync(&hsr->announce_timer); 369 del_timer_sync(&hsr->announce_timer);
diff --git a/net/hsr/hsr_main.c b/net/hsr/hsr_main.c
index 779d28b65417..cd37d0011b42 100644
--- a/net/hsr/hsr_main.c
+++ b/net/hsr/hsr_main.c
@@ -36,6 +36,10 @@ static int hsr_netdev_notify(struct notifier_block *nb, unsigned long event,
36 return NOTIFY_DONE; /* Not an HSR device */ 36 return NOTIFY_DONE; /* Not an HSR device */
37 hsr = netdev_priv(dev); 37 hsr = netdev_priv(dev);
38 port = hsr_port_get_hsr(hsr, HSR_PT_MASTER); 38 port = hsr_port_get_hsr(hsr, HSR_PT_MASTER);
39 if (port == NULL) {
40 /* Resend of notification concerning removed device? */
41 return NOTIFY_DONE;
42 }
39 } else { 43 } else {
40 hsr = port->hsr; 44 hsr = port->hsr;
41 } 45 }
diff --git a/net/hsr/hsr_slave.c b/net/hsr/hsr_slave.c
index a348dcbcd683..7d37366cc695 100644
--- a/net/hsr/hsr_slave.c
+++ b/net/hsr/hsr_slave.c
@@ -181,8 +181,10 @@ void hsr_del_port(struct hsr_port *port)
181 list_del_rcu(&port->port_list); 181 list_del_rcu(&port->port_list);
182 182
183 if (port != master) { 183 if (port != master) {
184 netdev_update_features(master->dev); 184 if (master != NULL) {
185 dev_set_mtu(master->dev, hsr_get_max_mtu(hsr)); 185 netdev_update_features(master->dev);
186 dev_set_mtu(master->dev, hsr_get_max_mtu(hsr));
187 }
186 netdev_rx_handler_unregister(port->dev); 188 netdev_rx_handler_unregister(port->dev);
187 dev_set_promiscuity(port->dev, -1); 189 dev_set_promiscuity(port->dev, -1);
188 } 190 }
@@ -192,5 +194,7 @@ void hsr_del_port(struct hsr_port *port)
192 */ 194 */
193 195
194 synchronize_rcu(); 196 synchronize_rcu();
195 dev_put(port->dev); 197
198 if (port != master)
199 dev_put(port->dev);
196} 200}