summaryrefslogtreecommitdiff
path: root/ipc/kdbus/handle.c
blob: f72dbe513b4aeae6a66fe864ebf759f36d2832e2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
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
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
/*
 * Copyright (C) 2013-2015 Kay Sievers
 * Copyright (C) 2013-2015 Greg Kroah-Hartman <gregkh@linuxfoundation.org>
 * Copyright (C) 2013-2015 Daniel Mack <daniel@zonque.org>
 * Copyright (C) 2013-2015 David Herrmann <dh.herrmann@gmail.com>
 * Copyright (C) 2013-2015 Linux Foundation
 * Copyright (C) 2014-2015 Djalal Harouni <tixxdz@opendz.org>
 *
 * kdbus is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 */

#include <linux/file.h>
#include <linux/fs.h>
#include <linux/idr.h>
#include <linux/init.h>
#include <linux/kdev_t.h>
#include <linux/module.h>
#include <linux/poll.h>
#include <linux/rwsem.h>
#include <linux/sched.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/syscalls.h>

#include "bus.h"
#include "connection.h"
#include "endpoint.h"
#include "fs.h"
#include "handle.h"
#include "item.h"
#include "match.h"
#include "message.h"
#include "names.h"
#include "domain.h"
#include "policy.h"

static int kdbus_args_verify(struct kdbus_args *args)
{
	struct kdbus_item *item;
	size_t i;
	int ret;

	KDBUS_ITEMS_FOREACH(item, args->items, args->items_size) {
		struct kdbus_arg *arg = NULL;

		if (!KDBUS_ITEM_VALID(item, args->items, args->items_size))
			return -EINVAL;

		for (i = 0; i < args->argc; ++i)
			if (args->argv[i].type == item->type)
				break;
		if (i >= args->argc)
			return -EINVAL;

		arg = &args->argv[i];

		ret = kdbus_item_validate(item);
		if (ret < 0)
			return ret;

		if (arg->item && !arg->multiple)
			return -EINVAL;

		arg->item = item;
	}

	if (!KDBUS_ITEMS_END(item, args->items, args->items_size))
		return -EINVAL;

	for (i = 0; i < args->argc; ++i)
		if (args->argv[i].mandatory && !args->argv[i].item)
			return -EINVAL;

	return 0;
}

static int kdbus_args_negotiate(struct kdbus_args *args)
{
	struct kdbus_item __user *user;
	struct kdbus_item *negotiation;
	size_t i, j, num;

	/*
	 * If KDBUS_FLAG_NEGOTIATE is set, we overwrite the flags field with
	 * the set of supported flags. Furthermore, if an KDBUS_ITEM_NEGOTIATE
	 * item is passed, we iterate its payload (array of u64, each set to an
	 * item type) and clear all unsupported item-types to 0.
	 * The caller might do this recursively, if other flags or objects are
	 * embedded in the payload itself.
	 */

	if (args->cmd->flags & KDBUS_FLAG_NEGOTIATE) {
		if (put_user(args->allowed_flags & ~KDBUS_FLAG_NEGOTIATE,
			     &args->user->flags))
			return -EFAULT;
	}

	if (args->argc < 1 || args->argv[0].type != KDBUS_ITEM_NEGOTIATE ||
	    !args->argv[0].item)
		return 0;

	negotiation = args->argv[0].item;
	user = (struct kdbus_item __user *)
		((u8 __user *)args->user +
		 ((u8 *)negotiation - (u8 *)args->cmd));
	num = KDBUS_ITEM_PAYLOAD_SIZE(negotiation) / sizeof(u64);

	for (i = 0; i < num; ++i) {
		for (j = 0; j < args->argc; ++j)
			if (negotiation->data64[i] == args->argv[j].type)
				break;

		if (j < args->argc)
			continue;

		/* this item is not supported, clear it out */
		negotiation->data64[i] = 0;
		if (put_user(negotiation->data64[i], &user->data64[i]))
			return -EFAULT;
	}

	return 0;
}

/**
 * __kdbus_args_parse() - parse payload of kdbus command
 * @args:		object to parse data into
 * @argp:		user-space location of command payload to parse
 * @type_size:		overall size of command payload to parse
 * @items_offset:	offset of items array in command payload
 * @out:		output variable to store pointer to copied payload
 *
 * This parses the ioctl payload at user-space location @argp into @args. @args
 * must be pre-initialized by the caller to reflect the supported flags and
 * items of this command. This parser will then copy the command payload into
 * kernel-space, verify correctness and consistency and cache pointers to parsed
 * items and other data in @args.
 *
 * If this function succeeded, you must call kdbus_args_clear() to release
 * allocated resources before destroying @args.
 *
 * Return: On failure a negative error code is returned. Otherwise, 1 is
 * returned if negotiation was requested, 0 if not.
 */
int __kdbus_args_parse(struct kdbus_args *args, void __user *argp,
		       size_t type_size, size_t items_offset, void **out)
{
	int ret;

	args->cmd = kdbus_memdup_user(argp, type_size, KDBUS_CMD_MAX_SIZE);
	if (IS_ERR(args->cmd))
		return PTR_ERR(args->cmd);

	args->cmd->return_flags = 0;
	args->user = argp;
	args->items = (void *)((u8 *)args->cmd + items_offset);
	args->items_size = args->cmd->size - items_offset;

	if (args->cmd->flags & ~args->allowed_flags) {
		ret = -EINVAL;
		goto error;
	}

	ret = kdbus_args_verify(args);
	if (ret < 0)
		goto error;

	ret = kdbus_args_negotiate(args);
	if (ret < 0)
		goto error;

	*out = args->cmd;
	return !!(args->cmd->flags & KDBUS_FLAG_NEGOTIATE);

error:
	return kdbus_args_clear(args, ret);
}

/**
 * kdbus_args_clear() - release allocated command resources
 * @args:	object to release resources of
 * @ret:	return value of this command
 *
 * This frees all allocated resources on @args and copies the command result
 * flags into user-space. @ret is usually returned unchanged by this function,
 * so it can be used in the final 'return' statement of the command handler.
 *
 * Return: -EFAULT if return values cannot be copied into user-space, otherwise
 *         @ret is returned unchanged.
 */
int kdbus_args_clear(struct kdbus_args *args, int ret)
{
	if (!args)
		return ret;

	if (!IS_ERR_OR_NULL(args->cmd)) {
		if (put_user(args->cmd->return_flags,
			     &args->user->return_flags))
			ret = -EFAULT;
		kfree(args->cmd);
		args->cmd = NULL;
	}

	return ret;
}

/**
 * enum kdbus_handle_type - type an handle can be of
 * @KDBUS_HANDLE_NONE:		no type set, yet
 * @KDBUS_HANDLE_BUS_OWNER:	bus owner
 * @KDBUS_HANDLE_EP_OWNER:	endpoint owner
 * @KDBUS_HANDLE_CONNECTED:	endpoint connection after HELLO
 */
enum kdbus_handle_type {
	KDBUS_HANDLE_NONE,
	KDBUS_HANDLE_BUS_OWNER,
	KDBUS_HANDLE_EP_OWNER,
	KDBUS_HANDLE_CONNECTED,
};

/**
 * struct kdbus_handle - handle to the kdbus system
 * @rwlock:		handle lock
 * @type:		type of this handle (KDBUS_HANDLE_*)
 * @bus_owner:		bus this handle owns
 * @ep_owner:		endpoint this handle owns
 * @conn:		connection this handle owns
 * @privileged:		Flag to mark a handle as privileged
 */
struct kdbus_handle {
	struct rw_semaphore rwlock;

	enum kdbus_handle_type type;
	union {
		struct kdbus_bus *bus_owner;
		struct kdbus_ep *ep_owner;
		struct kdbus_conn *conn;
	};

	bool privileged:1;
};

static int kdbus_handle_open(struct inode *inode, struct file *file)
{
	struct kdbus_handle *handle;
	struct kdbus_node *node;
	int ret;

	node = kdbus_node_from_inode(inode);
	if (!kdbus_node_acquire(node))
		return -ESHUTDOWN;

	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
	if (!handle) {
		ret = -ENOMEM;
		goto exit;
	}

	init_rwsem(&handle->rwlock);
	handle->type = KDBUS_HANDLE_NONE;

	if (node->type == KDBUS_NODE_ENDPOINT) {
		struct kdbus_ep *ep = kdbus_ep_from_node(node);
		struct kdbus_bus *bus = ep->bus;

		/*
		 * A connection is privileged if it is opened on an endpoint
		 * without custom policy and either:
		 *   * the user has CAP_IPC_OWNER in the domain user namespace
		 * or
		 *   * the callers euid matches the uid of the bus creator
		 */
		if (!ep->user &&
		    (ns_capable(bus->domain->user_namespace, CAP_IPC_OWNER) ||
		     uid_eq(file->f_cred->euid, bus->node.uid)))
			handle->privileged = true;
	}

	file->private_data = handle;
	ret = 0;

exit:
	kdbus_node_release(node);
	return ret;
}

static int kdbus_handle_release(struct inode *inode, struct file *file)
{
	struct kdbus_handle *handle = file->private_data;

	switch (handle->type) {
	case KDBUS_HANDLE_BUS_OWNER:
		if (handle->bus_owner) {
			kdbus_node_deactivate(&handle->bus_owner->node);
			kdbus_bus_unref(handle->bus_owner);
		}
		break;
	case KDBUS_HANDLE_EP_OWNER:
		if (handle->ep_owner) {
			kdbus_node_deactivate(&handle->ep_owner->node);
			kdbus_ep_unref(handle->ep_owner);
		}
		break;
	case KDBUS_HANDLE_CONNECTED:
		kdbus_conn_disconnect(handle->conn, false);
		kdbus_conn_unref(handle->conn);
		break;
	case KDBUS_HANDLE_NONE:
		/* nothing to clean up */
		break;
	}

	kfree(handle);

	return 0;
}

static long kdbus_handle_ioctl_control(struct file *file, unsigned int cmd,
				       void __user *argp)
{
	struct kdbus_handle *handle = file->private_data;
	struct kdbus_node *node = file_inode(file)->i_private;
	struct kdbus_domain *domain;
	int ret = 0;

	if (!kdbus_node_acquire(node))
		return -ESHUTDOWN;

	/*
	 * The parent of control-nodes is always a domain, make sure to pin it
	 * so the parent is actually valid.
	 */
	domain = kdbus_domain_from_node(node->parent);
	if (!kdbus_node_acquire(&domain->node)) {
		kdbus_node_release(node);
		return -ESHUTDOWN;
	}

	switch (cmd) {
	case KDBUS_CMD_BUS_MAKE: {
		struct kdbus_bus *bus;

		bus = kdbus_cmd_bus_make(domain, argp);
		if (IS_ERR_OR_NULL(bus)) {
			ret = PTR_ERR_OR_ZERO(bus);
			break;
		}

		handle->type = KDBUS_HANDLE_BUS_OWNER;
		handle->bus_owner = bus;
		break;
	}

	default:
		ret = -EBADFD;
		break;
	}

	kdbus_node_release(&domain->node);
	kdbus_node_release(node);
	return ret;
}

static long kdbus_handle_ioctl_ep(struct file *file, unsigned int cmd,
				  void __user *buf)
{
	struct kdbus_handle *handle = file->private_data;
	struct kdbus_node *node = file_inode(file)->i_private;
	struct kdbus_ep *ep, *file_ep = kdbus_ep_from_node(node);
	struct kdbus_conn *conn;
	int ret = 0;

	if (!kdbus_node_acquire(node))
		return -ESHUTDOWN;

	switch (cmd) {
	case KDBUS_CMD_ENDPOINT_MAKE:
		/* creating custom endpoints is a privileged operation */
		if (!handle->privileged) {
			ret = -EPERM;
			break;
		}

		ep = kdbus_cmd_ep_make(file_ep->bus, buf);
		if (IS_ERR_OR_NULL(ep)) {
			ret = PTR_ERR_OR_ZERO(ep);
			break;
		}

		handle->type = KDBUS_HANDLE_EP_OWNER;
		handle->ep_owner = ep;
		break;

	case KDBUS_CMD_HELLO:
		conn = kdbus_cmd_hello(file_ep, handle->privileged, buf);
		if (IS_ERR_OR_NULL(conn)) {
			ret = PTR_ERR_OR_ZERO(conn);
			break;
		}

		handle->type = KDBUS_HANDLE_CONNECTED;
		handle->conn = conn;
		break;

	default:
		ret = -EBADFD;
		break;
	}

	kdbus_node_release(node);
	return ret;
}

static long kdbus_handle_ioctl_ep_owner(struct file *file, unsigned int command,
					void __user *buf)
{
	struct kdbus_handle *handle = file->private_data;
	struct kdbus_ep *ep = handle->ep_owner;
	int ret;

	if (!kdbus_node_acquire(&ep->node))
		return -ESHUTDOWN;

	switch (command) {
	case KDBUS_CMD_ENDPOINT_UPDATE:
		ret = kdbus_cmd_ep_update(ep, buf);
		break;
	default:
		ret = -EBADFD;
		break;
	}

	kdbus_node_release(&ep->node);
	return ret;
}

static long kdbus_handle_ioctl_connected(struct file *file,
					 unsigned int command, void __user *buf)
{
	struct kdbus_handle *handle = file->private_data;
	struct kdbus_conn *conn = handle->conn;
	struct kdbus_conn *release_conn = NULL;
	int ret;

	release_conn = conn;
	ret = kdbus_conn_acquire(release_conn);
	if (ret < 0)
		return ret;

	switch (command) {
	case KDBUS_CMD_BYEBYE:
		/*
		 * BYEBYE is special; we must not acquire a connection when
		 * calling into kdbus_conn_disconnect() or we will deadlock,
		 * because kdbus_conn_disconnect() will wait for all acquired
		 * references to be dropped.
		 */
		kdbus_conn_release(release_conn);
		release_conn = NULL;
		ret = kdbus_cmd_byebye_unlocked(conn, buf);
		break;
	case KDBUS_CMD_NAME_ACQUIRE:
		ret = kdbus_cmd_name_acquire(conn, buf);
		break;
	case KDBUS_CMD_NAME_RELEASE:
		ret = kdbus_cmd_name_release(conn, buf);
		break;
	case KDBUS_CMD_LIST:
		ret = kdbus_cmd_list(conn, buf);
		break;
	case KDBUS_CMD_CONN_INFO:
		ret = kdbus_cmd_conn_info(conn, buf);
		break;
	case KDBUS_CMD_BUS_CREATOR_INFO:
		ret = kdbus_cmd_bus_creator_info(conn, buf);
		break;
	case KDBUS_CMD_UPDATE:
		ret = kdbus_cmd_update(conn, buf);
		break;
	case KDBUS_CMD_MATCH_ADD:
		ret = kdbus_cmd_match_add(conn, buf);
		break;
	case KDBUS_CMD_MATCH_REMOVE:
		ret = kdbus_cmd_match_remove(conn, buf);
		break;
	case KDBUS_CMD_SEND:
		ret = kdbus_cmd_send(conn, file, buf);
		break;
	case KDBUS_CMD_RECV:
		ret = kdbus_cmd_recv(conn, buf);
		break;
	case KDBUS_CMD_FREE:
		ret = kdbus_cmd_free(conn, buf);
		break;
	default:
		ret = -EBADFD;
		break;
	}

	kdbus_conn_release(release_conn);
	return ret;
}

static long kdbus_handle_ioctl(struct file *file, unsigned int cmd,
			       unsigned long arg)
{
	struct kdbus_handle *handle = file->private_data;
	struct kdbus_node *node = kdbus_node_from_inode(file_inode(file));
	void __user *argp = (void __user *)arg;
	long ret = -EBADFD;

	switch (cmd) {
	case KDBUS_CMD_BUS_MAKE:
	case KDBUS_CMD_ENDPOINT_MAKE:
	case KDBUS_CMD_HELLO:
		/* bail out early if already typed */
		if (handle->type != KDBUS_HANDLE_NONE)
			break;

		down_write(&handle->rwlock);
		if (handle->type == KDBUS_HANDLE_NONE) {
			if (node->type == KDBUS_NODE_CONTROL)
				ret = kdbus_handle_ioctl_control(file, cmd,
								 argp);
			else if (node->type == KDBUS_NODE_ENDPOINT)
				ret = kdbus_handle_ioctl_ep(file, cmd, argp);
		}
		up_write(&handle->rwlock);
		break;

	case KDBUS_CMD_ENDPOINT_UPDATE:
	case KDBUS_CMD_BYEBYE:
	case KDBUS_CMD_NAME_ACQUIRE:
	case KDBUS_CMD_NAME_RELEASE:
	case KDBUS_CMD_LIST:
	case KDBUS_CMD_CONN_INFO:
	case KDBUS_CMD_BUS_CREATOR_INFO:
	case KDBUS_CMD_UPDATE:
	case KDBUS_CMD_MATCH_ADD:
	case KDBUS_CMD_MATCH_REMOVE:
	case KDBUS_CMD_SEND:
	case KDBUS_CMD_RECV:
	case KDBUS_CMD_FREE:
		down_read(&handle->rwlock);
		if (handle->type == KDBUS_HANDLE_EP_OWNER)
			ret = kdbus_handle_ioctl_ep_owner(file, cmd, argp);
		else if (handle->type == KDBUS_HANDLE_CONNECTED)
			ret = kdbus_handle_ioctl_connected(file, cmd, argp);
		up_read(&handle->rwlock);
		break;
	default:
		ret = -ENOTTY;
		break;
	}

	return ret < 0 ? ret : 0;
}

static unsigned int kdbus_handle_poll(struct file *file,
				      struct poll_table_struct *wait)
{
	struct kdbus_handle *handle = file->private_data;
	unsigned int mask = POLLOUT | POLLWRNORM;
	int ret;

	/* Only a connected endpoint can read/write data */
	down_read(&handle->rwlock);
	if (handle->type != KDBUS_HANDLE_CONNECTED) {
		up_read(&handle->rwlock);
		return POLLERR | POLLHUP;
	}
	up_read(&handle->rwlock);

	ret = kdbus_conn_acquire(handle->conn);
	if (ret < 0)
		return POLLERR | POLLHUP;

	poll_wait(file, &handle->conn->wait, wait);

	if (!list_empty(&handle->conn->queue.msg_list) ||
	    atomic_read(&handle->conn->lost_count) > 0)
		mask |= POLLIN | POLLRDNORM;

	kdbus_conn_release(handle->conn);

	return mask;
}

static int kdbus_handle_mmap(struct file *file, struct vm_area_struct *vma)
{
	struct kdbus_handle *handle = file->private_data;
	int ret = -EBADFD;

	if (down_read_trylock(&handle->rwlock)) {
		if (handle->type == KDBUS_HANDLE_CONNECTED)
			ret = kdbus_pool_mmap(handle->conn->pool, vma);
		up_read(&handle->rwlock);
	}
	return ret;
}

const struct file_operations kdbus_handle_ops = {
	.owner =		THIS_MODULE,
	.open =			kdbus_handle_open,
	.release =		kdbus_handle_release,
	.poll =			kdbus_handle_poll,
	.llseek =		noop_llseek,
	.unlocked_ioctl =	kdbus_handle_ioctl,
	.mmap =			kdbus_handle_mmap,
#ifdef CONFIG_COMPAT
	.compat_ioctl =		kdbus_handle_ioctl,
#endif
};