[Git][xorg-team/lib/libinput][debian-unstable] 7 commits: lua: separate the API from the metatables

"Timo Aaltonen (@tjaalton)" <[email protected]>
Newsgroups gmane.linux.debian.devel.x
Message-ID <[email protected]>

Timo Aaltonen pushed to branch debian-unstable at X Strike Force / lib / libinput


Commits:
45506c7b by Peter Hutterer at 2026-04-02T10:17:52+10:00
lua: separate the API from the metatables

Previously we had one vtable for the libinputplugin and EvdevDevice
objects. This allowed plugins to call __gc(), a decidedly internal
method.

This fixes a use-after-free: A plugin that called EvdevDevice::__gc()
frees the plugin's copy of device->name but leaves the pointer in-place,
a subsequent call will thus cause a UAF read.

Fix this by separating what is the object's metatable from the public
methods that are accessible to a plugin.

CVE-2026-35094

Fixes: #1272

Found-by: Koen Tange <[email protected]>

(cherry picked from commit 45dfd0f0301af855f068df27b2e40cc9f5713acd)

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1461>

- - - - -
49f9a815 by Peter Hutterer at 2026-04-02T10:18:00+10:00
lua: force text mode for loading plugins

luaL_loadfile() by default allows for both text files and precompiled
lua files. Precompiled files are not verified on load allowing for a
sandbox escape.

CVE-2026-35093

Fixes: #1271

Found-by: Koen Tange <[email protected]>

(cherry picked from commit 356c498fd4ba25ec99f6866fc96847ec3d1f16bf)

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1461>

- - - - -
19206869 by Peter Hutterer at 2026-04-02T11:04:12+10:00
libinput 1.31.1

Signed-off-by: Peter Hutterer <[email protected]>

- - - - -
3b10f1fa by Timo Aaltonen at 2026-04-02T22:19:35+03:00
Merge branch 'upstream-unstable' into debian-unstable

- - - - -
3f108ac7 by Timo Aaltonen at 2026-04-02T22:20:38+03:00
version bump

- - - - -
14966aa8 by Timo Aaltonen at 2026-04-02T22:24:42+03:00
Add quirks support for Goodix touchpad 27C6:0F96 and 27C6:0F90

- - - - -
dcb3df27 by Timo Aaltonen at 2026-04-02T22:25:03+03:00
releasing package libinput version 1.31.1-1

- - - - -


7 changed files:

- debian/changelog
- + debian/patches/0001-Add-Goodix-haptic-touchpad-27C6-0F90-support.patch
- + debian/patches/0002-quirks-add-support-for-Goodix-touchpad-27C6-0F96.patch
- debian/patches/series
- meson.build
- src/libinput-plugin-lua.c
- test/test-plugins-lua.c


Changes:

=====================================
debian/changelog
=====================================
@@ -1,3 +1,14 @@
+libinput (1.31.1-1) unstable; urgency=medium
+
+  * New upstream release. (Closes: #1132550)
+    - CVE-2026-35093
+    - CVE-2026-35094
+  * Add quirks support for Goodix touchpad 27C6:0F96 and 27C6:0F90 (LP: #2146518)
+    d/p/0001-Add-Goodix-haptic-touchpad-27C6-0F90-support.patch
+    d/p/0002-quirks-add-support-for-Goodix-touchpad-27C6-0F96.patch
+
+ -- Timo Aaltonen <[email protected]>  Thu, 02 Apr 2026 22:24:51 +0300
+
 libinput (1.31.0-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
debian/patches/0001-Add-Goodix-haptic-touchpad-27C6-0F90-support.patch
=====================================
@@ -0,0 +1,29 @@
+From: Freeman Zhang <[email protected]>
+Date: Sun, 22 Mar 2026 16:19:47 +0800
+Subject: [PATCH] Add Goodix haptic touchpad 27C6:0F90 support
+
+Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1451>
+
+Origin: upstream, https://gitlab.freedesktop.org/libinput/libinput/-/commit/a1c5f35a69493a16354391b3dfbac4ad35e3bcab
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/2146518
+Signed-off-by: Bin Li <[email protected]>
+---
+ quirks/30-vendor-goodix.quirks | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+diff --git a/quirks/30-vendor-goodix.quirks b/quirks/30-vendor-goodix.quirks
+index b7aab62..e294156 100644
+--- a/quirks/30-vendor-goodix.quirks
++++ b/quirks/30-vendor-goodix.quirks
+@@ -17,3 +17,11 @@ MatchVendor=0x27C6
+ MatchProduct=0x0F95
+ MatchUdevType=touchpad
+ AttrInputProp=+INPUT_PROP_PRESSUREPAD
++
++# "GXTP5420 Touchpad": pressure touchpad mostly used in Lenovo laptops.
++[Goodix Haptic Touchpad (27C6:0F90)]
++MatchBus=i2c
++MatchVendor=0x27C6
++MatchProduct=0x0F90
++MatchUdevType=touchpad
++AttrInputProp=+INPUT_PROP_PRESSUREPAD


=====================================
debian/patches/0002-quirks-add-support-for-Goodix-touchpad-27C6-0F96.patch
=====================================
@@ -0,0 +1,42 @@
+From: Richie Roy Jayme <[email protected]>
+Date: Wed, 25 Mar 2026 18:33:21 +0900
+Subject: [PATCH] quirks: add support for Goodix touchpad GXTP5400:00
+ 27C6:0F96
+
+This commit adds a specific vendor HWID for Goodix Haptic Touchpad to
+improve detection and handling.
+
+Signed-off-by: Richie Roy Jayme <[email protected]>
+Signed-off-by: Richie Roy Jayme <[email protected]>
+Reviewed-by: Vishnu Sankar <[email protected]>
+Reviewed-by: Vishnu Sankar <[email protected]>
+Tested-by: Ameer Ivan Julkarnain <[email protected]>
+Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1455>
+
+Origin: backport, https://gitlab.freedesktop.org/libinput/libinput/-/commit/baddf1e2b6db43d4e726d58a940f7720d9368c57
+Bug-Ubuntu: https://bugs.launchpad.net/bugs/2146518
+Signed-off-by: Bin Li <[email protected]>
+---
+ quirks/30-vendor-goodix.quirks | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/quirks/30-vendor-goodix.quirks b/quirks/30-vendor-goodix.quirks
+index e294156..53b0391 100644
+--- a/quirks/30-vendor-goodix.quirks
++++ b/quirks/30-vendor-goodix.quirks
+@@ -9,6 +9,15 @@ MatchProduct=0x01E8
+ MatchUdevType=touchpad
+ AttrInputProp=+INPUT_PROP_PRESSUREPAD
+ 
++# "GXTP5400 Touchpad": pressure touchpad mostly used in Lenovo laptops.
++# GXTP5400:00 27C6:0F96 Touchpad
++[Goodix Haptic Touchpad (27C6:0F96)]
++MatchBus=i2c
++MatchVendor=0x27C6
++MatchProduct=0x0F96
++MatchUdevType=touchpad
++AttrInputProp=+INPUT_PROP_PRESSUREPAD
++
+ # "GXTP5420 Touchpad": pressure touchpad mostly used in Lenovo laptops.
+ # GXTP5420:00 27C6:0F95 Touchpad
+ [Goodix Haptic Touchpad (27C6:0F95)]


=====================================
debian/patches/series
=====================================
@@ -1,2 +1,4 @@
 #placeholder
 tools-remove-references-to-libinput_quir.patch
+0001-Add-Goodix-haptic-touchpad-27C6-0F90-support.patch
+0002-quirks-add-support-for-Goodix-touchpad-27C6-0F96.patch


=====================================
meson.build
=====================================
@@ -1,5 +1,5 @@
 project('libinput', 'c',
-	version : '1.31.0',
+	version : '1.31.1',
 	license : 'MIT/Expat',
 	default_options : [ 'c_std=gnu99', 'warning_level=2' ],
 	meson_version : '>= 0.64.0')


=====================================
src/libinput-plugin-lua.c
=====================================
@@ -562,6 +562,12 @@ libinputplugin_unregister(lua_State *L)
 	return luaL_error(L, "@@unregistering@@");
 }
 
+static int
+readonly_newindex(lua_State *L)
+{
+	return luaL_error(L, "attempt to modify a read-only table");
+}
+
 static int
 libinputplugin_gc(lua_State *L)
 {
@@ -673,7 +679,28 @@ libinputplugin_log_error(lua_State *L)
 	return libinputplugin_log(L, LIBINPUT_LOG_PRIORITY_ERROR);
 }
 
-static const struct luaL_Reg libinputplugin_vtable[] = {
+static void
+setup_vfuncs(lua_State *L,
+	     const char *metatable_name,
+	     const struct luaL_Reg *vfuncs,
+	     const struct luaL_Reg *public_methods)
+{
+	luaL_newmetatable(L, metatable_name);
+	luaL_setfuncs(L, vfuncs, 0);
+
+	lua_newtable(L);
+	luaL_setfuncs(L, public_methods, 0);
+	lua_setfield(L, -2, "__index");
+
+	/* set metatable.__metatable = false to prevent a script from getmetatable(),
+	   which is blocked anyway but safe and sorry and whatnot */
+	lua_pushboolean(L, 0);
+	lua_setfield(L, -2, "__metatable");
+
+	lua_pop(L, 1);
+}
+
+static const struct luaL_Reg libinputplugin_methods[] = {
 	{ "now", libinputplugin_now },
 	{ "version", libinputplugin_version },
 	{ "connect", libinputplugin_connect },
@@ -685,18 +712,18 @@ static const struct luaL_Reg libinputplugin_vtable[] = {
 	{ "log_debug", libinputplugin_log_debug },
 	{ "log_info", libinputplugin_log_info },
 	{ "log_error", libinputplugin_log_error },
-	{ "__gc", libinputplugin_gc },
 	{ NULL, NULL }
 };
 
+static const struct luaL_Reg libinputplugin_meta[] = { { "__gc", libinputplugin_gc },
+						       { "__newindex",
+							 readonly_newindex },
+						       { NULL, NULL } };
+
 static void
 libinputplugin_init(lua_State *L)
 {
-	luaL_newmetatable(L, PLUGIN_METATABLE);
-	lua_pushstring(L, "__index");
-	lua_pushvalue(L, -2); /* push metatable */
-	lua_settable(L, -3);  /* metatable.__index = metatable */
-	luaL_setfuncs(L, libinputplugin_vtable, 0);
+	setup_vfuncs(L, PLUGIN_METATABLE, libinputplugin_meta, libinputplugin_methods);
 }
 
 static int
@@ -1073,7 +1100,7 @@ evdevdevice_gc(lua_State *L)
 	return 0;
 }
 
-static const struct luaL_Reg evdevdevice_vtable[] = {
+static const struct luaL_Reg evdevdevice_methods[] = {
 	{ "info", evdevdevice_info },
 	{ "name", evdevdevice_name },
 	{ "usages", evdevdevice_usages },
@@ -1087,18 +1114,17 @@ static const struct luaL_Reg evdevdevice_vtable[] = {
 	{ "prepend_frame", evdevdevice_prepend_frame },
 	{ "append_frame", evdevdevice_append_frame },
 	{ "disable_feature", evdevdevice_disable_feature },
-	{ "__gc", evdevdevice_gc },
 	{ NULL, NULL }
 };
 
+static const struct luaL_Reg evdevdevice_meta[] = { { "__gc", evdevdevice_gc },
+						    { "__newindex", readonly_newindex },
+						    { NULL, NULL } };
+
 static void
 evdevdevice_init(lua_State *L)
 {
-	luaL_newmetatable(L, EVDEV_DEVICE_METATABLE);
-	lua_pushstring(L, "__index");
-	lua_pushvalue(L, -2); /* push metatable */
-	lua_settable(L, -3);  /* metatable.__index = metatable */
-	luaL_setfuncs(L, evdevdevice_vtable, 0);
+	setup_vfuncs(L, EVDEV_DEVICE_METATABLE, evdevdevice_meta, evdevdevice_methods);
 }
 
 static void
@@ -1336,7 +1362,7 @@ libinput_lua_plugin_new_from_path(struct libinput *libinput, const char *path)
 		return NULL;
 	}
 
-	int ret = luaL_loadfile(L, path);
+	int ret = luaL_loadfilex(L, path, "t");
 	if (ret == LUA_OK) {
 		plugin->L = steal(&L);
 


=====================================
test/test-plugins-lua.c
=====================================
@@ -526,6 +526,38 @@ START_TEST(lua_disallowed_functions)
 }
 END_TEST
 
+START_TEST(lua_gc_not_accessible)
+{
+	_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
+	const char *lua =
+		"libinput:register({1})\n"
+		"assert(libinput.__gc == nil)\n"
+		"function check_device_gc(device)\n"
+		"  assert(device.__gc == nil)\n"
+		"  libinput:log_info(\"gc_not_accessible: ok\")\n"
+		"end\n"
+		"libinput:connect(\"new-evdev-device\", check_device_gc)\n";
+
+	_autofree_ char *path = litest_write_plugin(tmpdir->path, lua);
+	_litest_context_destroy_ struct libinput *li =
+		litest_create_context_with_plugindir(tmpdir->path);
+	if (libinput_log_get_priority(li) > LIBINPUT_LOG_PRIORITY_INFO)
+		libinput_log_set_priority(li, LIBINPUT_LOG_PRIORITY_INFO);
+
+	litest_with_logcapture(li, capture) {
+		libinput_plugin_system_load_plugins(li,
+						    LIBINPUT_PLUGIN_SYSTEM_FLAG_NONE);
+		litest_drain_events(li);
+
+		_destroy_(litest_device) *device = litest_add_device(li, LITEST_MOUSE);
+		litest_drain_events(li);
+
+		litest_assert_logcapture_no_errors(capture);
+		litest_assert_strv_substring(capture->infos, "gc_not_accessible: ok");
+	}
+}
+END_TEST
+
 START_TEST(lua_frame_handler)
 {
 	_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
@@ -1172,10 +1204,79 @@ START_TEST(lua_remove_plugin_on_timeout)
 }
 END_TEST
 
+/* Pre-compiled Lua 5.4 bytecode for the following source:
+ *
+ *   libinput:register({1})
+ *   libinput:connect("new-evdev-device", function(device)
+ *       libinput:log_info("loaded from binary lua file")
+ *   end)
+ *
+ * To regenerate:
+ *   luac5.4 -o /dev/stdout /tmp/plugin.lua | xxd -i
+ */
+static const unsigned char binary_lua_plugin[] = {
+	0x1b, 0x4c, 0x75, 0x61, 0x54, 0x00, 0x19, 0x93, 0x0d, 0x0a, 0x1a, 0x0a, 0x04,
+	0x08, 0x08, 0x78, 0x56, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x28, 0x77, 0x40, 0x01, 0x9b, 0x40, 0x74, 0x65, 0x73, 0x74, 0x2f,
+	0x31, 0x30, 0x2d, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x2d, 0x70, 0x6c, 0x75,
+	0x67, 0x69, 0x6e, 0x2e, 0x6c, 0x75, 0x61, 0x80, 0x80, 0x00, 0x01, 0x04, 0x8e,
+	0x51, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x01, 0x13,
+	0x01, 0x00, 0x01, 0x52, 0x00, 0x00, 0x00, 0x81, 0x01, 0x00, 0x80, 0x4e, 0x01,
+	0x01, 0x00, 0x44, 0x00, 0x03, 0x01, 0x0b, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00,
+	0x02, 0x03, 0x81, 0x01, 0x00, 0xcf, 0x01, 0x00, 0x00, 0x44, 0x00, 0x04, 0x01,
+	0x46, 0x00, 0x01, 0x01, 0x84, 0x04, 0x89, 0x6c, 0x69, 0x62, 0x69, 0x6e, 0x70,
+	0x75, 0x74, 0x04, 0x89, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x04,
+	0x88, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x04, 0x91, 0x6e, 0x65, 0x77,
+	0x2d, 0x65, 0x76, 0x64, 0x65, 0x76, 0x2d, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65,
+	0x81, 0x01, 0x00, 0x00, 0x81, 0x80, 0x8d, 0x8f, 0x01, 0x00, 0x04, 0x85, 0x8b,
+	0x00, 0x00, 0x00, 0x94, 0x80, 0x01, 0x01, 0x83, 0x01, 0x01, 0x00, 0xc4, 0x00,
+	0x03, 0x01, 0xc7, 0x00, 0x01, 0x00, 0x83, 0x04, 0x89, 0x6c, 0x69, 0x62, 0x69,
+	0x6e, 0x70, 0x75, 0x74, 0x04, 0x89, 0x6c, 0x6f, 0x67, 0x5f, 0x69, 0x6e, 0x66,
+	0x6f, 0x04, 0x9c, 0x6c, 0x6f, 0x61, 0x64, 0x65, 0x64, 0x20, 0x66, 0x72, 0x6f,
+	0x6d, 0x20, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x6c, 0x75, 0x61, 0x20,
+	0x66, 0x69, 0x6c, 0x65, 0x81, 0x00, 0x00, 0x00, 0x80, 0x85, 0x01, 0x00, 0x00,
+	0x00, 0x01, 0x80, 0x81, 0x87, 0x64, 0x65, 0x76, 0x69, 0x63, 0x65, 0x80, 0x85,
+	0x81, 0x85, 0x5f, 0x45, 0x4e, 0x56, 0x8e, 0x01, 0x0b, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x01, 0x00, 0x00, 0x02, 0xfe, 0x02, 0x80, 0x80, 0x81, 0x85, 0x5f,
+	0x45, 0x4e, 0x56,
+};
+
+START_TEST(lua_reject_precompiled_files)
+{
+	_destroy_(tmpdir) *tmpdir = tmpdir_create(NULL);
+
+	/* Write the binary bytecode to a .lua file in the tmpdir.
+	 * Binary (pre-compiled) Lua files must be rejected by the
+	 * plugin loader for security reasons. */
+	_autofree_ char *path = strdup_printf("%s/10-binary-plugin.lua", tmpdir->path);
+	_autoclose_ int fd = open(path, O_WRONLY | O_CREAT, 0644);
+	litest_assert_errno_success(fd);
+
+	ssize_t written = write(fd, binary_lua_plugin, sizeof(binary_lua_plugin));
+	litest_assert_int_eq((int)written, (int)sizeof(binary_lua_plugin));
+	fsync(fd);
+
+	_litest_context_destroy_ struct libinput *li =
+		litest_create_context_with_plugindir(tmpdir->path);
+
+	litest_with_logcapture(li, capture) {
+		libinput_plugin_system_load_plugins(li,
+						    LIBINPUT_PLUGIN_SYSTEM_FLAG_NONE);
+		litest_drain_events(li);
+
+		size_t index = 0;
+		litest_assert(
+			strv_find_substring(capture->errors, "Failed to load", &index));
+		litest_assert_str_in(path, capture->errors[index]);
+	}
+}
+END_TEST
+
 TEST_COLLECTION(lua)
 {
 	/* clang-format off */
 	litest_add_no_device(lua_load_failure);
+	litest_add_no_device(lua_reject_precompiled_files);
 	litest_with_parameters(params,
 			       "content", 'I', 6,
 					litest_named_i32(EMPTY),
@@ -1219,6 +1320,7 @@ TEST_COLLECTION(lua)
 	litest_add_no_device(lua_register_multiversions);
 	litest_add_no_device(lua_allowed_functions);
 	litest_add_no_device(lua_disallowed_functions);
+	litest_add_no_device(lua_gc_not_accessible);
 
 	litest_add_no_device(lua_frame_handler);
 	litest_add_no_device(lua_device_info);



View it on GitLab: https://salsa.debian.org/xorg-team/lib/libinput/-/compare/c264d27be4ecd2c69555a96da6236bf7e5900548...dcb3df2705bbc80b3d30b1aa315851aa897fa677

-- 
View it on GitLab: https://salsa.debian.org/xorg-team/lib/libinput/-/compare/c264d27be4ecd2c69555a96da6236bf7e5900548...dcb3df2705bbc80b3d30b1aa315851aa897fa677
You're receiving this email because of your account on salsa.debian.org.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.