From 146a06a0d225cae240065233fd168fb0b95a10ff Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:13 +0200 Subject: HID: rename struct hid_bpf_ops into hid_ops Those operations are the ones from HID, not HID-BPF, and I'd like to reuse hid_bpf_ops as the user facing struct_ops API. Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-1-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index eec2592dec12..a66103618e6e 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -97,7 +97,7 @@ enum hid_bpf_prog_type { struct hid_report_enum; -struct hid_bpf_ops { +struct hid_ops { struct hid_report *(*hid_get_report)(struct hid_report_enum *report_enum, const u8 *data); int (*hid_hw_raw_request)(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, @@ -110,7 +110,7 @@ struct hid_bpf_ops { const struct bus_type *bus_type; }; -extern struct hid_bpf_ops *hid_bpf_ops; +extern struct hid_ops *hid_ops; struct hid_bpf_prog_list { u16 prog_idx[HID_BPF_MAX_PROGS_PER_DEV]; -- cgit v1.2.3 From ebc0d8093e8c97de459615438edefad1a4ac352c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:15 +0200 Subject: HID: bpf: implement HID-BPF through bpf_struct_ops We do this implementation in several steps to not have the CI failing: - first (this patch), we add struct_ops while keeping the existing infra available - then we change the selftests, the examples and the existing in-tree HID-BPF programs - then we remove the existing trace points making old HID-BPF obsolete There are a few advantages of struct_ops over tracing: - compatibility with sleepable programs (for hid_hw_raw_request() in a later patch) - a lot simpler in the kernel: it's a simple rcu protected list - we can add more parameters to the function called without much trouble - the "attach" is now generic through BPF-core: the caller just needs to set hid_id and flags before calling __load(). - all the BPF tough part is not handled in BPF-core through generic processing - hid_bpf_ctx is now only writable where it needs be Acked-by: Alexei Starovoitov Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-3-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 61 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index a66103618e6e..c4f4ce10b7dd 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -65,11 +65,12 @@ struct hid_bpf_ctx { * @HID_BPF_FLAG_INSERT_HEAD: insert the given program before any other program * currently attached to the device. This doesn't * guarantee that this program will always be first - * @HID_BPF_FLAG_MAX: sentinel value, not to be used by the callers */ enum hid_bpf_attach_flags { HID_BPF_FLAG_NONE = 0, HID_BPF_FLAG_INSERT_HEAD = _BITUL(0), + + /* private: internal use only */ HID_BPF_FLAG_MAX, }; @@ -112,6 +113,60 @@ struct hid_ops { extern struct hid_ops *hid_ops; +/** + * struct hid_bpf_ops - A BPF struct_ops of callbacks allowing to attach HID-BPF + * programs to a HID device + * @hid_id: the HID uniq ID to attach to. This is writeable before ``load()``, and + * cannot be changed after + * @flags: flags used while attaching the struct_ops to the device. Currently only + * available value is %0 or ``BPF_F_BEFORE``. + * Writeable only before ``load()`` + */ +struct hid_bpf_ops { + /* hid_id needs to stay first so we can easily change it + * from userspace. + */ + int hid_id; + u32 flags; + + /* private: do not show up in the docs */ + struct list_head list; + + /* public: rest should show up in the docs */ + + /** + * @hid_device_event: called whenever an event is coming in from the device + * + * It has the following arguments: + * + * ``ctx``: The HID-BPF context as &struct hid_bpf_ctx + * + * 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 + * + * Context: Interrupt context. + */ + int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type); + + /** + * @hid_rdesc_fixup: called when the probe function parses the report descriptor + * of the HID device + * + * It has the following arguments: + * + * ``ctx``: The HID-BPF context as &struct hid_bpf_ctx + * + * 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 device + */ + int (*hid_rdesc_fixup)(struct hid_bpf_ctx *ctx); + + /* private: do not show up in the docs */ + struct hid_device *hdev; +}; + struct hid_bpf_prog_list { u16 prog_idx[HID_BPF_MAX_PROGS_PER_DEV]; u8 prog_cnt; @@ -129,6 +184,10 @@ struct hid_bpf { bool destroyed; /* prevents the assignment of any progs */ spinlock_t progs_lock; /* protects RCU update of progs */ + + struct hid_bpf_ops *rdesc_ops; + struct list_head prog_list; + struct mutex prog_list_lock; /* protects prog_list update */ }; /* specific HID-BPF link when a program is attached to a device */ -- cgit v1.2.3 From 4a86220e046da009bef0948e9f51d1d26d68f93c Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:20 +0200 Subject: HID: bpf: remove tracing HID-BPF capability We can now rely on struct_ops as we cleared the users in-tree. Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-8-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 54 +------------------------------------------------ 1 file changed, 1 insertion(+), 53 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index c4f4ce10b7dd..447b94aa99ab 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -4,7 +4,7 @@ #define __HID_BPF_H #include -#include +#include #include struct hid_device; @@ -24,11 +24,7 @@ struct hid_device; * * All of these fields are currently read-only. * - * @index: program index in the jump table. No special meaning (a smaller index - * doesn't mean the program will be executed before another program with - * a bigger index). * @hid: the ``struct hid_device`` representing the device itself - * @report_type: used for ``hid_bpf_device_event()`` * @allocated_size: Allocated size of data. * * This is how much memory is available and can be requested @@ -47,54 +43,21 @@ struct hid_device; * @retval: Return value of the previous program. */ struct hid_bpf_ctx { - __u32 index; const struct hid_device *hid; __u32 allocated_size; - enum hid_report_type report_type; union { __s32 retval; __s32 size; }; }; -/** - * enum hid_bpf_attach_flags - flags used when attaching a HIF-BPF program - * - * @HID_BPF_FLAG_NONE: no specific flag is used, the kernel choses where to - * insert the program - * @HID_BPF_FLAG_INSERT_HEAD: insert the given program before any other program - * currently attached to the device. This doesn't - * guarantee that this program will always be first - */ -enum hid_bpf_attach_flags { - HID_BPF_FLAG_NONE = 0, - HID_BPF_FLAG_INSERT_HEAD = _BITUL(0), - - /* private: internal use only */ - HID_BPF_FLAG_MAX, -}; - -/* 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); - /* * Below is HID internal */ -/* internal function to call eBPF programs, not to be used by anybody */ -int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx); - #define HID_BPF_MAX_PROGS_PER_DEV 64 #define HID_BPF_FLAG_MASK (((HID_BPF_FLAG_MAX - 1) << 1) - 1) -/* types of HID programs to attach to */ -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, -}; struct hid_report_enum; @@ -167,11 +130,6 @@ struct hid_bpf_ops { struct hid_device *hdev; }; -struct hid_bpf_prog_list { - u16 prog_idx[HID_BPF_MAX_PROGS_PER_DEV]; - u8 prog_cnt; -}; - /* stored in each device */ struct hid_bpf { u8 *device_data; /* allocated when a bpf program of type @@ -179,23 +137,13 @@ struct hid_bpf { * to this HID device */ u32 allocated_data; - - struct hid_bpf_prog_list __rcu *progs[HID_BPF_PROG_TYPE_MAX]; /* attached BPF progs */ bool destroyed; /* prevents the assignment of any progs */ - spinlock_t progs_lock; /* protects RCU update of progs */ - struct hid_bpf_ops *rdesc_ops; struct list_head prog_list; struct mutex prog_list_lock; /* protects prog_list update */ }; -/* specific HID-BPF link when a program is attached to a device */ -struct hid_bpf_link { - struct bpf_link link; - int hid_table_index; -}; - #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); -- cgit v1.2.3 From c5958697a5fa29d3ba9332205a88725afe9ed912 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:22 +0200 Subject: Documentation: HID: amend HID-BPF for struct_ops Now that we are using struct_ops, the docs need to be changed. Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-10-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 447b94aa99ab..1b4cc1b2c31d 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -20,11 +20,9 @@ struct hid_device; * struct hid_bpf_ctx - User accessible data for all HID programs * * ``data`` is not directly accessible from the context. We need to issue - * a call to ``hid_bpf_get_data()`` in order to get a pointer to that field. + * a call to hid_bpf_get_data() in order to get a pointer to that field. * - * All of these fields are currently read-only. - * - * @hid: the ``struct hid_device`` representing the device itself + * @hid: the &struct hid_device representing the device itself * @allocated_size: Allocated size of data. * * This is how much memory is available and can be requested @@ -41,6 +39,8 @@ struct hid_device; * ``size`` must always be less or equal than ``allocated_size`` (it is enforced * once all BPF programs have been run). * @retval: Return value of the previous program. + * + * ``hid`` and ``allocated_size`` are read-only, ``size`` and ``retval`` are read-write. */ struct hid_bpf_ctx { const struct hid_device *hid; -- cgit v1.2.3 From 33c0fb85b571b0f1bbdbf466e770eebeb29e6f41 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Sat, 8 Jun 2024 11:01:28 +0200 Subject: HID: bpf: make part of struct hid_device writable It is useful to change the name, the phys and/or the uniq of a struct hid_device during .rdesc_fixup(). For example, hid-uclogic.ko changes the uniq to store the firmware version to differentiate between 2 devices sharing the same PID. In the same way, changing the device name is useful when the device export 3 nodes, all with the same name. Link: https://lore.kernel.org/r/20240608-hid_bpf_struct_ops-v3-16-6ac6ade58329@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 1b4cc1b2c31d..65d7e0acc8c2 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -43,7 +43,7 @@ struct hid_device; * ``hid`` and ``allocated_size`` are read-only, ``size`` and ``retval`` are read-write. */ struct hid_bpf_ctx { - const struct hid_device *hid; + struct hid_device *hid; __u32 allocated_size; union { __s32 retval; -- cgit v1.2.3 From 67eccf151d76a9939ad8a50c6db5cb486b01df24 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:23 +0200 Subject: HID: add source argument to HID low level functions This allows to know who actually sent what when we process the request to the device. This will be useful for a BPF firewall program to allow or not requests coming from a dedicated hidraw node client. Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-2-cfd60fb6c79f@kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 6 ++++++ include/linux/hid_bpf.h | 16 ++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid.h b/include/linux/hid.h index 8e06d89698e6..dac2804b4562 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1125,6 +1125,12 @@ int __must_check hid_hw_open(struct hid_device *hdev); void hid_hw_close(struct hid_device *hdev); void hid_hw_request(struct hid_device *hdev, struct hid_report *report, enum hid_class_request reqtype); +int __hid_hw_raw_request(struct hid_device *hdev, + unsigned char reportnum, __u8 *buf, + size_t len, enum hid_report_type rtype, + enum hid_class_request reqtype, + __u64 source); +int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source); int hid_hw_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, enum hid_report_type rtype, diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 65d7e0acc8c2..a54741db0415 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -66,10 +66,12 @@ struct hid_ops { int (*hid_hw_raw_request)(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, enum hid_report_type rtype, - enum hid_class_request reqtype); - int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len); + enum hid_class_request reqtype, + __u64 source); + int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, + __u64 source); int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type, - u8 *data, u32 size, int interrupt); + u8 *data, u32 size, int interrupt, u64 source); struct module *owner; const struct bus_type *bus_type; }; @@ -110,7 +112,8 @@ struct hid_bpf_ops { * * Context: Interrupt context. */ - int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type); + int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type, + __u64 source); /** * @hid_rdesc_fixup: called when the probe function parses the report descriptor @@ -146,7 +149,7 @@ 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); + u32 *size, int interrupt, 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); @@ -154,7 +157,8 @@ 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 data; } + u8 *data, u32 *size, int interrupt, + u64 source) { return data; } 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) {} -- cgit v1.2.3 From 6cd735f0e57a6c8510ad92f5b63837a8d0cff3a7 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:24 +0200 Subject: HID: bpf: protect HID-BPF prog_list access by a SRCU We want to add sleepable callbacks for hid_hw_raw_request() and hid_hw_output_report(), but we can not use a plain RCU for those. Prepare for a SRCU so we can extend HID-BPF. This changes a little bit how hid_bpf_device_init() behaves, as it may now fail, so there is a tiny hid-core.c change to accommodate for this. Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-3-cfd60fb6c79f@kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index a54741db0415..f93845de5cac 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -5,6 +5,7 @@ #include #include +#include #include struct hid_device; @@ -145,6 +146,7 @@ struct hid_bpf { struct hid_bpf_ops *rdesc_ops; struct list_head prog_list; struct mutex prog_list_lock; /* protects prog_list update */ + struct srcu_struct srcu; /* protects prog_list read-only access */ }; #ifdef CONFIG_HID_BPF @@ -153,7 +155,7 @@ u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type t 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); +int 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, @@ -162,7 +164,7 @@ static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid 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 int hid_bpf_device_init(struct hid_device *hid) { return 0; } #define call_hid_bpf_rdesc_fixup(_hdev, _rdesc, _size) \ ((u8 *)kmemdup(_rdesc, *(_size), GFP_KERNEL)) -- cgit v1.2.3 From 8bd0488b5ea58655ad6fdcbe0408ef49b16882b1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:25 +0200 Subject: 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 Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'include/linux') 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) {} -- cgit v1.2.3 From 75839101ce52e319cb2154a027d14f1f0aa3be09 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:26 +0200 Subject: HID: bpf: prevent infinite recursions with hid_hw_raw_requests hooks When we attach a sleepable hook to hid_hw_raw_requests, we can (and in many cases should) call ourself hid_bpf_raw_request(), to actually fetch data from the device itself. However, this means that we might enter an infinite loop between hid_hw_raw_requests hooks and hid_bpf_hw_request() call. To prevent that, if a hid_bpf_hw_request() call is emitted, we prevent any new call of this kfunc by storing the information in the context. This way we can always trace/monitor/filter the incoming bpf requests, while preventing those loops to happen. I don't think exposing "from_bpf" is very interesting because while writing such a bpf program, you need to match at least the report number and/or the source of the call. So a blind "if there is a hid_hw_raw_request() call, I'm emitting another one" makes no real sense. Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-5-cfd60fb6c79f@kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 2 +- include/linux/hid_bpf.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid.h b/include/linux/hid.h index dac2804b4562..24d0d7c0bd33 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1129,7 +1129,7 @@ int __hid_hw_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, enum hid_report_type rtype, enum hid_class_request reqtype, - __u64 source); + __u64 source, bool from_bpf); int __hid_hw_output_report(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source); int hid_hw_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 3c01f7f8b6fc..088c94b6d8ec 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -68,7 +68,7 @@ struct hid_ops { unsigned char reportnum, __u8 *buf, size_t len, enum hid_report_type rtype, enum hid_class_request reqtype, - __u64 source); + __u64 source, bool from_bpf); int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, __u64 source); int (*hid_input_report)(struct hid_device *hid, enum hid_report_type type, @@ -181,7 +181,7 @@ 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); + __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); @@ -195,7 +195,7 @@ 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; } + 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) {} -- cgit v1.2.3 From 9286675a2aed40a517be8cc4e283a04f473275b5 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:28 +0200 Subject: 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 Signed-off-by: Benjamin Tissoires --- include/linux/hid.h | 3 ++- include/linux/hid_bpf.h | 24 +++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid.h b/include/linux/hid.h index 24d0d7c0bd33..1533c9dcd3a6 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -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, diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 088c94b6d8ec..f35508a73067 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -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) {} -- cgit v1.2.3 From fa03f398a8ac46f46927e0b509b302ebe0ed7e8a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:30 +0200 Subject: HID: bpf: make hid_bpf_input_report() sleep until the device is ready hid_bpf_input_report() is already marked to be used in sleepable context only. So instead of hammering with timers the device to hopefully get an available slot where the device is not sending events, we can make that kfunc wait for the current event to be terminated before it goes in. This allows to work with the following pseudo code: in struct_ops/hid_device_event: - schedule a bpf_wq, which calls hid_bpf_input_report() - once this struct_ops function terminates, hid_bpf_input_report() immediately starts before the next event Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-9-cfd60fb6c79f@kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index f35508a73067..7f04353d09e9 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -72,7 +72,8 @@ struct hid_ops { int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, __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); + u8 *data, u32 size, int interrupt, u64 source, + bool lock_already_taken); struct module *owner; const struct bus_type *bus_type; }; -- cgit v1.2.3 From 9acbb7ba4589d4715141d4e14230a828ddc95f3d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Wed, 26 Jun 2024 15:46:32 +0200 Subject: HID: bpf: allow hid_device_event hooks to inject input reports on self This is the same logic than hid_hw_raw_request or hid_hw_output_report: we can allow hid_bpf_try_input_report to be called from a hook on hid_input_report if we ensure that the call can not be made twice in a row. There is one extra subtlety in which there is a lock in hid_input_report. But given that we can detect if we are already in the hook, we can notify hid_input_report to not take the lock. This is done by checking if ctx_kern data is valid or null, and if it is equal to the dedicated incoming data buffer. In order to have more control on whether the lock needs to be taken or not we introduce a new kfunc for it: hid_bpf_try_input_report() Link: https://patch.msgid.link/20240626-hid_hw_req_bpf-v2-11-cfd60fb6c79f@kernel.org Acked-by: Jiri Kosina Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 7f04353d09e9..93546ee7677a 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -72,7 +72,7 @@ struct hid_ops { int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, __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, + u8 *data, u32 size, int interrupt, u64 source, bool from_bpf, bool lock_already_taken); struct module *owner; const struct bus_type *bus_type; @@ -195,7 +195,7 @@ 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); + u32 *size, int interrupt, u64 source, bool from_bpf); int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, u32 size, enum hid_report_type rtype, @@ -211,7 +211,7 @@ u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *s #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, - u64 source) { return data; } + u64 source, bool from_bpf) { 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, -- cgit v1.2.3 From 762ced1630a97a457ad2fd5f5a36849009808431 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 1 Jul 2024 14:39:50 +0200 Subject: HID: bpf: fix gcc warning and unify __u64 into u64 I've got multiple reports of: error: cast from pointer to integer of different size [-Werror=pointer-to-int-cast]. Let's use the same trick than kernel/bpf/helpers.c to shut up that warning. Even if we were on an architecture with addresses on more than 64 bits, this isn't much of an issue as the address is not used as a pointer, but as an hash and the caller is not supposed to go back to the kernel address ever. And while we change those, make sure we use u64 instead of __u64 for consistency Reported-by: Stephen Rothwell Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202406280633.OPB5uIFj-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202406282304.UydSVncq-lkp@intel.com/ Closes: https://lore.kernel.org/oe-kbuild-all/202406282242.Fk738zzy-lkp@intel.com/ Reported-by: Mirsad Todorovac Fixes: 67eccf151d76 ("HID: add source argument to HID low level functions") Link: https://patch.msgid.link/20240701-fix-cki-v2-2-20564e2e1393@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 93546ee7677a..3f6584014311 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -68,9 +68,9 @@ struct hid_ops { unsigned char reportnum, __u8 *buf, size_t len, enum hid_report_type rtype, enum hid_class_request reqtype, - __u64 source, bool from_bpf); + u64 source, bool from_bpf); int (*hid_hw_output_report)(struct hid_device *hdev, __u8 *buf, size_t len, - __u64 source, bool from_bpf); + 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, bool from_bpf, bool lock_already_taken); @@ -115,7 +115,7 @@ struct hid_bpf_ops { * Context: Interrupt context. */ int (*hid_device_event)(struct hid_bpf_ctx *ctx, enum hid_report_type report_type, - __u64 source); + u64 source); /** * @hid_rdesc_fixup: called when the probe function parses the report descriptor -- cgit v1.2.3 From 260ffc9676b635c2ededc39285bfa41f83536ee1 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 1 Jul 2024 14:39:51 +0200 Subject: HID: bpf: doc fixes for hid_hw_request() hooks We had the following errors while doing make htmldocs: Documentation/hid/hid-bpf:185: include/linux/hid_bpf.h:144: ERROR: Unexpected indentation. Documentation/hid/hid-bpf:185: include/linux/hid_bpf.h:145: WARNING: Block quote ends without a blank line; unexpected unindent. Documentation/hid/hid-bpf:185: include/linux/hid_bpf.h:147: ERROR: Unexpected indentation. Reported-by: Stephen Rothwell Fixes: 8bd0488b5ea5 ("HID: bpf: add HID-BPF hooks for hid_hw_raw_requests") Link: https://patch.msgid.link/20240701-fix-cki-v2-3-20564e2e1393@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index 3f6584014311..c30c31b79419 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -139,12 +139,15 @@ struct hid_bpf_ops { * * ``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``) + * ``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 *``. + * 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 @@ -153,7 +156,7 @@ struct hid_bpf_ops { */ int (*hid_hw_request)(struct hid_bpf_ctx *ctx, unsigned char reportnum, enum hid_report_type rtype, enum hid_class_request reqtype, - __u64 source); + u64 source); /** * @hid_hw_output_report: called whenever a hid_hw_output_report() call is emitted -- cgit v1.2.3 From c79de517a226b86419a5baa867e65e3f8118829f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Mon, 1 Jul 2024 14:39:52 +0200 Subject: HID: bpf: doc fixes for hid_hw_request() hooks We had the following errors while doing make htmldocs: Documentation/hid/hid-bpf:185: include/linux/hid_bpf.h:167: ERROR: Unexpected indentation. Also ensure consistency with the rest of the __u64 vs u64. Reported-by: Stephen Rothwell Fixes: 9286675a2aed ("HID: bpf: add HID-BPF hooks for hid_hw_output_report") Link: https://patch.msgid.link/20240701-fix-cki-v2-4-20564e2e1393@kernel.org Signed-off-by: Benjamin Tissoires --- include/linux/hid_bpf.h | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'include/linux') diff --git a/include/linux/hid_bpf.h b/include/linux/hid_bpf.h index c30c31b79419..9ca96fc90449 100644 --- a/include/linux/hid_bpf.h +++ b/include/linux/hid_bpf.h @@ -138,6 +138,7 @@ struct hid_bpf_ops { * 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``, @@ -165,16 +166,17 @@ struct hid_bpf_ops { * 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 *``. + * 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); + int (*hid_hw_output_report)(struct hid_bpf_ctx *ctx, u64 source); /* private: do not show up in the docs */ @@ -203,9 +205,9 @@ 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, bool from_bpf); + u64 source, bool from_bpf); int dispatch_hid_bpf_output_report(struct hid_device *hdev, __u8 *buf, u32 size, - __u64 source, bool from_bpf); + 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); @@ -221,7 +223,7 @@ static inline int dispatch_hid_bpf_raw_requests(struct hid_device *hdev, 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; } + 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) {} -- cgit v1.2.3