aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/p9auth/p9auth.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/p9auth/p9auth.c')
-rw-r--r--drivers/staging/p9auth/p9auth.c383
1 files changed, 383 insertions, 0 deletions
diff --git a/drivers/staging/p9auth/p9auth.c b/drivers/staging/p9auth/p9auth.c
new file mode 100644
index 000000000000..3cac89b26faf
--- /dev/null
+++ b/drivers/staging/p9auth/p9auth.c
@@ -0,0 +1,383 @@
+/*
+ * 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;
+
+ node_ptr = kmalloc(sizeof(struct cap_node), GFP_KERNEL);
+ user_buf = kzalloc(count, GFP_KERNEL);
+
+ 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)) {
+ 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));
+ } else {
+ /*
+ * 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, "@");
+
+ /* 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);
+ /* 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:
+ 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);
+
+