Messages in this thread Patch in this message | | | Date | Tue, 31 Mar 1998 12:31:00 +1000 | From | Jeremy Fitzhardinge <> | Subject | Security patch for /proc |
| |
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.
There's still a couple of warts on this patch: - kswapd and bdflush don't have proper roots in their fs structures, so I always disallow access to them. I'm not sure of the implications of starting them after root is mounted. - I disallow access to all proc entries for a process, which is probably a little draconian. The rationale is to prevent a process from manipulating the memory or state of another process with the same UID, thereby acting beyond its chrooted domain.
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.
J==== //depot/linux/2.1/fs/proc/base.c#11 - //depot/linux/local/fs/proc/base.c#6 ==== @@ -47,7 +47,7 @@ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ - NULL /* permission */ + proc_permission /* permission */ }; /* ==== //depot/linux/2.1/fs/proc/inode.c#12 - //depot/linux/local/fs/proc/inode.c#6 ==== @@ -129,6 +129,109 @@ 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... */ + printk("pid %d has null root\n", pid); + 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) { ==== //depot/linux/2.1/fs/proc/link.c#13 - //depot/linux/local/fs/proc/link.c#9 ==== @@ -58,7 +58,7 @@ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ - NULL /* permission */ + proc_permission /* permission */ }; static struct dentry * proc_follow_link(struct dentry *dentry, ==== //depot/linux/2.1/fs/proc/root.c#30 - //depot/linux/local/fs/proc/root.c#15 ==== @@ -73,7 +73,7 @@ NULL, /* writepage */ NULL, /* bmap */ NULL, /* truncate */ - NULL /* permission */ + NULL, /* permission */ }; /* ==== //depot/linux/2.1/include/linux/proc_fs.h#33 - //depot/linux/local/include/linux/proc_fs.h#14 ==== @@ -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 *); | |