br_ioctl.c 9.4 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0-or-later
L
Linus Torvalds 已提交
2 3 4 5 6 7 8 9
/*
 *	Ioctl handler
 *	Linux ethernet bridge
 *
 *	Authors:
 *	Lennert Buytenhek		<buytenh@gnu.org>
 */

10
#include <linux/capability.h>
L
Linus Torvalds 已提交
11 12 13
#include <linux/kernel.h>
#include <linux/if_bridge.h>
#include <linux/netdevice.h>
14
#include <linux/slab.h>
L
Linus Torvalds 已提交
15
#include <linux/times.h>
16
#include <net/net_namespace.h>
17
#include <linux/uaccess.h>
L
Linus Torvalds 已提交
18 19
#include "br_private.h"

20
static int get_bridge_ifindices(struct net *net, int *indices, int num)
L
Linus Torvalds 已提交
21 22 23 24
{
	struct net_device *dev;
	int i = 0;

25 26
	rcu_read_lock();
	for_each_netdev_rcu(net, dev) {
27 28
		if (i >= num)
			break;
29
		if (netif_is_bridge_master(dev))
L
Linus Torvalds 已提交
30 31
			indices[i++] = dev->ifindex;
	}
32
	rcu_read_unlock();
L
Linus Torvalds 已提交
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

	return i;
}

/* called with RTNL */
static void get_port_ifindices(struct net_bridge *br, int *ifindices, int num)
{
	struct net_bridge_port *p;

	list_for_each_entry(p, &br->port_list, list) {
		if (p->port_no < num)
			ifindices[p->port_no] = p->dev->ifindex;
	}
}

/*
 * Format up to a page worth of forwarding table entries
 * userbuf -- where to copy result
 * maxnum  -- maximum number of entries desired
 *            (limited to a page for sanity)
 * offset  -- number of records to skip
 */
55
static int get_fdb_entries(struct net_bridge *br, void __user *userbuf,
L
Linus Torvalds 已提交
56 57 58 59
			   unsigned long maxnum, unsigned long offset)
{
	int num;
	void *buf;
60
	size_t size;
L
Linus Torvalds 已提交
61

62 63
	/* Clamp size to PAGE_SIZE, test maxnum to avoid overflow */
	if (maxnum > PAGE_SIZE/sizeof(struct __fdb_entry))
L
Linus Torvalds 已提交
64
		maxnum = PAGE_SIZE/sizeof(struct __fdb_entry);
65 66

	size = maxnum * sizeof(struct __fdb_entry);
L
Linus Torvalds 已提交
67 68 69 70

	buf = kmalloc(size, GFP_USER);
	if (!buf)
		return -ENOMEM;
71

L
Linus Torvalds 已提交
72 73
	num = br_fdb_fillbuf(br, buf, maxnum, offset);
	if (num > 0) {
74 75
		if (copy_to_user(userbuf, buf,
				 array_size(num, sizeof(struct __fdb_entry))))
L
Linus Torvalds 已提交
76 77 78 79 80 81 82
			num = -EFAULT;
	}
	kfree(buf);

	return num;
}

83
/* called with RTNL */
L
Linus Torvalds 已提交
84 85
static int add_del_if(struct net_bridge *br, int ifindex, int isadd)
{
86
	struct net *net = dev_net(br->dev);
L
Linus Torvalds 已提交
87 88 89
	struct net_device *dev;
	int ret;

90
	if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
91 92
		return -EPERM;

93
	dev = __dev_get_by_index(net, ifindex);
L
Linus Torvalds 已提交
94 95
	if (dev == NULL)
		return -EINVAL;
96

L
Linus Torvalds 已提交
97
	if (isadd)
98
		ret = br_add_if(br, dev, NULL);
99
	else
L
Linus Torvalds 已提交
100 101 102 103 104
		ret = br_del_if(br, dev);

	return ret;
}

105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
#define BR_UARGS_MAX 4
static int br_dev_read_uargs(unsigned long *args, size_t nr_args,
			     void __user **argp, void __user *data)
{
	int ret;

	if (nr_args < 2 || nr_args > BR_UARGS_MAX)
		return -EINVAL;

	if (in_compat_syscall()) {
		unsigned int cargs[BR_UARGS_MAX];
		int i;

		ret = copy_from_user(cargs, data, nr_args * sizeof(*cargs));
		if (ret)
			goto fault;

		for (i = 0; i < nr_args; ++i)
			args[i] = cargs[i];

		*argp = compat_ptr(args[1]);
	} else {
		ret = copy_from_user(args, data, nr_args * sizeof(*args));
		if (ret)
			goto fault;
		*argp = (void __user *)args[1];
	}

	return 0;
fault:
	return -EFAULT;
}

L
Linus Torvalds 已提交
138 139
/*
 * Legacy ioctl's through SIOCDEVPRIVATE
140
 * This interface is deprecated because it was too difficult
L
Lucas De Marchi 已提交
141
 * to do the translation for 32/64bit ioctl compatibility.
L
Linus Torvalds 已提交
142
 */
143 144
int br_dev_siocdevprivate(struct net_device *dev, struct ifreq *rq,
			  void __user *data, int cmd)
L
Linus Torvalds 已提交
145 146
{
	struct net_bridge *br = netdev_priv(dev);
147
	struct net_bridge_port *p = NULL;
L
Linus Torvalds 已提交
148
	unsigned long args[4];
A
Arnd Bergmann 已提交
149
	void __user *argp;
150
	int ret;
A
Arnd Bergmann 已提交
151

152 153 154
	ret = br_dev_read_uargs(args, ARRAY_SIZE(args), &argp, data);
	if (ret)
		return ret;
L
Linus Torvalds 已提交
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178

	switch (args[0]) {
	case BRCTL_ADD_IF:
	case BRCTL_DEL_IF:
		return add_del_if(br, args[1], args[0] == BRCTL_ADD_IF);

	case BRCTL_GET_BRIDGE_INFO:
	{
		struct __bridge_info b;

		memset(&b, 0, sizeof(struct __bridge_info));
		rcu_read_lock();
		memcpy(&b.designated_root, &br->designated_root, 8);
		memcpy(&b.bridge_id, &br->bridge_id, 8);
		b.root_path_cost = br->root_path_cost;
		b.max_age = jiffies_to_clock_t(br->max_age);
		b.hello_time = jiffies_to_clock_t(br->hello_time);
		b.forward_delay = br->forward_delay;
		b.bridge_max_age = br->bridge_max_age;
		b.bridge_hello_time = br->bridge_hello_time;
		b.bridge_forward_delay = jiffies_to_clock_t(br->bridge_forward_delay);
		b.topology_change = br->topology_change;
		b.topology_change_detected = br->topology_change_detected;
		b.root_port = br->root_port;
179 180

		b.stp_enabled = (br->stp_enabled != BR_NO_STP);
L
Linus Torvalds 已提交
181 182 183 184
		b.ageing_time = jiffies_to_clock_t(br->ageing_time);
		b.hello_timer_value = br_timer_value(&br->hello_timer);
		b.tcn_timer_value = br_timer_value(&br->tcn_timer);
		b.topology_change_timer_value = br_timer_value(&br->topology_change_timer);
185
		b.gc_timer_value = br_timer_value(&br->gc_work.timer);
186
		rcu_read_unlock();
L
Linus Torvalds 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205

		if (copy_to_user((void __user *)args[1], &b, sizeof(b)))
			return -EFAULT;

		return 0;
	}

	case BRCTL_GET_PORT_LIST:
	{
		int num, *indices;

		num = args[2];
		if (num < 0)
			return -EINVAL;
		if (num == 0)
			num = 256;
		if (num > BR_MAX_PORTS)
			num = BR_MAX_PORTS;

206
		indices = kcalloc(num, sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
207 208 209 210
		if (indices == NULL)
			return -ENOMEM;

		get_port_ifindices(br, indices, num);
211
		if (copy_to_user(argp, indices, array_size(num, sizeof(int))))
L
Linus Torvalds 已提交
212 213 214 215 216 217
			num =  -EFAULT;
		kfree(indices);
		return num;
	}

	case BRCTL_SET_BRIDGE_FORWARD_DELAY:
218
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
219 220
			return -EPERM;

221 222
		ret = br_set_forward_delay(br, args[1]);
		break;
L
Linus Torvalds 已提交
223 224

	case BRCTL_SET_BRIDGE_HELLO_TIME:
225
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
226 227
			return -EPERM;

228 229
		ret = br_set_hello_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
230 231

	case BRCTL_SET_BRIDGE_MAX_AGE:
232
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
233 234
			return -EPERM;

235 236
		ret = br_set_max_age(br, args[1]);
		break;
L
Linus Torvalds 已提交
237 238

	case BRCTL_SET_AGEING_TIME:
239
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
240 241
			return -EPERM;

242 243
		ret = br_set_ageing_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271

	case BRCTL_GET_PORT_INFO:
	{
		struct __port_info p;
		struct net_bridge_port *pt;

		rcu_read_lock();
		if ((pt = br_get_port(br, args[2])) == NULL) {
			rcu_read_unlock();
			return -EINVAL;
		}

		memset(&p, 0, sizeof(struct __port_info));
		memcpy(&p.designated_root, &pt->designated_root, 8);
		memcpy(&p.designated_bridge, &pt->designated_bridge, 8);
		p.port_id = pt->port_id;
		p.designated_port = pt->designated_port;
		p.path_cost = pt->path_cost;
		p.designated_cost = pt->designated_cost;
		p.state = pt->state;
		p.top_change_ack = pt->topology_change_ack;
		p.config_pending = pt->config_pending;
		p.message_age_timer_value = br_timer_value(&pt->message_age_timer);
		p.forward_delay_timer_value = br_timer_value(&pt->forward_delay_timer);
		p.hold_timer_value = br_timer_value(&pt->hold_timer);

		rcu_read_unlock();

A
Arnd Bergmann 已提交
272
		if (copy_to_user(argp, &p, sizeof(p)))
L
Linus Torvalds 已提交
273 274 275 276 277 278
			return -EFAULT;

		return 0;
	}

	case BRCTL_SET_BRIDGE_STP_STATE:
279
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
280 281
			return -EPERM;

282
		ret = br_stp_set_enabled(br, args[1], NULL);
283
		break;
L
Linus Torvalds 已提交
284 285

	case BRCTL_SET_BRIDGE_PRIORITY:
286
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
287 288 289
			return -EPERM;

		br_stp_set_bridge_priority(br, args[1]);
290 291
		ret = 0;
		break;
L
Linus Torvalds 已提交
292 293 294

	case BRCTL_SET_PORT_PRIORITY:
	{
295
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
296 297 298
			return -EPERM;

		spin_lock_bh(&br->lock);
299
		if ((p = br_get_port(br, args[1])) == NULL)
L
Linus Torvalds 已提交
300 301
			ret = -EINVAL;
		else
302
			ret = br_stp_set_port_priority(p, args[2]);
L
Linus Torvalds 已提交
303
		spin_unlock_bh(&br->lock);
304
		break;
L
Linus Torvalds 已提交
305 306 307 308
	}

	case BRCTL_SET_PATH_COST:
	{
309
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
310 311
			return -EPERM;

312
		spin_lock_bh(&br->lock);
L
Linus Torvalds 已提交
313 314 315
		if ((p = br_get_port(br, args[1])) == NULL)
			ret = -EINVAL;
		else
316 317
			ret = br_stp_set_path_cost(p, args[2]);
		spin_unlock_bh(&br->lock);
318
		break;
L
Linus Torvalds 已提交
319 320 321
	}

	case BRCTL_GET_FDB_ENTRIES:
A
Arnd Bergmann 已提交
322
		return get_fdb_entries(br, argp, args[2], args[3]);
323 324 325

	default:
		ret = -EOPNOTSUPP;
L
Linus Torvalds 已提交
326 327
	}

328 329
	if (!ret) {
		if (p)
330
			br_ifinfo_notify(RTM_NEWLINK, NULL, p);
331 332 333 334 335
		else
			netdev_state_change(br->dev);
	}

	return ret;
L
Linus Torvalds 已提交
336 337
}

338
static int old_deviceless(struct net *net, void __user *data)
L
Linus Torvalds 已提交
339 340
{
	unsigned long args[3];
341 342
	void __user *argp;
	int ret;
L
Linus Torvalds 已提交
343

344 345 346
	ret = br_dev_read_uargs(args, ARRAY_SIZE(args), &argp, data);
	if (ret)
		return ret;
L
Linus Torvalds 已提交
347 348 349 350 351 352 353 354 355 356 357 358

	switch (args[0]) {
	case BRCTL_GET_VERSION:
		return BRCTL_VERSION;

	case BRCTL_GET_BRIDGES:
	{
		int *indices;
		int ret = 0;

		if (args[2] >= 2048)
			return -ENOMEM;
359
		indices = kcalloc(args[2], sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
360 361 362
		if (indices == NULL)
			return -ENOMEM;

363
		args[2] = get_bridge_ifindices(net, indices, args[2]);
L
Linus Torvalds 已提交
364

365
		ret = copy_to_user(argp, indices,
366
				   array_size(args[2], sizeof(int)))
L
Linus Torvalds 已提交
367 368 369 370 371 372 373 374 375 376 377
			? -EFAULT : args[2];

		kfree(indices);
		return ret;
	}

	case BRCTL_ADD_BRIDGE:
	case BRCTL_DEL_BRIDGE:
	{
		char buf[IFNAMSIZ];

378
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
379 380
			return -EPERM;

381
		if (copy_from_user(buf, argp, IFNAMSIZ))
L
Linus Torvalds 已提交
382 383 384 385 386
			return -EFAULT;

		buf[IFNAMSIZ-1] = 0;

		if (args[0] == BRCTL_ADD_BRIDGE)
387
			return br_add_bridge(net, buf);
L
Linus Torvalds 已提交
388

389
		return br_del_bridge(net, buf);
L
Linus Torvalds 已提交
390 391 392 393 394 395
	}
	}

	return -EOPNOTSUPP;
}

396 397
int br_ioctl_stub(struct net *net, struct net_bridge *br, unsigned int cmd,
		  struct ifreq *ifr, void __user *uarg)
L
Linus Torvalds 已提交
398
{
399 400 401 402
	int ret = -EOPNOTSUPP;

	rtnl_lock();

L
Linus Torvalds 已提交
403 404 405
	switch (cmd) {
	case SIOCGIFBR:
	case SIOCSIFBR:
406 407
		ret = old_deviceless(net, uarg);
		break;
L
Linus Torvalds 已提交
408 409 410 411 412
	case SIOCBRADDBR:
	case SIOCBRDELBR:
	{
		char buf[IFNAMSIZ];

413 414 415 416
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) {
			ret = -EPERM;
			break;
		}
L
Linus Torvalds 已提交
417

418 419 420 421
		if (copy_from_user(buf, uarg, IFNAMSIZ)) {
			ret = -EFAULT;
			break;
		}
L
Linus Torvalds 已提交
422 423 424

		buf[IFNAMSIZ-1] = 0;
		if (cmd == SIOCBRADDBR)
425 426 427
			ret = br_add_bridge(net, buf);
		else
			ret = br_del_bridge(net, buf);
L
Linus Torvalds 已提交
428
	}
429
		break;
L
Linus Torvalds 已提交
430 431
	case SIOCBRADDIF:
	case SIOCBRDELIF:
432 433
		ret = add_del_if(br, ifr->ifr_ifindex, cmd == SIOCBRADDIF);
		break;
L
Linus Torvalds 已提交
434
	}
435 436 437 438

	rtnl_unlock();

	return ret;
L
Linus Torvalds 已提交
439
}