/proc/self/exe unreadable by suid programs

Jamie Lokier (jamie@rebellion.co.uk)
Sat, 1 Jun 96 03:50 BST


The particular example I have in mind, is a setuid root program that
drops its root euid as soon as it can, and then later tries to read its
own executable (which happens to have data stuck on the end). The
dropping of the root euid is done by a library, prior to calling `main',
so it's not so easy to open /proc/self/exe first.

The same problem prevents the program from opening /proc/self/mem.

It is unusual for a setuid root program to have less permission than the
same program without setuid!

The problem is due to the following test in fs/proc/inode.c:

if (ino == PROC_PID_INO || p->dumpable) {
inode->i_uid = p->euid;
inode->i_gid = p->egid;
}

When this condition is not satisfied, inode->i_uid and inode->i_gid are
both set to zero.

I understand the need for this test even in the "exe" case: otherwise, a
program that's switching its euid all the time (like a file server that
doesn't know about fsuid) could inadvertantly give access to a file (the
executable) which may not be reachable to ordinary users by any other
means.

It seems to me that everything referenced through /proc/self should be
accessible to the process, though the same information, fetched through
/proc/<pid>, may be denied to other processes.

The following patch implements this:

--- linux/fs/proc/base.c.orig Sat Jun 1 04:15:22 1996
+++ linux/fs/proc/base.c Sat Jun 1 04:13:46 1996
@@ -57,7 +57,7 @@

for_each_task(p) {
if (p->pid == pid) {
- if (p->dumpable || ino == PROC_PID_INO) {
+ if (p->dumpable || p == current || ino == PROC_PID_INO) {
inode->i_uid = p->euid;
inode->i_gid = p->gid;
}
--- linux/fs/proc/inode.c.orig Sat Jun 1 01:52:25 1996
+++ linux/fs/proc/inode.c Sat Jun 1 04:14:16 1996
@@ -193,7 +193,7 @@
return;
}
ino &= 0x0000ffff;
- if (ino == PROC_PID_INO || p->dumpable) {
+ if (ino == PROC_PID_INO || p->dumpable || p == current) {
inode->i_uid = p->euid;
inode->i_gid = p->egid;
}

BTW, there is some duplication between proc_read_inode,
proc_pid_fill_inode and proc_get_inode for /proc/<pid>/*. The
permissions, reference counts and operations are set by proc_read_inode,
and then overwritten by proc_get_inode (which uses the proc_dir_entry
structure). proc_pid_fill_inode then changes the user and group ids,
ignoring the values chosen by proc_read_inode. (That had me confused
for a while).

I would be inclined to delete parts of proc_read_inode, leaving the
settings in the proc_dir_entry structures.

There is some conflict: proc_read_inode sets the reference count for
/proc/<pid> to 4, but then (somehow) the directory's reference count
comes out as 3. The directory link counts are generally wrong, due to
conflicting calculations. For example, /proc has a reference count of
5 (it should be much larger). /proc/net has a reference count of 2,
despite proc_register and proc_register_dynamic going to all the effort
of counting the links. There are others, too.

Enjoy,
-- Jamie