Input: evdev - sanitize event type index when fetching event masks
Subsystems (2)
Dmitry Torokhov
maintainerPATCH 1/1 Input: evdev - sanitize event type index when fetching event masks
The user-supplied event type index passed to EVIOCGMASK / EVIOCSMASK ioctls is used to index the static counts array in evdev_get_mask_cnt() and client evmasks array in evdev_get_mask().
While the event type is architecturally bounded by EV_CNT, speculative execution may mispredict bounds checks and perform out-of-bounds loads.
Sanitize the event type index in evdev_get_mask_cnt() branchlessly using array_index_mask_nospec(). This clamps the index to 0 for safe array access and forces the returned count to 0 speculatively when the index is out of bounds.
We do not need additional array_index_nospec() calls in evdev_get_mask() because evdev_get_mask_cnt() speculatively forces the count (and resulting xfer_size) to 0 for out-of-bounds types, preventing any speculative memory access to client evmasks array.
--- drivers/input/evdev.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
drivers/input/evdev.c +4 -1
@@ -21,6 +21,7 @@#include <linux/init.h> #include <linux/input/mt.h> #include <linux/major.h> +#include <linux/nospec.h> #include <linux/device.h> #include <linux/cdev.h> #include "input-compat.h"
@@ -67,8 +68,10 @@ static size_t evdev_get_mask_cnt(unsigned int type) [EV_SND] = SND_CNT, [EV_FF] = FF_CNT, }; +unsigned long mask =array_index_mask_nospec (type,EV_CNT ); -return (type <EV_CNT ) ? counts[type] :0 ; +/* Returns 0 for out-of-bounds types, including speculatively */ +return counts[type & mask] & mask; }/* requires the buffer lock to be held */
Signature · 5 lines
2.55.0.629.g250fe7f194-goog -- Dmitry
[email protected]
AI reviewThank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Incomplete Spectre v1 mitigation due to misunderstanding of speculative execution on branches. --
--- Patch [1]: [PATCH] Input: evdev - sanitize event type index when fetching event masks --- commit e9eb78cd8dbba1d32ef31f1eec9a5c00e54b5aad
Author: Dmitry Torokhov <[email protected]>
Input: evdev - sanitize event type index when fetching event masks
This commit uses array_index_mask_nospec() in evdev_get_mask_cnt() to clamp out-of-bounds event types to 0. It asserts that this is sufficient to prevent speculative out-of-bounds memory accesses in functions like evdev_get_mask() because the returned count is speculatively forced to 0.
Quoted 4 lines
We do not need additional
array_index_nospec()calls inevdev_get_mask()becauseevdev_get_mask_cnt()speculatively forces the count (and resultingxfer_size) to 0 for out-of-bounds types, preventing any speculative memory access to client evmasks array.
[Severity: High]
This is a pre-existing issue, but does relying on the returned count being 0 fully prevent speculative out-of-bounds accesses in callers like evdev_get_mask() and evdev_set_mask()?
When CPU branch predictors mispredict the conditions checking the count, they do not wait for the condition to evaluate.
For example, if the if (cnt > 0) branch in evdev_get_mask() is mispredicted:
drivers/input/evdev.c:evdev_get_mask() {
...
cnt = evdev_get_mask_cnt(type);
...
if (cnt > 0) {
mask = client->evmasks[type];
...
}The branch predictor will speculatively execute the block using the original, unclamped type variable, performing an out-of-bounds read on client->evmasks[type]. The retrieved pointer is then accessed by bits_to_user().
A similar pattern exists in evdev_set_mask(), where a mispredicted if (!cnt) branch can lead to using the unclamped type:
drivers/input/evdev.c:evdev_set_mask() {
...
cnt = evdev_get_mask_cnt(type);
if (!cnt)
return 0;
...
oldmask = client->evmasks[type];
...
bitmap_free(oldmask);
}The speculative read of oldmask is followed by a dereference in bitmap_free(), which creates an exploitable side-channel gadget.
This pattern also appears in __evdev_is_filtered(). A mispredicted bounds check can lead to speculative out-of-bounds accesses for test_bit(type, mask) or mask = client->evmasks[type].
Do we still need to explicitly use array_index_nospec(type, EV_CNT) inside these caller functions to prevent the unclamped type from being used during speculative execution?
Quoted 4 lines
drivers/input/evdev.c +0
-0
[ ... ]
Quoted 12 lines
drivers/input/evdev.c +3
-1@@ -67,8 +68,10 @@ static size_t evdev_get_mask_cnt(unsigned int type) [EV_SND] = SND_CNT, [EV_FF] = FF_CNT, }; +unsigned long mask =array_index_mask_nospec (type,EV_CNT ); -return (type <EV_CNT ) ? counts[type] :0 ; +/* Returns 0 for out-of-bounds types, including speculatively */ +return counts[type & mask] & mask; }/* requires the buffer lock to be held */
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
Dmitry Torokhov
maintainerQuoted 41 lines · On Tue, Aug 04, 2026 at 01:54:13AM +0000, [email protected] wrote:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Incomplete Spectre v1 mitigation due to misunderstanding of speculative execution on branches. ----- Patch [1]: [PATCH] Input: evdev - sanitize event type index when fetching event masks --- commit
e9eb78cd8dbba1d32ef31f1eec9a5c00e54b5aad
Author: Dmitry Torokhov <[email protected]>Input: evdev - sanitize event type index when fetching event masks
This commit uses
array_index_mask_nospec()inevdev_get_mask_cnt()to clamp out-of-bounds event types to 0. It asserts that this is sufficient to prevent speculative out-of-bounds memory accesses in functions likeevdev_get_mask()because the returned count is speculatively forced to 0.Quoted 4 lines
We do not need additional
array_index_nospec()calls inevdev_get_mask()becauseevdev_get_mask_cnt()speculatively forces the count (and resultingxfer_size) to 0 for out-of-bounds types, preventing any speculative memory access to client evmasks array.[Severity: High]
This is a pre-existing issue, but does relying on the returned count being 0 fully prevent speculative out-of-bounds accesses in callers likeevdev_get_mask()andevdev_set_mask()?When CPU branch predictors mispredict the conditions checking the count, they do not wait for the condition to evaluate.
For example, if the if (cnt > 0) branch in
evdev_get_mask()is mispredicted:drivers/input/evdev.c:evdev_get_mask() { ... cnt = evdev_get_mask_cnt(type); ... if (cnt > 0) { mask = client->evmasks[type]; ... }
Quoted 4 lines
The branch predictor will speculatively execute the block using the original, unclamped type variable, performing an out-of-bounds read on client->evmasks[type]. The retrieved pointer is then accessed by
bits_to_user().
No it will not because xfer_size will be 0 and bits_to_user (copy_to_user) will not dereference the pointer at all.
Quoted 15 lines
A similar pattern exists in
evdev_set_mask(), where a mispredicted if (!cnt) branch can lead to using the unclamped type:drivers/input/evdev.c:evdev_set_mask() { ... cnt = evdev_get_mask_cnt(type); if (!cnt) return 0; ... oldmask = client->evmasks[type]; ... bitmap_free(oldmask); }
Quoted 2 lines
The speculative read of oldmask is followed by a dereference in
bitmap_free(), which creates an exploitable side-channel gadget.
kfree() can not serve as a side-channel gadget. Additionally there are speculation barriers in copy_from_user() and when taking the spinlock.
Quoted 4 lines
This pattern also appears in
__evdev_is_filtered(). A mispredicted bounds check can lead to speculative out-of-bounds accesses fortest_bit(type, mask)or mask = client->evmasks[type].
This is internal kernel function and is not user-controlled.
Quoted 4 lines
Do we still need to explicitly use
array_index_nospec(type, EV_CNT)inside these caller functions to prevent the unclamped type from being used during speculative execution?
So no.
Thanks.
Signature · 1 line
Dmitry
Quoted 55 lines · Dmitry Torokhov
The user-supplied event type index passed to EVIOCGMASK / EVIOCSMASK ioctls is used to index the static counts array in
evdev_get_mask_cnt()and client evmasks array inevdev_get_mask().While the event type is architecturally bounded by
EV_CNT, speculative execution may mispredict bounds checks and perform out-of-bounds loads.Sanitize the event type index in
evdev_get_mask_cnt()branchlessly usingarray_index_mask_nospec(). This clamps the index to 0 for safe array access and forces the returned count to 0 speculatively when the index is out of bounds.We do not need additional
array_index_nospec()calls inevdev_get_mask()becauseevdev_get_mask_cnt()speculatively forces the count (and resultingxfer_size) to 0 for out-of-bounds types, preventing any speculative memory access to client evmasks array.Reported-by "Wagenaar, C.C.J. (Chris)" <[email protected]>Assisted-by Antigravity:gemini-3.6-flashSigned-off-by Dmitry Torokhov <[email protected]>--- drivers/input/evdev.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)drivers/input/evdev.c +4
-1@@ -21,6 +21,7 @@#include <linux/init.h> #include <linux/input/mt.h> #include <linux/major.h> +#include <linux/nospec.h> #include <linux/device.h> #include <linux/cdev.h> #include "input-compat.h" @@ -67,8 +68,10 @@ static size_t evdev_get_mask_cnt(unsigned int type) [EV_SND] = SND_CNT, [EV_FF] = FF_CNT, }; +unsigned long mask =array_index_mask_nospec (type,EV_CNT ); -return (type <EV_CNT ) ? counts[type] :0 ; +/* Returns 0 for out-of-bounds types, including speculatively */ +return counts[type & mask] & mask; }/* requires the buffer lock to be held */ Signature · 5 lines
2.55.0.629.g250fe7f194-goog -- Dmitry
Quoted 54 lines · Greg KH
Quoted 53 lines · Dmitry Torokhov
The user-supplied event type index passed to EVIOCGMASK / EVIOCSMASK ioctls is used to index the static counts array in
evdev_get_mask_cnt()and client evmasks array inevdev_get_mask().While the event type is architecturally bounded by
EV_CNT, speculative execution may mispredict bounds checks and perform out-of-bounds loads.Sanitize the event type index in
evdev_get_mask_cnt()branchlessly usingarray_index_mask_nospec(). This clamps the index to 0 for safe array access and forces the returned count to 0 speculatively when the index is out of bounds.We do not need additional
array_index_nospec()calls inevdev_get_mask()becauseevdev_get_mask_cnt()speculatively forces the count (and resultingxfer_size) to 0 for out-of-bounds types, preventing any speculative memory access to client evmasks array.Reported-by "Wagenaar, C.C.J. (Chris)" <[email protected]>Assisted-by Antigravity:gemini-3.6-flashSigned-off-by Dmitry Torokhov <[email protected]>--- drivers/input/evdev.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)drivers/input/evdev.c +4
-1@@ -21,6 +21,7 @@#include <linux/init.h> #include <linux/input/mt.h> #include <linux/major.h> +#include <linux/nospec.h> #include <linux/device.h> #include <linux/cdev.h> #include "input-compat.h" @@ -67,8 +68,10 @@ static size_t evdev_get_mask_cnt(unsigned int type) [EV_SND] = SND_CNT, [EV_FF] = FF_CNT, }; +unsigned long mask =array_index_mask_nospec (type,EV_CNT ); -return (type <EV_CNT ) ? counts[type] :0 ; +/* Returns 0 for out-of-bounds types, including speculatively */ +return counts[type & mask] & mask; }/* requires the buffer lock to be held */ Signature · 1 line
2.55.0.629.g250fe7f194-goog
Quoted 4 lines
Quoted 2 lines
Signature · 1 line
Dmitry
Thanks Dmitry and Greg, I appreciate the quick fix and the attribution!