b67aa57f4a
[ Upstream commit 3a303cfdd28d5f930a307c82e8a9d996394d5ebd ]
The port->hsr is used in the hsr_handle_frame(), which is a
callback of rx_handler.
hsr master and slaves are initialized in hsr_add_port().
This function initializes several pointers, which includes port->hsr after
registering rx_handler.
So, in the rx_handler routine, un-initialized pointer would be used.
In order to fix this, pointers should be initialized before
registering rx_handler.
Test commands:
ip netns del left
ip netns del right
modprobe -rv veth
modprobe -rv hsr
killall ping
modprobe hsr
ip netns add left
ip netns add right
ip link add veth0 type veth peer name veth1
ip link add veth2 type veth peer name veth3
ip link add veth4 type veth peer name veth5
ip link set veth1 netns left
ip link set veth3 netns right
ip link set veth4 netns left
ip link set veth5 netns right
ip link set veth0 up
ip link set veth2 up
ip link set veth0 address fc:00:00:00:00:01
ip link set veth2 address fc:00:00:00:00:02
ip netns exec left ip link set veth1 up
ip netns exec left ip link set veth4 up
ip netns exec right ip link set veth3 up
ip netns exec right ip link set veth5 up
ip link add hsr0 type hsr slave1 veth0 slave2 veth2
ip a a 192.168.100.1/24 dev hsr0
ip link set hsr0 up
ip netns exec left ip link add hsr1 type hsr slave1 veth1 slave2 veth4
ip netns exec left ip a a 192.168.100.2/24 dev hsr1
ip netns exec left ip link set hsr1 up
ip netns exec left ip n a 192.168.100.1 dev hsr1 lladdr \
fc:00:00:00:00:01 nud permanent
ip netns exec left ip n r 192.168.100.1 dev hsr1 lladdr \
fc:00:00:00:00:01 nud permanent
for i in {1..100}
do
ip netns exec left ping 192.168.100.1 &
done
ip netns exec left hping3 192.168.100.1 -2 --flood &
ip netns exec right ip link add hsr2 type hsr slave1 veth3 slave2 veth5
ip netns exec right ip a a 192.168.100.3/24 dev hsr2
ip netns exec right ip link set hsr2 up
ip netns exec right ip n a 192.168.100.1 dev hsr2 lladdr \
fc:00:00:00:00:02 nud permanent
ip netns exec right ip n r 192.168.100.1 dev hsr2 lladdr \
fc:00:00:00:00:02 nud permanent
for i in {1..100}
do
ip netns exec right ping 192.168.100.1 &
done
ip netns exec right hping3 192.168.100.1 -2 --flood &
while :
do
ip link add hsr0 type hsr slave1 veth0 slave2 veth2
ip a a 192.168.100.1/24 dev hsr0
ip link set hsr0 up
ip link del hsr0
done
Splat looks like:
[ 120.954938][ C0] general protection fault, probably for non-canonical address 0xdffffc0000000006: 0000 [#1]I
[ 120.957761][ C0] KASAN: null-ptr-deref in range [0x0000000000000030-0x0000000000000037]
[ 120.959064][ C0] CPU: 0 PID: 1511 Comm: hping3 Not tainted 5.6.0-rc5+ #460
[ 120.960054][ C0] Hardware name: innotek GmbH VirtualBox/VirtualBox, BIOS VirtualBox 12/01/2006
[ 120.962261][ C0] RIP: 0010:hsr_addr_is_self+0x65/0x2a0 [hsr]
[ 120.963149][ C0] Code: 44 24 18 70 73 2f c0 48 c1 eb 03 48 8d 04 13 c7 00 f1 f1 f1 f1 c7 40 04 00 f2 f2 f2 4
[ 120.966277][ C0] RSP: 0018:ffff8880d9c09af0 EFLAGS: 00010206
[ 120.967293][ C0] RAX: 0000000000000006 RBX: 1ffff1101b38135f RCX: 0000000000000000
[ 120.968516][ C0] RDX: dffffc0000000000 RSI: ffff8880d17cb208 RDI: 0000000000000000
[ 120.969718][ C0] RBP: 0000000000000030 R08: ffffed101b3c0e3c R09: 0000000000000001
[ 120.972203][ C0] R10: 0000000000000001 R11: ffffed101b3c0e3b R12: 0000000000000000
[ 120.973379][ C0] R13: ffff8880aaf80100 R14: ffff8880aaf800f2 R15: ffff8880aaf80040
[ 120.974410][ C0] FS: 00007f58e693f740(0000) GS:ffff8880d9c00000(0000) knlGS:0000000000000000
[ 120.979794][ C0] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[ 120.980773][ C0] CR2: 00007ffcb8b38f29 CR3: 00000000afe8e001 CR4: 00000000000606f0
[ 120.981945][ C0] Call Trace:
[ 120.982411][ C0] <IRQ>
[ 120.982848][ C0] ? hsr_add_node+0x8c0/0x8c0 [hsr]
[ 120.983522][ C0] ? rcu_read_lock_held+0x90/0xa0
[ 120.984159][ C0] ? rcu_read_lock_sched_held+0xc0/0xc0
[ 120.984944][ C0] hsr_handle_frame+0x1db/0x4e0 [hsr]
[ 120.985597][ C0] ? hsr_nl_nodedown+0x2b0/0x2b0 [hsr]
[ 120.986289][ C0] __netif_receive_skb_core+0x6bf/0x3170
[ 120.992513][ C0] ? check_chain_key+0x236/0x5d0
[ 120.993223][ C0] ? do_xdp_generic+0x1460/0x1460
[ 120.993875][ C0] ? register_lock_class+0x14d0/0x14d0
[ 120.994609][ C0] ? __netif_receive_skb_one_core+0x8d/0x160
[ 120.995377][ C0] __netif_receive_skb_one_core+0x8d/0x160
[ 120.996204][ C0] ? __netif_receive_skb_core+0x3170/0x3170
[ ... ]
Reported-by: syzbot+fcf5dd39282ceb27108d@syzkaller.appspotmail.com
Fixes: c5a7591172
("net/hsr: Use list_head (and rcu) instead of array for slave devices.")
Signed-off-by: Taehee Yoo <ap420073@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
200 lines
4.2 KiB
C
200 lines
4.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright 2011-2014 Autronica Fire and Security AS
|
|
*
|
|
* Author(s):
|
|
* 2011-2014 Arvid Brodin, arvid.brodin@alten.se
|
|
*/
|
|
|
|
#include "hsr_slave.h"
|
|
#include <linux/etherdevice.h>
|
|
#include <linux/if_arp.h>
|
|
#include <linux/if_vlan.h>
|
|
#include "hsr_main.h"
|
|
#include "hsr_device.h"
|
|
#include "hsr_forward.h"
|
|
#include "hsr_framereg.h"
|
|
|
|
static rx_handler_result_t hsr_handle_frame(struct sk_buff **pskb)
|
|
{
|
|
struct sk_buff *skb = *pskb;
|
|
struct hsr_port *port;
|
|
u16 protocol;
|
|
|
|
if (!skb_mac_header_was_set(skb)) {
|
|
WARN_ONCE(1, "%s: skb invalid", __func__);
|
|
return RX_HANDLER_PASS;
|
|
}
|
|
|
|
rcu_read_lock(); /* hsr->node_db, hsr->ports */
|
|
port = hsr_port_get_rcu(skb->dev);
|
|
if (!port)
|
|
goto finish_pass;
|
|
|
|
if (hsr_addr_is_self(port->hsr, eth_hdr(skb)->h_source)) {
|
|
/* Directly kill frames sent by ourselves */
|
|
kfree_skb(skb);
|
|
goto finish_consume;
|
|
}
|
|
|
|
protocol = eth_hdr(skb)->h_proto;
|
|
if (protocol != htons(ETH_P_PRP) && protocol != htons(ETH_P_HSR))
|
|
goto finish_pass;
|
|
|
|
skb_push(skb, ETH_HLEN);
|
|
|
|
hsr_forward_skb(skb, port);
|
|
|
|
finish_consume:
|
|
rcu_read_unlock(); /* hsr->node_db, hsr->ports */
|
|
return RX_HANDLER_CONSUMED;
|
|
|
|
finish_pass:
|
|
rcu_read_unlock(); /* hsr->node_db, hsr->ports */
|
|
return RX_HANDLER_PASS;
|
|
}
|
|
|
|
bool hsr_port_exists(const struct net_device *dev)
|
|
{
|
|
return rcu_access_pointer(dev->rx_handler) == hsr_handle_frame;
|
|
}
|
|
|
|
static int hsr_check_dev_ok(struct net_device *dev)
|
|
{
|
|
/* Don't allow HSR on non-ethernet like devices */
|
|
if ((dev->flags & IFF_LOOPBACK) || dev->type != ARPHRD_ETHER ||
|
|
dev->addr_len != ETH_ALEN) {
|
|
netdev_info(dev, "Cannot use loopback or non-ethernet device as HSR slave.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Don't allow enslaving hsr devices */
|
|
if (is_hsr_master(dev)) {
|
|
netdev_info(dev, "Cannot create trees of HSR devices.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (hsr_port_exists(dev)) {
|
|
netdev_info(dev, "This device is already a HSR slave.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (is_vlan_dev(dev)) {
|
|
netdev_info(dev, "HSR on top of VLAN is not yet supported in this driver.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (dev->priv_flags & IFF_DONT_BRIDGE) {
|
|
netdev_info(dev, "This device does not support bridging.\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
/* HSR over bonded devices has not been tested, but I'm not sure it
|
|
* won't work...
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Setup device to be added to the HSR bridge. */
|
|
static int hsr_portdev_setup(struct net_device *dev, struct hsr_port *port)
|
|
{
|
|
int res;
|
|
|
|
dev_hold(dev);
|
|
res = dev_set_promiscuity(dev, 1);
|
|
if (res)
|
|
goto fail_promiscuity;
|
|
|
|
/* FIXME:
|
|
* What does net device "adjacency" mean? Should we do
|
|
* res = netdev_master_upper_dev_link(port->dev, port->hsr->dev); ?
|
|
*/
|
|
|
|
res = netdev_rx_handler_register(dev, hsr_handle_frame, port);
|
|
if (res)
|
|
goto fail_rx_handler;
|
|
dev_disable_lro(dev);
|
|
|
|
return 0;
|
|
|
|
fail_rx_handler:
|
|
dev_set_promiscuity(dev, -1);
|
|
fail_promiscuity:
|
|
dev_put(dev);
|
|
|
|
return res;
|
|
}
|
|
|
|
int hsr_add_port(struct hsr_priv *hsr, struct net_device *dev,
|
|
enum hsr_port_type type)
|
|
{
|
|
struct hsr_port *port, *master;
|
|
int res;
|
|
|
|
if (type != HSR_PT_MASTER) {
|
|
res = hsr_check_dev_ok(dev);
|
|
if (res)
|
|
return res;
|
|
}
|
|
|
|
port = hsr_port_get_hsr(hsr, type);
|
|
if (port)
|
|
return -EBUSY; /* This port already exists */
|
|
|
|
port = kzalloc(sizeof(*port), GFP_KERNEL);
|
|
if (!port)
|
|
return -ENOMEM;
|
|
|
|
port->hsr = hsr;
|
|
port->dev = dev;
|
|
port->type = type;
|
|
|
|
if (type != HSR_PT_MASTER) {
|
|
res = hsr_portdev_setup(dev, port);
|
|
if (res)
|
|
goto fail_dev_setup;
|
|
}
|
|
|
|
list_add_tail_rcu(&port->port_list, &hsr->ports);
|
|
synchronize_rcu();
|
|
|
|
master = hsr_port_get_hsr(hsr, HSR_PT_MASTER);
|
|
netdev_update_features(master->dev);
|
|
dev_set_mtu(master->dev, hsr_get_max_mtu(hsr));
|
|
|
|
return 0;
|
|
|
|
fail_dev_setup:
|
|
kfree(port);
|
|
return res;
|
|
}
|
|
|
|
void hsr_del_port(struct hsr_port *port)
|
|
{
|
|
struct hsr_priv *hsr;
|
|
struct hsr_port *master;
|
|
|
|
hsr = port->hsr;
|
|
master = hsr_port_get_hsr(hsr, HSR_PT_MASTER);
|
|
list_del_rcu(&port->port_list);
|
|
|
|
if (port != master) {
|
|
if (master) {
|
|
netdev_update_features(master->dev);
|
|
dev_set_mtu(master->dev, hsr_get_max_mtu(hsr));
|
|
}
|
|
netdev_rx_handler_unregister(port->dev);
|
|
dev_set_promiscuity(port->dev, -1);
|
|
}
|
|
|
|
/* FIXME?
|
|
* netdev_upper_dev_unlink(port->dev, port->hsr->dev);
|
|
*/
|
|
|
|
synchronize_rcu();
|
|
|
|
if (port != master)
|
|
dev_put(port->dev);
|
|
kfree(port);
|
|
}
|