Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

HID: bpf: add HID-BPF hooks for hid_hw_raw_requests

This allows to intercept and prevent or change the behavior of
hid_hw_raw_request() from a bpf program.

The intent is to solve a couple of use case:
- firewalling a HID device: a firewall can monitor who opens the hidraw
nodes and then prevent or allow access to write operations on that
hidraw node.
- change the behavior of a device and emulate a new HID feature request

The hook is allowed to be run as sleepable so it can itself call
hid_bpf_hw_request(), which allows to "convert" one feature request into
another or even call the feature request on a different HID device on the
same physical device.

Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-4-cfd60fb6c79f@kernel.org
Acked-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>

+80
+38
drivers/hid/bpf/hid_bpf_dispatch.c
··· 74 74 } 75 75 EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event); 76 76 77 + int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, 78 + unsigned char reportnum, u8 *buf, 79 + u32 size, enum hid_report_type rtype, 80 + enum hid_class_request reqtype, 81 + u64 source) 82 + { 83 + struct hid_bpf_ctx_kern ctx_kern = { 84 + .ctx = { 85 + .hid = hdev, 86 + .allocated_size = size, 87 + .size = size, 88 + }, 89 + .data = buf, 90 + }; 91 + struct hid_bpf_ops *e; 92 + int ret, idx; 93 + 94 + if (rtype >= HID_REPORT_TYPES) 95 + return -EINVAL; 96 + 97 + idx = srcu_read_lock(&hdev->bpf.srcu); 98 + list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list, 99 + srcu_read_lock_held(&hdev->bpf.srcu)) { 100 + if (!e->hid_hw_request) 101 + continue; 102 + 103 + ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source); 104 + if (ret) 105 + goto out; 106 + } 107 + ret = 0; 108 + 109 + out: 110 + srcu_read_unlock(&hdev->bpf.srcu, idx); 111 + return ret; 112 + } 113 + EXPORT_SYMBOL_GPL(dispatch_hid_bpf_raw_requests); 114 + 77 115 u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) 78 116 { 79 117 int ret;
+1
drivers/hid/bpf/hid_bpf_struct_ops.c
··· 44 44 45 45 switch (moff) { 46 46 case offsetof(struct hid_bpf_ops, hid_rdesc_fixup): 47 + case offsetof(struct hid_bpf_ops, hid_hw_request): 47 48 break; 48 49 default: 49 50 if (prog->sleepable)
+6
drivers/hid/hid-core.c
··· 2406 2406 __u64 source) 2407 2407 { 2408 2408 unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE; 2409 + int ret; 2409 2410 2410 2411 if (hdev->ll_driver->max_buffer_size) 2411 2412 max_buffer_size = hdev->ll_driver->max_buffer_size; 2412 2413 2413 2414 if (len < 1 || len > max_buffer_size || !buf) 2414 2415 return -EINVAL; 2416 + 2417 + ret = dispatch_hid_bpf_raw_requests(hdev, reportnum, buf, len, rtype, 2418 + reqtype, source); 2419 + if (ret) 2420 + return ret; 2415 2421 2416 2422 return hdev->ll_driver->raw_request(hdev, reportnum, buf, len, 2417 2423 rtype, reqtype);
+35
include/linux/hid_bpf.h
··· 130 130 */ 131 131 int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx); 132 132 133 + /** 134 + * @hid_hw_request: called whenever a hid_hw_raw_request() call is emitted 135 + * on the HID device 136 + * 137 + * It has the following arguments: 138 + * 139 + * ``ctx``: The HID-BPF context as &struct hid_bpf_ctx 140 + * ``reportnum``: the report number, as in hid_hw_raw_request() 141 + * ``rtype``: the report type (``HID_INPUT_REPORT``, ``HID_FEATURE_REPORT``, 142 + * ``HID_OUTPUT_REPORT``) 143 + * ``reqtype``: the request 144 + * ``source``: a u64 referring to a uniq but identifiable source. If %0, the 145 + * kernel itself emitted that call. For hidraw, ``source`` is set 146 + * to the associated ``struct file *``. 147 + * 148 + * Return: %0 to keep processing the request by hid-core; any other value 149 + * stops hid-core from processing that event. A positive value should be 150 + * returned with the number of bytes returned in the incoming buffer; a 151 + * negative error code interrupts the processing of this call. 152 + */ 153 + int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum, 154 + enum hid_report_type rtype, enum hid_class_request reqtype, 155 + __u64 source); 156 + 157 + 133 158 /* private: do not show up in the docs */ 134 159 struct hid_device *hdev; 135 160 }; ··· 177 152 #ifdef CONFIG_HID_BPF 178 153 u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data, 179 154 u32 *size, int interrupt, u64 source); 155 + int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, 156 + unsigned char reportnum, __u8 *buf, 157 + u32 size, enum hid_report_type rtype, 158 + enum hid_class_request reqtype, 159 + __u64 source); 180 160 int hid_bpf_connect_device(struct hid_device *hdev); 181 161 void hid_bpf_disconnect_device(struct hid_device *hdev); 182 162 void hid_bpf_destroy_device(struct hid_device *hid); ··· 191 161 static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, 192 162 u8 *data, u32 *size, int interrupt, 193 163 u64 source) { return data; } 164 + static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, 165 + unsigned char reportnum, u8 *buf, 166 + u32 size, enum hid_report_type rtype, 167 + enum hid_class_request reqtype, 168 + u64 source) { return 0; } 194 169 static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; } 195 170 static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {} 196 171 static inline void hid_bpf_destroy_device(struct hid_device *hid) {}