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

10
#include <linux/capability.h>
Linus Torvalds's avatar
Linus Torvalds committed
11 12 13
#include <linux/kernel.h>
#include <linux/if_bridge.h>
#include <linux/netdevice.h>
14
#include <linux/slab.h>
Linus Torvalds's avatar
Linus Torvalds committed
15
#include <linux/times.h>
16
#include <net/net_namespace.h>
17
#include <linux/uaccess.h>
Linus Torvalds's avatar
Linus Torvalds committed
18 19
#include "br_private.h"

20
static int get_bridge_ifindices(struct net *net, int *indices, int num)
Linus Torvalds's avatar
Linus Torvalds committed
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)
Linus Torvalds's avatar
Linus Torvalds committed
30 31
			indices[i++] = dev->ifindex;
	}
32
	rcu_read_unlock();
Linus Torvalds's avatar
Linus Torvalds committed
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,
Linus Torvalds's avatar
Linus Torvalds committed
56 57 58 59
			   unsigned long maxnum, unsigned long offset)
{
	int num;
	void *buf;
60
	size_t size;
Linus Torvalds's avatar
Linus Torvalds committed
61

62 63
	/* Clamp size to PAGE_SIZE, test maxnum to avoid overflow */
	if (maxnum > PAGE_SIZE/sizeof(struct __fdb_entry))
Linus Torvalds's avatar
Linus Torvalds committed
64
		maxnum = PAGE_SIZE/sizeof(struct __fdb_entry);
65 66

	size = maxnum * sizeof(struct __fdb_entry);
Linus Torvalds's avatar
Linus Torvalds committed
67 68 69 70

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

Linus Torvalds's avatar
Linus Torvalds committed
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 */
Linus Torvalds's avatar
Linus Torvalds committed
83 84
static int add_del_if(struct net_bridge *br, int ifindex, int isadd)
{
85
	struct net *net = dev_net(br->dev);
Linus Torvalds's avatar
Linus Torvalds committed
86 87 88
	struct net_device *dev;
	int ret;

89
	if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
90 91
		return -EPERM;

92
	dev = __dev_get_by_index(net, ifindex);
Linus Torvalds's avatar
Linus Torvalds committed
93 94
	if (dev == NULL)
		return -EINVAL;
95

Linus Torvalds's avatar
Linus Torvalds committed
96
	if (isadd)
97
		ret = br_add_if(br, dev, NULL);
98
	else
Linus Torvalds's avatar
Linus Torvalds committed
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
Lucas De Marchi's avatar
Lucas De Marchi committed
107
 * to do the translation for 32/64bit ioctl compatibility.
Linus Torvalds's avatar
Linus Torvalds committed
108
 */
109
int br_dev_siocdevprivate(struct net_device *dev, struct ifreq *rq, void __user *data, int cmd)
Linus Torvalds's avatar
Linus Torvalds committed
110 111
{
	struct net_bridge *br = netdev_priv(dev);
112
	struct net_bridge_port *p = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
113
	unsigned long args[4];
114
	void __user *argp;
115
	int ret = -EOPNOTSUPP;
116

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];
	}
Linus Torvalds's avatar
Linus Torvalds committed
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);
Linus Torvalds's avatar
Linus Torvalds committed
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();
Linus Torvalds's avatar
Linus Torvalds committed
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);
Linus Torvalds's avatar
Linus Torvalds committed
187 188 189 190
		if (indices == NULL)
			return -ENOMEM;

		get_port_ifindices(br, indices, num);
191
		if (copy_to_user(argp, indices, num * sizeof(int)))
Linus Torvalds's avatar
Linus Torvalds committed
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))
Linus Torvalds's avatar
Linus Torvalds committed
199 200
			return -EPERM;

201 202
		ret = br_set_forward_delay(br, args[1]);
		break;
Linus Torvalds's avatar
Linus Torvalds committed
203 204

	case BRCTL_SET_BRIDGE_HELLO_TIME:
205
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
206 207
			return -EPERM;

208 209
		ret = br_set_hello_time(br, args[1]);
		break;
Linus Torvalds's avatar
Linus Torvalds committed
210 211

	case BRCTL_SET_BRIDGE_MAX_AGE:
212
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
213 214
			return -EPERM;

215 216
		ret = br_set_max_age(br, args[1]);
		break;
Linus Torvalds's avatar
Linus Torvalds committed
217 218

	case BRCTL_SET_AGEING_TIME:
219
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
220 221
			return -EPERM;

222 223
		ret = br_set_ageing_time(br, args[1]);
		break;
Linus Torvalds's avatar
Linus Torvalds committed
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();

252
		if (copy_to_user(argp, &p, sizeof(p)))
Linus Torvalds's avatar
Linus Torvalds committed
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))
Linus Torvalds's avatar
Linus Torvalds committed
260 261
			return -EPERM;

262
		ret = br_stp_set_enabled(br, args[1], NULL);
263
		break;
Linus Torvalds's avatar
Linus Torvalds committed
264 265

	case BRCTL_SET_BRIDGE_PRIORITY:
266
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
267 268 269
			return -EPERM;

		br_stp_set_bridge_priority(br, args[1]);
270 271
		ret = 0;
		break;
Linus Torvalds's avatar
Linus Torvalds committed
272 273 274

	case BRCTL_SET_PORT_PRIORITY:
	{
275
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
276 277 278
			return -EPERM;

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

	case BRCTL_SET_PATH_COST:
	{
289
		if (!ns_capable(dev_net(dev)->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
290 291
			return -EPERM;

292
		spin_lock_bh(&br->lock);
Linus Torvalds's avatar
Linus Torvalds committed
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;
Linus Torvalds's avatar
Linus Torvalds committed
299 300 301
	}

	case BRCTL_GET_FDB_ENTRIES:
302
		return get_fdb_entries(br, argp, args[2], args[3]);
Linus Torvalds's avatar
Linus Torvalds committed
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;
Linus Torvalds's avatar
Linus Torvalds committed
313 314
}

315
static int old_deviceless(struct net *net, void __user *uarg)
Linus Torvalds's avatar
Linus Torvalds committed
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);
Linus Torvalds's avatar
Linus Torvalds committed
334 335 336
		if (indices == NULL)
			return -ENOMEM;

337
		args[2] = get_bridge_ifindices(net, indices, args[2]);
Linus Torvalds's avatar
Linus Torvalds committed
338

339
		ret = copy_to_user(uarg, indices, args[2]*sizeof(int))
Linus Torvalds's avatar
Linus Torvalds committed
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))
Linus Torvalds's avatar
Linus Torvalds committed
352 353
			return -EPERM;

354
		if (copy_from_user(buf, uarg, IFNAMSIZ))
Linus Torvalds's avatar
Linus Torvalds committed
355 356 357 358 359
			return -EFAULT;

		buf[IFNAMSIZ-1] = 0;

		if (args[0] == BRCTL_ADD_BRIDGE)
360
			return br_add_bridge(net, buf);
Linus Torvalds's avatar
Linus Torvalds committed
361

362
		return br_del_bridge(net, buf);
Linus Torvalds's avatar
Linus Torvalds committed
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)
Linus Torvalds's avatar
Linus Torvalds committed
371 372 373 374
{
	switch (cmd) {
	case SIOCGIFBR:
	case SIOCSIFBR:
375
		return old_deviceless(net, uarg);
376

Linus Torvalds's avatar
Linus Torvalds committed
377 378 379 380 381
	case SIOCBRADDBR:
	case SIOCBRDELBR:
	{
		char buf[IFNAMSIZ];

382
		if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
Linus Torvalds's avatar
Linus Torvalds committed
383 384 385 386 387 388 389
			return -EPERM;

		if (copy_from_user(buf, uarg, IFNAMSIZ))
			return -EFAULT;

		buf[IFNAMSIZ-1] = 0;
		if (cmd == SIOCBRADDBR)
390
			return br_add_bridge(net, buf);
Linus Torvalds's avatar
Linus Torvalds committed
391

392
		return br_del_bridge(net, buf);
Linus Torvalds's avatar
Linus Torvalds committed
393 394 395 396
	}

	case SIOCBRADDIF:
	case SIOCBRDELIF:
397
		return add_del_if(br, ifr->ifr_ifindex, cmd == SIOCBRADDIF);
Linus Torvalds's avatar
Linus Torvalds committed
398 399 400 401

	}
	return -EOPNOTSUPP;
}