diff mbox series

[HID,v2,04/13] HID: bpf: add HID-BPF hooks for hid_hw_raw_requests

Message ID 20240626-hid_hw_req_bpf-v2-4-cfd60fb6c79f@kernel.org
State Superseded
Headers show
Series HID: bpf_struct_ops, part 2 | expand

Commit Message

Benjamin Tissoires June 26, 2024, 1:46 p.m. UTC
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.

Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>

---

changes in v2:
- make use of SRCU
---
 drivers/hid/bpf/hid_bpf_dispatch.c   | 37 ++++++++++++++++++++++++++++++++++++
 drivers/hid/bpf/hid_bpf_struct_ops.c |  1 +
 drivers/hid/hid-core.c               |  6 ++++++
 include/linux/hid_bpf.h              | 35 ++++++++++++++++++++++++++++++++++
 4 files changed, 79 insertions(+)

Comments

Alexei Starovoitov June 26, 2024, 4:29 p.m. UTC | #1
On Wed, Jun 26, 2024 at 6:46 AM Benjamin Tissoires <bentiss@kernel.org> wrote:
>
> 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.
>
> Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
>
> ---
>
> changes in v2:
> - make use of SRCU
> ---
>  drivers/hid/bpf/hid_bpf_dispatch.c   | 37 ++++++++++++++++++++++++++++++++++++
>  drivers/hid/bpf/hid_bpf_struct_ops.c |  1 +
>  drivers/hid/hid-core.c               |  6 ++++++
>  include/linux/hid_bpf.h              | 35 ++++++++++++++++++++++++++++++++++
>  4 files changed, 79 insertions(+)
>
> diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
> index c026248e3d73..ac98bab4c96d 100644
> --- a/drivers/hid/bpf/hid_bpf_dispatch.c
> +++ b/drivers/hid/bpf/hid_bpf_dispatch.c
> @@ -74,6 +74,43 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
>  }
>  EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
>
> +int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
> +                                 unsigned char reportnum, u8 *buf,
> +                                 u32 size, enum hid_report_type rtype,
> +                                 enum hid_class_request reqtype,
> +                                 u64 source)
> +{
> +       struct hid_bpf_ctx_kern ctx_kern = {
> +               .ctx = {
> +                       .hid = hdev,
> +                       .allocated_size = size,
> +                       .size = size,
> +               },
> +               .data = buf,
> +       };
> +       struct hid_bpf_ops *e;
> +       int ret, idx;
> +
> +       if (rtype >= HID_REPORT_TYPES)
> +               return -EINVAL;
> +
> +       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_request) {
> +                       ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source);
> +                       if (ret)
> +                               goto out;
> +               }
> +       }

here and in patch 7 I would reduce indent by doing:
if (!e->hid_hw_request)
   continue;
ret = e->hid_hw_request(...);

otherwise lgtm
Benjamin Tissoires June 27, 2024, 9:45 a.m. UTC | #2
On Jun 26 2024, Alexei Starovoitov wrote:
> On Wed, Jun 26, 2024 at 6:46 AM Benjamin Tissoires <bentiss@kernel.org> wrote:
> >
> > 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.
> >
> > Signed-off-by: Benjamin Tissoires <bentiss@kernel.org>
> >
> > ---
> >
> > changes in v2:
> > - make use of SRCU
> > ---
> >  drivers/hid/bpf/hid_bpf_dispatch.c   | 37 ++++++++++++++++++++++++++++++++++++
> >  drivers/hid/bpf/hid_bpf_struct_ops.c |  1 +
> >  drivers/hid/hid-core.c               |  6 ++++++
> >  include/linux/hid_bpf.h              | 35 ++++++++++++++++++++++++++++++++++
> >  4 files changed, 79 insertions(+)
> >
> > diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
> > index c026248e3d73..ac98bab4c96d 100644
> > --- a/drivers/hid/bpf/hid_bpf_dispatch.c
> > +++ b/drivers/hid/bpf/hid_bpf_dispatch.c
> > @@ -74,6 +74,43 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
> >  }
> >  EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
> >
> > +int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
> > +                                 unsigned char reportnum, u8 *buf,
> > +                                 u32 size, enum hid_report_type rtype,
> > +                                 enum hid_class_request reqtype,
> > +                                 u64 source)
> > +{
> > +       struct hid_bpf_ctx_kern ctx_kern = {
> > +               .ctx = {
> > +                       .hid = hdev,
> > +                       .allocated_size = size,
> > +                       .size = size,
> > +               },
> > +               .data = buf,
> > +       };
> > +       struct hid_bpf_ops *e;
> > +       int ret, idx;
> > +
> > +       if (rtype >= HID_REPORT_TYPES)
> > +               return -EINVAL;
> > +
> > +       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_request) {
> > +                       ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source);
> > +                       if (ret)
> > +                               goto out;
> > +               }
> > +       }
> 
> here and in patch 7 I would reduce indent by doing:
> if (!e->hid_hw_request)
>    continue;
> ret = e->hid_hw_request(...);
> 
> otherwise lgtm

Thanks for the quick review.

I've changed the patches as you requested before applying them and also
added the Ack from Jiri he gave me over IRC.

Cheers,
Benjamin
diff mbox series

Patch

diff --git a/drivers/hid/bpf/hid_bpf_dispatch.c b/drivers/hid/bpf/hid_bpf_dispatch.c
index c026248e3d73..ac98bab4c96d 100644
--- a/drivers/hid/bpf/hid_bpf_dispatch.c
+++ b/drivers/hid/bpf/hid_bpf_dispatch.c
@@ -74,6 +74,43 @@  dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
 }
 EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
 
+int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
+				  unsigned char reportnum, u8 *buf,
+				  u32 size, enum hid_report_type rtype,
+				  enum hid_class_request reqtype,
+				  u64 source)
+{
+	struct hid_bpf_ctx_kern ctx_kern = {
+		.ctx = {
+			.hid = hdev,
+			.allocated_size = size,
+			.size = size,
+		},
+		.data = buf,
+	};
+	struct hid_bpf_ops *e;
+	int ret, idx;
+
+	if (rtype >= HID_REPORT_TYPES)
+		return -EINVAL;
+
+	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_request) {
+			ret = e->hid_hw_request(&ctx_kern.ctx, reportnum, rtype, reqtype, source);
+			if (ret)
+				goto out;
+		}
+	}
+	ret = 0;
+
+out:
+	srcu_read_unlock(&hdev->bpf.srcu, idx);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dispatch_hid_bpf_raw_requests);
+
 u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
 {
 	int ret;
diff --git a/drivers/hid/bpf/hid_bpf_struct_ops.c b/drivers/hid/bpf/hid_bpf_struct_ops.c
index d34731a1b457..a540a4417174 100644
--- a/drivers/hid/bpf/hid_bpf_struct_ops.c
+++ b/drivers/hid/bpf/hid_bpf_struct_ops.c
@@ -44,6 +44,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):
 		break;
 	default:
 		if (prog->sleepable)
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index ad08289752da..16731804c6bd 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2406,6 +2406,7 @@  int __hid_hw_raw_request(struct hid_device *hdev,
 			 __u64 source)
 {
 	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;
@@ -2413,6 +2414,11 @@  int __hid_hw_raw_request(struct hid_device *hdev,
 	if (len < 1 || len > max_buffer_size || !buf)
 		return -EINVAL;
 
+	ret = dispatch_hid_bpf_raw_requests(hdev, reportnum, buf, len, rtype,
+					    reqtype, source);
+	if (ret)
+		return ret;
+
 	return hdev->ll_driver->raw_request(hdev, reportnum, buf, len,
 					    rtype, reqtype);
 }
diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h
index f93845de5cac..3c01f7f8b6fc 100644
--- a/include/linux/hid_bpf.h
+++ b/include/linux/hid_bpf.h
@@ -130,6 +130,31 @@  struct hid_bpf_ops {
 	 */
 	int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx);
 
+	/**
+	 * @hid_hw_request: called whenever a hid_hw_raw_request() call is emitted
+	 * on the HID device
+	 *
+	 * It has the following arguments:
+	 *
+	 * ``ctx``: The HID-BPF context as &struct hid_bpf_ctx
+	 * ``reportnum``: the report number, as in hid_hw_raw_request()
+	 * ``rtype``: the report type (``HID_INPUT_REPORT``, ``HID_FEATURE_REPORT``,
+	 *            ``HID_OUTPUT_REPORT``)
+	 * ``reqtype``: the request
+	 * ``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 returned in the incoming buffer; a
+	 * negative error code interrupts the processing of this call.
+	 */
+	int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum,
+			       enum hid_report_type rtype, enum hid_class_request reqtype,
+			       __u64 source);
+
+
 	/* private: do not show up in the docs */
 	struct hid_device *hdev;
 };
@@ -152,6 +177,11 @@  struct hid_bpf {
 #ifdef CONFIG_HID_BPF
 u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
 				  u32 *size, int interrupt, u64 source);
+int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
+				  unsigned char reportnum, __u8 *buf,
+				  u32 size, enum hid_report_type rtype,
+				  enum hid_class_request reqtype,
+				  __u64 source);
 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);
@@ -161,6 +191,11 @@  u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s
 static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
 						u8 *data, u32 *size, int interrupt,
 						u64 source) { return data; }
+static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev,
+						unsigned char reportnum, u8 *buf,
+						u32 size, enum hid_report_type rtype,
+						enum hid_class_request reqtype,
+						u64 source) { 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) {}