#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
PROC_TGID_ATTR_PREV,
PROC_TGID_ATTR_EXEC,
PROC_TGID_ATTR_FSCREATE,
+ PROC_TGID_ATTR_KEYCREATE,
#endif
#ifdef CONFIG_AUDITSYSCALL
PROC_TGID_LOGINUID,
PROC_TID_ATTR_PREV,
PROC_TID_ATTR_EXEC,
PROC_TID_ATTR_FSCREATE,
+ PROC_TID_ATTR_KEYCREATE,
#endif
#ifdef CONFIG_AUDITSYSCALL
PROC_TID_LOGINUID,
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[] = {
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
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) {
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 && \
/* If the process being read is separated by chroot from the reading process,
* don't let the reader access the threads.
- *
- * note: this does dput(root) and mntput(vfsmnt) on exit.
*/
-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(¤t->fs->lock);
read_unlock(¤t->fs->lock);
spin_lock(&vfsmount_lock);
- de = root;
- mnt = vfsmnt;
while (mnt != our_vfsmnt) {
if (mnt == mnt->mnt_parent)
exit:
dput(base);
mntput(our_vfsmnt);
- dput(root);
- mntput(vfsmnt);
return res;
out:
spin_unlock(&vfsmount_lock);
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)
{
.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,
};
#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;
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);
}
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;
}
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;
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++;
/* 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);
*/
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;
}
return inode;
out_unlock:
- ei->pde = NULL;
iput(inode);
return NULL;
}
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 {
{
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);
if (!inode)
goto out;
ei = PROC_I(inode);
+ ei->fd = fd;
files = get_files_struct(task);
if (!files)
goto out_unlock;
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,
*/
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
*/
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;
#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
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
}
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;
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;
+}