Format string vulnerability in evdev device handling
Summary
A format string vulnerability exists in the libinput
library, allowing a local attacker to achieve arbitrary code execution in the context of libinput
.
Affected versions
- Current git commit 395d12d6
- Latest release 1.20.0
Cause
Newly connected evdev
devices are logged using evdev_log_msg
.
The format
parameter is manipulated at src/evdev.h:785 to prepend (among other things) the device name.
The resulting string buf
is then passed as the format
parameter to log_msg_va
at src/evdev.h:796
In X.org (and probably other users of libinput
), this logging function eventually leads into the system's sprintf
.
If the device name contains printf
-style formatting placeholders such as %s
or %d
, these will be passed on to the new format string, and interpreted incorrectly. User-controlled format strings are a known security vulnerability, CWE-134, and can be used by an attacker to execute malicious code.
Impact
This vulnerability requires physical access or write access to /dev/uinput
, in order to introduce a new evdev
device to the system. The attacker gains execution under the same privileges as the process where libinput
is loaded. In the case of X.org, this may be root
. This vulnerability could, for example, be used to bypass screen lockers and login screens.
Steps to reproduce
OS: Ubuntu 20.04 LTS
- Create a hardware device whose device name is
%s%s%s%s%s%s%s
. This bug was discovered by renaming a pair of bluetooth headphones supporting AVRCP. - Start X.Org and pair or plug in the device.
- Observe a crash
Example
The following is a backtrace from a system running Ubuntu 20.04 LTS, where a pair of bluetooth headphones were connected.
[Current thread is 1 (Thread 0x7fc09775c500 (LWP 283870))]
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007fc097d51859 in __GI_abort () at abort.c:79
#2 0x0000558f266eb810 in OsAbort () at ../../../../os/utils.c:1351
#3 0x0000558f266f0c49 in AbortServer () at ../../../../os/log.c:872
#4 0x0000558f266f1aaa in FatalError (f=f@entry=0x558f26725310 "Caught signal %d (%s). Server aborting\n") at ../../../../os/log.c:1010
#5 0x0000558f266e8b59 in OsSigHandler (unused=<optimized out>, sip=0x7fff57a7daf0, signo=11) at ../../../../os/osinit.c:156
#6 OsSigHandler (signo=11, sip=0x7fff57a7daf0, unused=<optimized out>) at ../../../../os/osinit.c:110
#7 0x00007fc097f353c0 in <signal handler called> () at /lib/x86_64-linux-gnu/libpthread.so.0
#8 __strlen_avx2 () at ../sysdeps/x86_64/multiarch/strlen-avx2.S:65
#9 0x00007fc097da7d45 in __vfprintf_internal (s=s@entry=0x7fff57a7ec30, format=format@entry=0x7fff57a7f220 "event25 - %s%s%s%s%s%s%s%s (AVRCP): is tagged by udev as:%s%s%s%s%s%s%s%s%s%s%s\n", ap=ap@entry=0x7fff57a7f208, mode_flags=mode_flags@entry=2)
at vfprintf-internal.c:1688
#10 0x00007fc097dbafca in __vsnprintf_internal (string=0x7fff57a7edc5 "event25 - Keyboard (AVRCP): is tagged by udev as:", maxlen=<optimized out>,
maxlen@entry=1019, format=0x7fff57a7f220 "event25 - %s%s%s%s%s%s%s%s (AVRCP): is tagged by udev as:%s%s%s%s%s%s%s%s%s%s%s\n",
format@entry=0x3fb <error: Cannot access memory at address 0x3fb>, args=args@entry=0x7fff57a7f208, mode_flags=mode_flags@entry=2) at vsnprintf.c:114
#11 0x00007fc097e5cfc2 in ___vsnprintf_chk (s=<optimized out>, maxlen=maxlen@entry=1019, flag=flag@entry=1, slen=slen@entry=18446744073709551615, format=format@entry=0x3fb <error: Cannot access memory at address 0x3fb>, ap=ap@entry=0x7fff57a7f208)
at vsnprintf_chk.c:34
#12 0x0000558f266f06e7 in vsnprintf (__ap=0x7fff57a7f208, __fmt=0x3fb <error: Cannot access memory at address 0x3fb>, __n=1019, __s=<optimized out>) at /usr/include/x86_64-linux-gnu/bits/stdio2.h:80
#13 Xvscnprintf (args=0x7fff57a7f208, format=0x3fb <error: Cannot access memory at address 0x3fb>, n=1019, s=<optimized out>) at ../../../../os/xprintf.c:207
#14 Xvscnprintf (s=<optimized out>, n=1019, format=format@entry=0x7fff57a7f220 "event25 - %s%s%s%s%s%s%s%s (AVRCP): is tagged by udev as:%s%s%s%s%s%s%s%s%s%s%s\n", args=args@entry=0x7fff57a7f208) at ../../../../os/xprintf.c:202
#15 0x0000558f266f230b in LogVMessageVerb (type=<optimized out>, verb=3, format=0x7fff57a7f220 "event25 - %s%s%s%s%s%s%s%s (AVRCP): is tagged by udev as:%s%s%s%s%s%s%s%s%s%s%s\n", args=0x7fff57a7f208) at ../../../../os/log.c:720
#16 0x00007fc08884b084 in evdev_log_msg (device=device@entry=0x558f285ba530, priority=priority@entry=LIBINPUT_LOG_PRIORITY_INFO, format=format@entry=0x7fc088872d00 "is tagged by udev as:%s%s%s%s%s%s%s%s%s%s%s\n") at ../src/evdev.h:779
#17 0x00007fc08884e9a4 in evdev_configure_device (device=<optimized out>) at ../src/evdev.c:1774
#18 evdev_device_create (seat=seat@entry=0x558f27a8ef70, udev_device=udev_device@entry=0x558f281914b0) at ../src/evdev.c:2225
#19 0x00007fc088869ad6 in path_device_enable (input=0x558f27a8dc90, udev_device=0x558f281914b0, seat_logical_name_override=<optimized out>) at ../src/path-seat.c:191
#20 0x00007fc088869cb9 in path_create_device (libinput=0x558f27a8dc90, udev_device=0x558f281914b0, seat_name=0x0) at ../src/path-seat.c:268
#21 0x00007fc088869eab in libinput_path_add_device (libinput=0x558f27a8dc90, path=0x558f2816b210 "/dev/input/event25") at ../src/path-seat.c:399
...
Proposed mitigation
Non-literal format strings should be avoided in general, and sanitized when unavoidable.
evdev.h
contains two vulnerabilities following this pattern, evdev_log_msg
at :785 and evdev_log_msg_ratelimit
at :823. Compiler warnings about format strings have been suppressed in these locations.
If the prepending of device name is important, a different method is probably more suitable. For example, rendering the prefix separately from the log message and concatenating the two.
Resolution
This section will be updated as we go along:
Patches
-
0001-evdev-strip-the-device-name-of-format-directives.patch: v1, replacing%
with_
-
0001-evdev-strip-the-device-name-of-format-directives.patch: v2, replacing%
with%%
and splitting devname and prefix - 0001-evdev-strip-the-device-name-of-format-directives.patch: v3, using zalloc and fixing some other issues with the previous patch
Writeup
CVE: CVE-2022-1215
Embargo cancelled due to independent bug filed here: xorg/xserver#1280 (moved)
Proposed writeup:
Title: Format string vulnerability in libinput
Component: libinput, affecting all Wayland compositors and X.Org when using xf86-input-libinput
Report URL: https://gitlab.freedesktop.org/libinput/libinput/-/issues/752
Reporter: Albin Eldstål-Ahrens and Lukas Lamster
CVSS: 7.1 AV:L/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H/E:U/RL:O/RC:C
Disclosure date: CANCELLED DUE TO INDEPENDENT PUBLIC BUG
When a device is detected by libinput, libinput logs several messages through log handlers
set up by the callers. These log handlers usually eventually result in a printf call.
Logging happens with the privileges of the caller, in the case of Xorg this may be root.
The device name ends up as part of the format string and a kernel device with printf-style
format string placeholders in the device name can enable an attacker to run malicious code.
An exploit is possible through any device where the attacker controls the device name,
e.g. /dev/uinput or Bluetooth devices.
All versions of libinput since 1.10 (released Feb 2018) are affected.
Upstream patches are available as commit 2a8b8fde90d63d48ce09ddae44142674bbca1c28
libinput releases that include these patches are:
- 1.20.1
- 1.19.4
- 1.18.2
Releases of versions 1.17.x and earlier are not planned at this stage.
Many thanks to Albin Eldstål-Ahrens and Benjamin Svensson from Assured AB for their
discovery and responsible reporting of this issue.
This issue was independently discovered by Lukas Lamster. Many thanks for their discovery
and responsible reporting.