summaryrefslogtreecommitdiff
path: root/drivers/staging/p9auth/p9auth.c
blob: 9111dcba37a128eaf9ebe18a4b1b13302d3efd92 (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
/*
 * Plan 9 style capability device implementation for the Linux Kernel
 *
 * Copyright 2008, 2009 Ashwin Ganti <ashwin.ganti@gmail.com>
 *
 * Released under the GPLv2
 *
 */
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/crypto.h>
#include <linux/highmem.h>
#include <linux/scatterlist.h>
#include <linux/sched.h>
#include <linux/cred.h>

#ifndef CAP_MAJOR
#define CAP_MAJOR 0
#endif

#ifndef CAP_NR_DEVS
#define CAP_NR_DEVS 2		/* caphash and capuse */
#endif

#ifndef CAP_NODE_SIZE
#define CAP_NODE_SIZE 20
#endif

#define MAX_DIGEST_SIZE  20

struct cap_node {
	char data[CAP_NODE_SIZE];
	struct list_head list;
};

struct cap_dev {
	struct cap_node *head;
	int node_size;
	unsigned long size;
	struct semaphore sem;
	struct cdev cdev;
};

static int cap_major = CAP_MAJOR;
static int cap_minor;
static int cap_nr_devs = CAP_NR_DEVS;
static int cap_node_size = CAP_NODE_SIZE;

module_param(cap_major, int, S_IRUGO);
module_param(cap_minor, int, S_IRUGO);
module_param(cap_nr_devs, int, S_IRUGO);

MODULE_AUTHOR("Ashwin Ganti");
MODULE_LICENSE("GPL");

static struct cap_dev *cap_devices;

static void hexdump(unsigned char *buf, unsigned int len)
{
	while (len--)
		printk("%02x", *buf++);
	printk("\n");
}

static char *cap_hash(char *plain_text, unsigned int plain_text_size,
		      char *key, unsigned int key_size)
{
	struct scatterlist sg;
	char *result;
	struct crypto_hash *tfm;
	struct hash_desc desc;
	int ret;

	tfm = crypto_alloc_hash("hmac(sha1)", 0, CRYPTO_ALG_ASYNC);
	if (IS_ERR(tfm)) {
		printk(KERN_ERR
		       "failed to load transform for hmac(sha1): %ld\n",
		       PTR_ERR(tfm));
		return NULL;
	}

	desc.tfm = tfm;
	desc.flags = 0;

	result = kzalloc(MAX_DIGEST_SIZE, GFP_KERNEL);
	if (!result) {
		printk(KERN_ERR "out of memory!\n");
		goto out;
	}

	sg_set_buf(&sg, plain_text, plain_text_size);

	ret = crypto_hash_setkey(tfm, key, key_size);
	if (ret) {
		printk(KERN_ERR "setkey() failed ret=%d\n", ret);
		kfree(result);
		result = NULL;
		goto out;
	}

	ret = crypto_hash_digest(&desc, &sg, plain_text_size, result);
	if (ret) {
		printk(KERN_ERR "digest () failed ret=%d\n", ret);
		kfree(result);
		result = NULL;
		goto out;
	}

	printk(KERN_DEBUG "crypto hash digest size %d\n",
	       crypto_hash_digestsize(tfm));
	hexdump(result, MAX_DIGEST_SIZE);

out:
	crypto_free_hash(tfm);
	return result;
}

static int cap_trim(struct cap_dev *dev)
{
	struct cap_node *tmp;
	struct list_head *pos, *q;
	if (dev->head != NULL) {
		list_for_each_safe(pos, q, &(dev->head->list)) {
			tmp = list_entry(pos, struct cap_node, list);
			list_del(pos);
			kfree(tmp);
		}
	}
	return 0;
}

static int cap_open(struct inode *inode, struct file *filp)
{
	struct cap_dev *dev;
	dev = container_of(inode->i_cdev, struct cap_dev, cdev);
	filp->private_data = dev;

	/* trim to 0 the length of the device if open was write-only */
	if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
		if (down_interruptible(&dev->sem))
			return -ERESTARTSYS;
		cap_trim(dev);
		up(&dev->sem);
	}
	/* initialise the head if it is NULL */
	if (dev->head == NULL) {
		dev->head = kmalloc(sizeof(struct cap_node), GFP_KERNEL);
		INIT_LIST_HEAD(&(dev->head->list));
	}
	return 0;
}

static int cap_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t cap_write(struct file *filp, const char __user *buf,
			 size_t count, loff_t *f_pos)
{
	struct cap_node *node_ptr, *tmp;
	struct list_head *pos;
	struct cap_dev *dev = filp->private_data;
	ssize_t retval = -ENOMEM;
	struct cred *new;
	int len, target_int, source_int, flag = 0;
	char *user_buf, *user_buf_running, *source_user, *target_user,
	    *rand_str, *hash_str, *result;

	if (down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	user_buf_running = NULL;
	hash_str = NULL;
	node_ptr = kmalloc(sizeof(struct cap_node), GFP_KERNEL);
	user_buf = kzalloc(count, GFP_KERNEL);
	if (!node_ptr || !user_buf)
		goto out;

	if (copy_from_user(user_buf, buf, count)) {
		retval = -EFAULT;
		goto out;
	}

	/*
	 * If the minor number is 0 ( /dev/caphash ) then simply add the
	 * hashed capability supplied by the user to the list of hashes
	 */
	if (0 == iminor(filp->f_dentry->d_inode)) {
		if (count > CAP_NODE_SIZE) {
			retval = -EINVAL;
			goto out;
		}
		printk(KERN_INFO "Capability being written to /dev/caphash : \n");
		hexdump(user_buf, count);
		memcpy(node_ptr->data, user_buf, count);
		list_add(&(node_ptr->list), &(dev->head->list));
		node_ptr = NULL;
	} else {
		if (!cap_devices[0].head ||
				list_empty(&(cap_devices[0].head->list))) {
			retval = -EINVAL;
			goto out;
		}
		/*
		 * break the supplied string into tokens with @ as the
		 * delimiter If the string is "user1@user2@randomstring" we
		 * need to split it and hash 'user1@user2' using 'randomstring'
		 * as the key.
		 */
		user_buf_running = kstrdup(user_buf, GFP_KERNEL);
		source_user = strsep(&user_buf_running, "@");
		target_user = strsep(&user_buf_running, "@");
		rand_str = strsep(&user_buf_running, "@");
		if (!source_user || !target_user || !rand_str) {
			retval = -EINVAL;
			goto out;
		}

		/* hash the string user1@user2 with rand_str as the key */
		len = strlen(source_user) + strlen(target_user) + 1;
		hash_str = kzalloc(len, GFP_KERNEL);
		strcat(hash_str, source_user);
		strcat(hash_str, "@");
		strcat(hash_str, target_user);

		printk(KERN_ALERT "the source user is %s \n", source_user);
		printk(KERN_ALERT "the target user is %s \n", target_user);

		result = cap_hash(hash_str, len, rand_str, strlen(rand_str));
		if (NULL == result) {
			retval = -EFAULT;
			goto out;
		}
		memcpy(node_ptr->data, result, CAP_NODE_SIZE);  /* why? */
		/* Change the process's uid if the hash is present in the
		 * list of hashes
		 */
		list_for_each(pos, &(cap_devices->head->list)) {
			/*
			 * Change the user id of the process if the hashes
			 * match
			 */
			if (0 ==
			    memcmp(result,
				   list_entry(pos, struct cap_node,
					      list)->data,
				   CAP_NODE_SIZE)) {
				target_int = (unsigned int)
				    simple_strtol(target_user, NULL, 0);
				source_int = (unsigned int)
				    simple_strtol(source_user, NULL, 0);
				flag = 1;

				/*
				 * Check whether the process writing to capuse
				 * is actually owned by the source owner
				 */
				if (source_int != current_uid()) {
					printk(KERN_ALERT
					       "Process is not owned by the source user of the capability.\n");
					retval = -EFAULT;
					goto out;
				}
				/*
				 * What all id's need to be changed here? uid,
				 * euid, fsid, savedids ??  Currently I am
				 * changing the effective user id since most of
				 * the authorisation decisions are based on it
				 */
				new = prepare_creds();
				if (!new) {
					retval = -ENOMEM;
					goto out;
				}
				new->uid = (uid_t) target_int;
				new->euid = (uid_t) target_int;
				retval = commit_creds(new);
				if (retval)
					goto out;

				/*
				 * Remove the capability from the list and
				 * break
				 */
				tmp = list_entry(pos, struct cap_node, list);
				list_del(pos);
				kfree(tmp);
				break;
			}
		}
		if (0 == flag) {
			/*
			 * The capability is not present in the list of the
			 * hashes stored, hence return failure
			 */
			printk(KERN_ALERT
			       "Invalid capabiliy written to /dev/capuse \n");
			retval = -EFAULT;
			goto out;
		}
	}
	*f_pos += count;
	retval = count;
	/* update the size */
	if (dev->size < *f_pos)
		dev->size = *f_pos;

out:
	kfree(node_ptr);
	kfree(user_buf);
	kfree(user_buf_running);
	kfree(hash_str);
	up(&dev->sem);
	return retval;
}

static const struct file_operations cap_fops = {
	.owner = THIS_MODULE,
	.write = cap_write,
	.open = cap_open,
	.release = cap_release,
};

static void cap_cleanup_module(void)
{
	int i;
	dev_t devno = MKDEV(cap_major, cap_minor);
	if (cap_devices) {
		for (i = 0; i < cap_nr_devs; i++) {
			cap_trim(cap_devices + i);
			cdev_del(&cap_devices[i].cdev);
		}
		kfree(cap_devices);
	}
	unregister_chrdev_region(devno, cap_nr_devs);

}

static void cap_setup_cdev(struct cap_dev *dev, int index)
{
	int err, devno = MKDEV(cap_major, cap_minor + index);
	cdev_init(&dev->cdev, &cap_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &cap_fops;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
		printk(KERN_NOTICE "Error %d adding cap%d", err, index);
}

static int cap_init_module(void)
{
	int result, i;
	dev_t dev = 0;

	if (cap_major) {
		dev = MKDEV(cap_major, cap_minor);
		result = register_chrdev_region(dev, cap_nr_devs, "cap");
	} else {
		result = alloc_chrdev_region(&dev, cap_minor, cap_nr_devs,
					     "cap");
		cap_major = MAJOR(dev);
	}

	if (result < 0) {
		printk(KERN_WARNING "cap: can't get major %d\n",
		       cap_major);
		return result;
	}

	cap_devices = kzalloc(cap_nr_devs * sizeof(struct cap_dev),
			      GFP_KERNEL);
	if (!cap_devices) {
		result = -ENOMEM;
		goto fail;
	}

	/* Initialize each device. */
	for (i = 0; i < cap_nr_devs; i++) {
		cap_devices[i].node_size = cap_node_size;
		init_MUTEX(&cap_devices[i].sem);
		cap_setup_cdev(&cap_devices[i], i);
	}

	return 0;

fail:
	cap_cleanup_module();
	return result;
}

module_init(cap_init_module);
module_exit(cap_cleanup_module);