Commit message: dix: Disallow GenericEvent in SendEvent request.

The SendEvent request holds xEvent which is exactly 32 bytes long, no more,
no less. Both ProcSendEvent and SProcSendEvent verify that the received data
exactly match the request size. However nothing stops the client from passing
in event with xEvent::type = GenericEvent and any value of
xGenericEvent::length.

In the case of ProcSendEvent, the event will be eventually passed to
WriteEventsToClient which will see that it is Generic event and copy the
arbitrary length from the receive buffer (and possibly past it) and send it to
the other client. This allows clients to copy unitialized heap memory out of X
server or to crash it.

In case of SProcSendEvent, it will attempt to swap the incoming event by
calling a swapping function from the EventSwapVector array. The swapped event
is written to target buffer, which in this case is local xEvent variable. The
xEvent variable is 32 bytes long, but the swapping functions for GenericEvents
expect that the target buffer has size matching the size of the source
GenericEvent. This allows clients to cause stack buffer overflows.

Signed-off-by: Michal Srb <msrb@suse.com>
Reviewed-by: Peter Hutterer <peter.hutterer@who-t.net>
Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
CVE: CVE-2017-10971
CWE: CWE-119
Vulnerability description: In the X.Org X server before 2017-06-19, a user authenticated to an X Session could crash or execute code in the context of the X Server by exploiting a stack overflow in the endianness conversion of X Events.

Project: xserver
File: events.c

ActivateKeyboardGrab(DeviceIntPtr keybd, GrabPtr grab, TimeStamp time,
                     Bool passive)
{
    GrabInfoPtr grabinfo = &keybd->deviceGrab;
    GrabPtr oldgrab = grabinfo->grab;
    WindowPtr oldWin;

    /* slave devices need to float for the duration of the grab. */
    if (grab->grabtype == XI2 && keybd->enabled &&
        !(passive & ImplicitGrabMask) && !IsMaster(keybd))
        DetachFromMaster(keybd);

    if (!keybd->enabled)
        oldWin = NULL;
    else if (grabinfo->grab)
        oldWin = grabinfo->grab->window;
    else if (keybd->focus)
        oldWin = keybd->focus->win;
    else
        oldWin = keybd->spriteInfo->sprite->win;
    if (oldWin == FollowKeyboardWin)
        oldWin = keybd->focus->win;
    if (keybd->valuator)
        keybd->valuator->motionHintWindow = NullWindow;
    if (oldWin)
        DoFocusEvents(keybd, oldWin, grab->window, NotifyGrab);
    if (syncEvents.playingEvents)
        grabinfo->grabTime = syncEvents.time;
    else
        grabinfo->grabTime = time;
    grabinfo->grab = AllocGrab(grab);
    grabinfo->fromPassiveGrab = passive;
    grabinfo->implicitGrab = passive & ImplicitGrabMask;
    CheckGrabForSyncs(keybd, (Bool) grab->keyboardMode,
                      (Bool) grab->pointerMode);
    if (oldgrab)
        FreeGrab(oldgrab);
}

Project: xserver
File: events.c

ActivatePassiveGrab(DeviceIntPtr device, GrabPtr grab, InternalEvent *event,
                    InternalEvent *real_event)
{
    SpritePtr pSprite = device->spriteInfo->sprite;
    GrabInfoPtr grabinfo = &device->deviceGrab;
    xEvent *xE = NULL;
    int count;
    int rc;

    /* The only consumers of corestate are Xi 1.x and core events, which
     * are guaranteed to come from DeviceEvents. */
    if (grab->grabtype == XI || grab->grabtype == CORE) {
        DeviceIntPtr gdev;

        event->device_event.corestate &= 0x1f00;

        if (grab->grabtype == CORE)
            gdev = GetMaster(device, KEYBOARD_OR_FLOAT);
        else
            gdev = grab->modifierDevice;

        if (gdev && gdev->key && gdev->key->xkbInfo)
            event->device_event.corestate |=
                gdev->key->xkbInfo->state.grab_mods & (~0x1f00);
    }

    if (grab->grabtype == CORE) {
        rc = EventToCore(event, &xE, &count);
        if (rc != Success) {
            BUG_WARN_MSG(rc != BadMatch, "[dix] %s: core conversion failed"
                         "(%d, %d).\n", device->name, event->any.type, rc);
            return FALSE;
        }
    }
    else if (grab->grabtype == XI2) {
        rc = EventToXI2(event, &xE);
        if (rc != Success) {
            if (rc != BadMatch)
                BUG_WARN_MSG(rc != BadMatch, "[dix] %s: XI2 conversion failed"
                             "(%d, %d).\n", device->name, event->any.type, rc);
            return FALSE;
        }
        count = 1;
    }
    else {
        rc = EventToXI(event, &xE, &count);
        if (rc != Success) {
            if (rc != BadMatch)
                BUG_WARN_MSG(rc != BadMatch, "[dix] %s: XI conversion failed"
                             "(%d, %d).\n", device->name, event->any.type, rc);
            return FALSE;
        }
    }

    (*grabinfo->ActivateGrab) (device, grab,
                               ClientTimeToServerTime(event->any.time), TRUE);

    if (xE) {
        FixUpEventFromWindow(pSprite, xE, grab->window, None, TRUE);

        /* XXX: XACE? */
        TryClientEvents(rClient(grab), device, xE, count,
                        GetEventFilter(device, xE),
                        GetEventFilter(device, xE), grab);
    }

    if (grabinfo->sync.state == FROZEN_NO_EVENT)
        grabinfo->sync.state = FROZEN_WITH_EVENT;
    *grabinfo->sync.event = real_event->device_event;

    free(xE);
    return TRUE;
}

Project: xserver
File: events.c

AllowSome(ClientPtr client, TimeStamp time, DeviceIntPtr thisDev, int newState)
{
    Bool thisGrabbed, otherGrabbed, othersFrozen, thisSynced;
    TimeStamp grabTime;
    DeviceIntPtr dev;
    GrabInfoPtr devgrabinfo, grabinfo = &thisDev->deviceGrab;

    thisGrabbed = grabinfo->grab && SameClient(grabinfo->grab, client);
    thisSynced = FALSE;
    otherGrabbed = FALSE;
    othersFrozen = FALSE;
    grabTime = grabinfo->grabTime;
    for (dev = inputInfo.devices; dev; dev = dev->next) {
        devgrabinfo = &dev->deviceGrab;

        if (dev == thisDev)
            continue;
        if (devgrabinfo->grab && SameClient(devgrabinfo->grab, client)) {
            if (!(thisGrabbed || otherGrabbed) ||
                (CompareTimeStamps(devgrabinfo->grabTime, grabTime) == LATER))
                grabTime = devgrabinfo->grabTime;
            otherGrabbed = TRUE;
            if (grabinfo->sync.other == devgrabinfo->grab)
                thisSynced = TRUE;
            if (devgrabinfo->sync.state >= FROZEN)
                othersFrozen = TRUE;
        }
    }
    if (!((thisGrabbed && grabinfo->sync.state >= FROZEN) || thisSynced))
        return;
    if ((CompareTimeStamps(time, currentTime) == LATER) ||
        (CompareTimeStamps(time, grabTime) == EARLIER))
        return;
    switch (newState) {
    case THAWED:               /* Async */
        if (thisGrabbed)
            grabinfo->sync.state = THAWED;
        if (thisSynced)
            grabinfo->sync.other = NullGrab;
        ComputeFreezes();
        break;
    case FREEZE_NEXT_EVENT:    /* Sync */
        if (thisGrabbed) {
            grabinfo->sync.state = FREEZE_NEXT_EVENT;
            if (thisSynced)
                grabinfo->sync.other = NullGrab;
            ComputeFreezes();
        }
        break;
    case THAWED_BOTH:          /* AsyncBoth */
        if (othersFrozen) {
            for (dev = inputInfo.devices; dev; dev = dev->next) {
                devgrabinfo = &dev->deviceGrab;
                if (devgrabinfo->grab && SameClient(devgrabinfo->grab, client))
                    devgrabinfo->sync.state = THAWED;
                if (devgrabinfo->sync.other &&
                    SameClient(devgrabinfo->sync.other, client))
                    devgrabinfo->sync.other = NullGrab;
            }
            ComputeFreezes();
        }
        break;
    case FREEZE_BOTH_NEXT_EVENT:       /* SyncBoth */
        if (othersFrozen) {
            for (dev = inputInfo.devices; dev; dev = dev->next) {
                devgrabinfo = &dev->deviceGrab;
                if (devgrabinfo->grab && SameClient(devgrabinfo->grab, client))
                    devgrabinfo->sync.state = FREEZE_BOTH_NEXT_EVENT;
                if (devgrabinfo->sync.other
                    && SameClient(devgrabinfo->sync.other, client))
                    devgrabinfo->sync.other = NullGrab;
            }
            ComputeFreezes();
        }
        break;
    case NOT_GRABBED:          /* Replay */
        if (thisGrabbed && grabinfo->sync.state == FROZEN_WITH_EVENT) {
            if (thisSynced)
                grabinfo->sync.other = NullGrab;
            syncEvents.replayDev = thisDev;
            syncEvents.replayWin = grabinfo->grab->window;
            (*grabinfo->DeactivateGrab) (thisDev);
            syncEvents.replayDev = (DeviceIntPtr) NULL;
        }
        break;
    case THAW_OTHERS:          /* AsyncOthers */
        if (othersFrozen) {
            for (dev = inputInfo.devices; dev; dev = dev->next) {
                if (dev == thisDev)
                    continue;
                devgrabinfo = &dev->deviceGrab;
                if (devgrabinfo->grab && SameClient(devgrabinfo->grab, client))
                    devgrabinfo->sync.state = THAWED;
                if (devgrabinfo->sync.other
                    && SameClient(devgrabinfo->sync.other, client))
                    devgrabinfo->sync.other = NullGrab;
            }
            ComputeFreezes();
        }
        break;
    }

    /* We've unfrozen the grab. If the grab was a touch grab, we're now the
     * owner and expected to accept/reject it. Reject == ReplayPointer which
     * we've handled in ComputeFreezes() (during DeactivateGrab) above,
     * anything else is accept.
     */
    if (newState != NOT_GRABBED /* Replay */ &&
        IsTouchEvent((InternalEvent*)grabinfo->sync.event)) {
        TouchAcceptAndEnd(thisDev, grabinfo->sync.event->touchid);
    }
}

Project: xserver
File: events.c

CloseDownEvents(void)
{
    FreeEventList(InputEventList, GetMaximumEventsNum());
    InputEventList = NULL;
}

Project: xserver
File: events.c

ConfineToShape(DeviceIntPtr pDev, RegionPtr shape, int *px, int *py)
{
    BoxRec box;
    int x = *px, y = *py;
    int incx = 1, incy = 1;

    if (RegionContainsPoint(shape, x, y, &box))
        return;
    box = *RegionExtents(shape);
    /* this is rather crude */
    do {
        x += incx;
        if (x >= box.x2) {
            incx = -1;
            x = *px - 1;
        }
        else if (x < box.x1) {
            incx = 1;
            x = *px;
            y += incy;
            if (y >= box.y2) {
                incy = -1;
                y = *py - 1;
            }
            else if (y < box.y1)
                return;         /* should never get here! */
        }
    } while (!RegionContainsPoint(shape, x, y, &box));
    *px = x;
    *py = y;
}

Project: xserver
File: events.c

CoreFocusEvent(DeviceIntPtr dev, int type, int mode, int detail, WindowPtr pWin)
{
    xEvent event = {
        .u.u.type = type,
        .u.u.detail = detail
    };
    event.u.focus.mode = mode;
    event.u.focus.window = pWin->drawable.id;

    DeliverEventsToWindow(dev, pWin, &event, 1,
                          GetEventFilter(dev, &event), NullGrab);
    if ((type == FocusIn) &&
        ((pWin->eventMask | wOtherEventMasks(pWin)) & KeymapStateMask)) {
        xKeymapEvent ke = {
            .type = KeymapNotify
        };
        ClientPtr client = wClient(pWin);
        int rc;

        rc = XaceHook(XACE_DEVICE_ACCESS, client, dev, DixReadAccess);
        if (rc == Success)
            memcpy((char *) &ke.map[0], (char *) &dev->key->down[1], 31);

        DeliverEventsToWindow(dev, pWin, (xEvent *) &ke, 1,
                              KeymapStateMask, NullGrab);
    }
}

Project: xserver
File: events.c

FixKeyState(DeviceEvent *event, DeviceIntPtr keybd)
{
    int key = event->detail.key;

    if (event->type == ET_KeyPress) {
        DebugF("FixKeyState: Key %d %s\n", key,
               ((event->type == ET_KeyPress) ? "down" : "up"));
    }

    if (event->type == ET_KeyPress)
        set_key_down(keybd, key, KEY_PROCESSED);
    else if (event->type == ET_KeyRelease)
        set_key_up(keybd, key, KEY_PROCESSED);
    else
        FatalError("Impossible keyboard event");
}

Project: xserver
File: events.c

GetSpritePosition(DeviceIntPtr pDev, int *px, int *py)
{
    SpritePtr pSprite = pDev->spriteInfo->sprite;

    *px = pSprite->hotPhys.x;
    *py = pSprite->hotPhys.y;
}

Project: xserver
File: events.c

IsKeyboardDevice(DeviceIntPtr dev)
{
    return (dev->type == MASTER_KEYBOARD) ||
        ((dev->key && dev->kbdfeed) && !IsPointerDevice(dev));
}

Project: xserver
File: events.c

LastEventTimeToggleResetAll(Bool state)
{
    DeviceIntPtr dev;
    nt_list_for_each_entry(dev, inputInfo.devices, next) {
        LastEventTimeToggleResetFlag(dev->id, FALSE);
    }
    LastEventTimeToggleResetFlag(XIAllDevices, FALSE);
    LastEventTimeToggleResetFlag(XIAllMasterDevices, FALSE);
}

Project: xserver
File: events.c

LastEventTimeWasReset(int deviceid)
{
    return lastDeviceEventTime[deviceid].reset;
}

Project: xserver
File: events.c

NotImplemented(xEvent *from, xEvent *to)
{
    FatalError("Not implemented");
}

Project: xserver
File: events.c

ProcChangeActivePointerGrab(ClientPtr client)
{
    DeviceIntPtr device;
    GrabPtr grab;
    CursorPtr newCursor, oldCursor;

    REQUEST(xChangeActivePointerGrabReq);
    TimeStamp time;

    REQUEST_SIZE_MATCH(xChangeActivePointerGrabReq);
    if (stuff->eventMask & ~PointerGrabMask) {
        client->errorValue = stuff->eventMask;
        return BadValue;
    }
    if (stuff->cursor == None)
        newCursor = NullCursor;
    else {
        int rc = dixLookupResourceByType((void **) &newCursor, stuff->cursor,
                                         RT_CURSOR, client, DixUseAccess);

        if (rc != Success) {
            client->errorValue = stuff->cursor;
            return rc;
        }
    }

    device = PickPointer(client);
    grab = device->deviceGrab.grab;

    if (!grab)
        return Success;
    if (!SameClient(grab, client))
        return Success;
    UpdateCurrentTime();
    time = ClientTimeToServerTime(stuff->time);
    if ((CompareTimeStamps(time, currentTime) == LATER) ||
        (CompareTimeStamps(time, device->deviceGrab.grabTime) == EARLIER))
        return Success;
    oldCursor = grab->cursor;
    grab->cursor = RefCursor(newCursor);
    PostNewCursor(device);
    if (oldCursor)
        FreeCursor(oldCursor, (Cursor) 0);
    grab->eventMask = stuff->eventMask;
    return Success;
}

Project: xserver
File: events.c

ProcRecolorCursor(ClientPtr client)
{
    CursorPtr pCursor;
    int rc, nscr;
    ScreenPtr pscr;
    Bool displayed;
    SpritePtr pSprite = PickPointer(client)->spriteInfo->sprite;

    REQUEST(xRecolorCursorReq);

    REQUEST_SIZE_MATCH(xRecolorCursorReq);
    rc = dixLookupResourceByType((void **) &pCursor, stuff->cursor, RT_CURSOR,
                                 client, DixWriteAccess);
    if (rc != Success) {
        client->errorValue = stuff->cursor;
        return rc;
    }

    pCursor->foreRed = stuff->foreRed;
    pCursor->foreGreen = stuff->foreGreen;
    pCursor->foreBlue = stuff->foreBlue;

    pCursor->backRed = stuff->backRed;
    pCursor->backGreen = stuff->backGreen;
    pCursor->backBlue = stuff->backBlue;

    for (nscr = 0; nscr < screenInfo.numScreens; nscr++) {
        pscr = screenInfo.screens[nscr];
#ifdef PANORAMIX
        if (!noPanoramiXExtension)
            displayed = (pscr == pSprite->screen);
        else
#endif
            displayed = (pscr == pSprite->hotPhys.pScreen);
        (*pscr->RecolorCursor) (PickPointer(client), pscr, pCursor,
                                (pCursor == pSprite->current) && displayed);
    }
    return Success;
}

Project: xserver
File: events.c

WindowHasNewCursor(WindowPtr pWin)
{
    DeviceIntPtr pDev;

    for (pDev = inputInfo.devices; pDev; pDev = pDev->next)
        if (DevHasCursor(pDev))
            PostNewCursor(pDev);
}

Project: xserver
File: events.c

WriteEventsToClient(ClientPtr pClient, int count, xEvent *events)
{
#ifdef PANORAMIX
    xEvent eventCopy;
#endif
    xEvent *eventTo, *eventFrom;
    int i, eventlength = sizeof(xEvent);

    if (!pClient || pClient == serverClient || pClient->clientGone)
        return;

    for (i = 0; i < count; i++)
        if ((events[i].u.u.type & 0x7f) != KeymapNotify)
            events[i].u.u.sequenceNumber = pClient->sequence;

    /* Let XKB rewrite the state, as it depends on client preferences. */
    XkbFilterEvents(pClient, count, events);

#ifdef PANORAMIX
    if (!noPanoramiXExtension &&
        (screenInfo.screens[0]->x || screenInfo.screens[0]->y)) {
        switch (events->u.u.type) {
        case MotionNotify:
        case ButtonPress:
        case ButtonRelease:
        case KeyPress:
        case KeyRelease:
        case EnterNotify:
        case LeaveNotify:
            /*
               When multiple clients want the same event DeliverEventsToWindow
               passes the same event structure multiple times so we can't
               modify the one passed to us
             */
            count = 1;          /* should always be 1 */
            memcpy(&eventCopy, events, sizeof(xEvent));
            eventCopy.u.keyButtonPointer.rootX += screenInfo.screens[0]->x;
            eventCopy.u.keyButtonPointer.rootY += screenInfo.screens[0]->y;
            if (eventCopy.u.keyButtonPointer.event ==
                eventCopy.u.keyButtonPointer.root) {
                eventCopy.u.keyButtonPointer.eventX += screenInfo.screens[0]->x;
                eventCopy.u.keyButtonPointer.eventY += screenInfo.screens[0]->y;
            }
            events = &eventCopy;
            break;
        default:
            break;
        }
    }
#endif

    if (EventCallback) {
        EventInfoRec eventinfo;

        eventinfo.client = pClient;
        eventinfo.events = events;
        eventinfo.count = count;
        CallCallbacks(&EventCallback, (void *) &eventinfo);
    }
#ifdef XSERVER_DTRACE
    if (XSERVER_SEND_EVENT_ENABLED()) {
        for (i = 0; i < count; i++) {
            XSERVER_SEND_EVENT(pClient->index, events[i].u.u.type, &events[i]);
        }
    }
#endif
    /* Just a safety check to make sure we only have one GenericEvent, it just
     * makes things easier for me right now. (whot) */
    for (i = 1; i < count; i++) {
        if (events[i].u.u.type == GenericEvent) {
            ErrorF("[dix] TryClientEvents: Only one GenericEvent at a time.\n");
            return;
        }
    }

    if (events->u.u.type == GenericEvent) {
        eventlength += ((xGenericEvent *) events)->length * 4;
    }

    if (pClient->swapped) {
        if (eventlength > swapEventLen) {
            swapEventLen = eventlength;
            swapEvent = realloc(swapEvent, swapEventLen);
            if (!swapEvent) {
                FatalError("WriteEventsToClient: Out of memory.\n");
                return;
            }
        }

        for (i = 0; i < count; i++) {
            eventFrom = &events[i];
            eventTo = swapEvent;

            /* Remember to strip off the leading bit of type in case
               this event was sent with "SendEvent." */
            (*EventSwapVector[eventFrom->u.u.type & 0177])
                (eventFrom, eventTo);

            WriteToClient(pClient, eventlength, eventTo);
        }
    }
    else {
        /* only one GenericEvent, remember? that means either count is 1 and
         * eventlength is arbitrary or eventlength is 32 and count doesn't
         * matter. And we're all set. Woohoo. */
        WriteToClient(pClient, count * eventlength, events);
    }
}

Project: xserver
File: swapreq.c

SProcGetProperty(ClientPtr client)
{
    REQUEST(xGetPropertyReq);
    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xGetPropertyReq);
    swapl(&stuff->window);
    swapl(&stuff->property);
    swapl(&stuff->type);
    swapl(&stuff->longOffset);
    swapl(&stuff->longLength);
    return ((*ProcVector[X_GetProperty]) (client));
}

Project: xserver
File: swapreq.c

SProcReparentWindow(ClientPtr client)
{
    REQUEST(xReparentWindowReq);
    swaps(&stuff->length);
    REQUEST_SIZE_MATCH(xReparentWindowReq);
    swapl(&stuff->window);
    swapl(&stuff->parent);
    swaps(&stuff->x);
    swaps(&stuff->y);
    return ((*ProcVector[X_ReparentWindow]) (client));
}