Commit message: Check SetMap request length carefully.

Avoid out of bounds memory accesses on too short request.

ZDI-CAN 11572 /  CVE-2020-14360

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-14360
CWE: CWE-119
Vulnerability description: A flaw was found in the X.Org Server before version 1.20.10. An out-of-bounds access in the XkbSetMap function may lead to a privilege escalation vulnerability. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.

Project: xserver
File: xkb.c

ProcXkbSetControls(ClientPtr client)
{
    DeviceIntPtr dev, tmpd;
    XkbSrvInfoPtr xkbi;
    XkbControlsPtr ctrl;
    XkbControlsRec new, old;
    xkbControlsNotify cn;
    XkbEventCauseRec cause;
    XkbSrvLedInfoPtr sli;

    REQUEST(xkbSetControlsReq);
    REQUEST_SIZE_MATCH(xkbSetControlsReq);

    if (!(client->xkbClientFlags & _XkbClientInitialized))
        return BadAccess;

    CHK_KBD_DEVICE(dev, stuff->deviceSpec, client, DixManageAccess);
    CHK_MASK_LEGAL(0x01, stuff->changeCtrls, XkbAllControlsMask);

    for (tmpd = inputInfo.devices; tmpd; tmpd = tmpd->next) {
        if (!tmpd->key || !tmpd->key->xkbInfo)
            continue;
        if ((tmpd == dev) ||
            (!IsMaster(tmpd) && GetMaster(tmpd, MASTER_KEYBOARD) == dev)) {
            xkbi = tmpd->key->xkbInfo;
            ctrl = xkbi->desc->ctrls;
            new = *ctrl;
            XkbSetCauseXkbReq(&cause, X_kbSetControls, client);

            if (stuff->changeCtrls & XkbInternalModsMask) {
                CHK_MASK_MATCH(0x02, stuff->affectInternalMods,
                               stuff->internalMods);
                CHK_MASK_MATCH(0x03, stuff->affectInternalVMods,
                               stuff->internalVMods);

                new.internal.real_mods &= ~(stuff->affectInternalMods);
                new.internal.real_mods |= (stuff->affectInternalMods &
                                           stuff->internalMods);
                new.internal.vmods &= ~(stuff->affectInternalVMods);
                new.internal.vmods |= (stuff->affectInternalVMods &
                                       stuff->internalVMods);
                new.internal.mask = new.internal.real_mods |
                    XkbMaskForVMask(xkbi->desc, new.internal.vmods);
            }

            if (stuff->changeCtrls & XkbIgnoreLockModsMask) {
                CHK_MASK_MATCH(0x4, stuff->affectIgnoreLockMods,
                               stuff->ignoreLockMods);
                CHK_MASK_MATCH(0x5, stuff->affectIgnoreLockVMods,
                               stuff->ignoreLockVMods);

                new.ignore_lock.real_mods &= ~(stuff->affectIgnoreLockMods);
                new.ignore_lock.real_mods |= (stuff->affectIgnoreLockMods &
                                              stuff->ignoreLockMods);
                new.ignore_lock.vmods &= ~(stuff->affectIgnoreLockVMods);
                new.ignore_lock.vmods |= (stuff->affectIgnoreLockVMods &
                                          stuff->ignoreLockVMods);
                new.ignore_lock.mask = new.ignore_lock.real_mods |
                    XkbMaskForVMask(xkbi->desc, new.ignore_lock.vmods);
            }

            CHK_MASK_MATCH(0x06, stuff->affectEnabledCtrls,
                           stuff->enabledCtrls);
            if (stuff->affectEnabledCtrls) {
                CHK_MASK_LEGAL(0x07, stuff->affectEnabledCtrls,
                               XkbAllBooleanCtrlsMask);

                new.enabled_ctrls &= ~(stuff->affectEnabledCtrls);
                new.enabled_ctrls |= (stuff->affectEnabledCtrls &
                                      stuff->enabledCtrls);
            }

            if (stuff->changeCtrls & XkbRepeatKeysMask) {
                if (stuff->repeatDelay < 1 || stuff->repeatInterval < 1) {
                    client->errorValue = _XkbErrCode3(0x08, stuff->repeatDelay,
                                                      stuff->repeatInterval);
                    return BadValue;
                }

                new.repeat_delay = stuff->repeatDelay;
                new.repeat_interval = stuff->repeatInterval;
            }

            if (stuff->changeCtrls & XkbSlowKeysMask) {
                if (stuff->slowKeysDelay < 1) {
                    client->errorValue = _XkbErrCode2(0x09,
                                                      stuff->slowKeysDelay);
                    return BadValue;
                }

                new.slow_keys_delay = stuff->slowKeysDelay;
            }

            if (stuff->changeCtrls & XkbBounceKeysMask) {
                if (stuff->debounceDelay < 1) {
                    client->errorValue = _XkbErrCode2(0x0A,
                                                      stuff->debounceDelay);
                    return BadValue;
                }

                new.debounce_delay = stuff->debounceDelay;
            }

            if (stuff->changeCtrls & XkbMouseKeysMask) {
                if (stuff->mkDfltBtn > XkbMaxMouseKeysBtn) {
                    client->errorValue = _XkbErrCode2(0x0B, stuff->mkDfltBtn);
                    return BadValue;
                }

                new.mk_dflt_btn = stuff->mkDfltBtn;
            }

            if (stuff->changeCtrls & XkbMouseKeysAccelMask) {
                if (stuff->mkDelay < 1 || stuff->mkInterval < 1 ||
                    stuff->mkTimeToMax < 1 || stuff->mkMaxSpeed < 1 ||
                    stuff->mkCurve < -1000) {
                    client->errorValue = _XkbErrCode2(0x0C, 0);
                    return BadValue;
                }

                new.mk_delay = stuff->mkDelay;
                new.mk_interval = stuff->mkInterval;
                new.mk_time_to_max = stuff->mkTimeToMax;
                new.mk_max_speed = stuff->mkMaxSpeed;
                new.mk_curve = stuff->mkCurve;
                AccessXComputeCurveFactor(xkbi, &new);
            }

            if (stuff->changeCtrls & XkbGroupsWrapMask) {
                unsigned act, num;

                act = XkbOutOfRangeGroupAction(stuff->groupsWrap);
                switch (act) {
                case XkbRedirectIntoRange:
                    num = XkbOutOfRangeGroupNumber(stuff->groupsWrap);
                    if (num >= new.num_groups) {
                        client->errorValue = _XkbErrCode3(0x0D, new.num_groups,
                                                          num);
                        return BadValue;
                    }
                case XkbWrapIntoRange:
                case XkbClampIntoRange:
                    break;
                default:
                    client->errorValue = _XkbErrCode2(0x0E, act);
                    return BadValue;
                }

                new.groups_wrap = stuff->groupsWrap;
            }

            CHK_MASK_LEGAL(0x0F, stuff->axOptions, XkbAX_AllOptionsMask);
            if (stuff->changeCtrls & XkbAccessXKeysMask) {
                new.ax_options = stuff->axOptions & XkbAX_AllOptionsMask;
            }
            else {
                if (stuff->changeCtrls & XkbStickyKeysMask) {
                    new.ax_options &= ~(XkbAX_SKOptionsMask);
                    new.ax_options |= (stuff->axOptions & XkbAX_SKOptionsMask);
                }

                if (stuff->changeCtrls & XkbAccessXFeedbackMask) {
                    new.ax_options &= ~(XkbAX_FBOptionsMask);
                    new.ax_options |= (stuff->axOptions & XkbAX_FBOptionsMask);
                }
            }

            if (stuff->changeCtrls & XkbAccessXTimeoutMask) {
                if (stuff->axTimeout < 1) {
                    client->errorValue = _XkbErrCode2(0x10, stuff->axTimeout);
                    return BadValue;
                }
                CHK_MASK_MATCH(0x11, stuff->axtCtrlsMask,
                               stuff->axtCtrlsValues);
                CHK_MASK_LEGAL(0x12, stuff->axtCtrlsMask,
                               XkbAllBooleanCtrlsMask);
                CHK_MASK_MATCH(0x13, stuff->axtOptsMask, stuff->axtOptsValues);
                CHK_MASK_LEGAL(0x14, stuff->axtOptsMask, XkbAX_AllOptionsMask);
                new.ax_timeout = stuff->axTimeout;
                new.axt_ctrls_mask = stuff->axtCtrlsMask;
                new.axt_ctrls_values = (stuff->axtCtrlsValues &
                                        stuff->axtCtrlsMask);
                new.axt_opts_mask = stuff->axtOptsMask;
                new.axt_opts_values = (stuff->axtOptsValues &
                                       stuff->axtOptsMask);
            }

            if (stuff->changeCtrls & XkbPerKeyRepeatMask) {
                memcpy(new.per_key_repeat, stuff->perKeyRepeat,
                       XkbPerKeyBitArraySize);
                if (xkbi->repeatKey &&
                    !BitIsOn(new.per_key_repeat, xkbi->repeatKey)) {
                    AccessXCancelRepeatKey(xkbi, xkbi->repeatKey);
                }
            }

            old = *ctrl;
            *ctrl = new;
            XkbDDXChangeControls(tmpd, &old, ctrl);

            if (XkbComputeControlsNotify(tmpd, &old, ctrl, &cn, FALSE)) {
                cn.keycode = 0;
                cn.eventType = 0;
                cn.requestMajor = XkbReqCode;
                cn.requestMinor = X_kbSetControls;
                XkbSendControlsNotify(tmpd, &cn);
            }

            sli = XkbFindSrvLedInfo(tmpd, XkbDfltXIClass, XkbDfltXIId, 0);
            if (sli)
                XkbUpdateIndicators(tmpd, sli->usesControls, TRUE, NULL,
                                    &cause);

            /* If sticky keys were disabled, clear all locks and latches */
            if ((old.enabled_ctrls & XkbStickyKeysMask) &&
                !(ctrl->enabled_ctrls & XkbStickyKeysMask))
                XkbClearAllLatchesAndLocks(tmpd, xkbi, TRUE, &cause);
        }
    }

    return Success;
}

Project: xserver
File: xkb.c

XkbWriteGeomShapes(char *wire, XkbGeometryPtr geom, Bool swap)
{
    int i;
    XkbShapePtr shape;
    xkbShapeWireDesc *shapeWire;

    for (i = 0, shape = geom->shapes; i < geom->num_shapes; i++, shape++) {
        register int o;
        XkbOutlinePtr ol;
        xkbOutlineWireDesc *olWire;

        shapeWire = (xkbShapeWireDesc *) wire;
        shapeWire->name = shape->name;
        shapeWire->nOutlines = shape->num_outlines;
        if (shape->primary != NULL)
            shapeWire->primaryNdx = XkbOutlineIndex(shape, shape->primary);
        else
            shapeWire->primaryNdx = XkbNoShape;
        if (shape->approx != NULL)
            shapeWire->approxNdx = XkbOutlineIndex(shape, shape->approx);
        else
            shapeWire->approxNdx = XkbNoShape;
        shapeWire->pad = 0;
        if (swap) {
            swapl(&shapeWire->name);
        }
        wire = (char *) &shapeWire[1];
        for (o = 0, ol = shape->outlines; o < shape->num_outlines; o++, ol++) {
            register int p;
            XkbPointPtr pt;
            xkbPointWireDesc *ptWire;

            olWire = (xkbOutlineWireDesc *) wire;
            olWire->nPoints = ol->num_points;
            olWire->cornerRadius = ol->corner_radius;
            olWire->pad = 0;
            wire = (char *) &olWire[1];
            ptWire = (xkbPointWireDesc *) wire;
            for (p = 0, pt = ol->points; p < ol->num_points; p++, pt++) {
                ptWire[p].x = pt->x;
                ptWire[p].y = pt->y;
                if (swap) {
                    swaps(&ptWire[p].x);
                    swaps(&ptWire[p].y);
                }
            }
            wire = (char *) &ptWire[ol->num_points];
        }
    }
    return wire;
}

Project: xserver
File: xkb.c

XkbSizeGeomColors(XkbGeometryPtr geom)
{
    register int i, size;
    register XkbColorPtr color;

    for (i = size = 0, color = geom->colors; i < geom->num_colors; i++, color++) {
        size += XkbSizeCountedString(color->spec);
    }
    return size;
}

Project: xserver
File: xkb.c

ProcXkbSetNamedIndicator(ClientPtr client)
{
    int rc;
    DeviceIntPtr dev;
    int led = 0;
    XkbIndicatorMapPtr map;

    REQUEST(xkbSetNamedIndicatorReq);
    REQUEST_SIZE_MATCH(xkbSetNamedIndicatorReq);

    if (!(client->xkbClientFlags & _XkbClientInitialized))
        return BadAccess;

    CHK_LED_DEVICE(dev, stuff->deviceSpec, client, DixSetAttrAccess);
    CHK_ATOM_ONLY(stuff->indicator);
    CHK_MASK_LEGAL(0x10, stuff->whichGroups, XkbIM_UseAnyGroup);
    CHK_MASK_LEGAL(0x11, stuff->whichMods, XkbIM_UseAnyMods);

    /* Dry-run for checks */
    rc = _XkbCreateIndicatorMap(dev, stuff->indicator,
                                stuff->ledClass, stuff->ledID,
                                &map, &led, TRUE);
    if (rc != Success || !map)  /* couldn't be created or didn't exist */
        return rc;

    if (stuff->deviceSpec == XkbUseCoreKbd ||
        stuff->deviceSpec == XkbUseCorePtr) {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if ((other != dev) && !IsMaster(other) &&
                GetMaster(other, MASTER_KEYBOARD) == dev && (other->kbdfeed ||
                                                             other->leds) &&
                (XaceHook(XACE_DEVICE_ACCESS, client, other, DixSetAttrAccess)
                 == Success)) {
                rc = _XkbCreateIndicatorMap(other, stuff->indicator,
                                            stuff->ledClass, stuff->ledID, &map,
                                            &led, TRUE);
                if (rc != Success || !map)
                    return rc;
            }
        }
    }

    /* All checks passed, let's do it */
    rc = _XkbSetNamedIndicator(client, dev, stuff);
    if (rc != Success)
        return rc;

    if (stuff->deviceSpec == XkbUseCoreKbd ||
        stuff->deviceSpec == XkbUseCorePtr) {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if ((other != dev) && !IsMaster(other) &&
                GetMaster(other, MASTER_KEYBOARD) == dev && (other->kbdfeed ||
                                                             other->leds) &&
                (XaceHook(XACE_DEVICE_ACCESS, client, other, DixSetAttrAccess)
                 == Success)) {
                _XkbSetNamedIndicator(client, other, stuff);
            }
        }
    }

    return Success;
}

Project: xserver
File: xkb.c

XkbWriteCountedString(char *wire, const char *str, Bool swap)
{
    CARD16 len, *pLen, paddedLen;

    if (!str)
        return wire;

    len = strlen(str);
    pLen = (CARD16 *) wire;
    *pLen = len;
    if (swap) {
        swaps(pLen);
    }
    paddedLen = pad_to_int32(sizeof(len) + len) - sizeof(len);
    strncpy(&wire[sizeof(len)], str, paddedLen);
    wire += sizeof(len) + paddedLen;
    return wire;
}

Project: xserver
File: xkb.c

ProcXkbSetMap(ClientPtr client)
{
    DeviceIntPtr dev;
    char *tmp;
    int rc;

    REQUEST(xkbSetMapReq);
    REQUEST_AT_LEAST_SIZE(xkbSetMapReq);

    if (!(client->xkbClientFlags & _XkbClientInitialized))
        return BadAccess;

    CHK_KBD_DEVICE(dev, stuff->deviceSpec, client, DixManageAccess);
    CHK_MASK_LEGAL(0x01, stuff->present, XkbAllMapComponentsMask);

    /* first verify the request length carefully */
    rc = _XkbSetMapCheckLength(stuff);
    if (rc != Success)
        return rc;

    tmp = (char *) &stuff[1];

    /* Check if we can to the SetMap on the requested device. If this
       succeeds, do the same thing for all extension devices (if needed).
       If any of them fails, fail.  */
    rc = _XkbSetMapChecks(client, dev, stuff, tmp, TRUE);

    if (rc != Success)
        return rc;

    DeviceIntPtr master = GetMaster(dev, MASTER_KEYBOARD);

    if (stuff->deviceSpec == XkbUseCoreKbd) {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if ((other != dev) && other->key && !IsMaster(other) &&
                GetMaster(other, MASTER_KEYBOARD) == dev) {
                rc = XaceHook(XACE_DEVICE_ACCESS, client, other,
                              DixManageAccess);
                if (rc == Success) {
                    rc = _XkbSetMapChecks(client, other, stuff, tmp, FALSE);
                    if (rc != Success)
                        return rc;
                }
            }
        }
    } else {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if (other != dev && GetMaster(other, MASTER_KEYBOARD) != dev &&
                (other != master || dev != master->lastSlave))
                continue;

            rc = _XkbSetMapChecks(client, other, stuff, tmp, FALSE);
            if (rc != Success)
                return rc;
        }
    }

    /* We know now that we will succeed with the SetMap. In theory anyway. */
    rc = _XkbSetMap(client, dev, stuff, tmp);
    if (rc != Success)
        return rc;

    if (stuff->deviceSpec == XkbUseCoreKbd) {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if ((other != dev) && other->key && !IsMaster(other) &&
                GetMaster(other, MASTER_KEYBOARD) == dev) {
                rc = XaceHook(XACE_DEVICE_ACCESS, client, other,
                              DixManageAccess);
                if (rc == Success)
                    _XkbSetMap(client, other, stuff, tmp);
                /* ignore rc. if the SetMap failed although the check above
                   reported true there isn't much we can do. we still need to
                   set all other devices, hoping that at least they stay in
                   sync. */
            }
        }
    } else {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if (other != dev && GetMaster(other, MASTER_KEYBOARD) != dev &&
                (other != master || dev != master->lastSlave))
                continue;

            _XkbSetMap(client, other, stuff, tmp); //ignore rc
        }
    }

    return Success;
}

Project: xserver
File: xkb.c

ProcXkbSetIndicatorMap(ClientPtr client)
{
    int i, bit;
    int nIndicators;
    DeviceIntPtr dev;
    xkbIndicatorMapWireDesc *from;
    int rc;

    REQUEST(xkbSetIndicatorMapReq);
    REQUEST_AT_LEAST_SIZE(xkbSetIndicatorMapReq);

    if (!(client->xkbClientFlags & _XkbClientInitialized))
        return BadAccess;

    CHK_KBD_DEVICE(dev, stuff->deviceSpec, client, DixSetAttrAccess);

    if (stuff->which == 0)
        return Success;

    for (nIndicators = i = 0, bit = 1; i < XkbNumIndicators; i++, bit <<= 1) {
        if (stuff->which & bit)
            nIndicators++;
    }
    if (stuff->length != ((SIZEOF(xkbSetIndicatorMapReq) +
                           (nIndicators * SIZEOF(xkbIndicatorMapWireDesc))) /
                          4)) {
        return BadLength;
    }

    from = (xkbIndicatorMapWireDesc *) &stuff[1];
    for (i = 0, bit = 1; i < XkbNumIndicators; i++, bit <<= 1) {
        if (stuff->which & bit) {
            if (client->swapped) {
                swaps(&from->virtualMods);
                swapl(&from->ctrls);
            }
            CHK_MASK_LEGAL(i, from->whichGroups, XkbIM_UseAnyGroup);
            CHK_MASK_LEGAL(i, from->whichMods, XkbIM_UseAnyMods);
            from++;
        }
    }

    from = (xkbIndicatorMapWireDesc *) &stuff[1];
    rc = _XkbSetIndicatorMap(client, dev, stuff->which, from);
    if (rc != Success)
        return rc;

    if (stuff->deviceSpec == XkbUseCoreKbd) {
        DeviceIntPtr other;

        for (other = inputInfo.devices; other; other = other->next) {
            if ((other != dev) && other->key && !IsMaster(other) &&
                GetMaster(other, MASTER_KEYBOARD) == dev) {
                rc = XaceHook(XACE_DEVICE_ACCESS, client, other,
                              DixSetAttrAccess);
                if (rc == Success)
                    _XkbSetIndicatorMap(client, other, stuff->which, from);
            }
        }
    }

    return Success;
}

Project: xserver
File: xkb.c

_CheckSetOverlay(char **wire_inout,
                 XkbGeometryPtr geom, XkbSectionPtr section, ClientPtr client)
{
    register int r;
    char *wire;
    XkbOverlayPtr ol;
    xkbOverlayWireDesc *olWire;
    xkbOverlayRowWireDesc *rWire;

    wire = *wire_inout;
    olWire = (xkbOverlayWireDesc *) wire;
    if (client->swapped) {
        swapl(&olWire->name);
    }
    CHK_ATOM_ONLY(olWire->name);
    ol = XkbAddGeomOverlay(section, olWire->name, olWire->nRows);
    rWire = (xkbOverlayRowWireDesc *) &olWire[1];
    for (r = 0; r < olWire->nRows; r++) {
        register int k;
        xkbOverlayKeyWireDesc *kWire;
        XkbOverlayRowPtr row;

        if (rWire->rowUnder > section->num_rows) {
            client->errorValue = _XkbErrCode4(0x20, r, section->num_rows,
                                              rWire->rowUnder);
            return BadMatch;
        }
        row = XkbAddGeomOverlayRow(ol, rWire->rowUnder, rWire->nKeys);
        kWire = (xkbOverlayKeyWireDesc *) &rWire[1];
        for (k = 0; k < rWire->nKeys; k++, kWire++) {
            if (XkbAddGeomOverlayKey(ol, row,
                                     (char *) kWire->over,
                                     (char *) kWire->under) == NULL) {
                client->errorValue = _XkbErrCode3(0x21, r, k);
                return BadMatch;
            }
        }
        rWire = (xkbOverlayRowWireDesc *) kWire;
    }
    olWire = (xkbOverlayWireDesc *) rWire;
    wire = (char *) olWire;
    *wire_inout = wire;
    return Success;
}