diff -Naur linux-2.4.20/CREDITS linux-2.4.20-gran_suid/CREDITS --- linux-2.4.20/CREDITS 2002-11-28 15:53:08.000000000 -0800 +++ linux-2.4.20-gran_suid/CREDITS 2003-05-28 19:32:18.000000000 -0700 @@ -428,6 +428,11 @@ S: Lancaster, LA1 4DN S: UK, England +N: Bjorn Bringert +E: bjorn@bringert.net +W: http://www.bringert.net/ +D: Granular suid support + N: Lars Brinkhoff E: lars@nocrew.org W: http://lars.nocrew.org/ diff -Naur linux-2.4.20/Documentation/Configure.help linux-2.4.20-gran_suid/Documentation/Configure.help --- linux-2.4.20/Documentation/Configure.help 2002-11-28 15:53:08.000000000 -0800 +++ linux-2.4.20-gran_suid/Documentation/Configure.help 2003-05-28 19:21:38.000000000 -0700 @@ -12653,6 +12653,11 @@ . Probably the quota support is only useful for multi user systems. If unsure, say N. +Granular suid support +CONFIG_GRAN_SUID + If you say Y here you will be able to setup file permissions based + on the program that the process is running. If unsure, say N. + Memory Technology Device (MTD) support CONFIG_MTD Memory Technology Devices are flash, RAM and similar chips, often diff -Naur linux-2.4.20/fs/Config.in linux-2.4.20-gran_suid/fs/Config.in --- linux-2.4.20/fs/Config.in 2002-11-28 15:53:15.000000000 -0800 +++ linux-2.4.20-gran_suid/fs/Config.in 2003-05-28 19:14:44.000000000 -0700 @@ -8,6 +8,8 @@ tristate 'Kernel automounter support' CONFIG_AUTOFS_FS tristate 'Kernel automounter version 4 support (also supports v3)' CONFIG_AUTOFS4_FS +bool 'Granular suid support' CONFIG_GRAN_SUID + tristate 'Reiserfs support' CONFIG_REISERFS_FS dep_mbool ' Enable reiserfs debug mode' CONFIG_REISERFS_CHECK $CONFIG_REISERFS_FS dep_mbool ' Stats in /proc/fs/reiserfs' CONFIG_REISERFS_PROC_INFO $CONFIG_REISERFS_FS diff -Naur linux-2.4.20/fs/gran_suid.c linux-2.4.20-gran_suid/fs/gran_suid.c --- linux-2.4.20/fs/gran_suid.c 1969-12-31 16:00:00.000000000 -0800 +++ linux-2.4.20-gran_suid/fs/gran_suid.c 2003-05-29 14:11:40.000000000 -0700 @@ -0,0 +1,611 @@ +/* + * TODO: + * - support ':' in file names + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Types + */ + +struct permission_node { + char *file; + char *prog; + int perm; + struct permission_node *left; + struct permission_node *right; +}; + +struct stack { + struct permission_node *data; + struct stack *next; +}; + + +/* + * Macros + */ + +#define GRAN_SUID_NAME "gran_suid" +#define GRAN_SUID_PROC_ENTRY GRAN_SUID_NAME + +#define log_debug(...) printk (KERN_INFO GRAN_SUID_NAME ": " __VA_ARGS__) +#define log_info(...) printk (KERN_INFO GRAN_SUID_NAME ": " __VA_ARGS__) +#define log_warn(...) printk (KERN_WARNING GRAN_SUID_NAME ": " __VA_ARGS__) +#define log_err(...) printk (KERN_ERR GRAN_SUID_NAME ": " __VA_ARGS__) + +#define ALLOW_COMMAND "allow" +#define PERMISSION_PROC_ENTRY PERMISSION_NAME +#define EPERM_PARSE EIO + + +/* + * Global data + */ + +struct permission_node *permission_root = NULL; + +rwlock_t tree_lock = RW_LOCK_UNLOCKED; + + +/* + * Utulity functions + */ + +static char *strdup(const char *s, int priority) +{ + size_t l; + char *t; + + l = strlen(s) + 1; + t = kmalloc(l, priority); + if (t == NULL) + return NULL; + + return strncpy(t, s, l); +} + +static char *get_program_name(struct task_struct *task, char *buf, int len) +{ + struct mm_struct *mm; + struct vm_area_struct *vma; + char *path = NULL; + + task_lock(task); + mm = task->mm; + if (mm) + atomic_inc(&mm->mm_users); + + task_unlock(task); + if (!mm) + return NULL; + + down_read(&mm->mmap_sem); + for (vma = mm->mmap; vma; vma = vma->vm_next) { + if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file) { + path = d_path(vma->vm_file->f_dentry, + vma->vm_file->f_vfsmnt, + buf, len); + break; + } + } + up_read(&mm->mmap_sem); + mmput(mm); + + return path; +} + +/* + * Converts a permission to a string for printing. + * + * perm: A valid permission + * returns: A pointer to a static string describing the permissions. + */ +static char *perm_to_string(int perm) +{ + switch (perm) { + case 0: return ""; + case MAY_EXEC: return "x"; + case MAY_WRITE: return "w"; + case (MAY_WRITE|MAY_EXEC): return "wx"; + case MAY_READ: return "r"; + case (MAY_READ|MAY_EXEC): return "rx"; + case (MAY_READ|MAY_WRITE): return "rw"; + case (MAY_READ|MAY_WRITE|MAY_EXEC): return "rwx"; + default: + log_warn("bad permissions: %d\n", perm); + return ""; + } +} + +/* + * Permission tree functions + */ + +static struct permission_node *permission_new(const char *file, + const char *prog, + int perm) +{ + struct permission_node *node; + + node = kmalloc(sizeof(struct permission_node), GFP_KERNEL); + if (node == NULL) + return NULL; + + node->file = strdup(file, GFP_KERNEL); + if (node->file == NULL) { + kfree(node); + return NULL; + } + + node->prog = strdup(prog, GFP_KERNEL); + if (node->prog == NULL) { + kfree(node->file); + kfree(node); + return NULL; + } + + node->perm = perm; + node->left = NULL; + node->right = NULL; + + return node; +} + +static struct permission_node **permission_find(struct permission_node **node, + const char *file, + const char *prog) +{ + int c; + + while (*node != NULL) { + c = strcmp(file, (*node)->file); + if (c == 0) + c = strcmp(prog, (*node)->prog); + if (c < 0) { + node = &(*node)->left; + } else if (c > 0) { + node = &(*node)->right; + } else { + return node; + } + } + + return node; +} + +static int permission_set(struct permission_node **tree, const char *file, + const char *prog, int perm) +{ + struct permission_node *node, **add_at; + + add_at = permission_find(tree, file, prog); + + if (*add_at == NULL) { + /* no point in adding a node with no permissions */ + if (perm == 0) + return 0; + + node = permission_new(file, prog, perm); + if (node == NULL) + return -ENOMEM; + + *add_at = node; + } else { + /* FIXME: should delete the node if perm == 0 */ + (*add_at)->perm = perm; + } + + return 0; +} + +static void permission_free(struct permission_node *node) +{ + kfree(node->prog); + kfree(node->file); + kfree(node); +} + +static void permission_clear(struct permission_node *tree) +{ + if (tree == NULL) + return; + + permission_clear(tree->left); + permission_clear(tree->right); + permission_free(tree); +} + +/* + * Stack functions + */ + +static struct stack *stack_push(struct permission_node *data, struct stack *st) +{ + struct stack *new_st; + + new_st = kmalloc(sizeof(struct stack), GFP_KERNEL); + if (new_st == NULL) + return NULL; + + new_st->data = data; + new_st->next = st; + + return new_st; +} + +static struct stack *stack_pop(struct stack *st) +{ + struct stack *next; + + if (st == NULL) + return NULL; + + next = st->next; + kfree(st); + + return next; +} + +/* + * Permission tree iterator + */ + +static struct stack *permission_iter_start(struct permission_node *tree) +{ + struct stack *st = NULL; + + for (; tree != NULL; tree = tree->left) + st = stack_push(tree, st); + + return st; +} + +static struct stack *permission_iter_next(struct stack *st) +{ + struct permission_node *node; + + if (st == NULL) + return NULL; + + node = st->data->right; + st = stack_pop(st); + + for (; node != NULL; node = node->left) + st = stack_push(node, st); + + return st; +} + +static struct permission_node *permission_iter_get(struct stack * st) +{ + return st->data; +} + +static void permission_iter_stop(struct stack *st) +{ + while (st != NULL) + st = stack_pop(st); +} + + +/* + * Gets the permissions that a program has been given to a file. + * A read lock should be obtained before calling this function. + * + * file: The absoulte path to the file + * prog: The absoulte path to the program + * returns: the given permissions, or 0 if no permissions + * have been added for the (file, program) pair. + */ +static int get_permissions(const char *file, const char *prog) +{ + struct permission_node *node; + + node = *permission_find(&permission_root, file, prog); + + return (node == NULL) ? 0 : node->perm; +} + +extern int gran_suid_permission(struct inode *inode, int wants) +{ + int has, retval; + struct vfsmount *mnt, *rootmnt; + struct dentry *dentry, *root; + char *prog, *prog_buf; + char *path, *path_buf; + + log_debug("gran_suid_permission\n"); + + prog_buf = __getname(); + if (IS_ERR(prog_buf)) { + return PTR_ERR(prog_buf); + } + + path_buf = __getname(); + if (IS_ERR(path_buf)) { + putname(prog_buf); + return PTR_ERR(path_buf); + } + + read_lock(¤t->fs->lock); + rootmnt = mntget(current->fs->rootmnt); + root = dget(current->fs->root); + read_unlock(¤t->fs->lock); + + prog = get_program_name(current, prog_buf, PATH_MAX); + + retval = -EACCES; + + spin_lock(&dcache_lock); + list_for_each_entry(dentry, &inode->i_dentry, d_alias) { + + if (retval == 0) + break; + + list_for_each_entry(mnt, ¤t->namespace->list, mnt_list) { + + if (dentry->d_sb != mnt->mnt_sb) + continue; + + path = __d_path(dentry, mnt, root, rootmnt, path_buf, + PATH_MAX); + + read_lock(&tree_lock); + has = get_permissions(path, prog); + read_unlock(&tree_lock); + + if ((has & wants) == wants) { + log_debug("allowing %s %s access to %s\n", + prog, perm_to_string(wants), path); + + retval = 0; + break; + } + + log_debug("denying %s %s access to %s\n", + prog, perm_to_string(wants), path); + } + } + spin_unlock(&dcache_lock); + + dput(root); + mntput(rootmnt); + putname(path_buf); + putname(prog_buf); + return retval; +} + + +/* + * /proc interface + */ + +/* + * seq_file interface (for reading the /proc entry) + */ + +static void *gran_suid_seq_start(struct seq_file *s, loff_t *pos) +{ + loff_t i; + struct stack *st; + + read_lock(&tree_lock); + + st = permission_iter_start(permission_root); + + for (i = 0; i < *pos; i++) + st = permission_iter_next(st); + + return st; +} + +static void *gran_suid_seq_next(struct seq_file *s, void *v, loff_t *pos) +{ + (*pos)++; + return permission_iter_next((struct stack *)v); +} + +static void gran_suid_seq_stop(struct seq_file *s, void *v) +{ + permission_iter_stop((struct stack *)v); + + read_unlock(&tree_lock); +} + +static int gran_suid_seq_show(struct seq_file *s, void *v) +{ + struct permission_node *node; + + node = permission_iter_get((struct stack *)v); + + seq_printf(s, "%s:%s:allow:%s\n", node->file, node->prog, + perm_to_string(node->perm)); + + return 0; +} + +static struct seq_operations gran_suid_seq_ops = { + .start = gran_suid_seq_start, + .next = gran_suid_seq_next, + .stop = gran_suid_seq_stop, + .show = gran_suid_seq_show +}; + +static int gran_suid_proc_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &gran_suid_seq_ops); +} + +/* + * /proc write interface + */ + +/* + * Reads one permission line from a null-terminated buffer and adds it. + * + * returns: The number of bytes consumed, or a negative error code. + */ +static int permission_parse(char *buffer) +{ + char *start, *file, *prog, *command, *perm_str; + int perm; + int ret; + + start = buffer; + + if ((file = strsep(&start, ":")) == NULL) { + log_info("parse error\n"); + return -EPERM_PARSE; + } + if ((prog = strsep(&start, ":")) == NULL) { + log_info("parse error\n"); + return -EPERM_PARSE; + } + if ((command = strsep(&start, ":")) == NULL) { + log_info("parse error\n"); + return -EPERM_PARSE; + } + if ((perm_str = strsep(&start, "\n")) == NULL) { + log_info("parse error\n"); + return -EPERM_PARSE; + } + + /* make sure start points to after the last real character */ + if (start == NULL) + start = perm_str + strlen(perm_str); + + /* file path must be absolute */ + if (file[0] != '/') { + log_info("file path is not absolute: \"%s\"\n", file); + return -EPERM_PARSE; + } + + /* program path must be absolute */ + if (prog[0] != '/') { + log_info("program path is not absolute: \"%s\"\n", prog); + return -EPERM_PARSE; + } + + /* check that command is "allow" */ + if (strcmp(command, ALLOW_COMMAND) != 0) { + log_info("invalid command: \"%s\"\n", command); + return -EPERM_PARSE; + } + + /* parse permissions */ + perm = 0; + + if (perm_str[0] == 'r') { + perm |= MAY_READ; + perm_str++; + } + + if (perm_str[0] == 'w') { + perm |= MAY_WRITE; + perm_str++; + } + + if (perm_str[0] == 'x') { + perm |= MAY_EXEC; + perm_str++; + } + + if (perm_str[0] != '\0') { + log_info("bad permission: \"%s\"\n", perm_str); + return -EPERM_PARSE; + } + + log_info("Set %s:%s:allow:%s\n", file, prog, perm_to_string(perm)); + + write_lock(&tree_lock); + ret = permission_set(&permission_root, file, prog, perm); + write_unlock(&tree_lock); + + if (ret != 0) + return ret; + + return start - buffer; +} + +/* + * Handles write() on the /proc entry. + */ +static int gran_suid_proc_write(struct file *file, const char *buffer, + size_t count, loff_t *data) +{ + char *start, *buf; + int ret; + + buf = kmalloc(count + 1, GFP_KERNEL); + if (buf == NULL) { + ret = -ENOMEM; + goto out; + } + + buf[count] = '\0'; + + if (copy_from_user(buf, buffer, count) != 0) { + ret = -EFAULT; + goto free_buf; + } + + start = buf; + + while (start < buf + count) { + ret = permission_parse(start); + if (ret < 0) + goto free_buf; + start += ret; + } + + ret = start - buf; + +free_buf: + kfree(buf); +out: + log_debug("proc_write(count = %d) = %d\n", count, ret); + + return ret; +} + +static struct file_operations gran_suid_proc_ops = { + .open = gran_suid_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, + .write = gran_suid_proc_write +}; + + +extern int gran_suid_proc_init(void) +{ + struct proc_dir_entry *res; + + res = create_proc_entry(GRAN_SUID_PROC_ENTRY, + S_IFREG|S_IRUGO|S_IWUSR, NULL); + if (res == NULL) { + log_err("could not create /proc/%s\n", GRAN_SUID_PROC_ENTRY); + return -EBUSY; + } + + res->proc_fops = &gran_suid_proc_ops; + + return 0; +} diff -Naur linux-2.4.20/fs/Makefile linux-2.4.20-gran_suid/fs/Makefile --- linux-2.4.20/fs/Makefile 2002-11-28 15:53:15.000000000 -0800 +++ linux-2.4.20-gran_suid/fs/Makefile 2003-05-28 19:16:53.000000000 -0700 @@ -22,6 +22,10 @@ obj-y += noquot.o endif +ifeq ($(CONFIG_GRAN_SUID),y) +obj-y += gran_suid.o +endif + subdir-$(CONFIG_PROC_FS) += proc subdir-y += partitions diff -Naur linux-2.4.20/fs/namei.c linux-2.4.20-gran_suid/fs/namei.c --- linux-2.4.20/fs/namei.c 2002-11-28 15:53:15.000000000 -0800 +++ linux-2.4.20-gran_suid/fs/namei.c 2003-05-29 15:13:08.000000000 -0700 @@ -196,7 +196,7 @@ return -EACCES; } -int permission(struct inode * inode,int mask) +static int normal_permission(struct inode * inode,int mask) { if (inode->i_op && inode->i_op->permission) { int retval; @@ -208,6 +208,18 @@ return vfs_permission(inode, mask); } +int permission(struct inode *inode, int mask) +{ + int retval = normal_permission(inode, mask); + +#ifdef CONFIG_GRAN_SUID + if (retval == -EACCES) + retval = gran_suid_permission(inode, mask); +#endif + + return retval; +} + /* * get_write_access() gets write permission for a file. * put_write_access() releases this write permission. diff -Naur linux-2.4.20/fs/proc/root.c linux-2.4.20-gran_suid/fs/proc/root.c --- linux-2.4.20/fs/proc/root.c 2002-08-02 17:39:45.000000000 -0700 +++ linux-2.4.20-gran_suid/fs/proc/root.c 2003-05-28 11:58:38.000000000 -0700 @@ -67,6 +67,9 @@ #ifdef CONFIG_PPC_RTAS proc_rtas_init(); #endif +#ifdef CONFIG_GRAN_SUID + gran_suid_proc_init(); +#endif proc_bus = proc_mkdir("bus", 0); } diff -Naur linux-2.4.20/include/linux/fs.h linux-2.4.20-gran_suid/include/linux/fs.h --- linux-2.4.20/include/linux/fs.h 2002-11-28 15:53:15.000000000 -0800 +++ linux-2.4.20-gran_suid/include/linux/fs.h 2003-05-29 14:21:35.000000000 -0700 @@ -1243,6 +1243,7 @@ extern void sync_supers(kdev_t); extern int bmap(struct inode *, int); extern int notify_change(struct dentry *, struct iattr *); +extern int gran_suid_permission(struct inode *, int); extern int permission(struct inode *, int); extern int vfs_permission(struct inode *, int); extern int get_write_access(struct inode *); diff -Naur linux-2.4.20/include/linux/proc_fs.h linux-2.4.20-gran_suid/include/linux/proc_fs.h --- linux-2.4.20/include/linux/proc_fs.h 2002-08-02 17:39:45.000000000 -0700 +++ linux-2.4.20-gran_suid/include/linux/proc_fs.h 2003-05-28 12:25:38.000000000 -0700 @@ -138,6 +138,12 @@ extern void proc_ppc64_init(void); extern void iSeries_proc_create(void); +/* + * gran_suid.c + */ +extern int gran_suid_proc_init(void); + + extern struct proc_dir_entry *proc_symlink(const char *, struct proc_dir_entry *, const char *); extern struct proc_dir_entry *proc_mknod(const char *,mode_t,