/proc paranoia patch (1.2.13)

Ian Jackson (iwj10@cus.cam.ac.uk)
Thu, 28 Sep 95 02:19 BST


I've been worried about security holes in the procfs - both real and
potential - for some time. It seems, for example, sometimes to forget
to change the ownerships of files like /proc/<pid>/fd when processes
become undumpable (eg, by exec'ing setuid programs), and there have
been reports of a security hole involving /proc/<pid>/mem and mmap.

I therefore wrote the patch below. It is enabled by the use of the
`paranoid' mount option. Its effect is to set the permissions and
functionality of the files in /proc in such a way that the owner of a
process has no special access to it. This will, I think, protect
against most of the attacks that seem plausible.

You MUST also mount the procfs read-only, as otherwise the user can
just chmod the files in /proc to have the appropriate modes. (This is
required at the moment even if you do not apply my patch.)

The motivation for using this patch, rather than just making the whole
of procfs accessible only to priveliged programs, is that procps will
continue to work without needing to be setuid/setgid. If one were to
have to make procps and similar programs setuid/setgid then (a) users
can't write and install their own proc-a-likes and (b) the whole of
procps is dragged inside the security perimeter.

I'd very much appreciate it if my patch were considered for inclusion
in the standard kernel. It is so simple that it is unlikely to have
any bugs that make matters worse than they are already.

(I have had to do one rather nasty hack: the procfs doesn't have a
member of the superblock per-filesystem info union, so I have encoded
the paranoid option into the root directory's sticky bit.)

Whether or not this patch does make it into the standard kernel I'd
advise lots of caution in this area.

Ian.

I have indented the patch to stop the digestifier from mangling it. I
wish everyone else would do the same ! `patch' is quite happy to
apply indented diffs.

diff -ru --exclude=*.o --exclude=.depend linux-1.2.13/fs/proc/base.c linux-1.2.13-procpar/fs/proc/base.c
--- linux-1.2.13/fs/proc/base.c Sat Oct 22 15:41:18 1994
+++ linux-1.2.13-procpar/fs/proc/base.c Wed Sep 27 21:35:29 1995
@@ -67,6 +67,13 @@

#define NR_BASE_DIRENTRY ((sizeof (base_dir))/(sizeof (base_dir[0])))

+int proc_paranoid(struct inode *i) {
+ if (i->i_sb->s_mounted->i_mode & S_ISVTX)
+ return 1;
+ else
+ return 0;
+}
+
int proc_match(int len,const char * name,struct proc_dir_entry * de)
{
if (!de || !de->low_ino)
diff -ru --exclude=*.o --exclude=.depend linux-1.2.13/fs/proc/inode.c linux-1.2.13-procpar/fs/proc/inode.c
--- linux-1.2.13/fs/proc/inode.c Tue Aug 1 11:06:57 1995
+++ linux-1.2.13-procpar/fs/proc/inode.c Thu Sep 28 01:52:26 1995
@@ -45,7 +45,7 @@
};

-static int parse_options(char *options,uid_t *uid,gid_t *gid)
+static int parse_options(char *options,uid_t *uid,gid_t *gid,mode_t *mode)
{
char *this_char,*value;

@@ -69,6 +69,11 @@
if (*value)
return 0;
}
+ else if (!strcmp(this_char,"paranoid")) {
+ if (value)
+ return 0;
+ *mode |= S_ISVTX; /* sticky bit represents paranoia */
+ }
else return 0;
}
return 1;
@@ -89,7 +94,8 @@
printk("get root inode failed\n");
return NULL;
}
- parse_options(data, &s->s_mounted->i_uid, &s->s_mounted->i_gid);
+ parse_options(data, &s->s_mounted->i_uid, &s->s_mounted->i_gid,
+ &s->s_mounted->i_mode);
return s;
}

@@ -206,22 +212,34 @@
return;
case PROC_PID_MEM:
inode->i_op = &proc_mem_inode_operations;
- inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR;
+ if (proc_paranoid(inode))
+ inode->i_mode = S_IFREG;
+ else
+ inode->i_mode = S_IFREG | S_IRUSR | S_IWUSR;
return;
case PROC_PID_CWD:
case PROC_PID_ROOT:
case PROC_PID_EXE:
inode->i_op = &proc_link_inode_operations;
inode->i_size = 64;
- inode->i_mode = S_IFLNK | S_IRWXU;
+ if (proc_paranoid(inode))
+ inode->i_mode = S_IFLNK;
+ else
+ inode->i_mode = S_IFLNK | S_IRWXU;
return;
case PROC_PID_FD:
- inode->i_mode = S_IFDIR | S_IRUSR | S_IXUSR;
+ if (proc_paranoid(inode))
+ inode->i_mode = S_IFDIR;
+ else
+ inode->i_mode = S_IFDIR | S_IRUSR | S_IXUSR;
inode->i_op = &proc_fd_inode_operations;
inode->i_nlink = 2;
return;
case PROC_PID_ENVIRON:
- inode->i_mode = S_IFREG | S_IRUSR;
+ if (proc_paranoid(inode))
+ inode->i_mode = S_IFREG;
+ else
+ inode->i_mode = S_IFREG | S_IRUSR;
inode->i_op = &proc_array_inode_operations;
return;
case PROC_PID_CMDLINE:
@@ -243,10 +261,12 @@
inode->i_op = &proc_link_inode_operations;
inode->i_size = 64;
inode->i_mode = S_IFLNK;
- if (p->files->fd[ino]->f_mode & 1)
- inode->i_mode |= S_IRUSR | S_IXUSR;
- if (p->files->fd[ino]->f_mode & 2)
- inode->i_mode |= S_IWUSR | S_IXUSR;
+ if (!proc_paranoid(inode)) {
+ if (p->files->fd[ino]->f_mode & 1)
+ inode->i_mode |= S_IRUSR | S_IXUSR;
+ if (p->files->fd[ino]->f_mode & 2)
+ inode->i_mode |= S_IWUSR | S_IXUSR;
+ }
return;
}
return;
diff -ru --exclude=*.o --exclude=.depend linux-1.2.13/fs/proc/link.c linux-1.2.13-procpar/fs/proc/link.c
--- linux-1.2.13/fs/proc/link.c Mon Jan 23 21:04:10 1995
+++ linux-1.2.13-procpar/fs/proc/link.c Wed Sep 27 20:29:52 1995
@@ -76,6 +76,10 @@
if (fd>=NR_OPEN)
return -ENOENT; /* should never happen */

+ if (proc_paranoid(inode) && !suser()) {
+ return -EPERM;
+ }
+
ino = inode->i_ino;
pid = ino >> 16;
ino &= 0x0000ffff;
diff -ru --exclude=*.o --exclude=.depend linux-1.2.13/include/linux/proc_fs.h linux-1.2.13-procpar/include/linux/proc_fs.h
--- linux-1.2.13/include/linux/proc_fs.h Sun Feb 12 19:11:02 1995
+++ linux-1.2.13-procpar/include/linux/proc_fs.h Wed Sep 27 21:35:29 1995
@@ -129,4 +129,6 @@
extern struct inode_operations proc_link_inode_operations;
extern struct inode_operations proc_fd_inode_operations;

+extern int proc_paranoid(struct inode *i);
+
#endif