From 13e7d1a9583df4323ac78b3afebdc97f48baf42a Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Wed, 11 Nov 2020 19:14:11 -0800 Subject: [PATCH] joystick: Allow libudev to be disabled at runtime Device enumeration via libudev can fail in a container for two reasons: * the netlink protocol between udevd and libudev is considered private, so there is no API guarantee that the version of libudev in a container will understand netlink messages from a dissimilar version of udevd on the host system; * the netlink protocol between udevd and libudev relies for security on being able to check the uid of each message, but in a container with a user namespace where host uid 0 is not mapped, the libudev client cannot distinguish between messages from host uid 0 and messages from a different, malicious user on the host To make this easier to experiment with, always compile the fallback code path even if libudev is disabled. libudev remains the default if enabled at compile time, but the fallback code path can be forced. Signed-off-by: Simon McVittie --- src/joystick/linux/SDL_sysjoystick.c | 116 +++++++++++++++++++-------- 1 file changed, 81 insertions(+), 35 deletions(-) diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index e41e5d966..0d1079912 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -84,6 +84,19 @@ #define DEBUG_INPUT_EVENTS 1 #endif +typedef enum +{ + ENUMERATION_UNSET, + ENUMERATION_LIBUDEV, + 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); @@ -108,10 +121,8 @@ static SDL_joylist_item *SDL_joylist = NULL; static SDL_joylist_item *SDL_joylist_tail = NULL; static int numjoysticks = 0; -#if !SDL_USE_LIBUDEV static Uint32 last_joy_detect_time; static time_t last_input_dir_mtime; -#endif #define test_bit(nr, addr) \ (((1UL << ((nr) % (sizeof(long) * 8))) & ((addr)[(nr) / (sizeof(long) * 8)])) != 0) @@ -147,15 +158,8 @@ IsVirtualJoystick(Uint16 vendor, Uint16 product, Uint16 version, const char *nam #endif /* SDL_JOYSTICK_HIDAPI */ static int -IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid) +GuessIsJoystick(int fd) { - struct input_id inpid; - Uint16 *guid16 = (Uint16 *)guid->data; - char *name; - char product_string[128]; - -#if !SDL_USE_LIBUDEV - /* When udev is enabled we only get joystick devices here, so there's no need to test them */ unsigned long evbit[NBITS(EV_MAX)] = { 0 }; unsigned long keybit[NBITS(KEY_MAX)] = { 0 }; unsigned long absbit[NBITS(ABS_MAX)] = { 0 }; @@ -170,7 +174,22 @@ IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid) test_bit(ABS_X, absbit) && test_bit(ABS_Y, absbit))) { return 0; } -#endif + + return 1; +} + +static int +IsJoystick(int fd, char **name_return, SDL_JoystickGUID *guid) +{ + struct input_id inpid; + Uint16 *guid16 = (Uint16 *)guid->data; + char *name; + 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)) { + return 0; + } if (ioctl(fd, EVIOCGID, &inpid) < 0) { return 0; @@ -484,11 +503,8 @@ static void SteamControllerDisconnectedCallback(int device_instance) } static void -LINUX_JoystickDetect(void) +LINUX_FallbackJoystickDetect(void) { -#if SDL_USE_LIBUDEV - SDL_UDEV_Poll(); -#else const Uint32 SDL_JOY_DETECT_INTERVAL_MS = 3000; /* Update every 3 seconds */ Uint32 now = SDL_GetTicks(); @@ -519,7 +535,20 @@ LINUX_JoystickDetect(void) last_joy_detect_time = now; } +} + +static void +LINUX_JoystickDetect(void) +{ +#if SDL_USE_LIBUDEV + if (enumeration_method == ENUMERATION_LIBUDEV) { + SDL_UDEV_Poll(); + } + else #endif + { + LINUX_FallbackJoystickDetect(); + } HandlePendingRemovals(); @@ -529,6 +558,17 @@ LINUX_JoystickDetect(void) static int LINUX_JoystickInit(void) { +#if SDL_USE_LIBUDEV + if (enumeration_method == ENUMERATION_UNSET) { + if (SDL_getenv("SDL_JOYSTICK_DISABLE_UDEV") != NULL) { + enumeration_method = ENUMERATION_FALLBACK; + } + else { + enumeration_method = ENUMERATION_LIBUDEV; + } + } +#endif + /* First see if the user specified one or more joysticks to use */ if (SDL_getenv("SDL_JOYSTICK_DEVICE") != NULL) { char *envcopy, *envpath, *delim; @@ -549,26 +589,30 @@ LINUX_JoystickInit(void) SteamControllerDisconnectedCallback); #if SDL_USE_LIBUDEV - if (SDL_UDEV_Init() < 0) { - return SDL_SetError("Could not initialize UDEV"); + if (enumeration_method == ENUMERATION_LIBUDEV) { + if (SDL_UDEV_Init() < 0) { + return SDL_SetError("Could not initialize UDEV"); + } + + /* Set up the udev callback */ + if (SDL_UDEV_AddCallback(joystick_udev_callback) < 0) { + SDL_UDEV_Quit(); + return SDL_SetError("Could not set up joystick <-> udev callback"); + } + + /* Force a scan to build the initial device list */ + SDL_UDEV_Scan(); } - - /* Set up the udev callback */ - if (SDL_UDEV_AddCallback(joystick_udev_callback) < 0) { - SDL_UDEV_Quit(); - return SDL_SetError("Could not set up joystick <-> udev callback"); - } - - /* Force a scan to build the initial device list */ - SDL_UDEV_Scan(); -#else - /* Force immediate joystick detection */ - last_joy_detect_time = 0; - last_input_dir_mtime = 0; - - /* Report all devices currently present */ - LINUX_JoystickDetect(); + else #endif + { + /* Force immediate joystick detection */ + last_joy_detect_time = 0; + last_input_dir_mtime = 0; + + /* Report all devices currently present */ + LINUX_JoystickDetect(); + } return 0; } @@ -1191,8 +1235,10 @@ LINUX_JoystickQuit(void) numjoysticks = 0; #if SDL_USE_LIBUDEV - SDL_UDEV_DelCallback(joystick_udev_callback); - SDL_UDEV_Quit(); + if (enumeration_method == ENUMERATION_LIBUDEV) { + SDL_UDEV_DelCallback(joystick_udev_callback); + SDL_UDEV_Quit(); + } #endif SDL_QuitSteamControllers();