Commit 778c1144 authored by Eric W. Biederman's avatar Eric W. Biederman Committed by Linus Torvalds

[PATCH] proc: Use sane permission checks on the /proc/<pid>/fd/ symlinks

Since 2.2 we have been doing a chroot check to see if it is appropriate to
return a read or follow one of these magic symlinks.  The chroot check was
asking a question about the visibility of files to the calling process and
it was actually checking the destination process, and not the files
themselves.  That test was clearly bogus.

In my first pass through I simply fixed the test to check the visibility of
the files themselves.  That naive approach to fixing the permissions was
too strict and resulted in cases where a task could not even see all of
it's file descriptors.

What has disturbed me about relaxing this check is that file descriptors
are per-process private things, and they are occasionaly used a user space
capability tokens.  Looking a little farther into the symlink path on /proc
I did find userid checks and a check for capability (CAP_DAC_OVERRIDE) so
there were permissions checking this.

But I was still concerned about privacy.  Besides /proc there is only one
other way to find out this kind of information, and that is ptrace.  ptrace
has been around for a long time and it has a well established security
model.

So after thinking about it I finally realized that the permission checks
that make sense are the permission checks applied to ptrace_attach.  The
checks are simple per process, and won't cause nasty surprises for people
coming from less capable unices.

Unfortunately there is one case that the current ptrace_attach test does
not cover: Zombies and kernel threads.  Single stepping those kinds of
processes is impossible.  Being able to see which file descriptors are open
on these tasks is important to lsof, fuser and friends.  So for these
special processes I made the rule you can't find out unless you have
CAP_SYS_PTRACE.

These proc permission checks should now conform to the principle of least
surprise.  As well as using much less code to implement :)
Signed-off-by: default avatarEric W. Biederman <ebiederm@xmission.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 5b0c1dd3
...@@ -532,42 +532,34 @@ static int proc_oom_score(struct task_struct *task, char *buffer) ...@@ -532,42 +532,34 @@ static int proc_oom_score(struct task_struct *task, char *buffer)
/************************************************************************/ /************************************************************************/
/* permission checks */ /* permission checks */
static int proc_fd_access_allowed(struct inode *inode)
/* If the process being read is separated by chroot from the reading process,
* don't let the reader access the threads.
*/
static int proc_check_chroot(struct dentry *de, struct vfsmount *mnt)
{ {
struct dentry *base; struct task_struct *task;
struct vfsmount *our_vfsmnt; int allowed = 0;
int res = 0; /* Allow access to a task's file descriptors if either we may
* use ptrace attach to the process and find out that
read_lock(&current->fs->lock); * information, or if the task cannot possibly be ptraced
our_vfsmnt = mntget(current->fs->rootmnt); * allow access if we have the proper capability.
base = dget(current->fs->root); */
read_unlock(&current->fs->lock); task = get_proc_task(inode);
if (task == current)
spin_lock(&vfsmount_lock); allowed = 1;
if (task && !allowed) {
int alive;
while (mnt != our_vfsmnt) { task_lock(task);
if (mnt == mnt->mnt_parent) alive = !!task->mm;
goto out; task_unlock(task);
de = mnt->mnt_mountpoint; if (alive)
mnt = mnt->mnt_parent; /* For a living task obey ptrace_may_attach */
allowed = ptrace_may_attach(task);
else
/* For a special task simply check the capability */
allowed = capable(CAP_SYS_PTRACE);
} }
if (task)
if (!is_subdir(de, base)) put_task_struct(task);
goto out; return allowed;
spin_unlock(&vfsmount_lock);
exit:
dput(base);
mntput(our_vfsmnt);
return res;
out:
spin_unlock(&vfsmount_lock);
res = -EACCES;
goto exit;
} }
extern struct seq_operations mounts_op; extern struct seq_operations mounts_op;
...@@ -1062,52 +1054,6 @@ static struct file_operations proc_seccomp_operations = { ...@@ -1062,52 +1054,6 @@ static struct file_operations proc_seccomp_operations = {
}; };
#endif /* CONFIG_SECCOMP */ #endif /* CONFIG_SECCOMP */
static int proc_check_dentry_visible(struct inode *inode,
struct dentry *dentry, struct vfsmount *mnt)
{
/* Verify that the current process can already see the
* file pointed at by the file descriptor.
* This prevents /proc from being an accidental information leak.
*
* This prevents access to files that are not visible do to
* being on the otherside of a chroot, in a different
* namespace, or are simply process local (like pipes).
*/
struct task_struct *task;
int error = -EACCES;
/* See if the the two tasks share a commone set of
* file descriptors. If so everything is visible.
*/
rcu_read_lock();
task = tref_task(proc_tref(inode));
if (task) {
struct files_struct *task_files, *files;
/* This test answeres the question:
* Is there a point in time since we looked up the
* file descriptor where the two tasks share the
* same files struct?
*/
rmb();
files = current->files;
task_files = task->files;
if (files && (files == task_files))
error = 0;
}
rcu_read_unlock();
if (!error)
goto out;
/* If the two tasks don't share a common set of file
* descriptors see if the destination dentry is already
* visible in the current tasks filesystem namespace.
*/
error = proc_check_chroot(dentry, mnt);
out:
return error;
}
static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd) static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd)
{ {
struct inode *inode = dentry->d_inode; struct inode *inode = dentry->d_inode;
...@@ -1116,18 +1062,12 @@ static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd) ...@@ -1116,18 +1062,12 @@ static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd)
/* We don't need a base pointer in the /proc filesystem */ /* We don't need a base pointer in the /proc filesystem */
path_release(nd); path_release(nd);
if (current->fsuid != inode->i_uid && !capable(CAP_DAC_OVERRIDE)) /* Are we allowed to snoop on the tasks file descriptors? */
if (!proc_fd_access_allowed(inode))
goto out; goto out;
error = PROC_I(inode)->op.proc_get_link(inode, &nd->dentry, &nd->mnt); error = PROC_I(inode)->op.proc_get_link(inode, &nd->dentry, &nd->mnt);
nd->last_type = LAST_BIND; nd->last_type = LAST_BIND;
if (error)
goto out;
/* Only return files this task can already see */
error = proc_check_dentry_visible(inode, nd->dentry, nd->mnt);
if (error)
path_release(nd);
out: out:
return ERR_PTR(error); return ERR_PTR(error);
} }
...@@ -1165,21 +1105,15 @@ static int proc_pid_readlink(struct dentry * dentry, char __user * buffer, int b ...@@ -1165,21 +1105,15 @@ static int proc_pid_readlink(struct dentry * dentry, char __user * buffer, int b
struct dentry *de; struct dentry *de;
struct vfsmount *mnt = NULL; struct vfsmount *mnt = NULL;
/* Are we allowed to snoop on the tasks file descriptors? */
if (current->fsuid != inode->i_uid && !capable(CAP_DAC_OVERRIDE)) if (!proc_fd_access_allowed(inode))
goto out; goto out;
error = PROC_I(inode)->op.proc_get_link(inode, &de, &mnt); error = PROC_I(inode)->op.proc_get_link(inode, &de, &mnt);
if (error) if (error)
goto out; goto out;
/* Only return files this task can already see */
error = proc_check_dentry_visible(inode, de, mnt);
if (error)
goto out_put;
error = do_proc_readlink(de, mnt, buffer, buflen); error = do_proc_readlink(de, mnt, buffer, buflen);
out_put:
dput(de); dput(de);
mntput(mnt); mntput(mnt);
out: out:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment