mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 10:40:15 +02:00 
			
		
		
		
	HID: bpf: add HID-BPF hooks for hid_hw_output_report
Same story than hid_hw_raw_requests:
This allows to intercept and prevent or change the behavior of
hid_hw_output_report() 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_hw_output_report(), 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-7-cfd60fb6c79f@kernel.org
Acked-by: Jiri Kosina <jkosina@suse.com>
Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
			
			
This commit is contained in:
		
							parent
							
								
									015a4a2a43
								
							
						
					
					
						commit
						9286675a2a
					
				
					 6 changed files with 70 additions and 9 deletions
				
			
		| 
						 | 
				
			
			@ -113,6 +113,40 @@ int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
 | 
			
		|||
}
 | 
			
		||||
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_raw_requests);
 | 
			
		||||
 | 
			
		||||
int dispatch_hid_bpf_output_report(struct hid_device *hdev,
 | 
			
		||||
				   __u8 *buf, u32 size, __u64 source,
 | 
			
		||||
				   bool from_bpf)
 | 
			
		||||
{
 | 
			
		||||
	struct hid_bpf_ctx_kern ctx_kern = {
 | 
			
		||||
		.ctx = {
 | 
			
		||||
			.hid = hdev,
 | 
			
		||||
			.allocated_size = size,
 | 
			
		||||
			.size = size,
 | 
			
		||||
		},
 | 
			
		||||
		.data = buf,
 | 
			
		||||
		.from_bpf = from_bpf,
 | 
			
		||||
	};
 | 
			
		||||
	struct hid_bpf_ops *e;
 | 
			
		||||
	int ret, idx;
 | 
			
		||||
 | 
			
		||||
	idx = srcu_read_lock(&hdev->bpf.srcu);
 | 
			
		||||
	list_for_each_entry_srcu(e, &hdev->bpf.prog_list, list,
 | 
			
		||||
				 srcu_read_lock_held(&hdev->bpf.srcu)) {
 | 
			
		||||
		if (!e->hid_hw_output_report)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		ret = e->hid_hw_output_report(&ctx_kern.ctx, source);
 | 
			
		||||
		if (ret)
 | 
			
		||||
			goto out;
 | 
			
		||||
	}
 | 
			
		||||
	ret = 0;
 | 
			
		||||
 | 
			
		||||
out:
 | 
			
		||||
	srcu_read_unlock(&hdev->bpf.srcu, idx);
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_output_report);
 | 
			
		||||
 | 
			
		||||
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
 | 
			
		||||
{
 | 
			
		||||
	int ret;
 | 
			
		||||
| 
						 | 
				
			
			@ -443,10 +477,7 @@ hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, __u8 *buf, size_t buf__sz)
 | 
			
		|||
	if (!dma_data)
 | 
			
		||||
		return -ENOMEM;
 | 
			
		||||
 | 
			
		||||
	ret = hid_ops->hid_hw_output_report(hdev,
 | 
			
		||||
						dma_data,
 | 
			
		||||
						size,
 | 
			
		||||
						(__u64)ctx);
 | 
			
		||||
	ret = hid_ops->hid_hw_output_report(hdev, dma_data, size, (__u64)ctx, true);
 | 
			
		||||
 | 
			
		||||
	kfree(dma_data);
 | 
			
		||||
	return ret;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -45,6 +45,7 @@ static int hid_bpf_ops_check_member(const struct btf_type *t,
 | 
			
		|||
	switch (moff) {
 | 
			
		||||
	case offsetof(struct hid_bpf_ops, hid_rdesc_fixup):
 | 
			
		||||
	case offsetof(struct hid_bpf_ops, hid_hw_request):
 | 
			
		||||
	case offsetof(struct hid_bpf_ops, hid_hw_output_report):
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		if (prog->sleepable)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2445,9 +2445,11 @@ int hid_hw_raw_request(struct hid_device *hdev,
 | 
			
		|||
}
 | 
			
		||||
EXPORT_SYMBOL_GPL(hid_hw_raw_request);
 | 
			
		||||
 | 
			
		||||
int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source)
 | 
			
		||||
int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source,
 | 
			
		||||
			   bool from_bpf)
 | 
			
		||||
{
 | 
			
		||||
	unsigned int max_buffer_size = HID_MAX_BUFFER_SIZE;
 | 
			
		||||
	int ret;
 | 
			
		||||
 | 
			
		||||
	if (hdev->ll_driver->max_buffer_size)
 | 
			
		||||
		max_buffer_size = hdev->ll_driver->max_buffer_size;
 | 
			
		||||
| 
						 | 
				
			
			@ -2455,6 +2457,10 @@ int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64
 | 
			
		|||
	if (len < 1 || len > max_buffer_size || !buf)
 | 
			
		||||
		return -EINVAL;
 | 
			
		||||
 | 
			
		||||
	ret = dispatch_hid_bpf_output_report(hdev, buf, len, source, from_bpf);
 | 
			
		||||
	if (ret)
 | 
			
		||||
		return ret;
 | 
			
		||||
 | 
			
		||||
	if (hdev->ll_driver->output_report)
 | 
			
		||||
		return hdev->ll_driver->output_report(hdev, buf, len);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2472,7 +2478,7 @@ int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64
 | 
			
		|||
 */
 | 
			
		||||
int hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	return __hid_hw_output_report(hdev, buf, len, 0);
 | 
			
		||||
	return __hid_hw_output_report(hdev, buf, len, 0, false);
 | 
			
		||||
}
 | 
			
		||||
EXPORT_SYMBOL_GPL(hid_hw_output_report);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -140,7 +140,7 @@ static ssize_t hidraw_send_report(struct file *file, const char __user *buffer,
 | 
			
		|||
 | 
			
		||||
	if ((report_type == HID_OUTPUT_REPORT) &&
 | 
			
		||||
	    !(dev->quirks & HID_QUIRK_NO_OUTPUT_REPORTS_ON_INTR_EP)) {
 | 
			
		||||
		ret = __hid_hw_output_report(dev, buf, count, (__u64)file);
 | 
			
		||||
		ret = __hid_hw_output_report(dev, buf, count, (__u64)file, false);
 | 
			
		||||
		/*
 | 
			
		||||
		 * compatibility with old implementation of USB-HID and I2C-HID:
 | 
			
		||||
		 * if the device does not support receiving output reports,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1130,7 +1130,8 @@ int __hid_hw_raw_request(struct hid_device *hdev,
 | 
			
		|||
			 size_t len, enum hid_report_type rtype,
 | 
			
		||||
			 enum hid_class_request reqtype,
 | 
			
		||||
			 __u64 source, bool from_bpf);
 | 
			
		||||
int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source);
 | 
			
		||||
int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source,
 | 
			
		||||
			   bool from_bpf);
 | 
			
		||||
int hid_hw_raw_request(struct hid_device *hdev,
 | 
			
		||||
		       unsigned char reportnum, __u8 *buf,
 | 
			
		||||
		       size_t len, enum hid_report_type rtype,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,7 @@ struct hid_ops {
 | 
			
		|||
				  enum hid_class_request reqtype,
 | 
			
		||||
				  __u64 source, bool from_bpf);
 | 
			
		||||
	int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len,
 | 
			
		||||
				    __u64 source);
 | 
			
		||||
				    __u64 source, bool from_bpf);
 | 
			
		||||
	int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type,
 | 
			
		||||
				u8 *data, u32 size, int interrupt, u64 source);
 | 
			
		||||
	struct module *owner;
 | 
			
		||||
| 
						 | 
				
			
			@ -154,6 +154,24 @@ struct hid_bpf_ops {
 | 
			
		|||
			       enum hid_report_type rtype, enum hid_class_request reqtype,
 | 
			
		||||
			       __u64 source);
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @hid_hw_output_report: called whenever a hid_hw_output_report() call is emitted
 | 
			
		||||
	 * on the HID device
 | 
			
		||||
	 *
 | 
			
		||||
	 * It has the following arguments:
 | 
			
		||||
	 *
 | 
			
		||||
	 * ``ctx``: The HID-BPF context as &struct hid_bpf_ctx
 | 
			
		||||
	 * ``source``: a u64 referring to a uniq but identifiable source. If %0, the
 | 
			
		||||
	 *             kernel itself emitted that call. For hidraw, ``source`` is set
 | 
			
		||||
	 *             to the associated ``struct file *``.
 | 
			
		||||
	 *
 | 
			
		||||
	 * Return: %0 to keep processing the request by hid-core; any other value
 | 
			
		||||
	 * stops hid-core from processing that event. A positive value should be
 | 
			
		||||
	 * returned with the number of bytes written to the device; a negative error
 | 
			
		||||
	 * code interrupts the processing of this call.
 | 
			
		||||
	 */
 | 
			
		||||
	int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, __u64 source);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	/* private: do not show up in the docs */
 | 
			
		||||
	struct hid_device *hdev;
 | 
			
		||||
| 
						 | 
				
			
			@ -182,6 +200,8 @@ int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
 | 
			
		|||
				  u32 size, enum hid_report_type rtype,
 | 
			
		||||
				  enum hid_class_request reqtype,
 | 
			
		||||
				  __u64 source, bool from_bpf);
 | 
			
		||||
int dispatch_hid_bpf_output_report(struct hid_device *hdev, __u8 *buf, u32 size,
 | 
			
		||||
				   __u64 source, bool from_bpf);
 | 
			
		||||
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);
 | 
			
		||||
| 
						 | 
				
			
			@ -196,6 +216,8 @@ static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
 | 
			
		|||
						u32 size, enum hid_report_type rtype,
 | 
			
		||||
						enum hid_class_request reqtype,
 | 
			
		||||
						u64 source, bool from_bpf) { return 0; }
 | 
			
		||||
static inline int dispatch_hid_bpf_output_report(struct hid_device *hdev, __u8 *buf, u32 size,
 | 
			
		||||
						 __u64 source, bool from_bpf) { return 0; }
 | 
			
		||||
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) {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue