joystick: Use inotify to detect joystick unplug if not using udev
This improves SDL's ability to detect joystick hotplug in a container environment. We cannot reliably receive events from udev in a container, because they are delivered as netlink events, which are authenticated by their uid being 0. However, in a user namespace created by an unprivileged user (for example bubblewrap, as used by Flatpak and Steam's pressure-vessel-wrap), the kernel does not allow us to map uid 0, and the netlink events appear to be from the kernel's overflowuid (typically 65534/nobody), meaning libudev cannot distinguish between genuine uevents from udevd and an attack by a malicious local user. Signed-off-by: Simon McVittie <smcv@collabora.com>
parent
aae53d5972
commit
b0eba1c55d
|
@ -32,6 +32,7 @@
|
|||
#include <errno.h> /* errno, strerror */
|
||||
#include <fcntl.h>
|
||||
#include <limits.h> /* For the definition of PATH_MAX */
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
|
@ -40,6 +41,7 @@
|
|||
#include "SDL_assert.h"
|
||||
#include "SDL_hints.h"
|
||||
#include "SDL_joystick.h"
|
||||
#include "SDL_log.h"
|
||||
#include "SDL_endian.h"
|
||||
#include "SDL_timer.h"
|
||||
#include "../../events/SDL_events_c.h"
|
||||
|
@ -92,16 +94,10 @@ typedef enum
|
|||
ENUMERATION_FALLBACK
|
||||
} EnumerationMethod;
|
||||
|
||||
#if SDL_USE_LIBUDEV
|
||||
static EnumerationMethod enumeration_method = ENUMERATION_UNSET;
|
||||
#else
|
||||
const EnumerationMethod enumeration_method = ENUMERATION_FALLBACK;
|
||||
#endif
|
||||
|
||||
static int MaybeAddDevice(const char *path);
|
||||
#if SDL_USE_LIBUDEV
|
||||
static int MaybeRemoveDevice(const char *path);
|
||||
#endif /* SDL_USE_LIBUDEV */
|
||||
|
||||
/* A linked list of available joysticks */
|
||||
typedef struct SDL_joylist_item
|
||||
|
@ -121,6 +117,7 @@ typedef struct SDL_joylist_item
|
|||
static SDL_joylist_item *SDL_joylist = NULL;
|
||||
static SDL_joylist_item *SDL_joylist_tail = NULL;
|
||||
static int numjoysticks = 0;
|
||||
static int inotify_fd = -1;
|
||||
|
||||
static Uint32 last_joy_detect_time;
|
||||
static time_t last_input_dir_mtime;
|
||||
|
@ -188,7 +185,7 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid)
|
|||
char product_string[128];
|
||||
|
||||
/* When udev is enabled we only get joystick devices here, so there's no need to test them */
|
||||
if (enumeration_method == ENUMERATION_FALLBACK && !GuessIsJoystick(fd)) {
|
||||
if (enumeration_method != ENUMERATION_LIBUDEV && !GuessIsJoystick(fd)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -503,6 +500,75 @@ static void SteamControllerDisconnectedCallback(int device_instance)
|
|||
}
|
||||
}
|
||||
|
||||
static int
|
||||
StrHasPrefix(const char *string, const char *prefix)
|
||||
{
|
||||
return (SDL_strncmp(string, prefix, SDL_strlen(prefix)) == 0);
|
||||
}
|
||||
|
||||
static int
|
||||
StrIsInteger(const char *string)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (*string == '\0') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (p = string; *p != '\0'; p++) {
|
||||
if (*p < '0' || *p > '9') {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
LINUX_InotifyJoystickDetect(void)
|
||||
{
|
||||
union
|
||||
{
|
||||
struct inotify_event event;
|
||||
char storage[4096];
|
||||
char enough_for_inotify[sizeof (struct inotify_event) + NAME_MAX + 1];
|
||||
} buf;
|
||||
ssize_t bytes;
|
||||
size_t remain = 0;
|
||||
size_t len;
|
||||
|
||||
bytes = read(inotify_fd, &buf, sizeof (buf));
|
||||
|
||||
if (bytes > 0) {
|
||||
remain = (size_t) bytes;
|
||||
}
|
||||
|
||||
while (remain > 0) {
|
||||
if (buf.event.len > 0) {
|
||||
if (StrHasPrefix(buf.event.name, "event") &&
|
||||
StrIsInteger(buf.event.name + strlen ("event"))) {
|
||||
char path[PATH_MAX];
|
||||
|
||||
SDL_snprintf(path, SDL_arraysize(path), "/dev/input/%s", buf.event.name);
|
||||
|
||||
if (buf.event.mask & (IN_CREATE | IN_MOVED_TO | IN_ATTRIB)) {
|
||||
MaybeAddDevice(path);
|
||||
}
|
||||
else if (buf.event.mask & (IN_DELETE | IN_MOVED_FROM)) {
|
||||
MaybeRemoveDevice(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
len = sizeof (struct inotify_event) + buf.event.len;
|
||||
remain -= len;
|
||||
|
||||
if (remain != 0) {
|
||||
memmove (&buf.storage[0], &buf.storage[len], remain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
LINUX_FallbackJoystickDetect(void)
|
||||
{
|
||||
|
@ -547,7 +613,10 @@ LINUX_JoystickDetect(void)
|
|||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (inotify_fd >= 0) {
|
||||
LINUX_InotifyJoystickDetect();
|
||||
}
|
||||
else {
|
||||
LINUX_FallbackJoystickDetect();
|
||||
}
|
||||
|
||||
|
@ -589,6 +658,10 @@ LINUX_JoystickInit(void)
|
|||
SDL_InitSteamControllers(SteamControllerConnectedCallback,
|
||||
SteamControllerDisconnectedCallback);
|
||||
|
||||
/* Force immediate joystick detection if using fallback */
|
||||
last_joy_detect_time = 0;
|
||||
last_input_dir_mtime = 0;
|
||||
|
||||
#if SDL_USE_LIBUDEV
|
||||
if (enumeration_method == ENUMERATION_LIBUDEV) {
|
||||
if (SDL_UDEV_Init() < 0) {
|
||||
|
@ -607,9 +680,28 @@ LINUX_JoystickInit(void)
|
|||
else
|
||||
#endif
|
||||
{
|
||||
/* Force immediate joystick detection */
|
||||
last_joy_detect_time = 0;
|
||||
last_input_dir_mtime = 0;
|
||||
inotify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
|
||||
|
||||
if (inotify_fd < 0) {
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
|
||||
"Unable to initialize inotify, falling back to polling: %s",
|
||||
strerror (errno));
|
||||
}
|
||||
else {
|
||||
/* We need to watch for attribute changes in addition to
|
||||
* creation, because when a device is first created, it has
|
||||
* permissions that we can't read. When udev chmods it to
|
||||
* something that we maybe *can* read, we'll get an
|
||||
* IN_ATTRIB event to tell us. */
|
||||
if (inotify_add_watch(inotify_fd, "/dev/input",
|
||||
IN_CREATE | IN_DELETE | IN_MOVE | IN_ATTRIB) < 0) {
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT,
|
||||
"Unable to add inotify watch, falling back to polling: %s",
|
||||
strerror (errno));
|
||||
}
|
||||
}
|
||||
|
||||
/* Report all devices currently present */
|
||||
LINUX_JoystickDetect();
|
||||
|
@ -1224,6 +1316,9 @@ LINUX_JoystickQuit(void)
|
|||
SDL_joylist_item *item = NULL;
|
||||
SDL_joylist_item *next = NULL;
|
||||
|
||||
close(inotify_fd);
|
||||
inotify_fd = -1;
|
||||
|
||||
for (item = SDL_joylist; item; item = next) {
|
||||
next = item->next;
|
||||
SDL_free(item->path);
|
||||
|
|
Loading…
Reference in New Issue