Commit message: Fix XIChangeHierarchy() integer underflow

CVE-2020-14346 / ZDI-CAN-11429

This vulnerability was discovered by:
Jan-Niklas Sohn working with Trend Micro Zero Day Initiative

Signed-off-by: Matthieu Herrb <matthieu@herrb.eu>
CVE: CVE-2020-14346
CWE: CWE-191
Vulnerability description: A flaw was found in xorg-x11-server before 1.20.9. An integer underflow in the X input extension protocol decoding in the X server may lead to arbitrary access of memory contents. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.

Project: xserver
File: xichangehierarchy.c

ProcXIChangeHierarchy(ClientPtr client)
{
    xXIAnyHierarchyChangeInfo *any;
    size_t len;			/* length of data remaining in request */
    int rc = Success;
    int flags[MAXDEVICES] = { 0 };

    REQUEST(xXIChangeHierarchyReq);
    REQUEST_AT_LEAST_SIZE(xXIChangeHierarchyReq);

    if (!stuff->num_changes)
        return rc;

    len = ((size_t)client->req_len << 2) - sizeof(xXIChangeHierarchyReq);

    any = (xXIAnyHierarchyChangeInfo *) &stuff[1];
    while (stuff->num_changes--) {
        if (len < sizeof(xXIAnyHierarchyChangeInfo)) {
            rc = BadLength;
            goto unwind;
        }

        SWAPIF(swaps(&any->type));
        SWAPIF(swaps(&any->length));

        if (len < ((size_t)any->length << 2))
            return BadLength;

#define CHANGE_SIZE_MATCH(type) \
    do { \
        if ((len < sizeof(type)) || (any->length != (sizeof(type) >> 2))) { \
            rc = BadLength; \
            goto unwind; \
        } \
    } while(0)

        switch (any->type) {
        case XIAddMaster:
        {
            xXIAddMasterInfo *c = (xXIAddMasterInfo *) any;

            /* Variable length, due to appended name string */
            if (len < sizeof(xXIAddMasterInfo)) {
                rc = BadLength;
                goto unwind;
            }
            SWAPIF(swaps(&c->name_len));
            if (c->name_len > (len - sizeof(xXIAddMasterInfo))) {
                rc = BadLength;
                goto unwind;
            }

            rc = add_master(client, c, flags);
            if (rc != Success)
                goto unwind;
        }
            break;
        case XIRemoveMaster:
        {
            xXIRemoveMasterInfo *r = (xXIRemoveMasterInfo *) any;

            CHANGE_SIZE_MATCH(xXIRemoveMasterInfo);
            rc = remove_master(client, r, flags);
            if (rc != Success)
                goto unwind;
        }
            break;
        case XIDetachSlave:
        {
            xXIDetachSlaveInfo *c = (xXIDetachSlaveInfo *) any;

            CHANGE_SIZE_MATCH(xXIDetachSlaveInfo);
            rc = detach_slave(client, c, flags);
            if (rc != Success)
                goto unwind;
        }
            break;
        case XIAttachSlave:
        {
            xXIAttachSlaveInfo *c = (xXIAttachSlaveInfo *) any;

            CHANGE_SIZE_MATCH(xXIAttachSlaveInfo);
            rc = attach_slave(client, c, flags);
            if (rc != Success)
                goto unwind;
        }
            break;
        }

        len -= any->length * 4;
        any = (xXIAnyHierarchyChangeInfo *) ((char *) any + any->length * 4);
    }

 unwind:

    XISendDeviceHierarchyEvent(flags);
    return rc;
}