From 4c7f7b1e5c1e270e8be4108082f8e769a640bbbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quentin=20Th=C3=A9bault?= Date: Wed, 17 Jun 2026 08:30:28 +0200 Subject: [PATCH] evdev: verify fd via cached devnum on FreeBSD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit evdev_device_have_same_syspath() guards against opening the wrong device by re-resolving the fd's st_rdev through udev and comparing syspaths. On FreeBSD this works when the input device nodes are present in the caller's devfs, but not inside a jail whose devfs does not expose them: udev_device_new_from_devnum() then has nothing to resolve, and the check rejects every fd -- even fds that a seat manager (e.g. seatd) opened on the host and passed in. On FreeBSD the evdev syspath (/dev/input/eventN) is determined by the device's minor number, while the cached devnum carries both that minor and the evdev major. Comparing the full devnum against fstat(fd).st_rdev therefore verifies the same identity as the syspath comparison -- in fact slightly more strongly, since it also confirms the major -- without needing the device node to be visible to the caller. libudev-devd populates that cached devnum from the kern.evdev.input.N.devnum sysctl, which remains readable inside a jail. Guard the new path with __FreeBSD__ and move the existing udev-based lookup into the #else branch. The lookup is rewritten to return directly on each exit instead of using a shared goto label, because the FreeBSD branch returns before those locals are declared and would otherwise leave them (and the label) unused on a FreeBSD build. Linux behaviour is unchanged. Signed-off-by: Quentin Thébault Sponsored-by: Defenso Part-of: --- src/evdev.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/evdev.c b/src/evdev.c index 0043b11f..83b1b00e 100644 --- a/src/evdev.c +++ b/src/evdev.c @@ -2116,24 +2116,34 @@ evdev_notify_added_device(struct evdev_device *device) static bool evdev_device_have_same_syspath(struct udev_device *udev_device, int fd) { - struct udev *udev = udev_device_get_udev(udev_device); - struct udev_device *udev_device_new = NULL; struct stat st; - bool rc = false; if (fstat(fd, &st) < 0) - goto out; + return false; - udev_device_new = udev_device_new_from_devnum(udev, 'c', st.st_rdev); - if (!udev_device_new) - goto out; +#ifdef __FreeBSD__ + /* On FreeBSD the evdev syspath (/dev/input/eventN) is determined by + * the device's minor number, while the cached devnum carries both + * that minor and the evdev major. Comparing the full devnum against + * the fd's st_rdev therefore verifies the same identity as the + * syspath comparison -- in fact slightly more strongly, since it + * also confirms the major -- without needing the device node to be + * visible to the caller. libudev-devd populates that devnum from + * the kern.evdev.input.N.devnum sysctl, which is readable even in a + * jail whose devfs does not expose the input node. + */ + return udev_device_get_devnum(udev_device) == st.st_rdev; +#else + struct udev *udev = udev_device_get_udev(udev_device); + _unref_(udev_device) *udev_device_new = + udev_device_new_from_devnum(udev, 'c', st.st_rdev); + bool rc = false; - rc = streq(udev_device_get_syspath(udev_device_new), - udev_device_get_syspath(udev_device)); -out: if (udev_device_new) - udev_device_unref(udev_device_new); + rc = streq(udev_device_get_syspath(udev_device_new), + udev_device_get_syspath(udev_device)); return rc; +#endif } static bool