Messages in this thread Patch in this message | | | Date | Tue, 31 Mar 1998 13:17:36 +1000 | From | Jeremy Fitzhardinge <> | Subject | Re: Security patch for /proc |
| |
[ Following up to my own posting - sigh. This is basically a resend with a slightly more liberal access policy and more patch-friendly diff. ]
Hi all,
Here's a patch which prevents chrooted processes from escaping from their chrooted area via /proc.
At present, if you set up a chroot domain, you can't mount /proc in it, because processes can easily escape by chdiring through another non-chrooted process's root or cwd. This patch disallows access to a process in /proc unless it has the same or more restrictive root than your own.
This still doesn't allow you to run root processes in a chrooted area with complete safety, but it does mean you can have processes with the same uid in different chrooted domains.
Missing features: - signal and ptrace should do similar checks, otherwise chrooted processes can still cause system-wide havoc - root processes should (optionally) lose priviledge which chrooted - a capability mask is probably the right way of doing this - chrooted processes which have been leaked file descriptors (particularly directory fds) from outside the domain are still an escape path
Jdiff -u ../2.1/fs/proc/fd.c fs/proc/fd.c --- ../2.1/fs/proc/fd.c Fri Mar 27 12:58:20 1998 +++ fs/proc/fd.c Tue Mar 31 12:58:13 1998 @@ -50,7 +50,7 @@ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ - NULL /* permission */ + proc_permission /* permission */ }; /* diff -u ../2.1/fs/proc/inode.c fs/proc/inode.c --- ../2.1/fs/proc/inode.c Fri Mar 27 12:58:20 1998 +++ fs/proc/inode.c Tue Mar 31 12:59:03 1998 @@ -129,6 +129,108 @@ return 1; } +/* + * The standard rules, copied from fs/namei.c:permission(). + */ +static int standard_permission(struct inode *inode, int mask) +{ + int mode = inode->i_mode; + + if ((mask & S_IWOTH) && IS_RDONLY(inode) && + (S_ISREG(mode) || S_ISDIR(mode) || S_ISLNK(mode))) + return -EROFS; /* Nobody gets write access to a read-only fs */ + else if ((mask & S_IWOTH) && IS_IMMUTABLE(inode)) + return -EACCES; /* Nobody gets write access to an immutable file */ + else if (current->fsuid == inode->i_uid) + mode >>= 6; + else if (in_group_p(inode->i_gid)) + mode >>= 3; + if (((mode & mask & 0007) == mask) || fsuser()) + return 0; + return -EACCES; +} + +/* + * Set up permission rules for processes looking at other processes. + * You're not allowed to see a process unless it has the same or more + * restricted root than your own. This prevents a chrooted processes + * from escaping through the /proc entries of less restricted + * processes, and thus allows /proc to be safely mounted in a chrooted + * area. + * + * Note that root (uid 0) doesn't get permission for this either, + * since chroot is stronger than root. + * + * XXX TODO: use the dentry mechanism to make off-limits procs simply + * invisible rather than denied? Does each namespace root get its own + * dentry tree? + * + * This also applies the default permissions checks, as it only adds + * restrictions. + * + * Jeremy Fitzhardinge <jeremy@zip.com.au> + */ +int proc_permission(struct inode *inode, int mask) +{ + struct task_struct *p; + unsigned long ino = inode->i_ino; + unsigned long pid; + struct dentry *de, *base; + + if (standard_permission(inode, mask) != 0) + return -EACCES; + + /* + * Find the root of the processes being examined (if any). + * XXX Surely there's a better way of doing this? + */ + if (ino >= PROC_OPENPROM_FIRST && + ino < PROC_OPENPROM_FIRST + PROC_NOPENPROM) + return 0; /* already allowed */ + + pid = ino >> 16; + if (pid == 0) + return 0; /* already allowed */ + + de = NULL; + base = current->fs->root; + + read_lock(&tasklist_lock); + p = find_task_by_pid(pid); + + if (p != NULL) + de = p->fs->root; + read_unlock(tasklist_lock); + + if (p == NULL) + return -EACCES; /* ENOENT? */ + + if (de == NULL) + { + /* kswapd and bdflush don't have proper root or cwd... */ + return -EACCES; + } + + /* XXX locking? */ + for(;;) + { + struct dentry *parent; + + if (de == base) + return 0; /* already allowed */ + + de = de->d_covers; + parent = de->d_parent; + + if (de == parent) + break; + + de = parent; + } + + return -EACCES; /* incompatible roots */ +} + struct inode * proc_get_inode(struct super_block * sb, int ino, struct proc_dir_entry * de) { diff -u ../2.1/fs/proc/link.c fs/proc/link.c --- ../2.1/fs/proc/link.c Fri Mar 27 12:58:20 1998 +++ fs/proc/link.c Mon Mar 30 15:28:49 1998 @@ -58,7 +58,7 @@ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ - NULL /* permission */ + proc_permission /* permission */ }; static struct dentry * proc_follow_link(struct dentry *dentry, diff -u ../2.1/fs/proc/mem.c fs/proc/mem.c --- ../2.1/fs/proc/mem.c Thu Mar 19 11:47:48 1998 +++ fs/proc/mem.c Tue Mar 31 13:14:59 1998 @@ -9,6 +9,7 @@ #include <linux/sched.h> #include <linux/kernel.h> #include <linux/mm.h> +#include <linux/proc_fs.h> #include <asm/page.h> #include <asm/uaccess.h> @@ -336,5 +337,5 @@ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ - NULL /* permission */ + proc_permission /* permission */ }; --- ../2.1/include/linux/proc_fs.h Thu Mar 19 11:46:04 1998 +++ include/linux/proc_fs.h Mon Mar 30 15:27:18 1998 @@ -327,6 +327,7 @@ extern int proc_statfs(struct super_block *, struct statfs *, int); extern void proc_read_inode(struct inode *); extern void proc_write_inode(struct inode *); +extern int proc_permission(struct inode *, int); extern int proc_match(int, const char *,struct proc_dir_entry *); | |