blob: 9111dcba37a128eaf9ebe18a4b1b13302d3efd92 [file] [log] [blame]
Ashwin Ganti55643172009-02-24 19:48:44 -08001/*
2 * Plan 9 style capability device implementation for the Linux Kernel
3 *
4 * Copyright 2008, 2009 Ashwin Ganti <ashwin.ganti@gmail.com>
5 *
6 * Released under the GPLv2
7 *
8 */
Ashwin Ganti55643172009-02-24 19:48:44 -08009#include <linux/init.h>
10#include <linux/kernel.h>
Greg Kroah-Hartman2418a622009-02-24 20:37:55 -080011#include <linux/moduleparam.h>
Ashwin Ganti55643172009-02-24 19:48:44 -080012#include <linux/slab.h>
13#include <linux/fs.h>
14#include <linux/errno.h>
Ashwin Ganti55643172009-02-24 19:48:44 -080015#include <linux/fcntl.h>
16#include <linux/cdev.h>
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -080017#include <linux/uaccess.h>
Ashwin Ganti55643172009-02-24 19:48:44 -080018#include <linux/list.h>
Ashwin Ganti55643172009-02-24 19:48:44 -080019#include <linux/mm.h>
20#include <linux/string.h>
21#include <linux/crypto.h>
22#include <linux/highmem.h>
Ashwin Ganti55643172009-02-24 19:48:44 -080023#include <linux/scatterlist.h>
Greg Kroah-Hartman5dba0822009-02-24 20:06:34 -080024#include <linux/sched.h>
25#include <linux/cred.h>
Greg Kroah-Hartman4bf04382009-02-24 20:11:39 -080026
27#ifndef CAP_MAJOR
28#define CAP_MAJOR 0
29#endif
30
31#ifndef CAP_NR_DEVS
32#define CAP_NR_DEVS 2 /* caphash and capuse */
33#endif
34
35#ifndef CAP_NODE_SIZE
36#define CAP_NODE_SIZE 20
37#endif
38
39#define MAX_DIGEST_SIZE 20
40
41struct cap_node {
42 char data[CAP_NODE_SIZE];
43 struct list_head list;
44};
45
46struct cap_dev {
47 struct cap_node *head;
48 int node_size;
49 unsigned long size;
50 struct semaphore sem;
51 struct cdev cdev;
52};
53
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -080054static int cap_major = CAP_MAJOR;
55static int cap_minor;
56static int cap_nr_devs = CAP_NR_DEVS;
57static int cap_node_size = CAP_NODE_SIZE;
Ashwin Ganti55643172009-02-24 19:48:44 -080058
59module_param(cap_major, int, S_IRUGO);
60module_param(cap_minor, int, S_IRUGO);
61module_param(cap_nr_devs, int, S_IRUGO);
62
63MODULE_AUTHOR("Ashwin Ganti");
64MODULE_LICENSE("GPL");
65
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -080066static struct cap_dev *cap_devices;
Ashwin Ganti55643172009-02-24 19:48:44 -080067
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -080068static void hexdump(unsigned char *buf, unsigned int len)
Ashwin Ganti55643172009-02-24 19:48:44 -080069{
70 while (len--)
71 printk("%02x", *buf++);
72 printk("\n");
73}
74
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -080075static char *cap_hash(char *plain_text, unsigned int plain_text_size,
76 char *key, unsigned int key_size)
77{
78 struct scatterlist sg;
Greg Kroah-Hartman6d0d63b2009-02-24 20:31:39 -080079 char *result;
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -080080 struct crypto_hash *tfm;
81 struct hash_desc desc;
82 int ret;
83
84 tfm = crypto_alloc_hash("hmac(sha1)", 0, CRYPTO_ALG_ASYNC);
85 if (IS_ERR(tfm)) {
86 printk(KERN_ERR
87 "failed to load transform for hmac(sha1): %ld\n",
88 PTR_ERR(tfm));
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -080089 return NULL;
90 }
91
92 desc.tfm = tfm;
93 desc.flags = 0;
94
Greg Kroah-Hartman6d0d63b2009-02-24 20:31:39 -080095 result = kzalloc(MAX_DIGEST_SIZE, GFP_KERNEL);
96 if (!result) {
97 printk(KERN_ERR "out of memory!\n");
98 goto out;
99 }
100
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800101 sg_set_buf(&sg, plain_text, plain_text_size);
102
103 ret = crypto_hash_setkey(tfm, key, key_size);
104 if (ret) {
105 printk(KERN_ERR "setkey() failed ret=%d\n", ret);
106 kfree(result);
107 result = NULL;
108 goto out;
109 }
110
111 ret = crypto_hash_digest(&desc, &sg, plain_text_size, result);
112 if (ret) {
113 printk(KERN_ERR "digest () failed ret=%d\n", ret);
114 kfree(result);
115 result = NULL;
116 goto out;
117 }
118
119 printk(KERN_DEBUG "crypto hash digest size %d\n",
120 crypto_hash_digestsize(tfm));
121 hexdump(result, MAX_DIGEST_SIZE);
122
123out:
124 crypto_free_hash(tfm);
125 return result;
126}
127
128static int cap_trim(struct cap_dev *dev)
Ashwin Ganti55643172009-02-24 19:48:44 -0800129{
130 struct cap_node *tmp;
131 struct list_head *pos, *q;
132 if (dev->head != NULL) {
133 list_for_each_safe(pos, q, &(dev->head->list)) {
134 tmp = list_entry(pos, struct cap_node, list);
135 list_del(pos);
136 kfree(tmp);
137 }
138 }
139 return 0;
140}
141
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800142static int cap_open(struct inode *inode, struct file *filp)
Ashwin Ganti55643172009-02-24 19:48:44 -0800143{
144 struct cap_dev *dev;
145 dev = container_of(inode->i_cdev, struct cap_dev, cdev);
146 filp->private_data = dev;
147
148 /* trim to 0 the length of the device if open was write-only */
149 if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
150 if (down_interruptible(&dev->sem))
151 return -ERESTARTSYS;
152 cap_trim(dev);
153 up(&dev->sem);
154 }
155 /* initialise the head if it is NULL */
156 if (dev->head == NULL) {
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800157 dev->head = kmalloc(sizeof(struct cap_node), GFP_KERNEL);
Ashwin Ganti55643172009-02-24 19:48:44 -0800158 INIT_LIST_HEAD(&(dev->head->list));
159 }
160 return 0;
161}
162
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800163static int cap_release(struct inode *inode, struct file *filp)
Ashwin Ganti55643172009-02-24 19:48:44 -0800164{
165 return 0;
166}
167
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800168static ssize_t cap_write(struct file *filp, const char __user *buf,
169 size_t count, loff_t *f_pos)
Ashwin Ganti55643172009-02-24 19:48:44 -0800170{
171 struct cap_node *node_ptr, *tmp;
172 struct list_head *pos;
173 struct cap_dev *dev = filp->private_data;
174 ssize_t retval = -ENOMEM;
Greg Kroah-Hartman5dba0822009-02-24 20:06:34 -0800175 struct cred *new;
Ashwin Ganti55643172009-02-24 19:48:44 -0800176 int len, target_int, source_int, flag = 0;
177 char *user_buf, *user_buf_running, *source_user, *target_user,
178 *rand_str, *hash_str, *result;
179
180 if (down_interruptible(&dev->sem))
181 return -ERESTARTSYS;
182
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500183 user_buf_running = NULL;
184 hash_str = NULL;
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800185 node_ptr = kmalloc(sizeof(struct cap_node), GFP_KERNEL);
Greg Kroah-Hartman6d0d63b2009-02-24 20:31:39 -0800186 user_buf = kzalloc(count, GFP_KERNEL);
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500187 if (!node_ptr || !user_buf)
188 goto out;
Ashwin Ganti55643172009-02-24 19:48:44 -0800189
190 if (copy_from_user(user_buf, buf, count)) {
191 retval = -EFAULT;
192 goto out;
193 }
194
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800195 /*
196 * If the minor number is 0 ( /dev/caphash ) then simply add the
Ashwin Ganti55643172009-02-24 19:48:44 -0800197 * hashed capability supplied by the user to the list of hashes
198 */
199 if (0 == iminor(filp->f_dentry->d_inode)) {
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500200 if (count > CAP_NODE_SIZE) {
201 retval = -EINVAL;
202 goto out;
203 }
Ashwin Ganti55643172009-02-24 19:48:44 -0800204 printk(KERN_INFO "Capability being written to /dev/caphash : \n");
205 hexdump(user_buf, count);
206 memcpy(node_ptr->data, user_buf, count);
207 list_add(&(node_ptr->list), &(dev->head->list));
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500208 node_ptr = NULL;
Ashwin Ganti55643172009-02-24 19:48:44 -0800209 } else {
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500210 if (!cap_devices[0].head ||
211 list_empty(&(cap_devices[0].head->list))) {
212 retval = -EINVAL;
213 goto out;
214 }
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800215 /*
216 * break the supplied string into tokens with @ as the
217 * delimiter If the string is "user1@user2@randomstring" we
218 * need to split it and hash 'user1@user2' using 'randomstring'
219 * as the key.
Ashwin Ganti55643172009-02-24 19:48:44 -0800220 */
221 user_buf_running = kstrdup(user_buf, GFP_KERNEL);
222 source_user = strsep(&user_buf_running, "@");
223 target_user = strsep(&user_buf_running, "@");
224 rand_str = strsep(&user_buf_running, "@");
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500225 if (!source_user || !target_user || !rand_str) {
226 retval = -EINVAL;
227 goto out;
228 }
Ashwin Ganti55643172009-02-24 19:48:44 -0800229
230 /* hash the string user1@user2 with rand_str as the key */
231 len = strlen(source_user) + strlen(target_user) + 1;
Greg Kroah-Hartman6d0d63b2009-02-24 20:31:39 -0800232 hash_str = kzalloc(len, GFP_KERNEL);
Ashwin Ganti55643172009-02-24 19:48:44 -0800233 strcat(hash_str, source_user);
234 strcat(hash_str, "@");
235 strcat(hash_str, target_user);
236
237 printk(KERN_ALERT "the source user is %s \n", source_user);
238 printk(KERN_ALERT "the target user is %s \n", target_user);
239
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800240 result = cap_hash(hash_str, len, rand_str, strlen(rand_str));
Ashwin Ganti55643172009-02-24 19:48:44 -0800241 if (NULL == result) {
242 retval = -EFAULT;
243 goto out;
244 }
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500245 memcpy(node_ptr->data, result, CAP_NODE_SIZE); /* why? */
Ashwin Ganti55643172009-02-24 19:48:44 -0800246 /* Change the process's uid if the hash is present in the
247 * list of hashes
248 */
249 list_for_each(pos, &(cap_devices->head->list)) {
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800250 /*
251 * Change the user id of the process if the hashes
252 * match
253 */
Ashwin Ganti55643172009-02-24 19:48:44 -0800254 if (0 ==
255 memcmp(result,
256 list_entry(pos, struct cap_node,
257 list)->data,
258 CAP_NODE_SIZE)) {
259 target_int = (unsigned int)
260 simple_strtol(target_user, NULL, 0);
261 source_int = (unsigned int)
262 simple_strtol(source_user, NULL, 0);
263 flag = 1;
264
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800265 /*
266 * Check whether the process writing to capuse
267 * is actually owned by the source owner
Ashwin Ganti55643172009-02-24 19:48:44 -0800268 */
Greg Kroah-Hartman5dba0822009-02-24 20:06:34 -0800269 if (source_int != current_uid()) {
Ashwin Ganti55643172009-02-24 19:48:44 -0800270 printk(KERN_ALERT
271 "Process is not owned by the source user of the capability.\n");
272 retval = -EFAULT;
273 goto out;
274 }
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800275 /*
276 * What all id's need to be changed here? uid,
277 * euid, fsid, savedids ?? Currently I am
278 * changing the effective user id since most of
279 * the authorisation decisions are based on it
Ashwin Ganti55643172009-02-24 19:48:44 -0800280 */
Greg Kroah-Hartman5dba0822009-02-24 20:06:34 -0800281 new = prepare_creds();
282 if (!new) {
283 retval = -ENOMEM;
284 goto out;
285 }
286 new->uid = (uid_t) target_int;
287 new->euid = (uid_t) target_int;
288 retval = commit_creds(new);
289 if (retval)
290 goto out;
Ashwin Ganti55643172009-02-24 19:48:44 -0800291
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800292 /*
293 * Remove the capability from the list and
294 * break
295 */
296 tmp = list_entry(pos, struct cap_node, list);
Ashwin Ganti55643172009-02-24 19:48:44 -0800297 list_del(pos);
298 kfree(tmp);
299 break;
300 }
301 }
302 if (0 == flag) {
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800303 /*
304 * The capability is not present in the list of the
305 * hashes stored, hence return failure
306 */
Ashwin Ganti55643172009-02-24 19:48:44 -0800307 printk(KERN_ALERT
308 "Invalid capabiliy written to /dev/capuse \n");
309 retval = -EFAULT;
310 goto out;
311 }
312 }
313 *f_pos += count;
314 retval = count;
315 /* update the size */
316 if (dev->size < *f_pos)
317 dev->size = *f_pos;
318
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800319out:
Serge E. Hallynf82ebea2009-05-20 10:15:28 -0500320 kfree(node_ptr);
321 kfree(user_buf);
322 kfree(user_buf_running);
323 kfree(hash_str);
Ashwin Ganti55643172009-02-24 19:48:44 -0800324 up(&dev->sem);
325 return retval;
326}
327
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800328static const struct file_operations cap_fops = {
Ashwin Ganti55643172009-02-24 19:48:44 -0800329 .owner = THIS_MODULE,
330 .write = cap_write,
331 .open = cap_open,
332 .release = cap_release,
333};
334
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800335static void cap_cleanup_module(void)
Ashwin Ganti55643172009-02-24 19:48:44 -0800336{
337 int i;
338 dev_t devno = MKDEV(cap_major, cap_minor);
339 if (cap_devices) {
340 for (i = 0; i < cap_nr_devs; i++) {
341 cap_trim(cap_devices + i);
342 cdev_del(&cap_devices[i].cdev);
343 }
344 kfree(cap_devices);
345 }
346 unregister_chrdev_region(devno, cap_nr_devs);
347
348}
349
Ashwin Ganti55643172009-02-24 19:48:44 -0800350static void cap_setup_cdev(struct cap_dev *dev, int index)
351{
352 int err, devno = MKDEV(cap_major, cap_minor + index);
353 cdev_init(&dev->cdev, &cap_fops);
354 dev->cdev.owner = THIS_MODULE;
355 dev->cdev.ops = &cap_fops;
356 err = cdev_add(&dev->cdev, devno, 1);
357 if (err)
358 printk(KERN_NOTICE "Error %d adding cap%d", err, index);
359}
360
Greg Kroah-Hartman0f386e22009-02-24 20:25:02 -0800361static int cap_init_module(void)
Ashwin Ganti55643172009-02-24 19:48:44 -0800362{
363 int result, i;
364 dev_t dev = 0;
365
366 if (cap_major) {
367 dev = MKDEV(cap_major, cap_minor);
368 result = register_chrdev_region(dev, cap_nr_devs, "cap");
369 } else {
370 result = alloc_chrdev_region(&dev, cap_minor, cap_nr_devs,
371 "cap");
372 cap_major = MAJOR(dev);
373 }
374
375 if (result < 0) {
376 printk(KERN_WARNING "cap: can't get major %d\n",
377 cap_major);
378 return result;
379 }
380
Greg Kroah-Hartman6d0d63b2009-02-24 20:31:39 -0800381 cap_devices = kzalloc(cap_nr_devs * sizeof(struct cap_dev),
382 GFP_KERNEL);
Ashwin Ganti55643172009-02-24 19:48:44 -0800383 if (!cap_devices) {
384 result = -ENOMEM;
385 goto fail;
386 }
Ashwin Ganti55643172009-02-24 19:48:44 -0800387
388 /* Initialize each device. */
389 for (i = 0; i < cap_nr_devs; i++) {
390 cap_devices[i].node_size = cap_node_size;
391 init_MUTEX(&cap_devices[i].sem);
392 cap_setup_cdev(&cap_devices[i], i);
393 }
394
395 return 0;
396
Greg Kroah-Hartman77dc1132009-02-24 20:21:55 -0800397fail:
Ashwin Ganti55643172009-02-24 19:48:44 -0800398 cap_cleanup_module();
399 return result;
400}
401
402module_init(cap_init_module);
403module_exit(cap_cleanup_module);
404
Ashwin Ganti55643172009-02-24 19:48:44 -0800405