https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=206761

            Bug ID: 206761
           Summary: Kernel stack overflow in sysctl handler for
                    kern.binmisc.add
           Product: Base System
           Version: 11.0-CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Only Me
          Priority: ---
         Component: kern
          Assignee: freebsd-bugs@FreeBSD.org
          Reporter: ct...@hardenedbsd.org

There is a stack overflow in the `sysctl_kern_binmisc` code path from
`sys/kern/imgact_binmisc.c`:

        switch(arg2) {
        case IBC_ADD:
                /* Add an entry. Limited to IBE_MAX_ENTRIES. */
                error = SYSCTL_IN(req, &xbe, sizeof(xbe));
                if (error)
                        return (error);
                if (IBE_VERSION != xbe.xbe_version)
                        return (EINVAL);
                if (interp_list_entry_count == IBE_MAX_ENTRIES)
                        return (ENOSPC);
                error = imgact_binmisc_add_entry(&xbe);
                break;

Notice that contents of `xbe` are user controlled, with just a check on the
`xbe_version` member before calling `imgact_binmisc_add_entry`:

static int
imgact_binmisc_add_entry(ximgact_binmisc_entry_t *xbe)
{
        imgact_binmisc_entry_t *ibe;
        char *p;

        if (xbe->xbe_msize > IBE_MAGIC_MAX)
                return (EINVAL);

        for(p = xbe->xbe_name; *p != 0; p++)
                if (!isascii((int)*p))
                        return (EINVAL);

        for(p = xbe->xbe_interpreter; *p != 0; p++)
                if (!isascii((int)*p))
                        return (EINVAL);

Notice that already 2 out of bounds reads are possible. Since these strings
come from userland, there is no guarantee that they are NULL truncated.
Limiting the contentents of `sb_interpreter` to ASCII characters only is a
minor annoyance, but let's continue:

        /* Make sure we don't have any invalid #'s. */
        p = xbe->xbe_interpreter;
        while (1) {
                p = strchr(p, '#');
                if (!p)
                        break;

                p++;
                switch(*p) {
                case ISM_POUND:
                        /* "##" */
                        p++;
                        break;

                case ISM_OLD_ARGV0:
                        /* "#a" */
                        p++;
                        break;

                case 0:
                default:
                        /* Anything besides the above is invalid. */
                        return (EINVAL);
                }
        }

        sx_xlock(&interp_list_sx);
        if (imgact_binmisc_find_entry(xbe->xbe_name) != NULL) {
                sx_xunlock(&interp_list_sx);
                return (EEXIST);
        }

        /* Preallocate a new entry. */
        ibe = imgact_binmisc_new_entry(xbe);
        if (!ibe)
                return (ENOMEM);

        SLIST_INSERT_HEAD(&interpreter_list, ibe, link);
        interp_list_entry_count++;
        sx_xunlock(&interp_list_sx);

        return (0);
}

Basically, some more checks on the contents, but nothing limiting the size.
Moving onto the `imgact_binmisc_new_entry` call:

static imgact_binmisc_entry_t *
imgact_binmisc_new_entry(ximgact_binmisc_entry_t *xbe)
{
        imgact_binmisc_entry_t *ibe = NULL;
        size_t namesz = min(strlen(xbe->xbe_name) + 1, IBE_NAME_MAX);

        ibe = malloc(sizeof(*ibe), M_BINMISC, M_WAITOK|M_ZERO);

        ibe->ibe_name = malloc(namesz, M_BINMISC, M_WAITOK|M_ZERO);
        strlcpy(ibe->ibe_name, xbe->xbe_name, namesz);

        imgact_binmisc_populate_interp(xbe->xbe_interpreter, ibe);

The important thing here is that `xbe->xbe_name` is limited to `IBE_NAME_MAX`,
but there is no check on the size of `xbe->xbe_interpreter` string, it is
passed directly to `imgact_binmisc_populate_interp`:

static void
imgact_binmisc_populate_interp(char *str, imgact_binmisc_entry_t *ibe)
{
        uint32_t len = 0, argc = 1;
        char t[IBE_INTERP_LEN_MAX];
        char *sp, *tp;

        memset(t, 0, sizeof(t));

        /*
         * Normalize interpreter string. Replace white space between args with
         * single space.
         */
        sp = str; tp = t;
        while (*sp != '\0') {
                if (*sp == ' ' || *sp == '\t') {
                        if (++len > IBE_INTERP_LEN_MAX)
                                break;
                        *tp++ = ' ';
                        argc++;
                        while (*sp == ' ' || *sp == '\t')
                                sp++;
                        continue;
                } else {
                        *tp++ = *sp++;
                        len++;
                }
        }
        *tp = '\0';
        len++;

        ibe->ibe_interpreter = malloc(len, M_BINMISC, M_WAITOK|M_ZERO);

        /* Populate all the ibe fields for the interpreter. */
        memcpy(ibe->ibe_interpreter, t, len);
        ibe->ibe_interp_argcnt = argc;
        ibe->ibe_interp_length = len;
}

Clearly, it is not safe to use this function on a user supplied string!

The characters of the input string (`str`) are iterated over without any check
on size:

    while (*sp != '\0') {

There is a check on the `len` value, but only when the string reaches
whitespace:

                if (*sp == ' ' || *sp == '\t') {
                        if (++len > IBE_INTERP_LEN_MAX)
                                break;

If the string consists of no whitespace we can increase `tp`, `sp`, and `len`
freely (beyond the `IBE_INTERP_LEN_MAX` limit):

                } else {
                        *tp++ = *sp++;
                        len++;
                }

We then have a write from `tp`:

    *tp = '\0';

And also a `malloc` and `copyin` with `len`:

        ibe->ibe_interpreter = malloc(len, M_BINMISC, M_WAITOK|M_ZERO);

        /* Populate all the ibe fields for the interpreter. */
        memcpy(ibe->ibe_interpreter, t, len);

Remember that `t` is a fixed stack buffer:

    char t[IBE_INTERP_LEN_MAX];

So, when `len` exceeds `IBE_INTERP_LEN_MAX`, we have an out of bounds stack
read here, and an out of bounds stack write from `*tp = '\0'`.

But the most exploitable thing is the `*tp++ = *sp++;` copy; we can use it
write past the `t` buffer on the stack.

Here's the `ximgact_binmisc_entry` struct from `/sys/sys/imgact_binmisc.h`:

typedef struct ximgact_binmisc_entry {
        uint32_t xbe_version;   /* Struct version(IBE_VERSION) */
        uint32_t xbe_flags;     /* Entry flags (IBF_*) */
        uint32_t xbe_moffset;   /* Magic offset in header */
        uint32_t xbe_msize;     /* Magic size */
        uint32_t spare[3];      /* Spare fields for future use */
        char xbe_name[IBE_NAME_MAX];    /* Unique interpreter name */
        char xbe_interpreter[IBE_INTERP_LEN_MAX]; /* Interpreter path + args */
        uint8_t xbe_magic[IBE_MAGIC_MAX]; /* Header Magic */
        uint8_t xbe_mask[IBE_MAGIC_MAX]; /* Magic Mask */
} ximgact_binmisc_entry_t;

If we make `xbe_interpreter` non-terminated, the following fields, `xbe_magic`
and `xbe_mask`, will be read from.

Let's look at these some `define`s to get an idea of the sizes:

#define MAXPATHLEN 1024
#define IBE_ARG_LEN_MAX 256
#define IBE_INTERP_LEN_MAX      (MAXPATHLEN + IBE_ARG_LEN_MAX)
#define IBE_MAGIC_MAX   256

`xbe_interpreter` is 1280 bytes.
`xbe_magic` is 256 bytes.
`xbe_mask` is 256 bytes.

Now that we know we control the 512 bytes after the designated area for
`xbe_interpreter`, let's go back to the target code:

        char t[IBE_INTERP_LEN_MAX];
        ...
        sp = str; tp = t;
        while (*sp != '\0') {
                if (*sp == ' ' || *sp == '\t') {
                        if (++len > IBE_INTERP_LEN_MAX)
                                break;
                        *tp++ = ' ';
                        argc++;
                        while (*sp == ' ' || *sp == '\t')
                                sp++;
                        continue;
                } else {
                        *tp++ = *sp++;
                        len++;
                }
        }
        *tp = '\0';

We fill our `xbe_interpreter` (`str`) with non-whitespace, ASCII bytes, 'a' for
example so that the following code happens 1280 times:

    *tp++ = *sp++;

We then have stack overflow past `t` from user controlled contents `xbe_magic`
and `xbe_mask` (ASCII values only) for any desired size up to the next 512
bytes (just place a 0 byte to end the overflow); we can use this to overflow
return addresses on the stack to get kernel to jump anywhere we want as soon as
this function returns, but only addresses with all ASCII bytes (but this isn't
a problem).

Unfortunately, the sysctl node, `kern.binmisc.add` is only accessible as root.

-- 
You are receiving this mail because:
You are the assignee for the bug.
_______________________________________________
freebsd-bugs@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/freebsd-bugs
To unsubscribe, send any mail to "freebsd-bugs-unsubscr...@freebsd.org"

Reply via email to