/* * Plan 9 style capability device implementation for the Linux Kernel * * Copyright 2008, 2009 Ashwin Ganti * * Released under the GPLv2 * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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);