evdev: verify fd via cached devnum on FreeBSD

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 <quentin.thebault@defenso.fr>
Sponsored-by: Defenso
Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1471>
This commit is contained in:
Quentin Thébault 2026-06-17 08:30:28 +02:00
parent b12bc3b4bd
commit 4c7f7b1e5c

View file

@ -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