br_ioctl.c 9.0 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 (dev->priv_flags & IFF_EBRIDGE)
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 105 106
		ret = br_del_if(br, dev);

	return ret;
}

/*
 * Legacy ioctl's through SIOCDEVPRIVATE
107
 * This interface is deprecated because it was too difficult
L
Lucas De Marchi 已提交
108
 * to do the translation for 32/64bit ioctl compatibility.
L
Linus Torvalds 已提交
109
 */
A
Arnd Bergmann 已提交
110
int br_dev_siocdevprivate(struct net_device *dev, struct ifreq *rq, void __user *data, int cmd)
L
Linus Torvalds 已提交
111 112
{
	struct net_bridge *br = netdev_priv(dev);
113
	struct net_bridge_port *p = NULL;
L
Linus Torvalds 已提交
114
	unsigned long args[4];
A
Arnd Bergmann 已提交
115
	void __user *argp;
116
	int ret = -EOPNOTSUPP;
117

A
Arnd Bergmann 已提交
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
	if (in_compat_syscall()) {
		unsigned int cargs[4];

		if (copy_from_user(cargs, data, sizeof(cargs)))
			return -EFAULT;

		args[0] = cargs[0];
		args[1] = cargs[1];
		args[2] = cargs[2];
		args[3] = cargs[3];

		argp = compat_ptr(args[1]);
	} else {
		if (copy_from_user(args, data, sizeof(args)))
			return -EFAULT;

		argp = (void __user *)args[1];
	}
L
Linus Torvalds 已提交
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159

	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;
160 161

		b.stp_enabled = (br->stp_enabled != BR_NO_STP);
L
Linus Torvalds 已提交
162 163 164 165
		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);
166
		b.gc_timer_value = br_timer_value(&br->gc_work.timer);
167
		rcu_read_unlock();
L
Linus Torvalds 已提交
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186

		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;

187
		indices = kcalloc(num, sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
188 189 190 191
		if (indices == NULL)
			return -ENOMEM;

		get_port_ifindices(br, indices, num);
192
		if (copy_to_user(argp, indices, array_size(num, sizeof(int))))
L
Linus Torvalds 已提交
193 194 195 196 197 198
			num =  -EFAULT;
		kfree(indices);
		return num;
	}

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

202 203
		ret = br_set_forward_delay(br, args[1]);
		break;
L
Linus Torvalds 已提交
204 205

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

209 210
		ret = br_set_hello_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
211 212

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

216 217
		ret = br_set_max_age(br, args[1]);
		break;
L
Linus Torvalds 已提交
218 219

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

223 224
		ret = br_set_ageing_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252

	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 已提交
253
		if (copy_to_user(argp, &p, sizeof(p)))
L
Linus Torvalds 已提交
254 255 256 257 258 259
			return -EFAULT;

		return 0;
	}

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

263
		ret = br_stp_set_enabled(br, args[1], NULL);
264
		break;
L
Linus Torvalds 已提交
265 266

	case BRCTL_SET_BRIDGE_PRIORITY:
267
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
268 269 270
			return -EPERM;

		br_stp_set_bridge_priority(br, args[1]);
271 272
		ret = 0;
		break;
L
Linus Torvalds 已提交
273 274 275

	case BRCTL_SET_PORT_PRIORITY:
	{
276
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
277 278 279
			return -EPERM;

		spin_lock_bh(&br->lock);
280
		if ((p = br_get_port(br, args[1])) == NULL)
L
Linus Torvalds 已提交
281 282
			ret = -EINVAL;
		else
283
			ret = br_stp_set_port_priority(p, args[2]);
L
Linus Torvalds 已提交
284
		spin_unlock_bh(&br->lock);
285
		break;
L
Linus Torvalds 已提交
286 287 288 289
	}

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

293
		spin_lock_bh(&br->lock);
L
Linus Torvalds 已提交
294 295 296
		if ((p = br_get_port(br, args[1])) == NULL)
			ret = -EINVAL;
		else
297 298
			ret = br_stp_set_path_cost(p, args[2]);
		spin_unlock_bh(&br->lock);
299
		break;
L
Linus Torvalds 已提交
300 301 302
	}

	case BRCTL_GET_FDB_ENTRIES:
A
Arnd Bergmann 已提交
303
		return get_fdb_entries(br, argp, args[2], args[3]);
L
Linus Torvalds 已提交
304 305
	}

306 307
	if (!ret) {
		if (p)
308
			br_ifinfo_notify(RTM_NEWLINK, NULL, p);
309 310 311 312 313
		else
			netdev_state_change(br->dev);
	}

	return ret;
L
Linus Torvalds 已提交
314 315
}

316
static int old_deviceless(struct net *net, void __user *uarg)
L
Linus Torvalds 已提交
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
{
	unsigned long args[3];

	if (copy_from_user(args, uarg, sizeof(args)))
		return -EFAULT;

	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;
334
		indices = kcalloc(args[2], sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
335 336 337
		if (indices == NULL)
			return -ENOMEM;

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

340 341
		ret = copy_to_user(uarg, indices,
				   array_size(args[2], sizeof(int)))
L
Linus Torvalds 已提交
342 343 344 345 346 347 348 349 350 351 352
			? -EFAULT : args[2];

		kfree(indices);
		return ret;
	}

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

353
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
L
Linus Torvalds 已提交
354 355
			return -EPERM;

356
		if (copy_from_user(buf, (void __user *)args[1], IFNAMSIZ))
L
Linus Torvalds 已提交
357 358 359 360 361
			return -EFAULT;

		buf[IFNAMSIZ-1] = 0;

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

364
		return br_del_bridge(net, buf);
L
Linus Torvalds 已提交
365 366 367 368 369 370
	}
	}

	return -EOPNOTSUPP;
}

371 372
int br_ioctl_stub(struct net *net, struct net_bridge *br, unsigned int cmd,
		  struct ifreq *ifr, void __user *uarg)
L
Linus Torvalds 已提交
373
{
374 375 376 377
	int ret = -EOPNOTSUPP;

	rtnl_lock();

L
Linus Torvalds 已提交
378 379 380
	switch (cmd) {
	case SIOCGIFBR:
	case SIOCSIFBR:
381 382
		ret = old_deviceless(net, uarg);
		break;
L
Linus Torvalds 已提交
383 384 385 386 387
	case SIOCBRADDBR:
	case SIOCBRDELBR:
	{
		char buf[IFNAMSIZ];

388 389 390 391
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) {
			ret = -EPERM;
			break;
		}
L
Linus Torvalds 已提交
392

393 394 395 396
		if (copy_from_user(buf, uarg, IFNAMSIZ)) {
			ret = -EFAULT;
			break;
		}
L
Linus Torvalds 已提交
397 398 399

		buf[IFNAMSIZ-1] = 0;
		if (cmd == SIOCBRADDBR)
400 401 402
			ret = br_add_bridge(net, buf);
		else
			ret = br_del_bridge(net, buf);
L
Linus Torvalds 已提交
403
	}
404
		break;
L
Linus Torvalds 已提交
405 406
	case SIOCBRADDIF:
	case SIOCBRDELIF:
407 408
		ret = add_del_if(br, ifr->ifr_ifindex, cmd == SIOCBRADDIF);
		break;
L
Linus Torvalds 已提交
409
	}
410 411 412 413

	rtnl_unlock();

	return ret;
L
Linus Torvalds 已提交
414
}