dix: fix DeviceStateNotify event calculation

The previous code only made sense if one considers buttons and keys to
be mutually exclusive on a device. That is not necessarily true, causing
a number of issues.

This function allocates and fills in the number of xEvents we need to
send the device state down the wire.  This is split across multiple
32-byte devices including one deviceStateNotify event and optional
deviceKeyStateNotify, deviceButtonStateNotify and (possibly multiple)
deviceValuator events.

The previous behavior would instead compose a sequence
of [state, buttonstate, state, keystate, valuator...]. This is not
protocol correct, and on top of that made the code extremely convoluted.

Fix this by streamlining: add both button and key into the deviceStateNotify
and then append the key state and button state, followed by the
valuators. Finally, the deviceValuator events contain up to 6 valuators
per event but we only ever sent through 3 at a time. Let's double that
troughput.

CVE-2024-0229, ZDI-CAN-22678

This vulnerability was discovered by:
Jan-Niklas Sohn working with Trend Micro Zero Day Initiative
This commit is contained in:
Peter Hutterer 2023-12-18 12:26:20 +10:00 committed by José Expósito
parent ece23be888
commit 219c54b8a3

View File

@ -615,9 +615,15 @@ FixDeviceValuator(DeviceIntPtr dev, deviceValuator * ev, ValuatorClassPtr v,
ev->type = DeviceValuator; ev->type = DeviceValuator;
ev->deviceid = dev->id; ev->deviceid = dev->id;
ev->num_valuators = nval < 3 ? nval : 3; ev->num_valuators = nval < 6 ? nval : 6;
ev->first_valuator = first; ev->first_valuator = first;
switch (ev->num_valuators) { switch (ev->num_valuators) {
case 6:
ev->valuator2 = v->axisVal[first + 5];
case 5:
ev->valuator2 = v->axisVal[first + 4];
case 4:
ev->valuator2 = v->axisVal[first + 3];
case 3: case 3:
ev->valuator2 = v->axisVal[first + 2]; ev->valuator2 = v->axisVal[first + 2];
case 2: case 2:
@ -626,7 +632,6 @@ FixDeviceValuator(DeviceIntPtr dev, deviceValuator * ev, ValuatorClassPtr v,
ev->valuator0 = v->axisVal[first]; ev->valuator0 = v->axisVal[first];
break; break;
} }
first += ev->num_valuators;
} }
static void static void
@ -646,7 +651,7 @@ FixDeviceStateNotify(DeviceIntPtr dev, deviceStateNotify * ev, KeyClassPtr k,
ev->num_buttons = b->numButtons; ev->num_buttons = b->numButtons;
memcpy((char *) ev->buttons, (char *) b->down, 4); memcpy((char *) ev->buttons, (char *) b->down, 4);
} }
else if (k) { if (k) {
ev->classes_reported |= (1 << KeyClass); ev->classes_reported |= (1 << KeyClass);
ev->num_keys = k->xkbInfo->desc->max_key_code - ev->num_keys = k->xkbInfo->desc->max_key_code -
k->xkbInfo->desc->min_key_code; k->xkbInfo->desc->min_key_code;
@ -670,15 +675,26 @@ FixDeviceStateNotify(DeviceIntPtr dev, deviceStateNotify * ev, KeyClassPtr k,
} }
} }
/**
* The device state notify event is split across multiple 32-byte events.
* The first one contains the first 32 button state bits, the first 32
* key state bits, and the first 3 valuator values.
*
* If a device has more than that, the server sends out:
* - one deviceButtonStateNotify for buttons 32 and above
* - one deviceKeyStateNotify for keys 32 and above
* - one deviceValuator event per 6 valuators above valuator 4
*
* All events but the last one have the deviceid binary ORed with MORE_EVENTS,
*/
static void static void
DeliverStateNotifyEvent(DeviceIntPtr dev, WindowPtr win) DeliverStateNotifyEvent(DeviceIntPtr dev, WindowPtr win)
{ {
/* deviceStateNotify, deviceKeyStateNotify, deviceButtonStateNotify
* and one deviceValuator for each 6 valuators */
deviceStateNotify sev[3 + (MAX_VALUATORS + 6)/6];
int evcount = 1; int evcount = 1;
deviceStateNotify sev[6 + (MAX_VALUATORS + 2)/3]; deviceStateNotify *ev = sev;
deviceStateNotify *ev;
deviceKeyStateNotify *kev;
deviceButtonStateNotify *bev;
KeyClassPtr k; KeyClassPtr k;
ButtonClassPtr b; ButtonClassPtr b;
@ -691,82 +707,49 @@ DeliverStateNotifyEvent(DeviceIntPtr dev, WindowPtr win)
if ((b = dev->button) != NULL) { if ((b = dev->button) != NULL) {
nbuttons = b->numButtons; nbuttons = b->numButtons;
if (nbuttons > 32) if (nbuttons > 32) /* first 32 are encoded in deviceStateNotify */
evcount++; evcount++;
} }
if ((k = dev->key) != NULL) { if ((k = dev->key) != NULL) {
nkeys = k->xkbInfo->desc->max_key_code - k->xkbInfo->desc->min_key_code; nkeys = k->xkbInfo->desc->max_key_code - k->xkbInfo->desc->min_key_code;
if (nkeys > 32) if (nkeys > 32) /* first 32 are encoded in deviceStateNotify */
evcount++; evcount++;
if (nbuttons > 0) {
evcount++;
}
} }
if ((v = dev->valuator) != NULL) { if ((v = dev->valuator) != NULL) {
nval = v->numAxes; nval = v->numAxes;
/* first three are encoded in deviceStateNotify, then
if (nval > 3) * it's 6 per deviceValuator event */
evcount++; evcount += ((nval - 3) + 6)/6;
if (nval > 6) {
if (!(k && b))
evcount++;
if (nval > 9)
evcount += ((nval - 7) / 3);
}
} }
ev = sev; BUG_RETURN(evcount <= ARRAY_SIZE(sev));
FixDeviceStateNotify(dev, ev, NULL, NULL, NULL, first);
if (b != NULL) { FixDeviceStateNotify(dev, ev, k, b, v, first);
FixDeviceStateNotify(dev, ev++, NULL, b, v, first);
first += 3; if (b != NULL && nbuttons > 32) {
nval -= 3; deviceButtonStateNotify *bev = (deviceButtonStateNotify *) ++ev;
if (nbuttons > 32) { (ev - 1)->deviceid |= MORE_EVENTS;
(ev - 1)->deviceid |= MORE_EVENTS; bev->type = DeviceButtonStateNotify;
bev = (deviceButtonStateNotify *) ev++; bev->deviceid = dev->id;
bev->type = DeviceButtonStateNotify; memcpy((char *) &bev->buttons[4], (char *) &b->down[4],
bev->deviceid = dev->id; DOWN_LENGTH - 4);
memcpy((char *) &bev->buttons[4], (char *) &b->down[4],
DOWN_LENGTH - 4);
}
if (nval > 0) {
(ev - 1)->deviceid |= MORE_EVENTS;
FixDeviceValuator(dev, (deviceValuator *) ev++, v, first);
first += 3;
nval -= 3;
}
} }
if (k != NULL) { if (k != NULL && nkeys > 32) {
FixDeviceStateNotify(dev, ev++, k, NULL, v, first); deviceKeyStateNotify *kev = (deviceKeyStateNotify *) ++ev;
first += 3; (ev - 1)->deviceid |= MORE_EVENTS;
nval -= 3; kev->type = DeviceKeyStateNotify;
if (nkeys > 32) { kev->deviceid = dev->id;
(ev - 1)->deviceid |= MORE_EVENTS; memmove((char *) &kev->keys[0], (char *) &k->down[4], 28);
kev = (deviceKeyStateNotify *) ev++;
kev->type = DeviceKeyStateNotify;
kev->deviceid = dev->id;
memmove((char *) &kev->keys[0], (char *) &k->down[4], 28);
}
if (nval > 0) {
(ev - 1)->deviceid |= MORE_EVENTS;
FixDeviceValuator(dev, (deviceValuator *) ev++, v, first);
first += 3;
nval -= 3;
}
} }
first = 3;
nval -= 3;
while (nval > 0) { while (nval > 0) {
FixDeviceStateNotify(dev, ev++, NULL, NULL, v, first); ev->deviceid |= MORE_EVENTS;
first += 3; FixDeviceValuator(dev, (deviceValuator *) ++ev, v, first);
nval -= 3; first += 6;
if (nval > 0) { nval -= 6;
(ev - 1)->deviceid |= MORE_EVENTS;
FixDeviceValuator(dev, (deviceValuator *) ev++, v, first);
first += 3;
nval -= 3;
}
} }
DeliverEventsToWindow(dev, win, (xEvent *) sev, evcount, DeliverEventsToWindow(dev, win, (xEvent *) sev, evcount,