HID: bpf: allow to change the report descriptor
authorBenjamin Tissoires <benjamin.tissoires@redhat.com>
Thu, 3 Nov 2022 15:57:51 +0000 (16:57 +0100)
committerJiri Kosina <jkosina@suse.cz>
Tue, 15 Nov 2022 15:28:39 +0000 (16:28 +0100)
Add a new tracepoint hid_bpf_rdesc_fixup() so we can trigger a
report descriptor fixup in the bpf world.

Whenever the program gets attached/detached, the device is reconnected
meaning that userspace will see it disappearing and reappearing with
the new report descriptor.

Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
drivers/hid/bpf/hid_bpf_dispatch.c
drivers/hid/bpf/hid_bpf_dispatch.h
drivers/hid/bpf/hid_bpf_jmp_table.c
drivers/hid/hid-core.c
include/linux/hid_bpf.h

index 0fe856d..c3920c7 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/hid_bpf.h>
 #include <linux/init.h>
 #include <linux/kfifo.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/workqueue.h>
 #include "hid_bpf_dispatch.h"
@@ -86,6 +87,65 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
 EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
 
 /**
+ * hid_bpf_rdesc_fixup - Called when the probe function parses the report
+ * descriptor of the HID device
+ *
+ * @ctx: The HID-BPF context
+ *
+ * @return 0 on success and keep processing; a positive value to change the
+ * incoming size buffer; a negative error code to interrupt the processing
+ * of this event
+ *
+ * Declare an %fmod_ret tracing bpf program to this function and attach this
+ * program through hid_bpf_attach_prog() to have this helper called before any
+ * parsing of the report descriptor by HID.
+ */
+/* never used by the kernel but declared so we can load and attach a tracepoint */
+__weak noinline int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
+{
+       return 0;
+}
+ALLOW_ERROR_INJECTION(hid_bpf_rdesc_fixup, ERRNO);
+
+u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
+{
+       int ret;
+       struct hid_bpf_ctx_kern ctx_kern = {
+               .ctx = {
+                       .hid = hdev,
+                       .size = *size,
+                       .allocated_size = HID_MAX_DESCRIPTOR_SIZE,
+               },
+       };
+
+       ctx_kern.data = kzalloc(ctx_kern.ctx.allocated_size, GFP_KERNEL);
+       if (!ctx_kern.data)
+               goto ignore_bpf;
+
+       memcpy(ctx_kern.data, rdesc, min_t(unsigned int, *size, HID_MAX_DESCRIPTOR_SIZE));
+
+       ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_RDESC_FIXUP, &ctx_kern);
+       if (ret < 0)
+               goto ignore_bpf;
+
+       if (ret) {
+               if (ret > ctx_kern.ctx.allocated_size)
+                       goto ignore_bpf;
+
+               *size = ret;
+       }
+
+       rdesc = krealloc(ctx_kern.data, *size, GFP_KERNEL);
+
+       return rdesc;
+
+ ignore_bpf:
+       kfree(ctx_kern.data);
+       return kmemdup(rdesc, *size, GFP_KERNEL);
+}
+EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);
+
+/**
  * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
  *
  * @ctx: The HID-BPF context
@@ -176,6 +236,14 @@ static int hid_bpf_allocate_event_data(struct hid_device *hdev)
        return __hid_bpf_allocate_data(hdev, &hdev->bpf.device_data, &hdev->bpf.allocated_data);
 }
 
+int hid_bpf_reconnect(struct hid_device *hdev)
+{
+       if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
+               return device_reprobe(&hdev->dev);
+
+       return 0;
+}
+
 /**
  * hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
  *
@@ -217,7 +285,17 @@ hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
                        return err;
        }
 
-       return __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
+       err = __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
+       if (err)
+               return err;
+
+       if (prog_type == HID_BPF_PROG_TYPE_RDESC_FIXUP) {
+               err = hid_bpf_reconnect(hdev);
+               if (err)
+                       return err;
+       }
+
+       return 0;
 }
 
 /**
index e2d64fa..2eeab1f 100644 (file)
@@ -18,6 +18,7 @@ int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_t
 void __hid_bpf_destroy_device(struct hid_device *hdev);
 int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
                     struct hid_bpf_ctx_kern *ctx_kern);
+int hid_bpf_reconnect(struct hid_device *hdev);
 
 struct bpf_prog;
 
index bfcdd7b..579a6c0 100644 (file)
@@ -58,12 +58,15 @@ static DECLARE_WORK(release_work, hid_bpf_release_progs);
 
 BTF_ID_LIST(hid_bpf_btf_ids)
 BTF_ID(func, hid_bpf_device_event)                     /* HID_BPF_PROG_TYPE_DEVICE_EVENT */
+BTF_ID(func, hid_bpf_rdesc_fixup)                      /* HID_BPF_PROG_TYPE_RDESC_FIXUP */
 
 static int hid_bpf_max_programs(enum hid_bpf_prog_type type)
 {
        switch (type) {
        case HID_BPF_PROG_TYPE_DEVICE_EVENT:
                return HID_BPF_MAX_PROGS_PER_DEV;
+       case HID_BPF_PROG_TYPE_RDESC_FIXUP:
+               return 1;
        default:
                return -EINVAL;
        }
@@ -233,6 +236,10 @@ static void hid_bpf_release_progs(struct work_struct *work)
                                if (next->hdev == hdev && next->type == type)
                                        next->hdev = NULL;
                        }
+
+                       /* if type was rdesc fixup, reconnect device */
+                       if (type == HID_BPF_PROG_TYPE_RDESC_FIXUP)
+                               hid_bpf_reconnect(hdev);
                }
        }
 
index 6bf47e3..fa4436b 100644 (file)
@@ -1218,7 +1218,8 @@ int hid_open_report(struct hid_device *device)
                return -ENODEV;
        size = device->dev_rsize;
 
-       buf = kmemdup(start, size, GFP_KERNEL);
+       /* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
+       buf = call_hid_bpf_rdesc_fixup(device, start, &size);
        if (buf == NULL)
                return -ENOMEM;
 
index 1d24b72..9b11f8f 100644 (file)
@@ -74,6 +74,7 @@ enum hid_bpf_attach_flags {
 
 /* Following functions are tracepoints that BPF programs can attach to */
 int hid_bpf_device_event(struct hid_bpf_ctx *ctx);
+int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx);
 
 /* Following functions are kfunc that we export to BPF programs */
 /* available everywhere in HID-BPF */
@@ -100,6 +101,7 @@ int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx);
 enum hid_bpf_prog_type {
        HID_BPF_PROG_TYPE_UNDEF = -1,
        HID_BPF_PROG_TYPE_DEVICE_EVENT,                 /* an event is emitted from the device */
+       HID_BPF_PROG_TYPE_RDESC_FIXUP,
        HID_BPF_PROG_TYPE_MAX,
 };
 
@@ -143,6 +145,7 @@ int hid_bpf_connect_device(struct hid_device *hdev);
 void hid_bpf_disconnect_device(struct hid_device *hdev);
 void hid_bpf_destroy_device(struct hid_device *hid);
 void hid_bpf_device_init(struct hid_device *hid);
+u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size);
 #else /* CONFIG_HID_BPF */
 static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
                                                u8 *data, u32 *size, int interrupt) { return NULL; }
@@ -150,6 +153,11 @@ static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
 static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
 static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
 static inline void hid_bpf_device_init(struct hid_device *hid) {}
+static inline u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
+{
+       return kmemdup(rdesc, *size, GFP_KERNEL);
+}
+
 #endif /* CONFIG_HID_BPF */
 
 #endif /* __HID_BPF_H */