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

            Bug ID: 219525
           Summary: Multiple bugs in mpr ioctl handler
           Product: Base System
           Version: CURRENT
          Hardware: Any
                OS: Any
            Status: New
          Severity: Affects Only Me
          Priority: ---
         Component: kern
          Assignee: freebsd-bugs@FreeBSD.org
          Reporter: ect...@gmail.com

There are multiple bugs in the `mpr` device's ioctl handler, `mpr_ioctl`.


Multiple `malloc` calls are made with unlimited, user controlled, unsigned
32bit integers and the `M_WAITOK` flag, meaning that if a large enough request
is made, it can crash the system. `M_WAITOK` should not be passed, and the call
should return `ENOMEM` if `malloc` failed (along with any necessary cleanup).

    case MPRIO_READ_CFG_PAGE:
        mpr_page = malloc(page_req->len, M_MPRUSER, M_WAITOK | M_ZERO);
        ...

    case MPRIO_READ_EXT_CFG_PAGE:
        mpr_page = malloc(ext_page_req->len, M_MPRUSER,
            M_WAITOK | M_ZERO);
        ...

    case MPRIO_WRITE_CFG_PAGE:
        mpr_page = malloc(page_req->len, M_MPRUSER, M_WAITOK|M_ZERO);


The `MPTIOCTL_PASS_THRU` command leads to a `copyin` call with user supplied,
unchecked, unsigned 32bit integer, and destination a struct on the stack.
Leading to stack overflow with arbitrary contents and size. The copy size here
should be `sizeof(tmphdr)` instead.

This command then performs some validation on `data->RequestSize`, but at this
point `mpr_lock` is not held, and so `IOCRequestFrameSize` value can change,
and this check can be raced, if for example a Diag Reset is made in another
thread (ioctl command `MPTIOCTL_RESET_ADAPTER` can do this for example). This
same bug is repeated in ioctl command `MPRIO_MPR_COMMAND`.

A `copyout` call can be made with user supplied unsigned 32bit integer
`ReplySize`. The code checks if `sz > data->ReplySize`, but doesn't check if
`sz < data->ReplySize`. Results in copying out of bounds memory to userland.

The header copied in from userland earlier is then copied to task, and uses
`data->RequestSize`, which should just be the size of the source struct,
`sizeof(tmphdr)`. This happens again later on as well.

Another `malloc` call is made with unlimited, user controlled, unsigned 32bit
integer and `M_WAITOK` flag.

    case MPTIOCTL_PASS_THRU:
        /*
         * The user has requested to pass through a command to be
         * executed by the MPT firmware.  Call our routine which does
         * this.  Only allow one passthru IOCTL at one time.
         */
        error = mpr_user_pass_thru(sc, (mpr_pass_thru_t *)arg);
        break;

    static int
    mpr_user_pass_thru(struct mpr_softc *sc, mpr_pass_thru_t *data)
    {
        MPI2_REQUEST_HEADER    *hdr, tmphdr;    

        ...

        /*
         * copy in the header so we know what we're dealing with before we
         * commit to allocating a command for it.
         */
        err = copyin(PTRIN(data->PtrRequest), &tmphdr, data->RequestSize);
        if (err != 0)
            goto RetFreeUnlocked;

        if (data->RequestSize > (int)sc->facts->IOCRequestFrameSize * 4) {
            err = EINVAL;
            goto RetFreeUnlocked;

        ...

            if (sz > data->ReplySize) {
                mpr_printf(sc, "%s: user reply buffer (%d) "
                    "smaller than returned buffer (%d)\n",
                    __func__, data->ReplySize, sz);
            }
            mpr_unlock(sc);
            copyout(cm->cm_reply, PTRIN(data->PtrReply),
                data->ReplySize);
            mpr_lock(sc);

    ...

        /* Copy the header in.  Only a small fixup is needed. */
        task = (MPI2_SCSI_TASK_MANAGE_REQUEST *)cm->cm_req;
        bcopy(&tmphdr, task, data->RequestSize);

    ...

        hdr = (MPI2_REQUEST_HEADER *)cm->cm_req;
        bcopy(&tmphdr, hdr, data->RequestSize);

    ...

        cm->cm_length = MAX(data->DataSize, data->DataOutSize);
        cm->cm_out_len = data->DataOutSize;
        cm->cm_flags = 0;
        if (cm->cm_length != 0) {
            cm->cm_data = malloc(cm->cm_length, M_MPRUSER, M_WAITOK |
                M_ZERO);

        ...
    }


The `MPTIOCTL_EVENT_REPORT` command calls `mpr_user_event_report`, which
incorrectly checks user supplied unsigned 32bit size. It checks if the user
supplied size is greater than the the size of the struct, and if so, copies
using the size which it just determined was too large. It should `copyout`
using `sizeof(sc->recorded_events)`.

    case MPTIOCTL_EVENT_REPORT:
        /*
         * The user has done an event report. Call our routine which
         * does this.
         */
        error = mpr_user_event_report(sc, (mpr_event_report_t *)arg);
        break;

    static int
    mpr_user_event_report(struct mpr_softc *sc, mpr_event_report_t *data)
    {
        int        status = 0;
        uint32_t    size;

        mpr_lock(sc);
        size = data->Size;
        if ((size >= sizeof(sc->recorded_events)) && (status == 0)) {
            mpr_unlock(sc);
            if (copyout((void *)sc->recorded_events,
                PTRIN(data->PtrEvents), size) != 0)
                status = EFAULT;
            mpr_lock(sc);
        } else {
            /*
             * data->Size value is not large enough to copy event data.
             */
            status = EFAULT;
        }

        /*
         * Change size value to match the number of bytes that were copied.
         */
        if (status == 0)
            data->Size = sizeof(sc->recorded_events);
        mpr_unlock(sc);

        return (status);
    }


The `MPTIOCTL_REG_ACCESS` command doesn't validate user supplied, unsigned
32bit `RegOffset` integer. This command leads to `bus_space_write_4` with
arbitrary offset. From the man page: "The bus_space_write_N() family of
functions writes a 1, 2, 4, or 8 byte data item to the offset specified by
offset into the region specified by handle of the bus space specified by space.
 The location being written must lie within the bus space region specified by
handle."

    case MPTIOCTL_REG_ACCESS:
        /*
         * The user has requested register access.  Call our routine
         * which does this.
         */
        mpr_lock(sc);
        error = mpr_user_reg_access(sc, (mpr_reg_access_t *)arg);
        mpr_unlock(sc);
        break;

    static int
    mpr_user_reg_access(struct mpr_softc *sc, mpr_reg_access_t *data)
    {
        int    status = 0;

        switch (data->Command) {
            ...
            case REG_MEM_READ:
                data->RegData = mpr_regread(sc, data->RegOffset);
                break;

            case REG_MEM_WRITE:
                mpr_regwrite(sc, data->RegOffset, data->RegData);
                break;
            ...
        }

        return (status);
    }

    ...

    static __inline void
    mpr_regwrite(struct mpr_softc *sc, uint32_t offset, uint32_t val)
    {
        bus_space_write_4(sc->mpr_btag, sc->mpr_bhandle, offset, val);
    }


The `MPTIOCTL_BTDH_MAPPING` command leads to an off-by-one error in
`mpr_user_btdh`.

    case MPTIOCTL_BTDH_MAPPING:
        /*
         * The user has requested to translate a bus/target to a
         * DevHandle or a DevHandle to a bus/target.  Call our routine
         * which does this.
         */
        error = mpr_user_btdh(sc, (mpr_btdh_mapping_t *)arg);
        break;

    static int
    mpr_user_btdh(struct mpr_softc *sc, mpr_btdh_mapping_t *data)
    {
        uint8_t        bt2dh = FALSE;
        uint8_t        dh2bt = FALSE;
        uint16_t    dev_handle, bus, target;

        bus = data->Bus;
        target = data->TargetID;
        dev_handle = data->DevHandle;

        ...

            if (target > sc->max_devices) {
                mpr_dprint(sc, MPR_XINFO, "Target ID is out of range "
                   "for Bus/Target to DevHandle mapping.");
                return (EINVAL);
            }
            dev_handle = sc->mapping_table[target].dev_handle;

        ...

        return (0);
    }

The check for `target` is that an error should be returned if `target >
sc->max_devices`, however this check should be changed to `target >=
sc->max_devices`, since if `target` is equal to `max_devices` it will be an out
of bounds access, since `mapping_table` is allocated with `max_devices`
elements:

    int
    mpr_mapping_allocate_memory(struct mpr_softc *sc)
    {
        uint32_t dpm_pg0_sz;

        sc->mapping_table = malloc((sizeof(struct dev_mapping_table) *
            sc->max_devices), M_MPR, M_ZERO|M_NOWAIT);
        ...

-- 
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