Security patch for /proc

Jeremy Fitzhardinge (jeremy@zip.com.au)
Tue, 31 Mar 1998 12:31:00 +1000


This is a multi-part message in MIME format.
--------------28E3D810F42C3343567429A8
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

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
--------------28E3D810F42C3343567429A8
Content-Type: text/plain; charset=us-ascii; name="proc.diff"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline; filename="proc.diff"

==== //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 *);

--------------28E3D810F42C3343567429A8--

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.rutgers.edu