br_ioctl.c 8.9 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 74 75 76 77 78 79 80 81
	num = br_fdb_fillbuf(br, buf, maxnum, offset);
	if (num > 0) {
		if (copy_to_user(userbuf, buf, num*sizeof(struct __fdb_entry)))
			num = -EFAULT;
	}
	kfree(buf);

	return num;
}

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

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

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

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

	return ret;
}

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

A
Arnd Bergmann 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
	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 已提交
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

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

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

		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;

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

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

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

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

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

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

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

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

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

222 223
		ret = br_set_ageing_time(br, args[1]);
		break;
L
Linus Torvalds 已提交
224 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

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

		return 0;
	}

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

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

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

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

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

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

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

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

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

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

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

315
static int old_deviceless(struct net *net, void __user *uarg)
L
Linus Torvalds 已提交
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
{
	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;
333
		indices = kcalloc(args[2], sizeof(int), GFP_KERNEL);
L
Linus Torvalds 已提交
334 335 336
		if (indices == NULL)
			return -ENOMEM;

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

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

		kfree(indices);
		return ret;
	}

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

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

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

		buf[IFNAMSIZ-1] = 0;

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

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

	return -EOPNOTSUPP;
}

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

	rtnl_lock();

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

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

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

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

	rtnl_unlock();

	return ret;
L
Linus Torvalds 已提交
412
}