lua: ignore unsupported event codes in modified frames

If a plugin adds events to an event frame that are not supported by the
target device we may eventually dereference a null pointer (for ABS_*
events) or, possibly, use an OOB index access (for buttons or keys).
Let's filter out any events that the device doesn't support immediately.

Fixes #1202

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1324>
This commit is contained in:
Peter Hutterer 2025-10-09 14:16:30 +10:00 committed by Marge Bot
parent 3250686e70
commit 533d5f6ee1
2 changed files with 66 additions and 14 deletions

View file

@ -221,7 +221,9 @@ lua_push_evdev_frame(lua_State *L, struct evdev_frame *frame)
}
static void
lua_pop_evdev_frame(struct libinput_lua_plugin *plugin, struct evdev_frame *frame_out)
lua_pop_evdev_frame(struct libinput_lua_plugin *plugin,
struct libevdev *libevdev,
struct evdev_frame *frame_out)
{
lua_State *L = plugin->L;
@ -252,7 +254,7 @@ lua_pop_evdev_frame(struct libinput_lua_plugin *plugin, struct evdev_frame *fram
}
lua_getfield(L, -1, "usage");
uint32_t usage = luaL_checkinteger(L, -1);
uint32_t usage_value = luaL_checkinteger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "value");
@ -261,13 +263,18 @@ lua_pop_evdev_frame(struct libinput_lua_plugin *plugin, struct evdev_frame *fram
lua_pop(L, 1); /* pop { usage = ..., value = ...} */
struct evdev_event *e = &events[nevents++];
e->usage = evdev_usage_from_uint32_t(usage);
e->value = value;
evdev_usage_t usage = evdev_usage_from_uint32_t(usage_value);
unsigned int type = evdev_usage_type(usage);
unsigned int code = evdev_usage_code(usage);
if (libevdev_has_event_code(libevdev, type, code)) {
struct evdev_event *e = &events[nevents++];
e->usage = usage;
e->value = value;
if (evdev_usage_eq(e->usage, EVDEV_SYN_REPORT)) {
lua_pop(L, 1); /* force-pop the nil */
break;
if (evdev_usage_eq(e->usage, EVDEV_SYN_REPORT)) {
lua_pop(L, 1); /* force-pop the nil */
break;
}
}
}
@ -417,7 +424,7 @@ libinput_lua_plugin_evdev_frame(struct libinput_plugin *libinput_plugin,
if (!libinput_lua_pcall(plugin, 3, 1))
return;
lua_pop_evdev_frame(plugin, frame);
lua_pop_evdev_frame(plugin, evdev->evdev, frame);
}
}
@ -932,10 +939,10 @@ evdevdevice_disconnect(lua_State *L)
}
static struct evdev_frame *
evdevdevice_frame(lua_State *L, struct libinput_lua_plugin *plugin)
evdevdevice_frame(lua_State *L, struct libinput_lua_plugin *plugin, EvdevDevice *device)
{
auto frame = evdev_frame_new(64);
lua_pop_evdev_frame(plugin, frame);
lua_pop_evdev_frame(plugin, device->evdev, frame);
struct libinput *libinput = lua_get_libinput(L);
uint64_t now = libinput_now(libinput);
@ -961,7 +968,7 @@ evdevdevice_inject_frame(lua_State *L)
if (!plugin->in_timer_func) {
return luaL_error(L, "Injecting events only possible in a timer func");
}
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin, device);
/* Lua is unhappy if we inject an event which calls into our lua state
* immediately so we need to queue this for later when we're out of the timer
@ -988,7 +995,7 @@ evdevdevice_prepend_frame(lua_State *L)
return 0;
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin, device);
/* FIXME: need to really ensure that the device can never be dangling */
libinput_plugin_prepend_evdev_frame(plugin->parent, device->device, frame);
@ -1009,7 +1016,7 @@ evdevdevice_append_frame(lua_State *L)
return 0;
struct libinput_lua_plugin *plugin = lua_get_libinput_lua_plugin(L);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin);
_unref_(evdev_frame) *frame = evdevdevice_frame(L, plugin, device);
/* FIXME: need to really ensure that the device can never be dangling */
libinput_plugin_append_evdev_frame(plugin->parent, device->device, frame);

View file

@ -868,6 +868,50 @@ START_TEST(lua_append_prepend_frame)
}
END_TEST
START_TEST(lua_ignore_unsupported_codes)
{
_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
_autofree_ char *lua = strdup_printf(
"libinput:register({1})\n"
"function frame_handler(device, frame, timestamp)\n"
" local events = {}\n"
" for _, v in ipairs(frame) do\n"
" table.insert(events, { usage = v.usage, value = v.value })\n"
" end\n"
" table.insert(events, { usage = evdev.ABS_X, value = 1000 })\n"
" table.insert(events, { usage = evdev.ABS_Y, value = 100 })\n"
" table.insert(events, { usage = evdev.BTN_BACK, value = 1 })\n"
" table.insert(events, { usage = evdev.BTN_LEFT, value = 1 })\n" /* this
one actually exists */
" return events\n"
"end\n"
"libinput:connect(\"new-evdev-device\", function(device)\n"
" device:connect(\"evdev-frame\", frame_handler)\n"
"end)\n");
_autofree_ char *path = litest_write_plugin(tmpdir->path, lua);
_litest_context_destroy_ struct libinput *li =
litest_create_context_with_plugindir(tmpdir->path);
libinput_plugin_system_load_plugins(li, LIBINPUT_PLUGIN_FLAG_NONE);
litest_drain_events(li);
_destroy_(litest_device) *device = litest_add_device(li, LITEST_MOUSE);
litest_drain_events(li);
litest_event(device, EV_REL, REL_X, 1);
litest_event(device, EV_REL, REL_Y, 2);
litest_event(device, EV_SYN, SYN_REPORT, 0);
litest_dispatch(li);
litest_timeout_debounce(li);
litest_dispatch(li);
_destroy_(libinput_event) *ev = libinput_get_event(li);
litest_is_motion_event(ev);
litest_assert_button_event(li, BTN_LEFT, LIBINPUT_BUTTON_STATE_PRESSED);
litest_assert_empty_queue(li);
}
END_TEST
START_TEST(lua_inject_frame)
{
bool in_timer = litest_test_param_get_bool(test_env->params, "in_timer");
@ -1191,6 +1235,7 @@ TEST_COLLECTION(lua)
litest_add_no_device(lua_device_info);
litest_add_no_device(lua_set_absinfo);
litest_add_no_device(lua_enable_disable_evdev_usage);
litest_add_no_device(lua_ignore_unsupported_codes);
litest_with_parameters(params,
"which", 'I', 3,