-
Notifications
You must be signed in to change notification settings - Fork 458
Description
When using hid_get_input_report on Linux, the first byte of the buffer containing the report ID gets overwritten by the report first byte of data while it should not (spec)
Here is a simple way to reproduce the bug with a uhid virtual device
test_dev.c
#include <fcntl.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/ioctl.h>
#include <linux/uhid.h>
#include <errno.h>
static volatile uint32_t done;
static unsigned char rdesc[] = {
0x06, 0x02, 0xFF, /* Usage Page (FF02h), */
0x09, 0x00, /* Usage (00h), */
0xA1, 0x01, /* Collection (Application), */
0x09, 0x01, /* Usage (01h), */
0xA1, 0x02, /* Collection (Logical), */
0x85, 0x01, /* Report ID (1), */
0x09, 0x02, /* Usage (02h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x88, /* Report Count (136), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x07, /* Usage (07h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x01, /* Logical Maximum (1), */
0x75, 0x01, /* Report Size (1), */
0x95, 0x10, /* Report Count (16), */
0x81, 0x02, /* Input (Variable), */
0x09, 0x03, /* Usage (03h), */
0x15, 0x00, /* Logical Minimum (0), */
0x25, 0x0F, /* Logical Maximum (15), */
0x75, 0x04, /* Report Size (4), */
0x95, 0x06, /* Report Count (6), */
0x81, 0x02, /* Input (Variable), */
0xC0, /* End Collection, */
0xC0 /* End Collection */
};
void sighndlr(int signal)
{
done = 1;
printf("\n");
}
static int uhid_write(int fd, const struct uhid_event *ev)
{
ssize_t ret;
ret = write(fd, ev, sizeof(*ev));
if (ret < 0) {
fprintf(stderr, "Cannot write to uhid: %m\n");
return -errno;
} else if (ret != sizeof(*ev)) {
fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n",
ret, sizeof(ev));
return -EFAULT;
} else {
return 0;
}
}
static int create(int fd)
{
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = UHID_CREATE;
strcpy((char*)ev.u.create.name, "test-uhid-device");
ev.u.create.rd_data = rdesc;
ev.u.create.rd_size = sizeof(rdesc);
ev.u.create.bus = BUS_USB;
ev.u.create.vendor = 0xDEAD;
ev.u.create.product = 0xBEAF;
ev.u.create.version = 0;
ev.u.create.country = 0;
return uhid_write(fd, &ev);
}
static void destroy(int fd)
{
struct uhid_event ev;
memset(&ev, 0, sizeof(ev));
ev.type = UHID_DESTROY;
uhid_write(fd, &ev);
}
static void handle_report(struct uhid_event *ev, int fd)
{
if (ev->u.get_report.rtype != UHID_START)
return;
struct uhid_event rep;
memset(&rep, 0, sizeof(rep));
rep.type = UHID_GET_REPORT_REPLY;
rep.u.get_report_reply.id = ev->u.get_report.id;
rep.u.get_report_reply.err = 0;
rep.u.get_report_reply.size = 16;
memset(rep.u.get_report_reply.data, 55, UHID_DATA_MAX);
uhid_write(fd, &rep);
return;
}
static int event(int fd)
{
struct uhid_event ev;
ssize_t ret;
memset(&ev, 0, sizeof(ev));
ret = read(fd, &ev, sizeof(ev));
if (ret == 0) {
fprintf(stderr, "Read HUP on uhid-cdev\n");
return -EFAULT;
} else if (ret < 0) {
fprintf(stderr, "Cannot read uhid-cdev: %m\n");
return -errno;
} else if (ret != sizeof(ev)) {
fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n",
ret, sizeof(ev));
return -EFAULT;
}
switch (ev.type) {
case UHID_START:
break;
case UHID_STOP:
break;
case UHID_OPEN:
break;
case UHID_CLOSE:
case UHID_OUTPUT:
break;
case UHID_OUTPUT_EV:
break;
case UHID_GET_REPORT:
handle_report(&ev, fd);
break;
default:
fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type);
}
return 0;
}
int main(int argc, char **argv)
{
signal(SIGINT, sighndlr);
// UHID
int fd = open("/dev/uhid", O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
fprintf(stderr, "Create uhid device\n");
if (create(fd)) {
close(fd);
return EXIT_FAILURE;
}
struct pollfd pfds;
pfds.fd = fd;
pfds.events = POLLIN;
while(!done) {
int ret = poll(&pfds, 1, 10);
if (ret < 0) {
fprintf(stderr, "Cannot poll for fds: %m\n");
break;
}
if (pfds.revents & POLLHUP) {
fprintf(stderr, "Received HUP on uhid-cdev\n");
break;
}
if (pfds.revents & POLLIN) {
ret = event(fd);
if (ret)
break;
}
}
destroy(fd);
return 0;
}test_get_report.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hidapi/hidapi.h>
#define VENDOR_ID 0xdead
#define PRODUCT_ID 0xbeaf
int main()
{
int res;
unsigned char buf[255];
hid_device *handle;
res = hid_init();
if (res != 0) {
printf("hid_init failed: %ls\n", hid_error(NULL));
return -1;
}
handle = hid_open(VENDOR_ID, PRODUCT_ID, NULL);
if (!handle) {
printf("Failed to open UHID device: %ls\n", hid_error(NULL));
return -1;
}
buf[0] = 42;
int bytesRead = hid_get_input_report(handle, buf, 16);
fprintf(stderr, "Report received for 42\n");
for (int i = 0; i < 16; i++)
{
printf("%d ", buf[i]);
}
printf("\n");
hid_close(handle);
hid_exit();
return 0;
}With the two previous files in the current directory, run the following commands
sudo modprobe uhid
gcc test_dev.c -lhidapi-hidraw -o testdev && sudo ./testdev &
gcc test_get_report.c -lhidapi-hidraw -o testhid_report && sudo ./testhid_reportExpected output
Report received for 42
42 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55
Actual output
Report received for 42
55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55
Version
libhidapi-hidraw: 0.11.2
Linux Kernel: 6.1.11
I'd be happy to help addressing the bug once I get confirmation on it. At first glance, it feels like the fix may have to go in the kernel driver (drivers/hid/hid-core.c or drivers/hid/hidraw.c) as I cannot see any ways that would allow to make a bug fix without a memmove on the library side. But since the name of Alan Ott is mentioned at a few places on the kernel sources/docs, I thought I would raise the bug first before escalating it :)