Commit message: net-sysfs: Fix mem leak in netdev_register_kobject

syzkaller report this:
BUG: memory leak
unreferenced object 0xffff88837a71a500 (size 256):
  comm "syz-executor.2", pid 9770, jiffies 4297825125 (age 17.843s)
  hex dump (first 32 bytes):
    00 00 00 00 ad 4e ad de ff ff ff ff 00 00 00 00  .....N..........
    ff ff ff ff ff ff ff ff 20 c0 ef 86 ff ff ff ff  ........ .......
  backtrace:
    [<00000000db12624b>] netdev_register_kobject+0x124/0x2e0 net/core/net-sysfs.c:1751
    [<00000000dc49a994>] register_netdevice+0xcc1/0x1270 net/core/dev.c:8516
    [<00000000e5f3fea0>] tun_set_iff drivers/net/tun.c:2649 [inline]
    [<00000000e5f3fea0>] __tun_chr_ioctl+0x2218/0x3d20 drivers/net/tun.c:2883
    [<000000001b8ac127>] vfs_ioctl fs/ioctl.c:46 [inline]
    [<000000001b8ac127>] do_vfs_ioctl+0x1a5/0x10e0 fs/ioctl.c:690
    [<0000000079b269f8>] ksys_ioctl+0x89/0xa0 fs/ioctl.c:705
    [<00000000de649beb>] __do_sys_ioctl fs/ioctl.c:712 [inline]
    [<00000000de649beb>] __se_sys_ioctl fs/ioctl.c:710 [inline]
    [<00000000de649beb>] __x64_sys_ioctl+0x74/0xb0 fs/ioctl.c:710
    [<000000007ebded1e>] do_syscall_64+0xc8/0x580 arch/x86/entry/common.c:290
    [<00000000db315d36>] entry_SYSCALL_64_after_hwframe+0x49/0xbe
    [<00000000115be9bb>] 0xffffffffffffffff

It should call kset_unregister to free 'dev->queues_kset'
in error path of register_queue_kobjects, otherwise will cause a mem leak.

Reported-by: Hulk Robot <hulkci@huawei.com>
Fixes: 1d24eb4815d1 ("xps: Transmit Packet Steering")
Signed-off-by: YueHaibing <yuehaibing@huawei.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2019-15916
CWE: CWE-401
Vulnerability description: An issue was discovered in the Linux kernel before 5.0.1. There is a memory leak in register_queue_kobjects() in net/core/net-sysfs.c, which will cause denial of service.

Project: linux
File: net-sysfs.c


static ssize_t xps_rxqs_show(struct netdev_queue *queue, char *buf)
{
	struct net_device *dev = queue->dev;
	struct xps_dev_maps *dev_maps;
	unsigned long *mask, index;
	int j, len, num_tc = 1, tc = 0;

	index = get_netdev_queue_index(queue);

	if (dev->num_tc) {
		num_tc = dev->num_tc;
		tc = netdev_txq_to_tc(dev, index);
		if (tc < 0)
			return -EINVAL;
	}
	mask = kcalloc(BITS_TO_LONGS(dev->num_rx_queues), sizeof(long),
		       GFP_KERNEL);
	if (!mask)
		return -ENOMEM;

	rcu_read_lock();
	dev_maps = rcu_dereference(dev->xps_rxqs_map);
	if (!dev_maps)
		goto out_no_maps;

	for (j = -1; j = netif_attrmask_next(j, NULL, dev->num_rx_queues),
	     j < dev->num_rx_queues;) {
		int i, tci = j * num_tc + tc;
		struct xps_map *map;

		map = rcu_dereference(dev_maps->attr_map[tci]);
		if (!map)
			continue;

		for (i = map->len; i--;) {
			if (map->queues[i] == index) {
				set_bit(j, mask);
				break;
			}
		}
	}
out_no_maps:
	rcu_read_unlock();

	len = bitmap_print_to_pagebuf(false, buf, mask, dev->num_rx_queues);
	kfree(mask);

	return len < PAGE_SIZE ? len : -EINVAL;

Project: linux
File: net-sysfs.c


static ssize_t xps_rxqs_store(struct netdev_queue *queue, const char *buf,
			      size_t len)
{
	struct net_device *dev = queue->dev;
	struct net *net = dev_net(dev);
	unsigned long *mask, index;
	int err;

	if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
		return -EPERM;

	mask = kcalloc(BITS_TO_LONGS(dev->num_rx_queues), sizeof(long),
		       GFP_KERNEL);
	if (!mask)
		return -ENOMEM;

	index = get_netdev_queue_index(queue);

	err = bitmap_parse(buf, len, mask, dev->num_rx_queues);
	if (err) {
		kfree(mask);
		return err;
	}

	cpus_read_lock();
	err = __netif_set_xps_queue(dev, mask, index, true);
	cpus_read_unlock();

	kfree(mask);
	return err ? : len;

Project: linux
File: net-sysfs.c


static ssize_t phys_switch_id_show(struct device *dev,
				   struct device_attribute *attr, char *buf)
{
	struct net_device *netdev = to_net_dev(dev);
	ssize_t ret = -EINVAL;

	if (!rtnl_trylock())
		return restart_syscall();

	if (dev_isalive(netdev)) {
		struct switchdev_attr attr = {
			.orig_dev = netdev,
			.id = SWITCHDEV_ATTR_ID_PORT_PARENT_ID,
			.flags = SWITCHDEV_F_NO_RECURSE,
		};

		ret = switchdev_port_attr_get(netdev, &attr);
		if (!ret)
			ret = sprintf(buf, "%*phN\n", attr.u.ppid.id_len,
				      attr.u.ppid.id);
	}
	rtnl_unlock();

	return ret;