]> err.no Git - linux-2.6/blobdiff - fs/proc/base.c
[PATCH] proc: Fix the link count for /proc/<pid>/task
[linux-2.6] / fs / proc / base.c
index 8f1f49ceebec531f68395cb32bdf6c6a491c749c..29539c2268a3cec9c7622e5103a08157f0249cca 100644 (file)
 #include <linux/poll.h>
 #include "internal.h"
 
+/* NOTE:
+ *     Implementing inode permission operations in /proc is almost
+ *     certainly an error.  Permission checks need to happen during
+ *     each system call not at open time.  The reason is that most of
+ *     what we wish to check for permissions in /proc varies at runtime.
+ *
+ *     The classic example of a problem is opening file descriptors
+ *     in /proc for a task before it execs a suid executable.
+ */
+
 /*
  * For hysterical raisins we keep the same inumbers as in the old procfs.
  * Feel free to change the macro below - just keep the range distinct from
@@ -121,6 +131,7 @@ enum pid_directory_inos {
        PROC_TGID_ATTR_PREV,
        PROC_TGID_ATTR_EXEC,
        PROC_TGID_ATTR_FSCREATE,
+       PROC_TGID_ATTR_KEYCREATE,
 #endif
 #ifdef CONFIG_AUDITSYSCALL
        PROC_TGID_LOGINUID,
@@ -162,6 +173,7 @@ enum pid_directory_inos {
        PROC_TID_ATTR_PREV,
        PROC_TID_ATTR_EXEC,
        PROC_TID_ATTR_FSCREATE,
+       PROC_TID_ATTR_KEYCREATE,
 #endif
 #ifdef CONFIG_AUDITSYSCALL
        PROC_TID_LOGINUID,
@@ -275,6 +287,7 @@ static struct pid_entry tgid_attr_stuff[] = {
        E(PROC_TGID_ATTR_PREV,     "prev",     S_IFREG|S_IRUGO),
        E(PROC_TGID_ATTR_EXEC,     "exec",     S_IFREG|S_IRUGO|S_IWUGO),
        E(PROC_TGID_ATTR_FSCREATE, "fscreate", S_IFREG|S_IRUGO|S_IWUGO),
+       E(PROC_TGID_ATTR_KEYCREATE, "keycreate", S_IFREG|S_IRUGO|S_IWUGO),
        {0,0,NULL,0}
 };
 static struct pid_entry tid_attr_stuff[] = {
@@ -282,6 +295,7 @@ static struct pid_entry tid_attr_stuff[] = {
        E(PROC_TID_ATTR_PREV,      "prev",     S_IFREG|S_IRUGO),
        E(PROC_TID_ATTR_EXEC,      "exec",     S_IFREG|S_IRUGO|S_IWUGO),
        E(PROC_TID_ATTR_FSCREATE,  "fscreate", S_IFREG|S_IRUGO|S_IWUGO),
+       E(PROC_TID_ATTR_KEYCREATE, "keycreate", S_IFREG|S_IRUGO|S_IWUGO),
        {0,0,NULL,0}
 };
 #endif
@@ -293,20 +307,24 @@ static int proc_fd_link(struct inode *inode, struct dentry **dentry, struct vfsm
        struct task_struct *task = proc_task(inode);
        struct files_struct *files;
        struct file *file;
-       int fd = proc_type(inode) - PROC_TID_FD_DIR;
+       int fd = proc_fd(inode);
 
        files = get_files_struct(task);
        if (files) {
-               rcu_read_lock();
+               /*
+                * We are not taking a ref to the file structure, so we must
+                * hold ->file_lock.
+                */
+               spin_lock(&files->file_lock);
                file = fcheck_files(files, fd);
                if (file) {
                        *mnt = mntget(file->f_vfsmnt);
                        *dentry = dget(file->f_dentry);
-                       rcu_read_unlock();
+                       spin_unlock(&files->file_lock);
                        put_files_struct(files);
                        return 0;
                }
-               rcu_read_unlock();
+               spin_unlock(&files->file_lock);
                put_files_struct(files);
        }
        return -ENOENT;
@@ -353,54 +371,6 @@ static int proc_root_link(struct inode *inode, struct dentry **dentry, struct vf
        return result;
 }
 
-
-/* Same as proc_root_link, but this addionally tries to get fs from other
- * threads in the group */
-static int proc_task_root_link(struct inode *inode, struct dentry **dentry,
-                               struct vfsmount **mnt)
-{
-       struct fs_struct *fs;
-       int result = -ENOENT;
-       struct task_struct *leader = proc_task(inode);
-
-       task_lock(leader);
-       fs = leader->fs;
-       if (fs) {
-               atomic_inc(&fs->count);
-               task_unlock(leader);
-       } else {
-               /* Try to get fs from other threads */
-               task_unlock(leader);
-               read_lock(&tasklist_lock);
-               if (pid_alive(leader)) {
-                       struct task_struct *task = leader;
-
-                       while ((task = next_thread(task)) != leader) {
-                               task_lock(task);
-                               fs = task->fs;
-                               if (fs) {
-                                       atomic_inc(&fs->count);
-                                       task_unlock(task);
-                                       break;
-                               }
-                               task_unlock(task);
-                       }
-               }
-               read_unlock(&tasklist_lock);
-       }
-
-       if (fs) {
-               read_lock(&fs->lock);
-               *mnt = mntget(fs->rootmnt);
-               *dentry = dget(fs->root);
-               read_unlock(&fs->lock);
-               result = 0;
-               put_fs_struct(fs);
-       }
-       return result;
-}
-
-
 #define MAY_PTRACE(task) \
        (task == current || \
        (task->parent == current && \
@@ -535,25 +505,24 @@ static int proc_oom_score(struct task_struct *task, char *buffer)
 /* 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 *root, struct vfsmount *vfsmnt)
+static int proc_check_chroot(struct dentry *de, struct vfsmount *mnt)
 {
-       struct dentry *de, *base;
-       struct vfsmount *our_vfsmnt, *mnt;
+       struct dentry *base;
+       struct vfsmount *our_vfsmnt;
        int res = 0;
+
        read_lock(&current->fs->lock);
        our_vfsmnt = mntget(current->fs->rootmnt);
        base = dget(current->fs->root);
        read_unlock(&current->fs->lock);
 
        spin_lock(&vfsmount_lock);
-       de = root;
-       mnt = vfsmnt;
 
-       while (vfsmnt != our_vfsmnt) {
-               if (vfsmnt == vfsmnt->mnt_parent)
+       while (mnt != our_vfsmnt) {
+               if (mnt == mnt->mnt_parent)
                        goto out;
-               de = vfsmnt->mnt_mountpoint;
-               vfsmnt = vfsmnt->mnt_parent;
+               de = mnt->mnt_mountpoint;
+               mnt = mnt->mnt_parent;
        }
 
        if (!is_subdir(de, base))
@@ -563,8 +532,6 @@ static int proc_check_chroot(struct dentry *root, struct vfsmount *vfsmnt)
 exit:
        dput(base);
        mntput(our_vfsmnt);
-       dput(root);
-       mntput(mnt);
        return res;
 out:
        spin_unlock(&vfsmount_lock);
@@ -572,37 +539,6 @@ out:
        goto exit;
 }
 
-static int proc_check_root(struct inode *inode)
-{
-       struct dentry *root;
-       struct vfsmount *vfsmnt;
-
-       if (proc_root_link(inode, &root, &vfsmnt)) /* Ewww... */
-               return -ENOENT;
-       return proc_check_chroot(root, vfsmnt);
-}
-
-static int proc_permission(struct inode *inode, int mask, struct nameidata *nd)
-{
-       if (generic_permission(inode, mask, NULL) != 0)
-               return -EACCES;
-       return proc_check_root(inode);
-}
-
-static int proc_task_permission(struct inode *inode, int mask, struct nameidata *nd)
-{
-       struct dentry *root;
-       struct vfsmount *vfsmnt;
-
-       if (generic_permission(inode, mask, NULL) != 0)
-               return -EACCES;
-
-       if (proc_task_root_link(inode, &root, &vfsmnt))
-               return -ENOENT;
-
-       return proc_check_chroot(root, vfsmnt);
-}
-
 extern struct seq_operations proc_pid_maps_op;
 static int maps_open(struct inode *inode, struct file *file)
 {
@@ -978,10 +914,6 @@ static struct file_operations proc_oom_adjust_operations = {
        .write          = oom_adjust_write,
 };
 
-static struct inode_operations proc_mem_inode_operations = {
-       .permission     = proc_permission,
-};
-
 #ifdef CONFIG_AUDITSYSCALL
 #define TMPBUFLEN 21
 static ssize_t proc_loginuid_read(struct file * file, char __user * buf,
@@ -1012,8 +944,8 @@ static ssize_t proc_loginuid_write(struct file * file, const char __user * buf,
        if (current != task)
                return -EPERM;
 
-       if (count > PAGE_SIZE)
-               count = PAGE_SIZE;
+       if (count >= PAGE_SIZE)
+               count = PAGE_SIZE - 1;
 
        if (*ppos != 0) {
                /* No partial writes. */
@@ -1026,6 +958,7 @@ static ssize_t proc_loginuid_write(struct file * file, const char __user * buf,
        if (copy_from_user(page, buf, count))
                goto out_free_page;
 
+       page[count] = '\0';
        loginuid = simple_strtoul(page, &tmp, 10);
        if (tmp == page) {
                length = -EINVAL;
@@ -1102,6 +1035,48 @@ static struct file_operations proc_seccomp_operations = {
 };
 #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;
+       struct files_struct *task_files, *files;
+       int error = -EACCES;
+
+       /* See if the the two tasks share a commone set of
+        * file descriptors.  If so everything is visible.
+        */
+       task = proc_task(inode);
+       if (!task)
+               goto out;
+       files = get_files_struct(current);
+       task_files = get_files_struct(task);
+       if (files && task_files && (files == task_files))
+               error = 0;
+       if (task_files)
+               put_files_struct(task_files);
+       if (files)
+               put_files_struct(files);
+       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)
 {
        struct inode *inode = dentry->d_inode;
@@ -1112,12 +1087,16 @@ static void *proc_pid_follow_link(struct dentry *dentry, struct nameidata *nd)
 
        if (current->fsuid != inode->i_uid && !capable(CAP_DAC_OVERRIDE))
                goto out;
-       error = proc_check_root(inode);
-       if (error)
-               goto out;
 
        error = PROC_I(inode)->op.proc_get_link(inode, &nd->dentry, &nd->mnt);
        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:
        return ERR_PTR(error);
 }
@@ -1155,23 +1134,24 @@ static int proc_pid_readlink(struct dentry * dentry, char __user * buffer, int b
        struct dentry *de;
        struct vfsmount *mnt = NULL;
 
-       lock_kernel();
 
        if (current->fsuid != inode->i_uid && !capable(CAP_DAC_OVERRIDE))
                goto out;
-       error = proc_check_root(inode);
-       if (error)
-               goto out;
 
        error = PROC_I(inode)->op.proc_get_link(inode, &de, &mnt);
        if (error)
                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);
+out_put:
        dput(de);
        mntput(mnt);
 out:
-       unlock_kernel();
        return error;
 }
 
@@ -1184,7 +1164,8 @@ static struct inode_operations proc_pid_link_inode_operations = {
 
 static int proc_readfd(struct file * filp, void * dirent, filldir_t filldir)
 {
-       struct inode *inode = filp->f_dentry->d_inode;
+       struct dentry *dentry = filp->f_dentry;
+       struct inode *inode = dentry->d_inode;
        struct task_struct *p = proc_task(inode);
        unsigned int fd, tid, ino;
        int retval;
@@ -1205,7 +1186,7 @@ static int proc_readfd(struct file * filp, void * dirent, filldir_t filldir)
                                goto out;
                        filp->f_pos++;
                case 1:
-                       ino = fake_ino(tid, PROC_TID_INO);
+                       ino = parent_ino(dentry);
                        if (filldir(dirent, "..", 2, 1, ino, DT_DIR) < 0)
                                goto out;
                        filp->f_pos++;
@@ -1346,7 +1327,6 @@ static struct inode *proc_pid_make_inode(struct super_block * sb, struct task_st
 
        /* Common stuff */
        ei = PROC_I(inode);
-       ei->task = NULL;
        inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
        inode->i_ino = fake_ino(task->pid, ino);
 
@@ -1358,10 +1338,9 @@ static struct inode *proc_pid_make_inode(struct super_block * sb, struct task_st
         */
        get_task_struct(task);
        ei->task = task;
-       ei->type = ino;
        inode->i_uid = 0;
        inode->i_gid = 0;
-       if (ino == PROC_TGID_INO || ino == PROC_TID_INO || task_dumpable(task)) {
+       if (task_dumpable(task)) {
                inode->i_uid = task->euid;
                inode->i_gid = task->egid;
        }
@@ -1371,7 +1350,6 @@ out:
        return inode;
 
 out_unlock:
-       ei->pde = NULL;
        iput(inode);
        return NULL;
 }
@@ -1391,7 +1369,7 @@ static int pid_revalidate(struct dentry *dentry, struct nameidata *nd)
        struct inode *inode = dentry->d_inode;
        struct task_struct *task = proc_task(inode);
        if (pid_alive(task)) {
-               if (proc_type(inode) == PROC_TGID_INO || proc_type(inode) == PROC_TID_INO || task_dumpable(task)) {
+               if (task_dumpable(task)) {
                        inode->i_uid = task->euid;
                        inode->i_gid = task->egid;
                } else {
@@ -1409,7 +1387,7 @@ static int tid_fd_revalidate(struct dentry *dentry, struct nameidata *nd)
 {
        struct inode *inode = dentry->d_inode;
        struct task_struct *task = proc_task(inode);
-       int fd = proc_type(inode) - PROC_TID_FD_DIR;
+       int fd = proc_fd(inode);
        struct files_struct *files;
 
        files = get_files_struct(task);
@@ -1516,11 +1494,17 @@ static struct dentry *proc_lookupfd(struct inode * dir, struct dentry * dentry,
        if (!inode)
                goto out;
        ei = PROC_I(inode);
+       ei->fd = fd;
        files = get_files_struct(task);
        if (!files)
                goto out_unlock;
        inode->i_mode = S_IFLNK;
-       rcu_read_lock();
+
+       /*
+        * We are not taking a ref to the file structure, so we must
+        * hold ->file_lock.
+        */
+       spin_lock(&files->file_lock);
        file = fcheck_files(files, fd);
        if (!file)
                goto out_unlock2;
@@ -1528,7 +1512,7 @@ static struct dentry *proc_lookupfd(struct inode * dir, struct dentry * dentry,
                inode->i_mode |= S_IRUSR | S_IXUSR;
        if (file->f_mode & 2)
                inode->i_mode |= S_IWUSR | S_IXUSR;
-       rcu_read_unlock();
+       spin_unlock(&files->file_lock);
        put_files_struct(files);
        inode->i_op = &proc_pid_link_inode_operations;
        inode->i_size = 64;
@@ -1538,7 +1522,7 @@ static struct dentry *proc_lookupfd(struct inode * dir, struct dentry * dentry,
        return NULL;
 
 out_unlock2:
-       rcu_read_unlock();
+       spin_unlock(&files->file_lock);
        put_files_struct(files);
 out_unlock:
        iput(inode);
@@ -1548,6 +1532,7 @@ out:
 
 static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldir);
 static struct dentry *proc_task_lookup(struct inode *dir, struct dentry * dentry, struct nameidata *nd);
+static int proc_task_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat);
 
 static struct file_operations proc_fd_operations = {
        .read           = generic_read_dir,
@@ -1564,12 +1549,11 @@ static struct file_operations proc_task_operations = {
  */
 static struct inode_operations proc_fd_inode_operations = {
        .lookup         = proc_lookupfd,
-       .permission     = proc_permission,
 };
 
 static struct inode_operations proc_task_inode_operations = {
        .lookup         = proc_task_lookup,
-       .permission     = proc_task_permission,
+       .getattr        = proc_task_getattr,
 };
 
 #ifdef CONFIG_SECURITY
@@ -1676,7 +1660,7 @@ static struct dentry *proc_pident_lookup(struct inode *dir,
         */
        switch(p->type) {
                case PROC_TGID_TASK:
-                       inode->i_nlink = 2 + get_tid_list(2, NULL, dir);
+                       inode->i_nlink = 2;
                        inode->i_op = &proc_task_inode_operations;
                        inode->i_fop = &proc_task_operations;
                        break;
@@ -1746,7 +1730,6 @@ static struct dentry *proc_pident_lookup(struct inode *dir,
 #endif
                case PROC_TID_MEM:
                case PROC_TGID_MEM:
-                       inode->i_op = &proc_mem_inode_operations;
                        inode->i_fop = &proc_mem_operations;
                        break;
 #ifdef CONFIG_SECCOMP
@@ -1788,6 +1771,8 @@ static struct dentry *proc_pident_lookup(struct inode *dir,
                case PROC_TGID_ATTR_EXEC:
                case PROC_TID_ATTR_FSCREATE:
                case PROC_TGID_ATTR_FSCREATE:
+               case PROC_TID_ATTR_KEYCREATE:
+               case PROC_TGID_ATTR_KEYCREATE:
                        inode->i_fop = &proc_pid_attr_operations;
                        break;
 #endif
@@ -2278,7 +2263,6 @@ static int proc_task_readdir(struct file * filp, void * dirent, filldir_t filldi
        }
 
        nr_tids = get_tid_list(pos, tid_array, inode);
-       inode->i_nlink = pos + nr_tids;
 
        for (i = 0; i < nr_tids; i++) {
                unsigned long j = PROC_NUMBUF;
@@ -2298,3 +2282,19 @@ out:
        filp->f_pos = pos;
        return retval;
 }
+
+static int proc_task_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
+{
+       struct inode *inode = dentry->d_inode;
+       struct task_struct *p = proc_task(inode);
+       generic_fillattr(inode, stat);
+
+       if (pid_alive(p)) {
+               task_lock(p);
+               if (p->signal)
+                       stat->nlink += atomic_read(&p->signal->count);
+               task_unlock(p);
+       }
+
+       return 0;
+}