-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Hey!
It seems as though events (EV_REL) with null values are being skipped by the Linux kernel. This creates an issue when combined with rotation, where sometimes an additional axis of movement has to be "injected" as it was missing in the original frame.
How to reproduce?
- Set rotation to a non-zero value (preferably something significant like 45 deg.)
- Move the mouse either exactly vertical or horizontal.
Expected behaviour
Mouse moves at the set angle from one of the axes.
Observed behaviour
Mouse moves in a straight line along one of the axes as if the rotation wasn't applied at all.
Possible solutions
On Kernel newer than 6.11.0
It is technically possible to inject new events into the frame by simply writing into the input_value * array. This can be done like so:
/* Inject missing axes if needed (transformed non-NONE but not seen in this frame) */
if (x != NONE_EVENT_VALUE && !seen_x && ksize(vals) > (end - vals + 1) * sizeof(*vals)) {
end->type = EV_REL;
end->code = REL_X;
end->value = x;
end++;
}
// Same for the other axis(where x is the horizontal move delta after acceleration, seen_x denotes whether a REL_X was spotted within this frame and vals/end is the input_value * array)
The problem
The more "sharp-eyed" will notice a quite obvious problem, we're writing into an end pointer, which causes a OOB write inside the kernel. Or does it? (VSauce music starts playing)
The even more "sharp-eyed" will also notice a "bounds check" of sorts ksize(vals) > (end - vals + 1) * sizeof(*vals). This should indeed ensure that we never write outside the allocated space for the buffer. But is it enough? From what I've checked this buffer usually is quite large (128 bytes - big enough for 16 frames). The problem is that perhaps not all of this was meant to be modified by the driver. This is unlikely, but possible and it's better not to take chances like this within the kernel. Though I've run tests with this code and it works just fine. After all, there'a a reason why one can return the number of events in a given frame.
Lines 73 to 75 in 43b624c
| #if __cleanup_events | |
| return _count; | |
| #endif |
The bigger problem (older kernel versions)
Unfortunately, this cannot be replicated on older versions of the kernel as it requires the ability to return the number of events in the frame. Which was introduced in 6.11.0. I've tried to modify the buffer and hope for the best but the kernel just ignores it (as to be expected). There also is something interesting within the input_dev structure:
struct input_dev {
// ...
unsigned int num_vals;
unsigned int max_vals;
struct input_value *vals;
// ...
}But modifying this (and only this) seems to be giving very inconsistent results.
In Conclusion
It’s better to support newer kernels than to support nothing. But let's not give up without a fight.
This issue is also tracked on my repo here.