From 533d5f6ee17bc2926b54278412564f91e9b65c2f Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Thu, 9 Oct 2025 14:16:30 +1000 Subject: [PATCH] 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: --- src/libinput-plugin-lua.c | 35 ++++++++++++++++++------------ test/test-plugins-lua.c | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/libinput-plugin-lua.c b/src/libinput-plugin-lua.c index daba7064..4ed8dc55 100644 --- a/src/libinput-plugin-lua.c +++ b/src/libinput-plugin-lua.c @@ -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); diff --git a/test/test-plugins-lua.c b/test/test-plugins-lua.c index 076efdc8..7f51190d 100644 --- a/test/test-plugins-lua.c +++ b/test/test-plugins-lua.c @@ -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,