Hi,
This is a followup of my v1 at [0] and v2 at [1].
The short summary of the previous cover letter and discussions is that HID could benefit from BPF for the following use cases:
- simple fixup of report descriptor: benefits are faster development time and testing, with the produced bpf program being shipped in the kernel directly (the shipping part is *not* addressed here).
- Universal Stylus Interface: allows a user-space program to define its own kernel interface
- Surface Dial: somehow similar to the previous one except that userspace can decide to change the shape of the exported device
- firewall: still partly missing there, there is not yet interception of hidraw calls, but it's coming in a followup series, I promise
- tracing: well, tracing.
I think I addressed the comments from the previous version, but there are a few things I'd like to note here:
- I did not take the various rev-by and tested-by (thanks a lot for those) because the uapi changed significantly in v3, so I am not very confident in taking those rev-by blindly
- I mentioned in my discussion with Song that I'll put a summary of the uapi in the cover letter, but I ended up adding a (long) file in the Documentation directory. So please maybe start by reading 17/17 to have an overview of what I want to achieve
- I added in the libbpf and bpf the new type BPF_HID_DRIVER_EVENT, even though I don't have a user of it right now in the kernel. I wanted to have them in the docs, but we might not want to have them ready here. In terms of code, it just means that we can attach such programs types but that they will never get triggered.
Anyway, I have been mulling on this for the past 2 weeks, and I think that maybe sharing this now is better than me just starring at the code over and over.
Short summary of changes:
v3: ===
- squashed back together most of the libbpf and bpf changes into bigger commits that give a better overview of the whole interactions
- reworked the user API to not expose .data as a directly accessible field from the context, but instead forces everyone to use hid_bpf_get_data (or get/set_bits)
- added BPF_HID_DRIVER_EVENT (see note above)
- addressed the various nitpicks from v2
- added a big Documentation file (and so adding now the doc maintainers to the long list of recipients)
v2: ===
- split the series by subsystem (bpf, HID, libbpf, selftests and samples)
- Added an extra patch at the beginning to not require CAP_NET_ADMIN for BPF_PROG_TYPE_LIRC_MODE2 (please shout if this is wrong)
- made the bpf context attached to HID program of dynamic size: * the first 1 kB will be able to be addressed directly * the rest can be retrieved through bpf_hid_{set|get}_data (note that I am definitivey not happy with that API, because there is part of it in bits and other in bytes. ouch)
- added an extra patch to prevent non GPL HID bpf programs to be loaded of type BPF_PROG_TYPE_HID * same here, not really happy but I don't know where to put that check in verifier.c
- added a new flag BPF_F_INSERT_HEAD for BPF_LINK_CREATE syscall when in used with HID program types. * this flag is used for tracing, to be able to load a program before any others that might already have been inserted and that might change the data stream.
Cheers, Benjamin
[0] https://lore.kernel.org/linux-input/20220224110828.2168231-1-benjamin.tissoi... [1] https://lore.kernel.org/linux-input/20220304172852.274126-1-benjamin.tissoir...
Benjamin Tissoires (17): bpf: add new is_sys_admin_prog_type() helper bpf: introduce hid program type bpf/verifier: prevent non GPL programs to be loaded against HID libbpf: add HID program type and API HID: hook up with bpf HID: allow to change the report descriptor from an eBPF program selftests/bpf: add tests for the HID-bpf initial implementation selftests/bpf: add report descriptor fixup tests selftests/bpf: Add a test for BPF_F_INSERT_HEAD selftests/bpf: add test for user call of HID bpf programs samples/bpf: add new hid_mouse example bpf/hid: add more HID helpers HID: bpf: implement hid_bpf_get|set_bits HID: add implementation of bpf_hid_raw_request selftests/bpf: add tests for hid_{get|set}_bits helpers selftests/bpf: add tests for bpf_hid_hw_request Documentation: add HID-BPF docs
Documentation/hid/hid-bpf.rst | 444 +++++++++++ Documentation/hid/index.rst | 1 + drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 328 ++++++++ drivers/hid/hid-core.c | 34 +- include/linux/bpf-hid.h | 127 +++ include/linux/bpf_types.h | 4 + include/linux/hid.h | 36 +- include/uapi/linux/bpf.h | 67 ++ include/uapi/linux/bpf_hid.h | 71 ++ include/uapi/linux/hid.h | 10 + kernel/bpf/Makefile | 3 + kernel/bpf/btf.c | 1 + kernel/bpf/hid.c | 728 +++++++++++++++++ kernel/bpf/syscall.c | 27 +- kernel/bpf/verifier.c | 7 + samples/bpf/.gitignore | 1 + samples/bpf/Makefile | 4 + samples/bpf/hid_mouse_kern.c | 117 +++ samples/bpf/hid_mouse_user.c | 129 +++ tools/include/uapi/linux/bpf.h | 67 ++ tools/lib/bpf/libbpf.c | 23 +- tools/lib/bpf/libbpf.h | 2 + tools/lib/bpf/libbpf.map | 1 + tools/testing/selftests/bpf/config | 3 + tools/testing/selftests/bpf/prog_tests/hid.c | 788 +++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 205 +++++ 27 files changed, 3204 insertions(+), 25 deletions(-) create mode 100644 Documentation/hid/hid-bpf.rst create mode 100644 drivers/hid/hid-bpf.c create mode 100644 include/linux/bpf-hid.h create mode 100644 include/uapi/linux/bpf_hid.h create mode 100644 kernel/bpf/hid.c create mode 100644 samples/bpf/hid_mouse_kern.c create mode 100644 samples/bpf/hid_mouse_user.c create mode 100644 tools/testing/selftests/bpf/prog_tests/hid.c create mode 100644 tools/testing/selftests/bpf/progs/hid.c
LIRC_MODE2 does not really need net_admin capability, but only sys_admin.
Extract a new helper for it, it will be also used for the HID bpf implementation.
Cc: Sean Young sean@mess.org Acked-by: Sean Young sean@mess.org Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - dropped BPF_PROG_TYPE_EXT from the new helper
new in v2 --- kernel/bpf/syscall.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 9beb585be5a6..b88688264ad0 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2165,7 +2165,6 @@ static bool is_net_admin_prog_type(enum bpf_prog_type prog_type) case BPF_PROG_TYPE_LWT_SEG6LOCAL: case BPF_PROG_TYPE_SK_SKB: case BPF_PROG_TYPE_SK_MSG: - case BPF_PROG_TYPE_LIRC_MODE2: case BPF_PROG_TYPE_FLOW_DISSECTOR: case BPF_PROG_TYPE_CGROUP_DEVICE: case BPF_PROG_TYPE_CGROUP_SOCK: @@ -2202,6 +2201,16 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type) } }
+static bool is_sys_admin_prog_type(enum bpf_prog_type prog_type) +{ + switch (prog_type) { + case BPF_PROG_TYPE_LIRC_MODE2: + return true; + default: + return false; + } +} + /* last field in 'union bpf_attr' used by this command */ #define BPF_PROG_LOAD_LAST_FIELD core_relo_rec_size
@@ -2252,6 +2261,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr) return -EPERM; if (is_perfmon_prog_type(type) && !perfmon_capable()) return -EPERM; + if (is_sys_admin_prog_type(type) && !capable(CAP_SYS_ADMIN)) + return -EPERM;
/* attach_prog_fd/attach_btf_obj_fd can specify fd of either bpf_prog * or btf, we need to check which one it is
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
LIRC_MODE2 does not really need net_admin capability, but only sys_admin.
Extract a new helper for it, it will be also used for the HID bpf implementation.
Cc: Sean Young sean@mess.org Acked-by: Sean Young sean@mess.org Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
Acked-by: Song Liu songliubraving@fb.com
changes in v3:
- dropped BPF_PROG_TYPE_EXT from the new helper
new in v2
kernel/bpf/syscall.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index 9beb585be5a6..b88688264ad0 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -2165,7 +2165,6 @@ static bool is_net_admin_prog_type(enum bpf_prog_type prog_type) case BPF_PROG_TYPE_LWT_SEG6LOCAL: case BPF_PROG_TYPE_SK_SKB: case BPF_PROG_TYPE_SK_MSG:
case BPF_PROG_TYPE_LIRC_MODE2: case BPF_PROG_TYPE_FLOW_DISSECTOR: case BPF_PROG_TYPE_CGROUP_DEVICE: case BPF_PROG_TYPE_CGROUP_SOCK:
@@ -2202,6 +2201,16 @@ static bool is_perfmon_prog_type(enum bpf_prog_type prog_type) } }
+static bool is_sys_admin_prog_type(enum bpf_prog_type prog_type) +{
switch (prog_type) {
case BPF_PROG_TYPE_LIRC_MODE2:
return true;
default:
return false;
}
+}
/* last field in 'union bpf_attr' used by this command */ #define BPF_PROG_LOAD_LAST_FIELD core_relo_rec_size
@@ -2252,6 +2261,8 @@ static int bpf_prog_load(union bpf_attr *attr, bpfptr_t uattr) return -EPERM; if (is_perfmon_prog_type(type) && !perfmon_capable()) return -EPERM;
if (is_sys_admin_prog_type(type) && !capable(CAP_SYS_ADMIN))
return -EPERM; /* attach_prog_fd/attach_btf_obj_fd can specify fd of either bpf_prog * or btf, we need to check which one it is
-- 2.35.1
HID is a protocol that could benefit from using BPF too.
This patch implements a net-like use of BPF capability for HID. Any incoming report coming from the device can be injected into a series of BPF programs that can modify it or even discard it by setting the size in the context to 0.
The kernel/bpf implementation is based on net-namespace.c, with only the bpf_link part kept, there is no real points in keeping the bpf_prog_{attach|detach} API.
The implementation here is only focusing on the bpf changes. The HID changes that hooks onto this are coming in separate patches.
Given that HID can be compiled in as a module, and the functions that kernel/bpf/hid.c needs to call in hid.ko are exported in struct hid_hooks.
This patch also adds a new flag to BPF_LINK_CREATE that allows to chose the position of the inserted program: at the beginning or at the end.
This way, we can have a tracing program that compare the raw event from the device and the transformed stream from all the other bpf programs.
This patch introduces the various attachment types the HID BPF programs need: - BPF_HID_DEVICE_EVENT for filtering events coming from the device - BPF_HID_USER_EVENT to initiate a call in a BPF program from userspace - BPF_HID_DRIVER_EVENT for getting notified of a driver action - BPF_HID_RDESC_FIXUP to be able to patch the report descriptor
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - squashed multiple patches from v2 - use of .convert_ctx_access to hide internal kernel API from userspace - also calls HID hook when a program has been updated (useful for reconnecting the device is the report descriptor fixup is used) - add BPF_HID_DRIVER_EVENT - s/hidraw_ino/hidraw_number/ - s/link_attach/pre_link_attach/ - s/link_attached/post_link_attach/ - s/array_detached/post_array_detach/
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples - unsigned long -> __u16 in uapi/linux/bpf_hid.h - change the bpf_ctx to be of variable size, with a min of 1024 bytes - make this 1 kB available directly from bpf program, the rest will need a helper - add some more doc comments in uapi --- include/linux/bpf-hid.h | 113 ++++++ include/linux/bpf_types.h | 4 + include/linux/hid.h | 5 + include/uapi/linux/bpf.h | 31 ++ include/uapi/linux/bpf_hid.h | 31 ++ kernel/bpf/Makefile | 3 + kernel/bpf/btf.c | 1 + kernel/bpf/hid.c | 643 +++++++++++++++++++++++++++++++++++ kernel/bpf/syscall.c | 14 + 9 files changed, 845 insertions(+) create mode 100644 include/linux/bpf-hid.h create mode 100644 include/uapi/linux/bpf_hid.h create mode 100644 kernel/bpf/hid.c
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h new file mode 100644 index 000000000000..9c8dbd389995 --- /dev/null +++ b/include/linux/bpf-hid.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BPF_HID_H +#define _BPF_HID_H + +#include <linux/mutex.h> +#include <uapi/linux/bpf.h> +#include <uapi/linux/bpf_hid.h> +#include <linux/list.h> +#include <linux/slab.h> + +struct bpf_prog; +struct bpf_prog_array; +struct hid_device; + +enum bpf_hid_attach_type { + BPF_HID_ATTACH_INVALID = -1, + BPF_HID_ATTACH_DEVICE_EVENT = 0, + BPF_HID_ATTACH_RDESC_FIXUP, + BPF_HID_ATTACH_USER_EVENT, + BPF_HID_ATTACH_DRIVER_EVENT, + MAX_BPF_HID_ATTACH_TYPE +}; + +struct hid_bpf_ctx_kern { + enum hid_bpf_event type; /* read-only */ + struct hid_device *hdev; /* read-only */ + + u16 size; /* used size in data (RW) */ + u8 *data; /* data buffer (RW) */ + u32 allocated_size; /* allocated size of data (RO) */ + + s32 retval; /* in use when BPF_HID_ATTACH_USER_EVENT (RW) */ +}; + +struct bpf_hid { + u8 *device_data; /* allocated when a bpf program of type + * SEC(hid/device_event) has been attached + * to this HID device + */ + u32 allocated_data; + + /* Array of programs to run compiled from links */ + struct bpf_prog_array __rcu *run_array[MAX_BPF_HID_ATTACH_TYPE]; + struct list_head links[MAX_BPF_HID_ATTACH_TYPE]; +}; + +static inline enum bpf_hid_attach_type +to_bpf_hid_attach_type(enum bpf_attach_type attach_type) +{ + switch (attach_type) { + case BPF_HID_DEVICE_EVENT: + return BPF_HID_ATTACH_DEVICE_EVENT; + case BPF_HID_RDESC_FIXUP: + return BPF_HID_ATTACH_RDESC_FIXUP; + case BPF_HID_USER_EVENT: + return BPF_HID_ATTACH_USER_EVENT; + case BPF_HID_DRIVER_EVENT: + return BPF_HID_ATTACH_DRIVER_EVENT; + default: + return BPF_HID_ATTACH_INVALID; + } +} + +union bpf_attr; +struct bpf_prog; + +#if IS_ENABLED(CONFIG_HID) +int bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr); +int bpf_hid_link_create(const union bpf_attr *attr, + struct bpf_prog *prog); +#else +static inline int bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr) +{ + return -EOPNOTSUPP; +} + +static inline int bpf_hid_link_create(const union bpf_attr *attr, + struct bpf_prog *prog) +{ + return -EOPNOTSUPP; +} +#endif + +static inline bool bpf_hid_link_empty(struct bpf_hid *bpf, + enum bpf_hid_attach_type type) +{ + return list_empty(&bpf->links[type]); +} + +struct bpf_hid_hooks { + struct hid_device *(*hdev_from_fd)(int fd); + int (*pre_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); + void (*post_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); + void (*array_detach)(struct hid_device *hdev, enum bpf_hid_attach_type type); +}; + +#ifdef CONFIG_BPF +int bpf_hid_init(struct hid_device *hdev); +void bpf_hid_exit(struct hid_device *hdev); +void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks); +#else +static inline int bpf_hid_init(struct hid_device *hdev) +{ + return 0; +} + +static inline void bpf_hid_exit(struct hid_device *hdev) {} +static inline void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) {} +#endif + +#endif /* _BPF_HID_H */ diff --git a/include/linux/bpf_types.h b/include/linux/bpf_types.h index 48a91c51c015..bb0190356949 100644 --- a/include/linux/bpf_types.h +++ b/include/linux/bpf_types.h @@ -76,6 +76,10 @@ BPF_PROG_TYPE(BPF_PROG_TYPE_EXT, bpf_extension, BPF_PROG_TYPE(BPF_PROG_TYPE_LSM, lsm, void *, void *) #endif /* CONFIG_BPF_LSM */ +#if IS_ENABLED(CONFIG_HID) +BPF_PROG_TYPE(BPF_PROG_TYPE_HID, hid, + struct hid_bpf_ctx, struct hid_bpf_ctx_kern) +#endif #endif BPF_PROG_TYPE(BPF_PROG_TYPE_SYSCALL, bpf_syscall, void *, void *) diff --git a/include/linux/hid.h b/include/linux/hid.h index 7487b0586fe6..56f6f4ad45a7 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -15,6 +15,7 @@
#include <linux/bitops.h> +#include <linux/bpf-hid.h> #include <linux/types.h> #include <linux/slab.h> #include <linux/list.h> @@ -639,6 +640,10 @@ struct hid_device { /* device report descriptor */ struct list_head debug_list; spinlock_t debug_list_lock; wait_queue_head_t debug_wait; + +#ifdef CONFIG_BPF + struct bpf_hid bpf; +#endif };
#define to_hid_device(pdev) \ diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 99fab54ae9c0..0e8438e93768 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */ + BPF_PROG_TYPE_HID, };
enum bpf_attach_type { @@ -997,6 +998,10 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT, + BPF_HID_DEVICE_EVENT, + BPF_HID_RDESC_FIXUP, + BPF_HID_USER_EVENT, + BPF_HID_DRIVER_EVENT, __MAX_BPF_ATTACH_TYPE };
@@ -1011,6 +1016,7 @@ enum bpf_link_type { BPF_LINK_TYPE_NETNS = 5, BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7, + BPF_LINK_TYPE_HID = 8,
MAX_BPF_LINK_TYPE, }; @@ -1118,6 +1124,16 @@ enum bpf_link_type { */ #define BPF_F_XDP_HAS_FRAGS (1U << 5)
+/* HID flag used in BPF_LINK_CREATE command + * + * NONE(default): The bpf program will be added at the tail of the list + * of existing bpf program for this type. + * + * BPF_F_INSERT_HEAD: The bpf program will be added at the beginning + * of the list of existing bpf program for this type.. + */ +#define BPF_F_INSERT_HEAD (1U << 0) + /* When BPF ldimm64's insn[0].src_reg != 0 then this can have * the following extensions: * @@ -5129,6 +5145,16 @@ union bpf_attr { * The **hash_algo** is returned on success, * **-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if * invalid arguments are passed. + * + * void *bpf_hid_get_data(void *ctx, u64 offset, u64 size) + * Description + * Returns a pointer to the data associated with context at the given + * offset and size (in bytes). + * + * Note: the returned pointer is refcounted and must be dereferenced + * by a call to bpf_hid_discard; + * Return + * The pointer to the data. On error, a null value is returned. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5325,6 +5351,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(skb_set_tstamp), \ FN(ima_file_hash), \ + FN(hid_get_data), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5925,6 +5952,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp; + struct { + __s32 hidraw_number; + __u32 attach_type; + } hid; }; } __attribute__((aligned(8)));
diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h new file mode 100644 index 000000000000..64a8b9dd8809 --- /dev/null +++ b/include/uapi/linux/bpf_hid.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */ + +/* + * HID BPF public headers + * + * Copyright (c) 2022 Benjamin Tissoires + */ + +#ifndef _UAPI__LINUX_BPF_HID_H__ +#define _UAPI__LINUX_BPF_HID_H__ + +#include <linux/types.h> + +enum hid_bpf_event { + HID_BPF_UNDEF = 0, + HID_BPF_DEVICE_EVENT, /* when attach type is BPF_HID_DEVICE_EVENT */ + HID_BPF_RDESC_FIXUP, /* ................... BPF_HID_RDESC_FIXUP */ + HID_BPF_USER_EVENT, /* ................... BPF_HID_USER_EVENT */ +}; + +/* User accessible data for HID programs. Add new fields at the end. */ +struct hid_bpf_ctx { + __u32 type; /* enum hid_bpf_event, RO */ + __u32 allocated_size; /* allocated size of data, RO */ + + __u32 size; /* valid data in data field, RW */ + __s32 retval; /* when type is HID_BPF_USER_EVENT, RW */ +}; + +#endif /* _UAPI__LINUX_BPF_HID_H__ */ + diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index c1a9be6a4b9f..8d5619d3d7e5 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -35,6 +35,9 @@ ifeq ($(CONFIG_BPF_JIT),y) obj-$(CONFIG_BPF_SYSCALL) += bpf_struct_ops.o obj-${CONFIG_BPF_LSM} += bpf_lsm.o endif +ifneq ($(CONFIG_HID),) +obj-$(CONFIG_BPF_SYSCALL) += hid.o +endif obj-$(CONFIG_BPF_PRELOAD) += preload/
obj-$(CONFIG_BPF_SYSCALL) += relo_core.o diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c index 8b34563a832e..a389626fdf75 100644 --- a/kernel/bpf/btf.c +++ b/kernel/bpf/btf.c @@ -16,6 +16,7 @@ #include <linux/kernel.h> #include <linux/idr.h> #include <linux/sort.h> +#include <linux/bpf-hid.h> #include <linux/bpf_verifier.h> #include <linux/btf.h> #include <linux/btf_ids.h> diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c new file mode 100644 index 000000000000..c21dc05f6207 --- /dev/null +++ b/kernel/bpf/hid.c @@ -0,0 +1,643 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/bpf.h> +#include <linux/bpf-hid.h> +#include <linux/filter.h> +#include <linux/hid.h> +#include <linux/hidraw.h> + +/* + * Functions to manage BPF programs attached to hid + */ + +struct bpf_hid_link { + struct bpf_link link; + enum bpf_attach_type type; + enum bpf_hid_attach_type hid_type; + + /* Must be accessed with bpf_hid_mutex held. */ + struct hid_device *hdev; + struct list_head node; /* node in list of links attached to hid */ +}; + +/* Protects updates to bpf_hid */ +DEFINE_MUTEX(bpf_hid_mutex); + +static struct bpf_hid_hooks hid_hooks = {0}; + +void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) +{ + if (hooks) + hid_hooks = *hooks; + else + memset(&hid_hooks, 0, sizeof(hid_hooks)); +} +EXPORT_SYMBOL_GPL(bpf_hid_set_hooks); + +BPF_CALL_3(bpf_hid_get_data, struct hid_bpf_ctx_kern*, ctx, u64, offset, u64, size) +{ + if (!size) + return 0UL; + + if (offset + size > ctx->allocated_size) + return 0UL; + + return (unsigned long)(ctx->data + offset); +} + +static const struct bpf_func_proto bpf_hid_get_data_proto = { + .func = bpf_hid_get_data, + .gpl_only = true, + .ret_type = RET_PTR_TO_ALLOC_MEM_OR_NULL, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_ANYTHING, + .arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO, +}; + +static const struct bpf_func_proto * +hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) +{ + switch (func_id) { + case BPF_FUNC_hid_get_data: + return &bpf_hid_get_data_proto; + default: + return bpf_base_func_proto(func_id); + } +} + +static bool hid_is_valid_access(int off, int size, + enum bpf_access_type access_type, + const struct bpf_prog *prog, + struct bpf_insn_access_aux *info) +{ + /* everything not in ctx is prohibited */ + const int size_default = sizeof(__u32); + + if (off < 0 || off >= sizeof(struct hid_bpf_ctx)) + return false; + + if (off % size != 0) + return false; + + switch (off) { + /* type is read-only */ + case offsetof(struct hid_bpf_ctx, type): + if (size != size_default) + return false; + return access_type == BPF_READ; + + /* allocated_size is read-only */ + case offsetof(struct hid_bpf_ctx, allocated_size): + if (size != size_default) + return false; + return access_type == BPF_READ; + + case offsetof(struct hid_bpf_ctx, retval): + if (size != size_default) + return false; + return (prog->expected_attach_type == BPF_HID_USER_EVENT || + prog->expected_attach_type == BPF_HID_DRIVER_EVENT); + default: + if (size != size_default) + return false; + break; + } + + /* everything else is read/write */ + return true; +} + +#define HID_ACCESS_FIELD(T, F) \ + T(BPF_FIELD_SIZEOF(struct hid_bpf_ctx_kern, F), \ + si->dst_reg, si->src_reg, \ + offsetof(struct hid_bpf_ctx_kern, F)) + +static u32 hid_convert_ctx_access(enum bpf_access_type type, + const struct bpf_insn *si, + struct bpf_insn *insn_buf, + struct bpf_prog *prog, + u32 *target_size) +{ + struct bpf_insn *insn = insn_buf; + + switch (si->off) { + case offsetof(struct hid_bpf_ctx, type): + *insn++ = HID_ACCESS_FIELD(BPF_LDX_MEM, type); + break; + case offsetof(struct hid_bpf_ctx, allocated_size): + *insn++ = HID_ACCESS_FIELD(BPF_LDX_MEM, allocated_size); + break; + case offsetof(struct hid_bpf_ctx, size): + if (type == BPF_WRITE) + *insn++ = HID_ACCESS_FIELD(BPF_STX_MEM, size); + else + *insn++ = HID_ACCESS_FIELD(BPF_LDX_MEM, size); + break; + case offsetof(struct hid_bpf_ctx, retval): + if (type == BPF_WRITE) + *insn++ = HID_ACCESS_FIELD(BPF_STX_MEM, retval); + else + *insn++ = HID_ACCESS_FIELD(BPF_LDX_MEM, retval); + break; + } + + return insn - insn_buf; +} + +const struct bpf_verifier_ops hid_verifier_ops = { + .get_func_proto = hid_func_proto, + .is_valid_access = hid_is_valid_access, + .convert_ctx_access = hid_convert_ctx_access, +}; + +/* Must be called with bpf_hid_mutex held. */ +static void bpf_hid_run_array_detach(struct hid_device *hdev, + enum bpf_hid_attach_type type) +{ + struct bpf_prog_array *run_array; + + run_array = rcu_replace_pointer(hdev->bpf.run_array[type], NULL, + lockdep_is_held(&bpf_hid_mutex)); + bpf_prog_array_free(run_array); + + if (hid_hooks.array_detach) + hid_hooks.array_detach(hdev, type); +} + +static int link_index(struct hid_device *hdev, enum bpf_hid_attach_type type, + struct bpf_hid_link *link) +{ + struct bpf_hid_link *pos; + int i = 0; + + list_for_each_entry(pos, &hdev->bpf.links[type], node) { + if (pos == link) + return i; + i++; + } + return -ENOENT; +} + +static int link_count(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + struct list_head *pos; + int i = 0; + + list_for_each(pos, &hdev->bpf.links[type]) + i++; + return i; +} + +static void fill_prog_array(struct hid_device *hdev, enum bpf_hid_attach_type type, + struct bpf_prog_array *prog_array) +{ + struct bpf_hid_link *pos; + unsigned int i = 0; + + list_for_each_entry(pos, &hdev->bpf.links[type], node) { + prog_array->items[i].prog = pos->link.prog; + i++; + } +} + +static void bpf_hid_link_release(struct bpf_link *link) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + enum bpf_hid_attach_type type = hid_link->hid_type; + struct bpf_prog_array *old_array, *new_array; + struct hid_device *hdev; + int cnt, idx; + + mutex_lock(&bpf_hid_mutex); + + hdev = hid_link->hdev; + if (!hdev) + goto out_unlock; + + /* Remember link position in case of safe delete */ + idx = link_index(hdev, type, hid_link); + list_del(&hid_link->node); + + cnt = link_count(hdev, type); + if (!cnt) { + bpf_hid_run_array_detach(hdev, type); + goto out_unlock; + } + + old_array = rcu_dereference_protected(hdev->bpf.run_array[type], + lockdep_is_held(&bpf_hid_mutex)); + new_array = bpf_prog_array_alloc(cnt, GFP_KERNEL); + if (!new_array) { + WARN_ON(bpf_prog_array_delete_safe_at(old_array, idx)); + goto out_unlock; + } + fill_prog_array(hdev, type, new_array); + rcu_assign_pointer(hdev->bpf.run_array[type], new_array); + bpf_prog_array_free(old_array); + +out_unlock: + hid_link->hdev = NULL; + mutex_unlock(&bpf_hid_mutex); +} + +static int bpf_hid_link_detach(struct bpf_link *link) +{ + bpf_hid_link_release(link); + return 0; +} + +static void bpf_hid_link_dealloc(struct bpf_link *link) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + + kfree(hid_link); +} + +static int bpf_hid_link_update_prog(struct bpf_link *link, + struct bpf_prog *new_prog, + struct bpf_prog *old_prog) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + enum bpf_hid_attach_type type = hid_link->hid_type; + struct bpf_prog_array *run_array; + struct hid_device *hdev; + int idx, ret; + + if (old_prog && old_prog != link->prog) + return -EPERM; + if (new_prog->type != link->prog->type) + return -EINVAL; + + mutex_lock(&bpf_hid_mutex); + + hdev = hid_link->hdev; + if (!hdev) { + /* hid dying */ + ret = -ENOLINK; + goto out_unlock; + } + + run_array = rcu_dereference_protected(hdev->bpf.run_array[type], + lockdep_is_held(&bpf_hid_mutex)); + idx = link_index(hdev, type, hid_link); + ret = bpf_prog_array_update_at(run_array, idx, new_prog); + if (ret) + goto out_unlock; + + old_prog = xchg(&link->prog, new_prog); + bpf_prog_put(old_prog); + + if (hid_hooks.post_link_attach) + hid_hooks.post_link_attach(hdev, type); + +out_unlock: + mutex_unlock(&bpf_hid_mutex); + return ret; +} + +static int bpf_hid_link_fill_info(const struct bpf_link *link, + struct bpf_link_info *info) +{ + const struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + int hidraw_number = -1; + struct hid_device *hdev; + struct hidraw *hidraw; + + mutex_lock(&bpf_hid_mutex); + hdev = hid_link->hdev; + if (hdev && hdev->hidraw) { + hidraw = hdev->hidraw; + hidraw_number = hidraw->minor; + } + mutex_unlock(&bpf_hid_mutex); + + info->hid.hidraw_number = hidraw_number; + info->hid.attach_type = hid_link->type; + return 0; +} + +static void bpf_hid_link_show_fdinfo(const struct bpf_link *link, + struct seq_file *seq) +{ + struct bpf_link_info info = {}; + + bpf_hid_link_fill_info(link, &info); + seq_printf(seq, + "hidraw_number:\t%u\n" + "attach_type:\t%u\n", + info.hid.hidraw_number, + info.hid.attach_type); +} + +static const struct bpf_link_ops bpf_hid_link_ops = { + .release = bpf_hid_link_release, + .dealloc = bpf_hid_link_dealloc, + .detach = bpf_hid_link_detach, + .update_prog = bpf_hid_link_update_prog, + .fill_link_info = bpf_hid_link_fill_info, + .show_fdinfo = bpf_hid_link_show_fdinfo, +}; + +/* Must be called with bpf_hid_mutex held. */ +static int __bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr, + struct hid_device *hdev, + enum bpf_hid_attach_type type) +{ + __u32 __user *prog_ids = u64_to_user_ptr(attr->query.prog_ids); + struct bpf_prog_array *run_array; + u32 prog_cnt = 0, flags = 0; + + run_array = rcu_dereference_protected(hdev->bpf.run_array[type], + lockdep_is_held(&bpf_hid_mutex)); + if (run_array) + prog_cnt = bpf_prog_array_length(run_array); + + if (copy_to_user(&uattr->query.attach_flags, &flags, sizeof(flags))) + return -EFAULT; + if (copy_to_user(&uattr->query.prog_cnt, &prog_cnt, sizeof(prog_cnt))) + return -EFAULT; + if (!attr->query.prog_cnt || !prog_ids || !prog_cnt) + return 0; + + return bpf_prog_array_copy_to_user(run_array, prog_ids, + attr->query.prog_cnt); +} + +int bpf_hid_prog_query(const union bpf_attr *attr, + union bpf_attr __user *uattr) +{ + enum bpf_hid_attach_type type; + struct hid_device *hdev; + int ret; + + if (attr->query.query_flags || !hid_hooks.hdev_from_fd) + return -EINVAL; + + type = to_bpf_hid_attach_type(attr->query.attach_type); + if (type < 0) + return -EINVAL; + + hdev = hid_hooks.hdev_from_fd(attr->query.target_fd); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + + mutex_lock(&bpf_hid_mutex); + ret = __bpf_hid_prog_query(attr, uattr, hdev, type); + mutex_unlock(&bpf_hid_mutex); + + return ret; +} + +static int bpf_hid_max_progs(enum bpf_hid_attach_type type) +{ + switch (type) { + case BPF_HID_ATTACH_DEVICE_EVENT: + return 64; + case BPF_HID_ATTACH_RDESC_FIXUP: + return 1; + case BPF_HID_ATTACH_USER_EVENT: + return 64; + case BPF_HID_ATTACH_DRIVER_EVENT: + return 64; + default: + return 0; + } +} + +static int bpf_hid_link_attach(struct hid_device *hdev, struct bpf_link *link, + enum bpf_hid_attach_type type, u32 flags) +{ + struct bpf_hid_link *hid_link = + container_of(link, struct bpf_hid_link, link); + struct bpf_prog_array *run_array; + int cnt, err = 0; + + mutex_lock(&bpf_hid_mutex); + + cnt = link_count(hdev, type); + if (cnt >= bpf_hid_max_progs(type)) { + err = -E2BIG; + goto out_unlock; + } + + if (hid_hooks.pre_link_attach) { + err = hid_hooks.pre_link_attach(hdev, type); + if (err) + goto out_unlock; + } + + run_array = bpf_prog_array_alloc(cnt + 1, GFP_KERNEL); + if (!run_array) { + err = -ENOMEM; + goto out_unlock; + } + + if (flags & BPF_F_INSERT_HEAD) + list_add(&hid_link->node, &hdev->bpf.links[type]); + else + list_add_tail(&hid_link->node, &hdev->bpf.links[type]); + + fill_prog_array(hdev, type, run_array); + run_array = rcu_replace_pointer(hdev->bpf.run_array[type], run_array, + lockdep_is_held(&bpf_hid_mutex)); + bpf_prog_array_free(run_array); + + if (hid_hooks.post_link_attach) + hid_hooks.post_link_attach(hdev, type); + +out_unlock: + mutex_unlock(&bpf_hid_mutex); + return err; +} + +int bpf_hid_link_create(const union bpf_attr *attr, struct bpf_prog *prog) +{ + enum bpf_hid_attach_type hid_type; + struct bpf_link_primer link_primer; + struct bpf_hid_link *hid_link; + enum bpf_attach_type type; + struct hid_device *hdev; + int err; + + if ((attr->link_create.flags & ~BPF_F_INSERT_HEAD) || !hid_hooks.hdev_from_fd) + return -EINVAL; + + type = attr->link_create.attach_type; + hid_type = to_bpf_hid_attach_type(type); + if (hid_type < 0) + return -EINVAL; + + hdev = hid_hooks.hdev_from_fd(attr->link_create.target_fd); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + + hid_link = kzalloc(sizeof(*hid_link), GFP_USER); + if (!hid_link) + return -ENOMEM; + + bpf_link_init(&hid_link->link, BPF_LINK_TYPE_HID, + &bpf_hid_link_ops, prog); + hid_link->hdev = hdev; + hid_link->type = type; + hid_link->hid_type = hid_type; + + err = bpf_link_prime(&hid_link->link, &link_primer); + if (err) { + kfree(hid_link); + return err; + } + + err = bpf_hid_link_attach(hdev, &hid_link->link, hid_type, attr->link_create.flags); + if (err) { + bpf_link_cleanup(&link_primer); + return err; + } + + return bpf_link_settle(&link_primer); +} + +static int hid_bpf_prog_test_run(struct bpf_prog *prog, + const union bpf_attr *attr, + union bpf_attr __user *uattr) +{ + struct hid_device *hdev = NULL; + struct bpf_prog_array *progs; + bool valid_prog = false; + int i; + int target_fd, ret; + void __user *data_out = u64_to_user_ptr(attr->test.data_out); + void __user *data_in = u64_to_user_ptr(attr->test.data_in); + u32 user_size_in = attr->test.data_size_in; + u32 user_size_out = attr->test.data_size_out; + u32 allocated_size = max(user_size_in, user_size_out); + struct hid_bpf_ctx_kern ctx = { + .type = HID_BPF_USER_EVENT, + .allocated_size = allocated_size, + }; + + if (!hid_hooks.hdev_from_fd) + return -EOPNOTSUPP; + + if (attr->test.ctx_size_in != sizeof(int)) + return -EINVAL; + + if (allocated_size > HID_MAX_BUFFER_SIZE) + return -E2BIG; + + if (copy_from_user(&target_fd, (void *)attr->test.ctx_in, attr->test.ctx_size_in)) + return -EFAULT; + + hdev = hid_hooks.hdev_from_fd(target_fd); + if (IS_ERR(hdev)) + return PTR_ERR(hdev); + + if (allocated_size) { + ctx.data = kzalloc(allocated_size, GFP_KERNEL); + if (!ctx.data) + return -ENOMEM; + + ctx.allocated_size = allocated_size; + } + ctx.hdev = hdev; + + ret = mutex_lock_interruptible(&bpf_hid_mutex); + if (ret) + return ret; + + /* check if the given program is of correct type and registered */ + progs = rcu_dereference_protected(hdev->bpf.run_array[BPF_HID_ATTACH_USER_EVENT], + lockdep_is_held(&bpf_hid_mutex)); + if (!progs) { + ret = -EFAULT; + goto unlock; + } + + for (i = 0; i < bpf_prog_array_length(progs); i++) { + if (progs->items[i].prog == prog) { + valid_prog = true; + break; + } + } + + if (!valid_prog) { + ret = -EINVAL; + goto unlock; + } + + /* copy data_in from userspace */ + if (user_size_in) { + if (copy_from_user(ctx.data, data_in, user_size_in)) { + ret = -EFAULT; + goto unlock; + } + + ctx.size = user_size_in; + } + + migrate_disable(); + + ret = bpf_prog_run(prog, &ctx); + + migrate_enable(); + + if (user_size_out && data_out) { + user_size_out = min3(user_size_out, (u32)ctx.size, allocated_size); + + if (copy_to_user(data_out, ctx.data, user_size_out)) { + ret = -EFAULT; + goto unlock; + } + + if (copy_to_user(&uattr->test.data_size_out, + &user_size_out, + sizeof(user_size_out))) { + ret = -EFAULT; + goto unlock; + } + } + + if (copy_to_user(&uattr->test.retval, &ctx.retval, sizeof(ctx.retval))) + ret = -EFAULT; + +unlock: + kfree(ctx.data); + + mutex_unlock(&bpf_hid_mutex); + return ret; +} + +const struct bpf_prog_ops hid_prog_ops = { + .test_run = hid_bpf_prog_test_run, +}; + +int bpf_hid_init(struct hid_device *hdev) +{ + int type; + + for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++) + INIT_LIST_HEAD(&hdev->bpf.links[type]); + + return 0; +} +EXPORT_SYMBOL_GPL(bpf_hid_init); + +void bpf_hid_exit(struct hid_device *hdev) +{ + enum bpf_hid_attach_type type; + struct bpf_hid_link *hid_link; + + mutex_lock(&bpf_hid_mutex); + for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++) { + bpf_hid_run_array_detach(hdev, type); + list_for_each_entry(hid_link, &hdev->bpf.links[type], node) { + hid_link->hdev = NULL; /* auto-detach link */ + } + } + mutex_unlock(&bpf_hid_mutex); +} +EXPORT_SYMBOL_GPL(bpf_hid_exit); diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index b88688264ad0..d1c05011e5ab 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3,6 +3,7 @@ */ #include <linux/bpf.h> #include <linux/bpf-cgroup.h> +#include <linux/bpf-hid.h> #include <linux/bpf_trace.h> #include <linux/bpf_lirc.h> #include <linux/bpf_verifier.h> @@ -2205,6 +2206,7 @@ static bool is_sys_admin_prog_type(enum bpf_prog_type prog_type) { switch (prog_type) { case BPF_PROG_TYPE_LIRC_MODE2: + case BPF_PROG_TYPE_HID: return true; default: return false; @@ -3199,6 +3201,11 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) return BPF_PROG_TYPE_SK_LOOKUP; case BPF_XDP: return BPF_PROG_TYPE_XDP; + case BPF_HID_DEVICE_EVENT: + case BPF_HID_RDESC_FIXUP: + case BPF_HID_USER_EVENT: + case BPF_HID_DRIVER_EVENT: + return BPF_PROG_TYPE_HID; default: return BPF_PROG_TYPE_UNSPEC; } @@ -3342,6 +3349,11 @@ static int bpf_prog_query(const union bpf_attr *attr, case BPF_SK_MSG_VERDICT: case BPF_SK_SKB_VERDICT: return sock_map_bpf_prog_query(attr, uattr); + case BPF_HID_DEVICE_EVENT: + case BPF_HID_RDESC_FIXUP: + case BPF_HID_USER_EVENT: + case BPF_HID_DRIVER_EVENT: + return bpf_hid_prog_query(attr, uattr); default: return -EINVAL; } @@ -4336,6 +4348,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr) ret = bpf_perf_link_attach(attr, prog); break; #endif + case BPF_PROG_TYPE_HID: + return bpf_hid_link_create(attr, prog); default: ret = -EINVAL; }
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
[...]
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h new file mode 100644 index 000000000000..9c8dbd389995 --- /dev/null +++ b/include/linux/bpf-hid.h
[...]
+struct hid_bpf_ctx_kern {
enum hid_bpf_event type; /* read-only */
struct hid_device *hdev; /* read-only */
u16 size; /* used size in data (RW) */
u8 *data; /* data buffer (RW) */
u32 allocated_size; /* allocated size of data (RO) */
Why u16 size vs. u32 allocated_size? Also, maybe shuffle the members to remove some holes?
s32 retval; /* in use when BPF_HID_ATTACH_USER_EVENT (RW) */
+};
[...]
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
We need to mirror these changes to tools/include/uapi/linux/bpf.h.
index 99fab54ae9c0..0e8438e93768 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
BPF_PROG_TYPE_HID,
};
[...]
/* When BPF ldimm64's insn[0].src_reg != 0 then this can have
- the following extensions:
@@ -5129,6 +5145,16 @@ union bpf_attr {
The **hash_algo** is returned on success,
**-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
invalid arguments are passed.
- void *bpf_hid_get_data(void *ctx, u64 offset, u64 size)
Description
Returns a pointer to the data associated with context at the given
offset and size (in bytes).
Note: the returned pointer is refcounted and must be dereferenced
by a call to bpf_hid_discard;
Return
The pointer to the data. On error, a null value is returned.
Please use annotations like *size*, **NULL**.
*/ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5325,6 +5351,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(skb_set_tstamp), \ FN(ima_file_hash), \
FN(hid_get_data), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5925,6 +5952,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp;
struct {
__s32 hidraw_number;
__u32 attach_type;
} hid; };
} __attribute__((aligned(8)));
diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h new file mode 100644 index 000000000000..64a8b9dd8809 --- /dev/null +++ b/include/uapi/linux/bpf_hid.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
+/*
- HID BPF public headers
- Copyright (c) 2022 Benjamin Tissoires
- */
+#ifndef _UAPI__LINUX_BPF_HID_H__ +#define _UAPI__LINUX_BPF_HID_H__
+#include <linux/types.h>
+enum hid_bpf_event {
HID_BPF_UNDEF = 0,
HID_BPF_DEVICE_EVENT, /* when attach type is BPF_HID_DEVICE_EVENT */
HID_BPF_RDESC_FIXUP, /* ................... BPF_HID_RDESC_FIXUP */
HID_BPF_USER_EVENT, /* ................... BPF_HID_USER_EVENT */
Why don't we have a DRIVER_EVENT type here?
[...]
+BPF_CALL_3(bpf_hid_get_data, struct hid_bpf_ctx_kern*, ctx, u64, offset, u64, size) +{
if (!size)
return 0UL;
if (offset + size > ctx->allocated_size)
return 0UL;
return (unsigned long)(ctx->data + offset);
+}
+static const struct bpf_func_proto bpf_hid_get_data_proto = {
.func = bpf_hid_get_data,
.gpl_only = true,
.ret_type = RET_PTR_TO_ALLOC_MEM_OR_NULL,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO,
I think we should use ARG_CONST_SIZE or ARG_CONST_SIZE_OR_ZERO?
+};
+static const struct bpf_func_proto * +hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) +{
switch (func_id) {
case BPF_FUNC_hid_get_data:
return &bpf_hid_get_data_proto;
default:
return bpf_base_func_proto(func_id);
}
+}
[...]
+static int hid_bpf_prog_test_run(struct bpf_prog *prog,
const union bpf_attr *attr,
union bpf_attr __user *uattr)
+{
struct hid_device *hdev = NULL;
struct bpf_prog_array *progs;
bool valid_prog = false;
int i;
int target_fd, ret;
void __user *data_out = u64_to_user_ptr(attr->test.data_out);
void __user *data_in = u64_to_user_ptr(attr->test.data_in);
u32 user_size_in = attr->test.data_size_in;
u32 user_size_out = attr->test.data_size_out;
u32 allocated_size = max(user_size_in, user_size_out);
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_USER_EVENT,
.allocated_size = allocated_size,
};
if (!hid_hooks.hdev_from_fd)
return -EOPNOTSUPP;
if (attr->test.ctx_size_in != sizeof(int))
return -EINVAL;
ctx_size_in is always 4 bytes?
if (allocated_size > HID_MAX_BUFFER_SIZE)
return -E2BIG;
if (copy_from_user(&target_fd, (void *)attr->test.ctx_in, attr->test.ctx_size_in))
return -EFAULT;
hdev = hid_hooks.hdev_from_fd(target_fd);
if (IS_ERR(hdev))
return PTR_ERR(hdev);
if (allocated_size) {
ctx.data = kzalloc(allocated_size, GFP_KERNEL);
if (!ctx.data)
return -ENOMEM;
ctx.allocated_size = allocated_size;
}
ctx.hdev = hdev;
ret = mutex_lock_interruptible(&bpf_hid_mutex);
if (ret)
return ret;
/* check if the given program is of correct type and registered */
progs = rcu_dereference_protected(hdev->bpf.run_array[BPF_HID_ATTACH_USER_EVENT],
lockdep_is_held(&bpf_hid_mutex));
if (!progs) {
ret = -EFAULT;
goto unlock;
}
for (i = 0; i < bpf_prog_array_length(progs); i++) {
if (progs->items[i].prog == prog) {
valid_prog = true;
break;
}
}
if (!valid_prog) {
ret = -EINVAL;
goto unlock;
}
/* copy data_in from userspace */
if (user_size_in) {
if (copy_from_user(ctx.data, data_in, user_size_in)) {
ret = -EFAULT;
goto unlock;
}
ctx.size = user_size_in;
}
migrate_disable();
ret = bpf_prog_run(prog, &ctx);
migrate_enable();
if (user_size_out && data_out) {
user_size_out = min3(user_size_out, (u32)ctx.size, allocated_size);
if (copy_to_user(data_out, ctx.data, user_size_out)) {
ret = -EFAULT;
goto unlock;
}
if (copy_to_user(&uattr->test.data_size_out,
&user_size_out,
sizeof(user_size_out))) {
ret = -EFAULT;
goto unlock;
}
}
if (copy_to_user(&uattr->test.retval, &ctx.retval, sizeof(ctx.retval)))
ret = -EFAULT;
+unlock:
kfree(ctx.data);
mutex_unlock(&bpf_hid_mutex);
return ret;
+}
+const struct bpf_prog_ops hid_prog_ops = {
.test_run = hid_bpf_prog_test_run,
+};
+int bpf_hid_init(struct hid_device *hdev) +{
int type;
for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++)
INIT_LIST_HEAD(&hdev->bpf.links[type]);
return 0;
+} +EXPORT_SYMBOL_GPL(bpf_hid_init);
+void bpf_hid_exit(struct hid_device *hdev) +{
enum bpf_hid_attach_type type;
struct bpf_hid_link *hid_link;
mutex_lock(&bpf_hid_mutex);
for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++) {
bpf_hid_run_array_detach(hdev, type);
list_for_each_entry(hid_link, &hdev->bpf.links[type], node) {
hid_link->hdev = NULL; /* auto-detach link */
}
}
mutex_unlock(&bpf_hid_mutex);
+} +EXPORT_SYMBOL_GPL(bpf_hid_exit); diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index b88688264ad0..d1c05011e5ab 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3,6 +3,7 @@ */ #include <linux/bpf.h> #include <linux/bpf-cgroup.h> +#include <linux/bpf-hid.h> #include <linux/bpf_trace.h> #include <linux/bpf_lirc.h> #include <linux/bpf_verifier.h> @@ -2205,6 +2206,7 @@ static bool is_sys_admin_prog_type(enum bpf_prog_type prog_type) { switch (prog_type) { case BPF_PROG_TYPE_LIRC_MODE2:
case BPF_PROG_TYPE_HID: return true; default: return false;
@@ -3199,6 +3201,11 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) return BPF_PROG_TYPE_SK_LOOKUP; case BPF_XDP: return BPF_PROG_TYPE_XDP;
case BPF_HID_DEVICE_EVENT:
case BPF_HID_RDESC_FIXUP:
case BPF_HID_USER_EVENT:
case BPF_HID_DRIVER_EVENT:
return BPF_PROG_TYPE_HID; default: return BPF_PROG_TYPE_UNSPEC; }
@@ -3342,6 +3349,11 @@ static int bpf_prog_query(const union bpf_attr *attr, case BPF_SK_MSG_VERDICT: case BPF_SK_SKB_VERDICT: return sock_map_bpf_prog_query(attr, uattr);
case BPF_HID_DEVICE_EVENT:
case BPF_HID_RDESC_FIXUP:
case BPF_HID_USER_EVENT:
case BPF_HID_DRIVER_EVENT:
return bpf_hid_prog_query(attr, uattr); default: return -EINVAL; }
@@ -4336,6 +4348,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr) ret = bpf_perf_link_attach(attr, prog); break; #endif
case BPF_PROG_TYPE_HID:
return bpf_hid_link_create(attr, prog); default: ret = -EINVAL; }
-- 2.35.1
Hi Song,
many thanks for the quick response.
On Fri, Mar 18, 2022 at 9:48 PM Song Liu song@kernel.org wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
[...]
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h new file mode 100644 index 000000000000..9c8dbd389995 --- /dev/null +++ b/include/linux/bpf-hid.h
[...]
+struct hid_bpf_ctx_kern {
enum hid_bpf_event type; /* read-only */
struct hid_device *hdev; /* read-only */
u16 size; /* used size in data (RW) */
u8 *data; /* data buffer (RW) */
u32 allocated_size; /* allocated size of data (RO) */
Why u16 size vs. u32 allocated_size?
Probably an oversight because I wrote u32 in the public uapi. Will change this into u16 too.
Also, maybe shuffle the members to remove some holes?
Ack will do in the next version.
s32 retval; /* in use when BPF_HID_ATTACH_USER_EVENT (RW) */
+};
[...]
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
We need to mirror these changes to tools/include/uapi/linux/bpf.h.
OK. I did that in patch 4/17 but I can bring in the changes there too.
index 99fab54ae9c0..0e8438e93768 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
BPF_PROG_TYPE_HID,
};
[...]
/* When BPF ldimm64's insn[0].src_reg != 0 then this can have
- the following extensions:
@@ -5129,6 +5145,16 @@ union bpf_attr {
The **hash_algo** is returned on success,
**-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
invalid arguments are passed.
- void *bpf_hid_get_data(void *ctx, u64 offset, u64 size)
Description
Returns a pointer to the data associated with context at the given
offset and size (in bytes).
Note: the returned pointer is refcounted and must be dereferenced
by a call to bpf_hid_discard;
Return
The pointer to the data. On error, a null value is returned.
Please use annotations like *size*, **NULL**.
Ack
*/ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5325,6 +5351,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(skb_set_tstamp), \ FN(ima_file_hash), \
FN(hid_get_data), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5925,6 +5952,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp;
struct {
__s32 hidraw_number;
__u32 attach_type;
} hid; };
} __attribute__((aligned(8)));
diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h new file mode 100644 index 000000000000..64a8b9dd8809 --- /dev/null +++ b/include/uapi/linux/bpf_hid.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
+/*
- HID BPF public headers
- Copyright (c) 2022 Benjamin Tissoires
- */
+#ifndef _UAPI__LINUX_BPF_HID_H__ +#define _UAPI__LINUX_BPF_HID_H__
+#include <linux/types.h>
+enum hid_bpf_event {
HID_BPF_UNDEF = 0,
HID_BPF_DEVICE_EVENT, /* when attach type is BPF_HID_DEVICE_EVENT */
HID_BPF_RDESC_FIXUP, /* ................... BPF_HID_RDESC_FIXUP */
HID_BPF_USER_EVENT, /* ................... BPF_HID_USER_EVENT */
Why don't we have a DRIVER_EVENT type here?
For driver event, I want to have a little bit more of information which tells which event we have: - HID_BPF_DRIVER_PROBE - HID_BPF_DRIVER_SUSPEND - HID_BPF_DRIVER_RAW_REQUEST - HID_BPF_DRIVER_RAW_REQUEST_ANSWER - etc...
However, I am not entirely sure on the implementation of all of those, so I left them aside for now.
I'll work on that for v4.
[...]
+BPF_CALL_3(bpf_hid_get_data, struct hid_bpf_ctx_kern*, ctx, u64, offset, u64, size) +{
if (!size)
return 0UL;
if (offset + size > ctx->allocated_size)
return 0UL;
return (unsigned long)(ctx->data + offset);
+}
+static const struct bpf_func_proto bpf_hid_get_data_proto = {
.func = bpf_hid_get_data,
.gpl_only = true,
.ret_type = RET_PTR_TO_ALLOC_MEM_OR_NULL,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO,
I think we should use ARG_CONST_SIZE or ARG_CONST_SIZE_OR_ZERO?
I initially tried this with ARG_CONST_SIZE_OR_ZERO but it doesn't work for 2 reasons: - we need to pair the argument ARG_CONST_SIZE_* with a pointer to a memory just before, which doesn't really make sense here - ARG_CONST_SIZE_* isn't handled in the same way ARG_CONST_ALLOC_SIZE_OR_ZERO is. The latter tells the verifier that the given size is the available size of the returned PTR_TO_ALLOC_MEM_OR_NULL, which is exactly what we want.
+};
+static const struct bpf_func_proto * +hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) +{
switch (func_id) {
case BPF_FUNC_hid_get_data:
return &bpf_hid_get_data_proto;
default:
return bpf_base_func_proto(func_id);
}
+}
[...]
+static int hid_bpf_prog_test_run(struct bpf_prog *prog,
const union bpf_attr *attr,
union bpf_attr __user *uattr)
+{
struct hid_device *hdev = NULL;
struct bpf_prog_array *progs;
bool valid_prog = false;
int i;
int target_fd, ret;
void __user *data_out = u64_to_user_ptr(attr->test.data_out);
void __user *data_in = u64_to_user_ptr(attr->test.data_in);
u32 user_size_in = attr->test.data_size_in;
u32 user_size_out = attr->test.data_size_out;
u32 allocated_size = max(user_size_in, user_size_out);
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_USER_EVENT,
.allocated_size = allocated_size,
};
if (!hid_hooks.hdev_from_fd)
return -EOPNOTSUPP;
if (attr->test.ctx_size_in != sizeof(int))
return -EINVAL;
ctx_size_in is always 4 bytes?
Yes. Basically what I had in mind is that the "ctx" for user_prog_test_run is the file descriptor to the sysfs that represent the HID device. This seemed to me to be the easiest to handle for users.
I'm open to suggestions though.
if (allocated_size > HID_MAX_BUFFER_SIZE)
return -E2BIG;
if (copy_from_user(&target_fd, (void *)attr->test.ctx_in, attr->test.ctx_size_in))
return -EFAULT;
hdev = hid_hooks.hdev_from_fd(target_fd);
if (IS_ERR(hdev))
return PTR_ERR(hdev);
if (allocated_size) {
ctx.data = kzalloc(allocated_size, GFP_KERNEL);
if (!ctx.data)
return -ENOMEM;
ctx.allocated_size = allocated_size;
}
ctx.hdev = hdev;
ret = mutex_lock_interruptible(&bpf_hid_mutex);
if (ret)
return ret;
/* check if the given program is of correct type and registered */
progs = rcu_dereference_protected(hdev->bpf.run_array[BPF_HID_ATTACH_USER_EVENT],
lockdep_is_held(&bpf_hid_mutex));
if (!progs) {
ret = -EFAULT;
goto unlock;
}
for (i = 0; i < bpf_prog_array_length(progs); i++) {
if (progs->items[i].prog == prog) {
valid_prog = true;
break;
}
}
if (!valid_prog) {
ret = -EINVAL;
goto unlock;
}
/* copy data_in from userspace */
if (user_size_in) {
if (copy_from_user(ctx.data, data_in, user_size_in)) {
ret = -EFAULT;
goto unlock;
}
ctx.size = user_size_in;
}
migrate_disable();
ret = bpf_prog_run(prog, &ctx);
migrate_enable();
if (user_size_out && data_out) {
user_size_out = min3(user_size_out, (u32)ctx.size, allocated_size);
if (copy_to_user(data_out, ctx.data, user_size_out)) {
ret = -EFAULT;
goto unlock;
}
if (copy_to_user(&uattr->test.data_size_out,
&user_size_out,
sizeof(user_size_out))) {
ret = -EFAULT;
goto unlock;
}
}
if (copy_to_user(&uattr->test.retval, &ctx.retval, sizeof(ctx.retval)))
ret = -EFAULT;
+unlock:
kfree(ctx.data);
mutex_unlock(&bpf_hid_mutex);
return ret;
+}
+const struct bpf_prog_ops hid_prog_ops = {
.test_run = hid_bpf_prog_test_run,
+};
+int bpf_hid_init(struct hid_device *hdev) +{
int type;
for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++)
INIT_LIST_HEAD(&hdev->bpf.links[type]);
return 0;
+} +EXPORT_SYMBOL_GPL(bpf_hid_init);
+void bpf_hid_exit(struct hid_device *hdev) +{
enum bpf_hid_attach_type type;
struct bpf_hid_link *hid_link;
mutex_lock(&bpf_hid_mutex);
for (type = 0; type < MAX_BPF_HID_ATTACH_TYPE; type++) {
bpf_hid_run_array_detach(hdev, type);
list_for_each_entry(hid_link, &hdev->bpf.links[type], node) {
hid_link->hdev = NULL; /* auto-detach link */
}
}
mutex_unlock(&bpf_hid_mutex);
+} +EXPORT_SYMBOL_GPL(bpf_hid_exit); diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c index b88688264ad0..d1c05011e5ab 100644 --- a/kernel/bpf/syscall.c +++ b/kernel/bpf/syscall.c @@ -3,6 +3,7 @@ */ #include <linux/bpf.h> #include <linux/bpf-cgroup.h> +#include <linux/bpf-hid.h> #include <linux/bpf_trace.h> #include <linux/bpf_lirc.h> #include <linux/bpf_verifier.h> @@ -2205,6 +2206,7 @@ static bool is_sys_admin_prog_type(enum bpf_prog_type prog_type) { switch (prog_type) { case BPF_PROG_TYPE_LIRC_MODE2:
case BPF_PROG_TYPE_HID: return true; default: return false;
@@ -3199,6 +3201,11 @@ attach_type_to_prog_type(enum bpf_attach_type attach_type) return BPF_PROG_TYPE_SK_LOOKUP; case BPF_XDP: return BPF_PROG_TYPE_XDP;
case BPF_HID_DEVICE_EVENT:
case BPF_HID_RDESC_FIXUP:
case BPF_HID_USER_EVENT:
case BPF_HID_DRIVER_EVENT:
return BPF_PROG_TYPE_HID; default: return BPF_PROG_TYPE_UNSPEC; }
@@ -3342,6 +3349,11 @@ static int bpf_prog_query(const union bpf_attr *attr, case BPF_SK_MSG_VERDICT: case BPF_SK_SKB_VERDICT: return sock_map_bpf_prog_query(attr, uattr);
case BPF_HID_DEVICE_EVENT:
case BPF_HID_RDESC_FIXUP:
case BPF_HID_USER_EVENT:
case BPF_HID_DRIVER_EVENT:
return bpf_hid_prog_query(attr, uattr); default: return -EINVAL; }
@@ -4336,6 +4348,8 @@ static int link_create(union bpf_attr *attr, bpfptr_t uattr) ret = bpf_perf_link_attach(attr, prog); break; #endif
case BPF_PROG_TYPE_HID:
return bpf_hid_link_create(attr, prog); default: ret = -EINVAL; }
-- 2.35.1
Cheers, Benjamin
On Mon, Mar 21, 2022 at 9:07 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Song,
many thanks for the quick response.
On Fri, Mar 18, 2022 at 9:48 PM Song Liu song@kernel.org wrote:
[...]
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
We need to mirror these changes to tools/include/uapi/linux/bpf.h.
OK. I did that in patch 4/17 but I can bring in the changes there too.
Let's keep changes to the two files in the same patch. This will make sure they are back ported together.
[...]
+enum hid_bpf_event {
HID_BPF_UNDEF = 0,
HID_BPF_DEVICE_EVENT, /* when attach type is BPF_HID_DEVICE_EVENT */
HID_BPF_RDESC_FIXUP, /* ................... BPF_HID_RDESC_FIXUP */
HID_BPF_USER_EVENT, /* ................... BPF_HID_USER_EVENT */
Why don't we have a DRIVER_EVENT type here?
For driver event, I want to have a little bit more of information which tells which event we have:
- HID_BPF_DRIVER_PROBE
- HID_BPF_DRIVER_SUSPEND
- HID_BPF_DRIVER_RAW_REQUEST
- HID_BPF_DRIVER_RAW_REQUEST_ANSWER
- etc...
However, I am not entirely sure on the implementation of all of those, so I left them aside for now.
I'll work on that for v4.
This set is already pretty big. I guess we can add them in a follow-up set.
[...]
+BPF_CALL_3(bpf_hid_get_data, struct hid_bpf_ctx_kern*, ctx, u64, offset, u64, size) +{
if (!size)
return 0UL;
if (offset + size > ctx->allocated_size)
return 0UL;
return (unsigned long)(ctx->data + offset);
+}
+static const struct bpf_func_proto bpf_hid_get_data_proto = {
.func = bpf_hid_get_data,
.gpl_only = true,
.ret_type = RET_PTR_TO_ALLOC_MEM_OR_NULL,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO,
I think we should use ARG_CONST_SIZE or ARG_CONST_SIZE_OR_ZERO?
I initially tried this with ARG_CONST_SIZE_OR_ZERO but it doesn't work for 2 reasons:
- we need to pair the argument ARG_CONST_SIZE_* with a pointer to a
memory just before, which doesn't really make sense here
- ARG_CONST_SIZE_* isn't handled in the same way
ARG_CONST_ALLOC_SIZE_OR_ZERO is. The latter tells the verifier that the given size is the available size of the returned PTR_TO_ALLOC_MEM_OR_NULL, which is exactly what we want.
I misread the logic initially. It makes sense now.
+};
+static const struct bpf_func_proto * +hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) +{
switch (func_id) {
case BPF_FUNC_hid_get_data:
return &bpf_hid_get_data_proto;
default:
return bpf_base_func_proto(func_id);
}
+}
[...]
+static int hid_bpf_prog_test_run(struct bpf_prog *prog,
const union bpf_attr *attr,
union bpf_attr __user *uattr)
+{
struct hid_device *hdev = NULL;
struct bpf_prog_array *progs;
bool valid_prog = false;
int i;
int target_fd, ret;
void __user *data_out = u64_to_user_ptr(attr->test.data_out);
void __user *data_in = u64_to_user_ptr(attr->test.data_in);
u32 user_size_in = attr->test.data_size_in;
u32 user_size_out = attr->test.data_size_out;
u32 allocated_size = max(user_size_in, user_size_out);
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_USER_EVENT,
.allocated_size = allocated_size,
};
if (!hid_hooks.hdev_from_fd)
return -EOPNOTSUPP;
if (attr->test.ctx_size_in != sizeof(int))
return -EINVAL;
ctx_size_in is always 4 bytes?
Yes. Basically what I had in mind is that the "ctx" for user_prog_test_run is the file descriptor to the sysfs that represent the HID device. This seemed to me to be the easiest to handle for users.
I'm open to suggestions though.
How about we use data_in? ctx for test_run usually means the program ctx, which is struct hid_bpf_ctx here.
Thanks, Song
On Mon, Mar 21, 2022 at 10:52 PM Song Liu song@kernel.org wrote:
On Mon, Mar 21, 2022 at 9:07 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Song,
many thanks for the quick response.
On Fri, Mar 18, 2022 at 9:48 PM Song Liu song@kernel.org wrote:
[...]
diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h
We need to mirror these changes to tools/include/uapi/linux/bpf.h.
OK. I did that in patch 4/17 but I can bring in the changes there too.
Let's keep changes to the two files in the same patch. This will make sure they are back ported together.
Ack
[...]
+enum hid_bpf_event {
HID_BPF_UNDEF = 0,
HID_BPF_DEVICE_EVENT, /* when attach type is BPF_HID_DEVICE_EVENT */
HID_BPF_RDESC_FIXUP, /* ................... BPF_HID_RDESC_FIXUP */
HID_BPF_USER_EVENT, /* ................... BPF_HID_USER_EVENT */
Why don't we have a DRIVER_EVENT type here?
For driver event, I want to have a little bit more of information which tells which event we have:
- HID_BPF_DRIVER_PROBE
- HID_BPF_DRIVER_SUSPEND
- HID_BPF_DRIVER_RAW_REQUEST
- HID_BPF_DRIVER_RAW_REQUEST_ANSWER
- etc...
However, I am not entirely sure on the implementation of all of those, so I left them aside for now.
I'll work on that for v4.
This set is already pretty big. I guess we can add them in a follow-up set.
[...]
[...]
+static int hid_bpf_prog_test_run(struct bpf_prog *prog,
const union bpf_attr *attr,
union bpf_attr __user *uattr)
+{
struct hid_device *hdev = NULL;
struct bpf_prog_array *progs;
bool valid_prog = false;
int i;
int target_fd, ret;
void __user *data_out = u64_to_user_ptr(attr->test.data_out);
void __user *data_in = u64_to_user_ptr(attr->test.data_in);
u32 user_size_in = attr->test.data_size_in;
u32 user_size_out = attr->test.data_size_out;
u32 allocated_size = max(user_size_in, user_size_out);
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_USER_EVENT,
.allocated_size = allocated_size,
};
if (!hid_hooks.hdev_from_fd)
return -EOPNOTSUPP;
if (attr->test.ctx_size_in != sizeof(int))
return -EINVAL;
ctx_size_in is always 4 bytes?
Yes. Basically what I had in mind is that the "ctx" for user_prog_test_run is the file descriptor to the sysfs that represent the HID device. This seemed to me to be the easiest to handle for users.
I'm open to suggestions though.
How about we use data_in? ctx for test_run usually means the program ctx, which is struct hid_bpf_ctx here.
I'd rather not use data_in. data_in is forwarded as it is in the ctx of the program, so adding a bulky API where the first byte is the target_fd doesn't make a lot of sense IMO.
However, I just managed to achieve what I initially wanted to do without luck: just use the struct bpf_prog as the sole argument. I thought iterating over all hid devices would be painful, but it turns out that is exactly what hid_bpf_fd_to_hdev() was doing, so there is no penalty in doing so.
Anyway, I'll drop ctx_in in the next version.
Cheers, Benjamin
This is just to hammer the obvious because I suspect you can not already load a bpf HID program which is not GPL because all of the useful functions are GPL only.
Anyway, this ensures that users are not tempted to bypass this requirement and will allow us to ship tested BPF programs in the kernel without having to aorry about the license.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
no changes in v3
new in v2: - Note: I placed this statement in check_attach_btf_id() to be local to other similar checks (regarding LSM), however, I have no idea if this is the correct place. Please shout at me if it isn't. --- include/linux/bpf-hid.h | 8 ++++++++ kernel/bpf/hid.c | 12 ++++++++++++ kernel/bpf/verifier.c | 7 +++++++ 3 files changed, 27 insertions(+)
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 9c8dbd389995..7f596554fe8c 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -2,6 +2,7 @@ #ifndef _BPF_HID_H #define _BPF_HID_H
+#include <linux/bpf_verifier.h> #include <linux/mutex.h> #include <uapi/linux/bpf.h> #include <uapi/linux/bpf_hid.h> @@ -69,6 +70,8 @@ int bpf_hid_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr); int bpf_hid_link_create(const union bpf_attr *attr, struct bpf_prog *prog); +int bpf_hid_verify_prog(struct bpf_verifier_log *vlog, + const struct bpf_prog *prog); #else static inline int bpf_hid_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr) @@ -81,6 +84,11 @@ static inline int bpf_hid_link_create(const union bpf_attr *attr, { return -EOPNOTSUPP; } +static inline int bpf_hid_verify_prog(struct bpf_verifier_log *vlog, + const struct bpf_prog *prog) +{ + return -EOPNOTSUPP; +} #endif
static inline bool bpf_hid_link_empty(struct bpf_hid *bpf, diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index c21dc05f6207..2dfeaaa8a83f 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -34,6 +34,18 @@ void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) } EXPORT_SYMBOL_GPL(bpf_hid_set_hooks);
+int bpf_hid_verify_prog(struct bpf_verifier_log *vlog, + const struct bpf_prog *prog) +{ + if (!prog->gpl_compatible) { + bpf_log(vlog, + "HID programs must have a GPL compatible license\n"); + return -EINVAL; + } + + return 0; +} + BPF_CALL_3(bpf_hid_get_data, struct hid_bpf_ctx_kern*, ctx, u64, offset, u64, size) { if (!size) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index cf92f9c01556..da06d633fb8d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -21,6 +21,7 @@ #include <linux/perf_event.h> #include <linux/ctype.h> #include <linux/error-injection.h> +#include <linux/bpf-hid.h> #include <linux/bpf_lsm.h> #include <linux/btf_ids.h>
@@ -14272,6 +14273,12 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) return check_struct_ops_btf_id(env);
+ if (prog->type == BPF_PROG_TYPE_HID) { + ret = bpf_hid_verify_prog(&env->log, prog); + if (ret < 0) + return ret; + } + if (prog->type != BPF_PROG_TYPE_TRACING && prog->type != BPF_PROG_TYPE_LSM && prog->type != BPF_PROG_TYPE_EXT)
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
This is just to hammer the obvious because I suspect you can not already load a bpf HID program which is not GPL because all of the useful functions are GPL only.
Anyway, this ensures that users are not tempted to bypass this requirement and will allow us to ship tested BPF programs in the kernel without having to aorry about the license.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
Acked-by: Song Liu songliubraving@fb.com
no changes in v3
new in v2:
- Note: I placed this statement in check_attach_btf_id() to be local to other similar checks (regarding LSM), however, I have no idea if this is the correct place. Please shout at me if it isn't.
include/linux/bpf-hid.h | 8 ++++++++ kernel/bpf/hid.c | 12 ++++++++++++ kernel/bpf/verifier.c | 7 +++++++ 3 files changed, 27 insertions(+)
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 9c8dbd389995..7f596554fe8c 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -2,6 +2,7 @@ #ifndef _BPF_HID_H #define _BPF_HID_H
+#include <linux/bpf_verifier.h> #include <linux/mutex.h> #include <uapi/linux/bpf.h> #include <uapi/linux/bpf_hid.h> @@ -69,6 +70,8 @@ int bpf_hid_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr); int bpf_hid_link_create(const union bpf_attr *attr, struct bpf_prog *prog); +int bpf_hid_verify_prog(struct bpf_verifier_log *vlog,
const struct bpf_prog *prog);
#else static inline int bpf_hid_prog_query(const union bpf_attr *attr, union bpf_attr __user *uattr) @@ -81,6 +84,11 @@ static inline int bpf_hid_link_create(const union bpf_attr *attr, { return -EOPNOTSUPP; } +static inline int bpf_hid_verify_prog(struct bpf_verifier_log *vlog,
const struct bpf_prog *prog)
+{
return -EOPNOTSUPP;
+} #endif
static inline bool bpf_hid_link_empty(struct bpf_hid *bpf, diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index c21dc05f6207..2dfeaaa8a83f 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -34,6 +34,18 @@ void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks) } EXPORT_SYMBOL_GPL(bpf_hid_set_hooks);
+int bpf_hid_verify_prog(struct bpf_verifier_log *vlog,
const struct bpf_prog *prog)
+{
if (!prog->gpl_compatible) {
bpf_log(vlog,
"HID programs must have a GPL compatible license\n");
return -EINVAL;
}
return 0;
+}
BPF_CALL_3(bpf_hid_get_data, struct hid_bpf_ctx_kern*, ctx, u64, offset, u64, size) { if (!size) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index cf92f9c01556..da06d633fb8d 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -21,6 +21,7 @@ #include <linux/perf_event.h> #include <linux/ctype.h> #include <linux/error-injection.h> +#include <linux/bpf-hid.h> #include <linux/bpf_lsm.h> #include <linux/btf_ids.h>
@@ -14272,6 +14273,12 @@ static int check_attach_btf_id(struct bpf_verifier_env *env) if (prog->type == BPF_PROG_TYPE_STRUCT_OPS) return check_struct_ops_btf_id(env);
if (prog->type == BPF_PROG_TYPE_HID) {
ret = bpf_hid_verify_prog(&env->log, prog);
if (ret < 0)
return ret;
}
if (prog->type != BPF_PROG_TYPE_TRACING && prog->type != BPF_PROG_TYPE_LSM && prog->type != BPF_PROG_TYPE_EXT)
-- 2.35.1
HID-bpf program type are needing new SECs. To bind a hid-bpf program, we can rely on bpf_program__attach_fd() so export a new function to the API.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - squashed the libbpf changes into 1 - moved bpf_program__attach_hid to 0.8.0 - added SEC_DEF("hid/driver_event")
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- tools/include/uapi/linux/bpf.h | 31 +++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.c | 23 +++++++++++++++++------ tools/lib/bpf/libbpf.h | 2 ++ tools/lib/bpf/libbpf.map | 1 + 4 files changed, 51 insertions(+), 6 deletions(-)
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 99fab54ae9c0..0e8438e93768 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */ + BPF_PROG_TYPE_HID, };
enum bpf_attach_type { @@ -997,6 +998,10 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT, + BPF_HID_DEVICE_EVENT, + BPF_HID_RDESC_FIXUP, + BPF_HID_USER_EVENT, + BPF_HID_DRIVER_EVENT, __MAX_BPF_ATTACH_TYPE };
@@ -1011,6 +1016,7 @@ enum bpf_link_type { BPF_LINK_TYPE_NETNS = 5, BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7, + BPF_LINK_TYPE_HID = 8,
MAX_BPF_LINK_TYPE, }; @@ -1118,6 +1124,16 @@ enum bpf_link_type { */ #define BPF_F_XDP_HAS_FRAGS (1U << 5)
+/* HID flag used in BPF_LINK_CREATE command + * + * NONE(default): The bpf program will be added at the tail of the list + * of existing bpf program for this type. + * + * BPF_F_INSERT_HEAD: The bpf program will be added at the beginning + * of the list of existing bpf program for this type.. + */ +#define BPF_F_INSERT_HEAD (1U << 0) + /* When BPF ldimm64's insn[0].src_reg != 0 then this can have * the following extensions: * @@ -5129,6 +5145,16 @@ union bpf_attr { * The **hash_algo** is returned on success, * **-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if * invalid arguments are passed. + * + * void *bpf_hid_get_data(void *ctx, u64 offset, u64 size) + * Description + * Returns a pointer to the data associated with context at the given + * offset and size (in bytes). + * + * Note: the returned pointer is refcounted and must be dereferenced + * by a call to bpf_hid_discard; + * Return + * The pointer to the data. On error, a null value is returned. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5325,6 +5351,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(skb_set_tstamp), \ FN(ima_file_hash), \ + FN(hid_get_data), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5925,6 +5952,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp; + struct { + __s32 hidraw_number; + __u32 attach_type; + } hid; }; } __attribute__((aligned(8)));
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 43161fdd44bb..6b9ba313eb5b 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8675,6 +8675,10 @@ static const struct bpf_sec_def section_defs[] = { SEC_DEF("cgroup/setsockopt", CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT, SEC_ATTACHABLE | SEC_SLOPPY_PFX), SEC_DEF("struct_ops+", STRUCT_OPS, 0, SEC_NONE), SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE | SEC_SLOPPY_PFX), + SEC_DEF("hid/device_event", HID, BPF_HID_DEVICE_EVENT, SEC_ATTACHABLE_OPT), + SEC_DEF("hid/rdesc_fixup", HID, BPF_HID_RDESC_FIXUP, SEC_ATTACHABLE_OPT), + SEC_DEF("hid/user_event", HID, BPF_HID_USER_EVENT, SEC_ATTACHABLE_OPT), + SEC_DEF("hid/driver_event", HID, BPF_HID_DRIVER_EVENT, SEC_ATTACHABLE_OPT), };
static size_t custom_sec_def_cnt; @@ -10630,10 +10634,11 @@ static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_li
static struct bpf_link * bpf_program__attach_fd(const struct bpf_program *prog, int target_fd, int btf_id, - const char *target_name) + const char *target_name, __u32 flags) { DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts, - .target_btf_id = btf_id); + .target_btf_id = btf_id, + .flags = flags); enum bpf_attach_type attach_type; char errmsg[STRERR_BUFSIZE]; struct bpf_link *link; @@ -10667,19 +10672,19 @@ bpf_program__attach_fd(const struct bpf_program *prog, int target_fd, int btf_id struct bpf_link * bpf_program__attach_cgroup(const struct bpf_program *prog, int cgroup_fd) { - return bpf_program__attach_fd(prog, cgroup_fd, 0, "cgroup"); + return bpf_program__attach_fd(prog, cgroup_fd, 0, "cgroup", 0); }
struct bpf_link * bpf_program__attach_netns(const struct bpf_program *prog, int netns_fd) { - return bpf_program__attach_fd(prog, netns_fd, 0, "netns"); + return bpf_program__attach_fd(prog, netns_fd, 0, "netns", 0); }
struct bpf_link *bpf_program__attach_xdp(const struct bpf_program *prog, int ifindex) { /* target_fd/target_ifindex use the same field in LINK_CREATE */ - return bpf_program__attach_fd(prog, ifindex, 0, "xdp"); + return bpf_program__attach_fd(prog, ifindex, 0, "xdp", 0); }
struct bpf_link *bpf_program__attach_freplace(const struct bpf_program *prog, @@ -10705,7 +10710,7 @@ struct bpf_link *bpf_program__attach_freplace(const struct bpf_program *prog, if (btf_id < 0) return libbpf_err_ptr(btf_id);
- return bpf_program__attach_fd(prog, target_fd, btf_id, "freplace"); + return bpf_program__attach_fd(prog, target_fd, btf_id, "freplace", 0); } else { /* no target, so use raw_tracepoint_open for compatibility * with old kernels @@ -10760,6 +10765,12 @@ static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_l return libbpf_get_error(*link); }
+struct bpf_link * +bpf_program__attach_hid(const struct bpf_program *prog, int hid_fd, __u32 flags) +{ + return bpf_program__attach_fd(prog, hid_fd, 0, "hid", flags); +} + struct bpf_link *bpf_program__attach(const struct bpf_program *prog) { struct bpf_link *link = NULL; diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index c1b0c2ef14d8..13dff4865da5 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -529,6 +529,8 @@ struct bpf_iter_attach_opts { LIBBPF_API struct bpf_link * bpf_program__attach_iter(const struct bpf_program *prog, const struct bpf_iter_attach_opts *opts); +LIBBPF_API struct bpf_link * +bpf_program__attach_hid(const struct bpf_program *prog, int hid_fd, __u32 flags);
/* * Libbpf allows callers to adjust BPF programs before being loaded diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index df1b947792c8..cd8da9a8bf36 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -442,6 +442,7 @@ LIBBPF_0.7.0 {
LIBBPF_0.8.0 { global: + bpf_program__attach_hid; libbpf_register_prog_handler; libbpf_unregister_prog_handler; } LIBBPF_0.7.0;
On Fri, Mar 18, 2022 at 9:18 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
HID-bpf program type are needing new SECs. To bind a hid-bpf program, we can rely on bpf_program__attach_fd() so export a new function to the API.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
Acked-by: Song Liu songliubraving@fb.com
changes in v3:
- squashed the libbpf changes into 1
- moved bpf_program__attach_hid to 0.8.0
- added SEC_DEF("hid/driver_event")
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
tools/include/uapi/linux/bpf.h | 31 +++++++++++++++++++++++++++++++ tools/lib/bpf/libbpf.c | 23 +++++++++++++++++------ tools/lib/bpf/libbpf.h | 2 ++ tools/lib/bpf/libbpf.map | 1 + 4 files changed, 51 insertions(+), 6 deletions(-)
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 99fab54ae9c0..0e8438e93768 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -952,6 +952,7 @@ enum bpf_prog_type { BPF_PROG_TYPE_LSM, BPF_PROG_TYPE_SK_LOOKUP, BPF_PROG_TYPE_SYSCALL, /* a program that can execute syscalls */
BPF_PROG_TYPE_HID,
};
enum bpf_attach_type { @@ -997,6 +998,10 @@ enum bpf_attach_type { BPF_SK_REUSEPORT_SELECT, BPF_SK_REUSEPORT_SELECT_OR_MIGRATE, BPF_PERF_EVENT,
BPF_HID_DEVICE_EVENT,
BPF_HID_RDESC_FIXUP,
BPF_HID_USER_EVENT,
BPF_HID_DRIVER_EVENT, __MAX_BPF_ATTACH_TYPE
};
@@ -1011,6 +1016,7 @@ enum bpf_link_type { BPF_LINK_TYPE_NETNS = 5, BPF_LINK_TYPE_XDP = 6, BPF_LINK_TYPE_PERF_EVENT = 7,
BPF_LINK_TYPE_HID = 8, MAX_BPF_LINK_TYPE,
}; @@ -1118,6 +1124,16 @@ enum bpf_link_type { */ #define BPF_F_XDP_HAS_FRAGS (1U << 5)
+/* HID flag used in BPF_LINK_CREATE command
- NONE(default): The bpf program will be added at the tail of the list
- of existing bpf program for this type.
- BPF_F_INSERT_HEAD: The bpf program will be added at the beginning
- of the list of existing bpf program for this type..
- */
+#define BPF_F_INSERT_HEAD (1U << 0)
/* When BPF ldimm64's insn[0].src_reg != 0 then this can have
- the following extensions:
@@ -5129,6 +5145,16 @@ union bpf_attr {
The **hash_algo** is returned on success,
**-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
invalid arguments are passed.
- void *bpf_hid_get_data(void *ctx, u64 offset, u64 size)
Description
Returns a pointer to the data associated with context at the given
offset and size (in bytes).
Note: the returned pointer is refcounted and must be dereferenced
by a call to bpf_hid_discard;
Return
*/
The pointer to the data. On error, a null value is returned.
#define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5325,6 +5351,7 @@ union bpf_attr { FN(copy_from_user_task), \ FN(skb_set_tstamp), \ FN(ima_file_hash), \
FN(hid_get_data), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper @@ -5925,6 +5952,10 @@ struct bpf_link_info { struct { __u32 ifindex; } xdp;
struct {
__s32 hidraw_number;
__u32 attach_type;
} hid; };
} __attribute__((aligned(8)));
diff --git a/tools/lib/bpf/libbpf.c b/tools/lib/bpf/libbpf.c index 43161fdd44bb..6b9ba313eb5b 100644 --- a/tools/lib/bpf/libbpf.c +++ b/tools/lib/bpf/libbpf.c @@ -8675,6 +8675,10 @@ static const struct bpf_sec_def section_defs[] = { SEC_DEF("cgroup/setsockopt", CGROUP_SOCKOPT, BPF_CGROUP_SETSOCKOPT, SEC_ATTACHABLE | SEC_SLOPPY_PFX), SEC_DEF("struct_ops+", STRUCT_OPS, 0, SEC_NONE), SEC_DEF("sk_lookup", SK_LOOKUP, BPF_SK_LOOKUP, SEC_ATTACHABLE | SEC_SLOPPY_PFX),
SEC_DEF("hid/device_event", HID, BPF_HID_DEVICE_EVENT, SEC_ATTACHABLE_OPT),
SEC_DEF("hid/rdesc_fixup", HID, BPF_HID_RDESC_FIXUP, SEC_ATTACHABLE_OPT),
SEC_DEF("hid/user_event", HID, BPF_HID_USER_EVENT, SEC_ATTACHABLE_OPT),
SEC_DEF("hid/driver_event", HID, BPF_HID_DRIVER_EVENT, SEC_ATTACHABLE_OPT),
};
static size_t custom_sec_def_cnt; @@ -10630,10 +10634,11 @@ static int attach_lsm(const struct bpf_program *prog, long cookie, struct bpf_li
static struct bpf_link * bpf_program__attach_fd(const struct bpf_program *prog, int target_fd, int btf_id,
const char *target_name)
const char *target_name, __u32 flags)
{ DECLARE_LIBBPF_OPTS(bpf_link_create_opts, opts,
.target_btf_id = btf_id);
.target_btf_id = btf_id,
.flags = flags); enum bpf_attach_type attach_type; char errmsg[STRERR_BUFSIZE]; struct bpf_link *link;
@@ -10667,19 +10672,19 @@ bpf_program__attach_fd(const struct bpf_program *prog, int target_fd, int btf_id struct bpf_link * bpf_program__attach_cgroup(const struct bpf_program *prog, int cgroup_fd) {
return bpf_program__attach_fd(prog, cgroup_fd, 0, "cgroup");
return bpf_program__attach_fd(prog, cgroup_fd, 0, "cgroup", 0);
}
struct bpf_link * bpf_program__attach_netns(const struct bpf_program *prog, int netns_fd) {
return bpf_program__attach_fd(prog, netns_fd, 0, "netns");
return bpf_program__attach_fd(prog, netns_fd, 0, "netns", 0);
}
struct bpf_link *bpf_program__attach_xdp(const struct bpf_program *prog, int ifindex) { /* target_fd/target_ifindex use the same field in LINK_CREATE */
return bpf_program__attach_fd(prog, ifindex, 0, "xdp");
return bpf_program__attach_fd(prog, ifindex, 0, "xdp", 0);
}
struct bpf_link *bpf_program__attach_freplace(const struct bpf_program *prog, @@ -10705,7 +10710,7 @@ struct bpf_link *bpf_program__attach_freplace(const struct bpf_program *prog, if (btf_id < 0) return libbpf_err_ptr(btf_id);
return bpf_program__attach_fd(prog, target_fd, btf_id, "freplace");
return bpf_program__attach_fd(prog, target_fd, btf_id, "freplace", 0); } else { /* no target, so use raw_tracepoint_open for compatibility * with old kernels
@@ -10760,6 +10765,12 @@ static int attach_iter(const struct bpf_program *prog, long cookie, struct bpf_l return libbpf_get_error(*link); }
+struct bpf_link * +bpf_program__attach_hid(const struct bpf_program *prog, int hid_fd, __u32 flags) +{
return bpf_program__attach_fd(prog, hid_fd, 0, "hid", flags);
+}
struct bpf_link *bpf_program__attach(const struct bpf_program *prog) { struct bpf_link *link = NULL; diff --git a/tools/lib/bpf/libbpf.h b/tools/lib/bpf/libbpf.h index c1b0c2ef14d8..13dff4865da5 100644 --- a/tools/lib/bpf/libbpf.h +++ b/tools/lib/bpf/libbpf.h @@ -529,6 +529,8 @@ struct bpf_iter_attach_opts { LIBBPF_API struct bpf_link * bpf_program__attach_iter(const struct bpf_program *prog, const struct bpf_iter_attach_opts *opts); +LIBBPF_API struct bpf_link * +bpf_program__attach_hid(const struct bpf_program *prog, int hid_fd, __u32 flags);
/*
- Libbpf allows callers to adjust BPF programs before being loaded
diff --git a/tools/lib/bpf/libbpf.map b/tools/lib/bpf/libbpf.map index df1b947792c8..cd8da9a8bf36 100644 --- a/tools/lib/bpf/libbpf.map +++ b/tools/lib/bpf/libbpf.map @@ -442,6 +442,7 @@ LIBBPF_0.7.0 {
LIBBPF_0.8.0 { global:
bpf_program__attach_hid; libbpf_register_prog_handler; libbpf_unregister_prog_handler;
} LIBBPF_0.7.0;
2.35.1
Now that BPF can be compatible with HID, add the capability into HID. drivers/hid/hid-bpf.c takes care of the glue between bpf and HID, and hid-core can then inject any incoming event from the device into a BPF program to filter/analyze it.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - squashed "only call hid_bpf_raw_event() if a ctx is available" and "bpf: compute only the required buffer size for the device" into this one - ensure the ctx.size is properly bounded by allocated size - s/link_attach/pre_link_attach/ - s/array_detached/array_detach/ - fix default switch case when doing nothing - reworked hid_bpf_pre_link_attach() to avoid the switch
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples - addressed review comments from v1 --- drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 174 +++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 24 +++++- include/linux/hid.h | 11 +++ 4 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 drivers/hid/hid-bpf.c
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..08d2d7619937 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,6 +4,7 @@ # hid-y := hid-core.o hid-input.o hid-quirks.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o +hid-$(CONFIG_BPF) += hid-bpf.o
obj-$(CONFIG_HID) += hid.o obj-$(CONFIG_UHID) += uhid.o diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c new file mode 100644 index 000000000000..5060ebcb9979 --- /dev/null +++ b/drivers/hid/hid-bpf.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * BPF in HID support for Linux + * + * Copyright (c) 2022 Benjamin Tissoires + */ + +#include <linux/filter.h> +#include <linux/mutex.h> +#include <linux/slab.h> + +#include <uapi/linux/bpf_hid.h> +#include <linux/hid.h> + +static int __hid_bpf_match_sysfs(struct device *dev, const void *data) +{ + struct kernfs_node *kn = dev->kobj.sd; + struct kernfs_node *uevent_kn; + + uevent_kn = kernfs_find_and_get_ns(kn, "uevent", NULL); + + return uevent_kn == data; +} + +static struct hid_device *hid_bpf_fd_to_hdev(int fd) +{ + struct device *dev; + struct hid_device *hdev; + struct fd f = fdget(fd); + struct inode *inode; + struct kernfs_node *node; + + if (!f.file) { + hdev = ERR_PTR(-EBADF); + goto out; + } + + inode = file_inode(f.file); + node = inode->i_private; + + dev = bus_find_device(&hid_bus_type, NULL, node, __hid_bpf_match_sysfs); + + if (dev) + hdev = to_hid_device(dev); + else + hdev = ERR_PTR(-EINVAL); + + out: + fdput(f); + return hdev; +} + +static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + int err = 0; + unsigned int i, j, max_report_len = 0; + unsigned int alloc_size = 0; + + if (type != BPF_HID_ATTACH_DEVICE_EVENT) + return 0; + + /* hdev->bpf.device_data is already allocated, abort */ + if (hdev->bpf.device_data) + return 0; + + /* compute the maximum report length for this device */ + for (i = 0; i < HID_REPORT_TYPES; i++) { + struct hid_report_enum *report_enum = hdev->report_enum + i; + + for (j = 0; j < HID_MAX_IDS; j++) { + struct hid_report *report = report_enum->report_id_hash[j]; + + if (report) + max_report_len = max(max_report_len, hid_report_len(report)); + } + } + + /* + * Give us a little bit of extra space and some predictability in the + * buffer length we create. This way, we can tell users that they can + * work on chunks of 64 bytes of memory without having the bpf verifier + * scream at them. + */ + alloc_size = DIV_ROUND_UP(max_report_len, 64) * 64; + + hdev->bpf.device_data = kzalloc(alloc_size, GFP_KERNEL); + if (!hdev->bpf.device_data) + err = -ENOMEM; + else + hdev->bpf.allocated_data = alloc_size; + + return err; +} + +static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + switch (type) { + case BPF_HID_ATTACH_DEVICE_EVENT: + kfree(hdev->bpf.device_data); + hdev->bpf.device_data = NULL; + break; + default: + /* do nothing */ + break; + } +} + +static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *ctx) +{ + enum bpf_hid_attach_type type; + + if (!ctx) + return -EINVAL; + + switch (ctx->type) { + case HID_BPF_DEVICE_EVENT: + type = BPF_HID_ATTACH_DEVICE_EVENT; + break; + default: + return -EINVAL; + } + + if (!hdev->bpf.run_array[type]) + return 0; + + return BPF_PROG_RUN_ARRAY(hdev->bpf.run_array[type], ctx, bpf_prog_run); +} + +u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) +{ + int ret; + struct hid_bpf_ctx_kern ctx = { + .type = HID_BPF_DEVICE_EVENT, + .hdev = hdev, + .size = *size, + .data = hdev->bpf.device_data, + .allocated_size = hdev->bpf.allocated_data, + }; + + if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_DEVICE_EVENT)) + return data; + + memset(ctx.data, 0, hdev->bpf.allocated_data); + memcpy(ctx.data, data, *size); + + ret = hid_bpf_run_progs(hdev, &ctx); + if (ret) + return ERR_PTR(-EIO); + + if (!ctx.size || ctx.size > ctx.allocated_size) + return ERR_PTR(-EINVAL); + + *size = ctx.size; + + return ctx.data; +} + +int __init hid_bpf_module_init(void) +{ + struct bpf_hid_hooks hooks = { + .hdev_from_fd = hid_bpf_fd_to_hdev, + .pre_link_attach = hid_bpf_pre_link_attach, + .array_detach = hid_bpf_array_detach, + }; + + bpf_hid_set_hooks(&hooks); + + return 0; +} + +void __exit hid_bpf_module_exit(void) +{ + bpf_hid_set_hooks(NULL); +} diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f1aed5bbd000..937fab7eb9c6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1748,13 +1748,24 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, struct hid_driver *hdrv; unsigned int a; u32 rsize, csize = size; - u8 *cdata = data; + u8 *cdata; int ret = 0;
+ /* we pre-test if device_data is available here to cut the calls at the earliest */ + if (hid->bpf.device_data) { + data = hid_bpf_raw_event(hid, data, &size); + if (IS_ERR(data)) { + ret = PTR_ERR(data); + goto out; + } + } + report = hid_get_report(report_enum, data); if (!report) goto out;
+ cdata = data; + if (report_enum->numbered) { cdata++; csize--; @@ -2528,10 +2539,12 @@ int hid_add_device(struct hid_device *hdev)
hid_debug_register(hdev, dev_name(&hdev->dev)); ret = device_add(&hdev->dev); - if (!ret) + if (!ret) { hdev->status |= HID_STAT_ADDED; - else + } else { hid_debug_unregister(hdev); + bpf_hid_exit(hdev); + }
return ret; } @@ -2567,6 +2580,7 @@ struct hid_device *hid_allocate_device(void) spin_lock_init(&hdev->debug_list_lock); sema_init(&hdev->driver_input_lock, 1); mutex_init(&hdev->ll_open_lock); + bpf_hid_init(hdev);
return hdev; } @@ -2574,6 +2588,7 @@ EXPORT_SYMBOL_GPL(hid_allocate_device);
static void hid_remove_device(struct hid_device *hdev) { + bpf_hid_exit(hdev); if (hdev->status & HID_STAT_ADDED) { device_del(&hdev->dev); hid_debug_unregister(hdev); @@ -2700,6 +2715,8 @@ static int __init hid_init(void)
hid_debug_init();
+ hid_bpf_module_init(); + return 0; err_bus: bus_unregister(&hid_bus_type); @@ -2709,6 +2726,7 @@ static int __init hid_init(void)
static void __exit hid_exit(void) { + hid_bpf_module_exit(); hid_debug_exit(); hidraw_exit(); bus_unregister(&hid_bus_type); diff --git a/include/linux/hid.h b/include/linux/hid.h index 56f6f4ad45a7..8fd79011f461 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -27,6 +27,7 @@ #include <linux/mutex.h> #include <linux/power_supply.h> #include <uapi/linux/hid.h> +#include <uapi/linux/bpf_hid.h>
/* * We parse each description item into this structure. Short items data @@ -1210,4 +1211,14 @@ do { \ #define hid_dbg_once(hid, fmt, ...) \ dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__)
+#ifdef CONFIG_BPF +u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +int hid_bpf_module_init(void); +void hid_bpf_module_exit(void); +#else +static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline int hid_bpf_module_init(void) { return 0; } +static inline void hid_bpf_module_exit(void) {} +#endif + #endif
On Fri, Mar 18, 2022 at 9:17 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Now that BPF can be compatible with HID, add the capability into HID. drivers/hid/hid-bpf.c takes care of the glue between bpf and HID, and hid-core can then inject any incoming event from the device into a BPF program to filter/analyze it.
So we only need this part for DEVICE EVENT?
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- squashed "only call hid_bpf_raw_event() if a ctx is available" and "bpf: compute only the required buffer size for the device" into this one
- ensure the ctx.size is properly bounded by allocated size
- s/link_attach/pre_link_attach/
- s/array_detached/array_detach/
- fix default switch case when doing nothing
- reworked hid_bpf_pre_link_attach() to avoid the switch
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
- addressed review comments from v1
drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 174 +++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 24 +++++- include/linux/hid.h | 11 +++ 4 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 drivers/hid/hid-bpf.c
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..08d2d7619937 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,6 +4,7 @@ # hid-y := hid-core.o hid-input.o hid-quirks.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o +hid-$(CONFIG_BPF) += hid-bpf.o
obj-$(CONFIG_HID) += hid.o obj-$(CONFIG_UHID) += uhid.o diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c new file mode 100644 index 000000000000..5060ebcb9979 --- /dev/null +++ b/drivers/hid/hid-bpf.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*
- BPF in HID support for Linux
- Copyright (c) 2022 Benjamin Tissoires
- */
+#include <linux/filter.h> +#include <linux/mutex.h> +#include <linux/slab.h>
+#include <uapi/linux/bpf_hid.h> +#include <linux/hid.h>
+static int __hid_bpf_match_sysfs(struct device *dev, const void *data) +{
struct kernfs_node *kn = dev->kobj.sd;
struct kernfs_node *uevent_kn;
uevent_kn = kernfs_find_and_get_ns(kn, "uevent", NULL);
return uevent_kn == data;
+}
+static struct hid_device *hid_bpf_fd_to_hdev(int fd) +{
struct device *dev;
struct hid_device *hdev;
struct fd f = fdget(fd);
struct inode *inode;
struct kernfs_node *node;
if (!f.file) {
hdev = ERR_PTR(-EBADF);
goto out;
}
inode = file_inode(f.file);
node = inode->i_private;
dev = bus_find_device(&hid_bus_type, NULL, node, __hid_bpf_match_sysfs);
if (dev)
hdev = to_hid_device(dev);
else
hdev = ERR_PTR(-EINVAL);
- out:
fdput(f);
return hdev;
+}
+static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
int err = 0;
unsigned int i, j, max_report_len = 0;
unsigned int alloc_size = 0;
if (type != BPF_HID_ATTACH_DEVICE_EVENT)
return 0;
/* hdev->bpf.device_data is already allocated, abort */
if (hdev->bpf.device_data)
return 0;
/* compute the maximum report length for this device */
for (i = 0; i < HID_REPORT_TYPES; i++) {
struct hid_report_enum *report_enum = hdev->report_enum + i;
for (j = 0; j < HID_MAX_IDS; j++) {
struct hid_report *report = report_enum->report_id_hash[j];
if (report)
max_report_len = max(max_report_len, hid_report_len(report));
}
}
/*
* Give us a little bit of extra space and some predictability in the
* buffer length we create. This way, we can tell users that they can
* work on chunks of 64 bytes of memory without having the bpf verifier
* scream at them.
*/
alloc_size = DIV_ROUND_UP(max_report_len, 64) * 64;
hdev->bpf.device_data = kzalloc(alloc_size, GFP_KERNEL);
if (!hdev->bpf.device_data)
err = -ENOMEM;
else
hdev->bpf.allocated_data = alloc_size;
return err;
+}
+static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
switch (type) {
case BPF_HID_ATTACH_DEVICE_EVENT:
kfree(hdev->bpf.device_data);
hdev->bpf.device_data = NULL;
break;
default:
/* do nothing */
break;
}
+}
+static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *ctx) +{
enum bpf_hid_attach_type type;
if (!ctx)
return -EINVAL;
switch (ctx->type) {
case HID_BPF_DEVICE_EVENT:
type = BPF_HID_ATTACH_DEVICE_EVENT;
break;
default:
return -EINVAL;
}
if (!hdev->bpf.run_array[type])
return 0;
return BPF_PROG_RUN_ARRAY(hdev->bpf.run_array[type], ctx, bpf_prog_run);
+}
+u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_DEVICE_EVENT,
.hdev = hdev,
.size = *size,
.data = hdev->bpf.device_data,
.allocated_size = hdev->bpf.allocated_data,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_DEVICE_EVENT))
return data;
memset(ctx.data, 0, hdev->bpf.allocated_data);
memcpy(ctx.data, data, *size);
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
return ERR_PTR(-EIO);
if (!ctx.size || ctx.size > ctx.allocated_size)
return ERR_PTR(-EINVAL);
*size = ctx.size;
return ctx.data;
+}
+int __init hid_bpf_module_init(void) +{
struct bpf_hid_hooks hooks = {
.hdev_from_fd = hid_bpf_fd_to_hdev,
.pre_link_attach = hid_bpf_pre_link_attach,
.array_detach = hid_bpf_array_detach,
};
bpf_hid_set_hooks(&hooks);
return 0;
+}
+void __exit hid_bpf_module_exit(void) +{
bpf_hid_set_hooks(NULL);
+} diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f1aed5bbd000..937fab7eb9c6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1748,13 +1748,24 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, struct hid_driver *hdrv; unsigned int a; u32 rsize, csize = size;
u8 *cdata = data;
u8 *cdata; int ret = 0;
/* we pre-test if device_data is available here to cut the calls at the earliest */
if (hid->bpf.device_data) {
data = hid_bpf_raw_event(hid, data, &size);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto out;
}
}
report = hid_get_report(report_enum, data); if (!report) goto out;
cdata = data;
if (report_enum->numbered) { cdata++; csize--;
@@ -2528,10 +2539,12 @@ int hid_add_device(struct hid_device *hdev)
hid_debug_register(hdev, dev_name(&hdev->dev)); ret = device_add(&hdev->dev);
if (!ret)
if (!ret) { hdev->status |= HID_STAT_ADDED;
else
} else { hid_debug_unregister(hdev);
bpf_hid_exit(hdev);
} return ret;
} @@ -2567,6 +2580,7 @@ struct hid_device *hid_allocate_device(void) spin_lock_init(&hdev->debug_list_lock); sema_init(&hdev->driver_input_lock, 1); mutex_init(&hdev->ll_open_lock);
bpf_hid_init(hdev); return hdev;
} @@ -2574,6 +2588,7 @@ EXPORT_SYMBOL_GPL(hid_allocate_device);
static void hid_remove_device(struct hid_device *hdev) {
bpf_hid_exit(hdev); if (hdev->status & HID_STAT_ADDED) { device_del(&hdev->dev); hid_debug_unregister(hdev);
@@ -2700,6 +2715,8 @@ static int __init hid_init(void)
hid_debug_init();
hid_bpf_module_init();
return 0;
err_bus: bus_unregister(&hid_bus_type); @@ -2709,6 +2726,7 @@ static int __init hid_init(void)
static void __exit hid_exit(void) {
hid_bpf_module_exit(); hid_debug_exit(); hidraw_exit(); bus_unregister(&hid_bus_type);
diff --git a/include/linux/hid.h b/include/linux/hid.h index 56f6f4ad45a7..8fd79011f461 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -27,6 +27,7 @@ #include <linux/mutex.h> #include <linux/power_supply.h> #include <uapi/linux/hid.h> +#include <uapi/linux/bpf_hid.h>
/*
- We parse each description item into this structure. Short items data
@@ -1210,4 +1211,14 @@ do { \ #define hid_dbg_once(hid, fmt, ...) \ dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__)
+#ifdef CONFIG_BPF +u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +int hid_bpf_module_init(void); +void hid_bpf_module_exit(void); +#else +static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline int hid_bpf_module_init(void) { return 0; } +static inline void hid_bpf_module_exit(void) {} +#endif
#endif
2.35.1
On Fri, Mar 18, 2022 at 2:02 PM Song Liu song@kernel.org wrote:
On Fri, Mar 18, 2022 at 9:17 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Now that BPF can be compatible with HID, add the capability into HID. drivers/hid/hid-bpf.c takes care of the glue between bpf and HID, and hid-core can then inject any incoming event from the device into a BPF program to filter/analyze it.
So we only need this part for DEVICE EVENT?
NVM, I found other types in the following patches.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- squashed "only call hid_bpf_raw_event() if a ctx is available" and "bpf: compute only the required buffer size for the device" into this one
- ensure the ctx.size is properly bounded by allocated size
- s/link_attach/pre_link_attach/
- s/array_detached/array_detach/
- fix default switch case when doing nothing
- reworked hid_bpf_pre_link_attach() to avoid the switch
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
- addressed review comments from v1
drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 174 +++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 24 +++++- include/linux/hid.h | 11 +++ 4 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 drivers/hid/hid-bpf.c
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..08d2d7619937 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -4,6 +4,7 @@ # hid-y := hid-core.o hid-input.o hid-quirks.o hid-$(CONFIG_DEBUG_FS) += hid-debug.o +hid-$(CONFIG_BPF) += hid-bpf.o
obj-$(CONFIG_HID) += hid.o obj-$(CONFIG_UHID) += uhid.o diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c new file mode 100644 index 000000000000..5060ebcb9979 --- /dev/null +++ b/drivers/hid/hid-bpf.c @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*
- BPF in HID support for Linux
- Copyright (c) 2022 Benjamin Tissoires
- */
+#include <linux/filter.h> +#include <linux/mutex.h> +#include <linux/slab.h>
+#include <uapi/linux/bpf_hid.h> +#include <linux/hid.h>
+static int __hid_bpf_match_sysfs(struct device *dev, const void *data) +{
struct kernfs_node *kn = dev->kobj.sd;
struct kernfs_node *uevent_kn;
uevent_kn = kernfs_find_and_get_ns(kn, "uevent", NULL);
return uevent_kn == data;
+}
+static struct hid_device *hid_bpf_fd_to_hdev(int fd) +{
struct device *dev;
struct hid_device *hdev;
struct fd f = fdget(fd);
struct inode *inode;
struct kernfs_node *node;
if (!f.file) {
hdev = ERR_PTR(-EBADF);
goto out;
}
inode = file_inode(f.file);
node = inode->i_private;
dev = bus_find_device(&hid_bus_type, NULL, node, __hid_bpf_match_sysfs);
if (dev)
hdev = to_hid_device(dev);
else
hdev = ERR_PTR(-EINVAL);
- out:
fdput(f);
return hdev;
+}
+static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
int err = 0;
unsigned int i, j, max_report_len = 0;
unsigned int alloc_size = 0;
if (type != BPF_HID_ATTACH_DEVICE_EVENT)
return 0;
/* hdev->bpf.device_data is already allocated, abort */
if (hdev->bpf.device_data)
return 0;
/* compute the maximum report length for this device */
for (i = 0; i < HID_REPORT_TYPES; i++) {
struct hid_report_enum *report_enum = hdev->report_enum + i;
for (j = 0; j < HID_MAX_IDS; j++) {
struct hid_report *report = report_enum->report_id_hash[j];
if (report)
max_report_len = max(max_report_len, hid_report_len(report));
}
}
/*
* Give us a little bit of extra space and some predictability in the
* buffer length we create. This way, we can tell users that they can
* work on chunks of 64 bytes of memory without having the bpf verifier
* scream at them.
*/
alloc_size = DIV_ROUND_UP(max_report_len, 64) * 64;
hdev->bpf.device_data = kzalloc(alloc_size, GFP_KERNEL);
if (!hdev->bpf.device_data)
err = -ENOMEM;
else
hdev->bpf.allocated_data = alloc_size;
return err;
+}
+static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
switch (type) {
case BPF_HID_ATTACH_DEVICE_EVENT:
kfree(hdev->bpf.device_data);
hdev->bpf.device_data = NULL;
break;
default:
/* do nothing */
break;
}
+}
+static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *ctx) +{
enum bpf_hid_attach_type type;
if (!ctx)
return -EINVAL;
switch (ctx->type) {
case HID_BPF_DEVICE_EVENT:
type = BPF_HID_ATTACH_DEVICE_EVENT;
break;
default:
return -EINVAL;
}
if (!hdev->bpf.run_array[type])
return 0;
return BPF_PROG_RUN_ARRAY(hdev->bpf.run_array[type], ctx, bpf_prog_run);
+}
+u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_DEVICE_EVENT,
.hdev = hdev,
.size = *size,
.data = hdev->bpf.device_data,
.allocated_size = hdev->bpf.allocated_data,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_DEVICE_EVENT))
return data;
memset(ctx.data, 0, hdev->bpf.allocated_data);
memcpy(ctx.data, data, *size);
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
return ERR_PTR(-EIO);
if (!ctx.size || ctx.size > ctx.allocated_size)
return ERR_PTR(-EINVAL);
*size = ctx.size;
return ctx.data;
+}
+int __init hid_bpf_module_init(void) +{
struct bpf_hid_hooks hooks = {
.hdev_from_fd = hid_bpf_fd_to_hdev,
.pre_link_attach = hid_bpf_pre_link_attach,
.array_detach = hid_bpf_array_detach,
};
bpf_hid_set_hooks(&hooks);
return 0;
+}
+void __exit hid_bpf_module_exit(void) +{
bpf_hid_set_hooks(NULL);
+} diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f1aed5bbd000..937fab7eb9c6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1748,13 +1748,24 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, struct hid_driver *hdrv; unsigned int a; u32 rsize, csize = size;
u8 *cdata = data;
u8 *cdata; int ret = 0;
/* we pre-test if device_data is available here to cut the calls at the earliest */
if (hid->bpf.device_data) {
data = hid_bpf_raw_event(hid, data, &size);
if (IS_ERR(data)) {
ret = PTR_ERR(data);
goto out;
}
}
report = hid_get_report(report_enum, data); if (!report) goto out;
cdata = data;
if (report_enum->numbered) { cdata++; csize--;
@@ -2528,10 +2539,12 @@ int hid_add_device(struct hid_device *hdev)
hid_debug_register(hdev, dev_name(&hdev->dev)); ret = device_add(&hdev->dev);
if (!ret)
if (!ret) { hdev->status |= HID_STAT_ADDED;
else
} else { hid_debug_unregister(hdev);
bpf_hid_exit(hdev);
} return ret;
} @@ -2567,6 +2580,7 @@ struct hid_device *hid_allocate_device(void) spin_lock_init(&hdev->debug_list_lock); sema_init(&hdev->driver_input_lock, 1); mutex_init(&hdev->ll_open_lock);
bpf_hid_init(hdev); return hdev;
} @@ -2574,6 +2588,7 @@ EXPORT_SYMBOL_GPL(hid_allocate_device);
static void hid_remove_device(struct hid_device *hdev) {
bpf_hid_exit(hdev); if (hdev->status & HID_STAT_ADDED) { device_del(&hdev->dev); hid_debug_unregister(hdev);
@@ -2700,6 +2715,8 @@ static int __init hid_init(void)
hid_debug_init();
hid_bpf_module_init();
return 0;
err_bus: bus_unregister(&hid_bus_type); @@ -2709,6 +2726,7 @@ static int __init hid_init(void)
static void __exit hid_exit(void) {
hid_bpf_module_exit(); hid_debug_exit(); hidraw_exit(); bus_unregister(&hid_bus_type);
diff --git a/include/linux/hid.h b/include/linux/hid.h index 56f6f4ad45a7..8fd79011f461 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -27,6 +27,7 @@ #include <linux/mutex.h> #include <linux/power_supply.h> #include <uapi/linux/hid.h> +#include <uapi/linux/bpf_hid.h>
/*
- We parse each description item into this structure. Short items data
@@ -1210,4 +1211,14 @@ do { \ #define hid_dbg_once(hid, fmt, ...) \ dev_dbg_once(&(hid)->dev, fmt, ##__VA_ARGS__)
+#ifdef CONFIG_BPF +u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +int hid_bpf_module_init(void); +void hid_bpf_module_exit(void); +#else +static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline int hid_bpf_module_init(void) { return 0; } +static inline void hid_bpf_module_exit(void) {} +#endif
#endif
2.35.1
Make use of BPF_HID_ATTACH_RDESC_FIXUP so we can trigger an rdesc 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.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - ensure the ctx.size is properly bounded by allocated size - s/link_attached/post_link_attach/ - removed the switch statement with only one case
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- drivers/hid/hid-bpf.c | 62 ++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/hid.h | 6 ++++ 3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 5060ebcb9979..45c87ff47324 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -50,6 +50,14 @@ static struct hid_device *hid_bpf_fd_to_hdev(int fd) return hdev; }
+static int hid_reconnect(struct hid_device *hdev) +{ + if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status)) + return device_reprobe(&hdev->dev); + + return 0; +} + static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) { int err = 0; @@ -92,6 +100,12 @@ static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_ return err; }
+static void hid_bpf_post_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{ + if (type == BPF_HID_ATTACH_RDESC_FIXUP) + hid_reconnect(hdev); +} + static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) { switch (type) { @@ -99,6 +113,9 @@ static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_ty kfree(hdev->bpf.device_data); hdev->bpf.device_data = NULL; break; + case BPF_HID_ATTACH_RDESC_FIXUP: + hid_reconnect(hdev); + break; default: /* do nothing */ break; @@ -116,6 +133,9 @@ static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *c case HID_BPF_DEVICE_EVENT: type = BPF_HID_ATTACH_DEVICE_EVENT; break; + case HID_BPF_RDESC_FIXUP: + type = BPF_HID_ATTACH_RDESC_FIXUP; + break; default: return -EINVAL; } @@ -155,11 +175,53 @@ u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) return ctx.data; }
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{ + int ret; + struct hid_bpf_ctx_kern ctx = { + .type = HID_BPF_RDESC_FIXUP, + .hdev = hdev, + .size = *size, + }; + + if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP)) + goto ignore_bpf; + + ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL); + if (!ctx.data) + goto ignore_bpf; + + ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE; + + ret = hid_bpf_run_progs(hdev, &ctx); + if (ret) + goto ignore_bpf; + + if (ctx.size > ctx.allocated_size) + goto ignore_bpf; + + *size = ctx.size; + + if (*size) { + rdesc = krealloc(ctx.data, *size, GFP_KERNEL); + } else { + rdesc = NULL; + kfree(ctx.data); + } + + return rdesc; + + ignore_bpf: + kfree(ctx.data); + return kmemdup(rdesc, *size, GFP_KERNEL); +} + int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach, + .post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
- buf = kmemdup(start, size, GFP_KERNEL); + /* hid_bpf_report_fixup() ensures we work on a copy of rdesc */ + buf = hid_bpf_report_fixup(device, start, &size); if (buf == NULL) return -ENOMEM;
diff --git a/include/linux/hid.h b/include/linux/hid.h index 8fd79011f461..66d949d10b78 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1213,10 +1213,16 @@ do { \
#ifdef CONFIG_BPF u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size); int hid_bpf_module_init(void); void hid_bpf_module_exit(void); #else static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, + unsigned int *size) +{ + return kmemdup(rdesc, *size, GFP_KERNEL); +} static inline int hid_bpf_module_init(void) { return 0; } static inline void hid_bpf_module_exit(void) {} #endif
On Fri, Mar 18, 2022 at 9:17 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Make use of BPF_HID_ATTACH_RDESC_FIXUP so we can trigger an rdesc 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.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- ensure the ctx.size is properly bounded by allocated size
- s/link_attached/post_link_attach/
- removed the switch statement with only one case
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
drivers/hid/hid-bpf.c | 62 ++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/hid.h | 6 ++++ 3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 5060ebcb9979..45c87ff47324 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -50,6 +50,14 @@ static struct hid_device *hid_bpf_fd_to_hdev(int fd) return hdev; }
+static int hid_reconnect(struct hid_device *hdev) +{
if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
return device_reprobe(&hdev->dev);
return 0;
+}
static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) { int err = 0; @@ -92,6 +100,12 @@ static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_ return err; }
+static void hid_bpf_post_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
if (type == BPF_HID_ATTACH_RDESC_FIXUP)
hid_reconnect(hdev);
+}
static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) { switch (type) { @@ -99,6 +113,9 @@ static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_ty kfree(hdev->bpf.device_data); hdev->bpf.device_data = NULL; break;
case BPF_HID_ATTACH_RDESC_FIXUP:
hid_reconnect(hdev);
break; default: /* do nothing */ break;
@@ -116,6 +133,9 @@ static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *c case HID_BPF_DEVICE_EVENT: type = BPF_HID_ATTACH_DEVICE_EVENT; break;
case HID_BPF_RDESC_FIXUP:
type = BPF_HID_ATTACH_RDESC_FIXUP;
break; default: return -EINVAL; }
@@ -155,11 +175,53 @@ u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) return ctx.data; }
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
Do we need to lock bpf_hid_mutex before calling bpf_hid_link_empty()? (or maybe we already did?)
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size); if (buf == NULL) return -ENOMEM;
diff --git a/include/linux/hid.h b/include/linux/hid.h index 8fd79011f461..66d949d10b78 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1213,10 +1213,16 @@ do { \
#ifdef CONFIG_BPF u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size); int hid_bpf_module_init(void); void hid_bpf_module_exit(void); #else static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc,
unsigned int *size)
+{
return kmemdup(rdesc, *size, GFP_KERNEL);
+} static inline int hid_bpf_module_init(void) { return 0; } static inline void hid_bpf_module_exit(void) {}
#endif
2.35.1
On Fri, Mar 18, 2022 at 10:10 PM Song Liu song@kernel.org wrote:
On Fri, Mar 18, 2022 at 9:17 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Make use of BPF_HID_ATTACH_RDESC_FIXUP so we can trigger an rdesc 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.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- ensure the ctx.size is properly bounded by allocated size
- s/link_attached/post_link_attach/
- removed the switch statement with only one case
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
drivers/hid/hid-bpf.c | 62 ++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/hid.h | 6 ++++ 3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 5060ebcb9979..45c87ff47324 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -50,6 +50,14 @@ static struct hid_device *hid_bpf_fd_to_hdev(int fd) return hdev; }
+static int hid_reconnect(struct hid_device *hdev) +{
if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
return device_reprobe(&hdev->dev);
return 0;
+}
static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) { int err = 0; @@ -92,6 +100,12 @@ static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_ return err; }
+static void hid_bpf_post_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
if (type == BPF_HID_ATTACH_RDESC_FIXUP)
hid_reconnect(hdev);
+}
static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) { switch (type) { @@ -99,6 +113,9 @@ static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_ty kfree(hdev->bpf.device_data); hdev->bpf.device_data = NULL; break;
case BPF_HID_ATTACH_RDESC_FIXUP:
hid_reconnect(hdev);
break; default: /* do nothing */ break;
@@ -116,6 +133,9 @@ static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *c case HID_BPF_DEVICE_EVENT: type = BPF_HID_ATTACH_DEVICE_EVENT; break;
case HID_BPF_RDESC_FIXUP:
type = BPF_HID_ATTACH_RDESC_FIXUP;
break; default: return -EINVAL; }
@@ -155,11 +175,53 @@ u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) return ctx.data; }
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
Do we need to lock bpf_hid_mutex before calling bpf_hid_link_empty()? (or maybe we already did?)
The mutex is not locked before this call, indeed.
However, bpf_hid_link_empty() is an inlined function that just calls in the end list_empty(). Given that all the list heads are created just once for the entire life of the HID device, I *think* this is thread safe and does not require mutex locking.
(I might be wrong)
So when first plugging in the device, if there is a fighting process that attempts to add a program, if the program managed to insert itself before we enter this code, then the list won't be empty and we will execute BPF_PROG_RUN_ARRAY(), and if not, well, we ignore it and wait for reconnect().
But now I am starting to wonder if I need to also protect BPF_PROG_RUN_ARRAY() under bpf_hid_mutex...
Cheers, Benjamin
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size); if (buf == NULL) return -ENOMEM;
diff --git a/include/linux/hid.h b/include/linux/hid.h index 8fd79011f461..66d949d10b78 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -1213,10 +1213,16 @@ do { \
#ifdef CONFIG_BPF u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size); +u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size); int hid_bpf_module_init(void); void hid_bpf_module_exit(void); #else static inline u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *rd, int *size) { return rd; } +static inline u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc,
unsigned int *size)
+{
return kmemdup(rdesc, *size, GFP_KERNEL);
+} static inline int hid_bpf_module_init(void) { return 0; } static inline void hid_bpf_module_exit(void) {}
#endif
2.35.1
On Mon, Mar 21, 2022 at 9:20 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
On Fri, Mar 18, 2022 at 10:10 PM Song Liu song@kernel.org wrote:
On Fri, Mar 18, 2022 at 9:17 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Make use of BPF_HID_ATTACH_RDESC_FIXUP so we can trigger an rdesc 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.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- ensure the ctx.size is properly bounded by allocated size
- s/link_attached/post_link_attach/
- removed the switch statement with only one case
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
drivers/hid/hid-bpf.c | 62 ++++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/hid.h | 6 ++++ 3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 5060ebcb9979..45c87ff47324 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -50,6 +50,14 @@ static struct hid_device *hid_bpf_fd_to_hdev(int fd) return hdev; }
+static int hid_reconnect(struct hid_device *hdev) +{
if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
return device_reprobe(&hdev->dev);
return 0;
+}
static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) { int err = 0; @@ -92,6 +100,12 @@ static int hid_bpf_pre_link_attach(struct hid_device *hdev, enum bpf_hid_attach_ return err; }
+static void hid_bpf_post_link_attach(struct hid_device *hdev, enum bpf_hid_attach_type type) +{
if (type == BPF_HID_ATTACH_RDESC_FIXUP)
hid_reconnect(hdev);
+}
static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_type type) { switch (type) { @@ -99,6 +113,9 @@ static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_ty kfree(hdev->bpf.device_data); hdev->bpf.device_data = NULL; break;
case BPF_HID_ATTACH_RDESC_FIXUP:
hid_reconnect(hdev);
break; default: /* do nothing */ break;
@@ -116,6 +133,9 @@ static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *c case HID_BPF_DEVICE_EVENT: type = BPF_HID_ATTACH_DEVICE_EVENT; break;
case HID_BPF_RDESC_FIXUP:
type = BPF_HID_ATTACH_RDESC_FIXUP;
break; default: return -EINVAL; }
@@ -155,11 +175,53 @@ u8 *hid_bpf_raw_event(struct hid_device *hdev, u8 *data, int *size) return ctx.data; }
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
Do we need to lock bpf_hid_mutex before calling bpf_hid_link_empty()? (or maybe we already did?)
The mutex is not locked before this call, indeed.
However, bpf_hid_link_empty() is an inlined function that just calls in the end list_empty(). Given that all the list heads are created just once for the entire life of the HID device, I *think* this is thread safe and does not require mutex locking.
Hmm.. I guess you are right.
(I might be wrong)
So when first plugging in the device, if there is a fighting process that attempts to add a program, if the program managed to insert itself before we enter this code, then the list won't be empty and we will execute BPF_PROG_RUN_ARRAY(), and if not, well, we ignore it and wait for reconnect().
But now I am starting to wonder if I need to also protect BPF_PROG_RUN_ARRAY() under bpf_hid_mutex...
I think this is not necessary.
Thanks, Song
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf. We suggest to use __weak global functions and unstable kfunc helpers to achieve the same goal. This way HID side and bpf side can evolve without introducing stable uapi burden. For example this particular patch can be compressed to: __weak int hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) { return 0; } ALLOW_ERROR_INJECTION(ALLOW_ERROR_INJECTION, ERRNO);
- buf = kmemdup(start, size, GFP_KERNEL); + if (!hid_bpf_report_fixup(device, start, &size)) + buf = kmemdup(start, size, GFP_KERNEL);
Then bpf program can replace hid_bpf_report_fixup function and adjust its return value while reading args.
Similar approach can be done with all other hooks.
Once api between HID and bpf stabilizes we can replace nop functions with writeable tracepoints to make things a bit more stable while still allowing for change of the interface in the future.
The amount of bpf specific code in HID core will be close to zero while bpf can be used to flexibly tweak it.
kfunc is a corresponding mechanism to introduce unstable api from bpf into the kernel instead of stable helpers. Just whitelist some functions as unstable kfunc helpers and call them from bpf progs. See net/bpf/test_run.c and bpf_kfunc_call* for inspiration.
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
We suggest to use __weak global functions and unstable kfunc helpers to achieve the same goal. This way HID side and bpf side can evolve without introducing stable uapi burden. For example this particular patch can be compressed to: __weak int hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) { return 0; } ALLOW_ERROR_INJECTION(ALLOW_ERROR_INJECTION, ERRNO);
- buf = kmemdup(start, size, GFP_KERNEL);
- if (!hid_bpf_report_fixup(device, start, &size))
- buf = kmemdup(start, size, GFP_KERNEL);
Then bpf program can replace hid_bpf_report_fixup function and adjust its return value while reading args.
I appreciate the suggestion and gave it a try, but AFAICT this doesn't work for HID (please correct me if I am wrong):
- I tried to use __weak to replace the ugly struct bpf_hid_hooks
This struct is in place simply because the HID module can be compiled in as a kernel module and we might not have the symbols available from kernel/bpf when it is a separate module. Either I did something wrong, but it seems that when we load the module in the kernel, there is no magic that overrides the weak symbols from the ones from the modules.
- for hid_bpf_report_fixup(), this would mean that a BPF program could overwrite the function
This is great, but I need to have one program per device, not one globally defined function. I can not have a generic report_fixup in the system, simply because you might need 2 different functions for 2 different devices.
We could solve that by auto-generating the bpf program based on which devices are available, but that would mean that users will see a reconnect of all of their input devices when they plug in a new one, and will also require them to have LLVM installed, which I do not want.
- for stuff like hid_bpf_raw_event(), I want to have multiple programs attached to the various devices, and not necessarily the same across devices.
This is basically the same as above, except that I need to chain programs.
For instance, we could have a program that "fixes" one device, but I also want to attach a tracing program on top of it to monitor what is happening.
Similar approach can be done with all other hooks.
Once api between HID and bpf stabilizes we can replace nop functions with writeable tracepoints to make things a bit more stable while still allowing for change of the interface in the future.
The amount of bpf specific code in HID core will be close to zero while bpf can be used to flexibly tweak it.
Again, I like the idea, but I clearly don't see where you want to go. From what I see, this is incompatible with the use cases I have.
kfunc is a corresponding mechanism to introduce unstable api from bpf into the kernel instead of stable helpers. Just whitelist some functions as unstable kfunc helpers and call them from bpf progs. See net/bpf/test_run.c and bpf_kfunc_call* for inspiration.
I also like this idea.
However, for hid_hw_raw_request() I can not blindly enable that function in all program types. This function makes the kernel sleep, and so we can not use it while in IRQ context. I think I can detect if we are in IRQ or not, but is it really worth enabling it across all BPF program types when we know that only SEC("hid/user_event") will use it?
Also, I am not sure how we can make bpf_hid_get_data() work with that. We need to teach the verifier how much memory is provided, and I do not see how you can do that with kfunc.
Cheers, Benjamin
On Wed, Mar 23, 2022 at 9:08 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
I think such a big feature as this one, especially that most BPF folks are (probably) not familiar with the HID subsystem in the kernel, would benefit from a bit of live discussion during BPF office hours. Do you think you can give a short overview of what you are trying to achieve with some background context on HID specifics at one of the next BPF office hours? We have a meeting scheduled every week on Thursday, 9am Pacific time. But people need to put their topic onto the agenda, otherwise the meeting is cancelled. See [0] for spreadsheet and links to Zoom meeting, agenda, etc.
[0] https://docs.google.com/spreadsheets/d/1LfrDXZ9-fdhvPEp_LHkxAMYyxxpwBXjywWa0...
[...]
On Fri, Mar 25, 2022 at 6:00 PM Andrii Nakryiko andrii.nakryiko@gmail.com wrote:
On Wed, Mar 23, 2022 at 9:08 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
I think such a big feature as this one, especially that most BPF folks are (probably) not familiar with the HID subsystem in the kernel, would benefit from a bit of live discussion during BPF office hours. Do you think you can give a short overview of what you are trying to achieve with some background context on HID specifics at one of the next BPF office hours? We have a meeting scheduled every week on Thursday, 9am Pacific time. But people need to put their topic onto the agenda, otherwise the meeting is cancelled. See [0] for spreadsheet and links to Zoom meeting, agenda, etc.
This sounds like a good idea. I just added my topic on the agenda and will prepare some slides.
Cheers, Benjamin
[0] https://docs.google.com/spreadsheets/d/1LfrDXZ9-fdhvPEp_LHkxAMYyxxpwBXjywWa0...
[...]
On Sun, Mar 27, 2022 at 11:57 PM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
On Fri, Mar 25, 2022 at 6:00 PM Andrii Nakryiko andrii.nakryiko@gmail.com wrote:
On Wed, Mar 23, 2022 at 9:08 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
I think such a big feature as this one, especially that most BPF folks are (probably) not familiar with the HID subsystem in the kernel, would benefit from a bit of live discussion during BPF office hours. Do you think you can give a short overview of what you are trying to achieve with some background context on HID specifics at one of the next BPF office hours? We have a meeting scheduled every week on Thursday, 9am Pacific time. But people need to put their topic onto the agenda, otherwise the meeting is cancelled. See [0] for spreadsheet and links to Zoom meeting, agenda, etc.
This sounds like a good idea. I just added my topic on the agenda and will prepare some slides.
Great! Unfortunately I personally have a conflict this week and won't be able to attend, so I'll have to catch up somehow through word of mouth :( Next week's BPF office hours would be best, but I don't want to delay discussions just because of me.
Cheers, Benjamin
[0] https://docs.google.com/spreadsheets/d/1LfrDXZ9-fdhvPEp_LHkxAMYyxxpwBXjywWa0...
[...]
On Mon, Mar 28, 2022 at 11:35 PM Andrii Nakryiko andrii.nakryiko@gmail.com wrote:
On Sun, Mar 27, 2022 at 11:57 PM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
On Fri, Mar 25, 2022 at 6:00 PM Andrii Nakryiko andrii.nakryiko@gmail.com wrote:
On Wed, Mar 23, 2022 at 9:08 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
I think such a big feature as this one, especially that most BPF folks are (probably) not familiar with the HID subsystem in the kernel, would benefit from a bit of live discussion during BPF office hours. Do you think you can give a short overview of what you are trying to achieve with some background context on HID specifics at one of the next BPF office hours? We have a meeting scheduled every week on Thursday, 9am Pacific time. But people need to put their topic onto the agenda, otherwise the meeting is cancelled. See [0] for spreadsheet and links to Zoom meeting, agenda, etc.
This sounds like a good idea. I just added my topic on the agenda and will prepare some slides.
Great! Unfortunately I personally have a conflict this week and won't be able to attend, so I'll have to catch up somehow through word of mouth :( Next week's BPF office hours would be best, but I don't want to delay discussions just because of me.
OK. FWIW, I'll have slides publicly available once I'll do a final roundup on them. Hopefully that will give you enough context on HID to understand the problem. If there are too many conflicts we can surely delay by a week, but I would rather have the discussion happening sooner :/
Cheers, Benjamin
[0] https://docs.google.com/spreadsheets/d/1LfrDXZ9-fdhvPEp_LHkxAMYyxxpwBXjywWa0...
[...]
On Tue, Mar 29, 2022 at 3:53 PM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
On Mon, Mar 28, 2022 at 11:35 PM Andrii Nakryiko andrii.nakryiko@gmail.com wrote:
On Sun, Mar 27, 2022 at 11:57 PM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
On Fri, Mar 25, 2022 at 6:00 PM Andrii Nakryiko andrii.nakryiko@gmail.com wrote:
On Wed, Mar 23, 2022 at 9:08 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote: > > +u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) > +{ > + int ret; > + struct hid_bpf_ctx_kern ctx = { > + .type = HID_BPF_RDESC_FIXUP, > + .hdev = hdev, > + .size = *size, > + }; > + > + if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP)) > + goto ignore_bpf; > + > + ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL); > + if (!ctx.data) > + goto ignore_bpf; > + > + ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE; > + > + ret = hid_bpf_run_progs(hdev, &ctx); > + if (ret) > + goto ignore_bpf; > + > + if (ctx.size > ctx.allocated_size) > + goto ignore_bpf; > + > + *size = ctx.size; > + > + if (*size) { > + rdesc = krealloc(ctx.data, *size, GFP_KERNEL); > + } else { > + rdesc = NULL; > + kfree(ctx.data); > + } > + > + return rdesc; > + > + ignore_bpf: > + kfree(ctx.data); > + return kmemdup(rdesc, *size, GFP_KERNEL); > +} > + > int __init hid_bpf_module_init(void) > { > struct bpf_hid_hooks hooks = { > .hdev_from_fd = hid_bpf_fd_to_hdev, > .pre_link_attach = hid_bpf_pre_link_attach, > + .post_link_attach = hid_bpf_post_link_attach, > .array_detach = hid_bpf_array_detach, > }; > > diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c > index 937fab7eb9c6..3182c39db006 100644 > --- a/drivers/hid/hid-core.c > +++ b/drivers/hid/hid-core.c > @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) > return -ENODEV; > size = device->dev_rsize; > > - buf = kmemdup(start, size, GFP_KERNEL); > + /* hid_bpf_report_fixup() ensures we work on a copy of rdesc */ > + buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
I think such a big feature as this one, especially that most BPF folks are (probably) not familiar with the HID subsystem in the kernel, would benefit from a bit of live discussion during BPF office hours. Do you think you can give a short overview of what you are trying to achieve with some background context on HID specifics at one of the next BPF office hours? We have a meeting scheduled every week on Thursday, 9am Pacific time. But people need to put their topic onto the agenda, otherwise the meeting is cancelled. See [0] for spreadsheet and links to Zoom meeting, agenda, etc.
This sounds like a good idea. I just added my topic on the agenda and will prepare some slides.
Great! Unfortunately I personally have a conflict this week and won't be able to attend, so I'll have to catch up somehow through word of mouth :( Next week's BPF office hours would be best, but I don't want to delay discussions just because of me.
OK. FWIW, I'll have slides publicly available once I'll do a final roundup on them. Hopefully that will give you enough context on HID to understand the problem. If there are too many conflicts we can surely delay by a week, but I would rather have the discussion happening sooner :/
Follow up on the discussion we had yesterday:
- this patchset should be dropped in its current form, because it's the "old way" of extending BPF:
The new goal is to extend the BPF core so we work around the limitations we find in HID so other subsystems can use the same approach. This is what Alexei was explaining in his answer in this thread. We need HID to solely declare which functions are replaced (by abusing SEC("fentry/function_name") - or fexit, fmod_ret), and also allow HID to declare its BPF API without having to change a single bit in the BPF core. This approach should allow other subsystems (USB???) to use a similar approach without having to mess up with BPF too much.
- being able to attach a SEC("fentry/function") to a particular HID device requires some changes in the BPF core
We can live without it, but it would be way cleaner to selectively attach those trampolines to only the selected devices without having to hand that decision to the BPF programmers
- patch 12 and 13 (the bits access helper) should be dropped
This should be done entirely in BPF, with a BPF helper as a .h that users include instead of having to maintain yet another UAPI
- Daniel raised the question of a flag which tells other BPF that a bug is fixed
In the case we drop in the filesystem a BPF program to fix a particular device, and then we include that same program in the kernel, how can we know that the feature is already fixed? It's still an open question.
- including BPF objects in the kernel is definitively doable through lskel
But again, the question is how do we attach those programs to a particular device?
- to export a new UAPI BPF helper, we should rely on kfunc as explained in Alexei's email
However, the current kfunc and ALLOW_ERROR_INJECTION API doesn't clearly allow to define which function is sleepable or not, making it currently hard to define that behavior. Once the BPF kAPI allows to mark kfunc and ALLOW_ERROR_INJECTION (_weak) functions to be sleepable or not, we will be able to define the BPF hooks directly in HID without having to touch the BPF core.
- running a SEC("fentry/...") BPF program as a standalone from userspace through a syscall is possible
- When defining a SEC("fentry/..."), all parameters are currently read-only
we either need a hid_bpf_get_data() hooks where we can mark the output as being read-write, or we need to be able to tag the target function as read-write for (part of) its arguments
(I would lean toward the extra helper that might be much easier to declare IMO).
I think that's it from yesterday's discussion.
Many thanks for listening to me and proposing a better solution. Now I have a lot to work on.
Cheers, Benjamin
On Wed, Mar 23, 2022 at 05:08:25PM +0100, Benjamin Tissoires wrote:
Hi Alexei,
On Tue, Mar 22, 2022 at 11:51 PM Alexei Starovoitov alexei.starovoitov@gmail.com wrote:
On Fri, Mar 18, 2022 at 9:16 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
+u8 *hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) +{
int ret;
struct hid_bpf_ctx_kern ctx = {
.type = HID_BPF_RDESC_FIXUP,
.hdev = hdev,
.size = *size,
};
if (bpf_hid_link_empty(&hdev->bpf, BPF_HID_ATTACH_RDESC_FIXUP))
goto ignore_bpf;
ctx.data = kmemdup(rdesc, HID_MAX_DESCRIPTOR_SIZE, GFP_KERNEL);
if (!ctx.data)
goto ignore_bpf;
ctx.allocated_size = HID_MAX_DESCRIPTOR_SIZE;
ret = hid_bpf_run_progs(hdev, &ctx);
if (ret)
goto ignore_bpf;
if (ctx.size > ctx.allocated_size)
goto ignore_bpf;
*size = ctx.size;
if (*size) {
rdesc = krealloc(ctx.data, *size, GFP_KERNEL);
} else {
rdesc = NULL;
kfree(ctx.data);
}
return rdesc;
- ignore_bpf:
kfree(ctx.data);
return kmemdup(rdesc, *size, GFP_KERNEL);
+}
int __init hid_bpf_module_init(void) { struct bpf_hid_hooks hooks = { .hdev_from_fd = hid_bpf_fd_to_hdev, .pre_link_attach = hid_bpf_pre_link_attach,
.post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, };
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 937fab7eb9c6..3182c39db006 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1213,7 +1213,8 @@ int hid_open_report(struct hid_device *device) return -ENODEV; size = device->dev_rsize;
buf = kmemdup(start, size, GFP_KERNEL);
/* hid_bpf_report_fixup() ensures we work on a copy of rdesc */
buf = hid_bpf_report_fixup(device, start, &size);
Looking at this patch and the majority of other patches... the code is doing a lot of work to connect HID side with bpf. At the same time the evolution of the patch series suggests that these hook points are not quite stable. More hooks and helpers are being added. It tells us that it's way too early to introduce a stable interface between HID and bpf.
I understand that you might be under the impression that the interface is changing a lot, but this is mostly due to my poor knowledge of all the arcanes of eBPF. The overall way HID-BPF works is to work on a single array, and we should pretty much be sorted out. There are a couple of helpers to be able to communicate with the device, but the API has been stable in the kernel for those for quite some time now.
The variations in the hooks is mostly because I don't know what is the best representation we can use in eBPF for those, and the review process is changing that.
We suggest to use __weak global functions and unstable kfunc helpers to achieve the same goal. This way HID side and bpf side can evolve without introducing stable uapi burden. For example this particular patch can be compressed to: __weak int hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) { return 0; } ALLOW_ERROR_INJECTION(ALLOW_ERROR_INJECTION, ERRNO);
- buf = kmemdup(start, size, GFP_KERNEL);
- if (!hid_bpf_report_fixup(device, start, &size))
- buf = kmemdup(start, size, GFP_KERNEL);
Then bpf program can replace hid_bpf_report_fixup function and adjust its return value while reading args.
I appreciate the suggestion and gave it a try, but AFAICT this doesn't work for HID (please correct me if I am wrong):
- I tried to use __weak to replace the ugly struct bpf_hid_hooks
This struct is in place simply because the HID module can be compiled in as a kernel module and we might not have the symbols available from kernel/bpf when it is a separate module.
why is that? The kernel modules should be compiled with BTF and bpf infra can attach to those functions the same way. __weak suggestion in the above is not to override it by the module. It's there to prevent compiler from inlining it. __weak int hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) {...} Will be only one. Either in kernel proper or in kernel module. It's up to HID subsystem.
Either I did something wrong, but it seems that when we load the module in the kernel, there is no magic that overrides the weak symbols from the ones from the modules.
- for hid_bpf_report_fixup(), this would mean that a BPF program could
overwrite the function
This is great, but I need to have one program per device, not one globally defined function. I can not have a generic report_fixup in the system, simply because you might need 2 different functions for 2 different devices.
That's fine. Take a look at how libxdp is doing the chaining. One bpf prog is attached to a main entry point and it does demux (or call other progs sequentially) based on its own logic. For example you have bpf prog that does: SEC("fentry/hid_bpf_report_fixup") int main_hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) { if (hdev->id == ..) another_bpf_prog(); }
Or call another bpf prog via bpf_tail_call.
There are lots of option to connect them.
You can also have N bpf progs. All look like: SEC("fentry/hid_bpf_report_fixup") int first_hid_bpf_report_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size) { if (hdev->id != mine_id) return 0; // do work; }
And attach them all as fmod_ret type bpf prog to the same kernel hid_bpf_report_fixup() function. The bpf trampoline will call them sequentially.
We could solve that by auto-generating the bpf program based on which devices are available, but that would mean that users will see a reconnect of all of their input devices when they plug in a new one, and will also require them to have LLVM installed, which I do not want.
Of course. No need to regenrated them with LLVM on the fly.
- for stuff like hid_bpf_raw_event(), I want to have multiple programs
attached to the various devices, and not necessarily the same across devices.
This is basically the same as above, except that I need to chain programs.
Chaining is already available for fentry/fexit/fmod_ret programs.
For instance, we could have a program that "fixes" one device, but I also want to attach a tracing program on top of it to monitor what is happening.
That's also possible. You can have a main bpf prog as entry point that calls global functions of this bpf program. Later you can install another bpf prog that will replace one of the previously loaded global bpf functions. So fully programmable and arbitrary chaining is available.
Similar approach can be done with all other hooks.
Once api between HID and bpf stabilizes we can replace nop functions with writeable tracepoints to make things a bit more stable while still allowing for change of the interface in the future.
The amount of bpf specific code in HID core will be close to zero while bpf can be used to flexibly tweak it.
Again, I like the idea, but I clearly don't see where you want to go. From what I see, this is incompatible with the use cases I have.
kfunc is a corresponding mechanism to introduce unstable api from bpf into the kernel instead of stable helpers. Just whitelist some functions as unstable kfunc helpers and call them from bpf progs. See net/bpf/test_run.c and bpf_kfunc_call* for inspiration.
I also like this idea.
However, for hid_hw_raw_request() I can not blindly enable that function in all program types. This function makes the kernel sleep, and so we can not use it while in IRQ context. I think I can detect if we are in IRQ or not, but is it really worth enabling it across all BPF program types when we know that only SEC("hid/user_event") will use it?
There are sleepable and non-sleepable fmod_ret/fentry/fexit programs. The hid subsystem would need to white list all ALLOW_ERROR_INJECTION kernel functions approriately. So that sleepable bpf prog won't attach to a hook where IRQs are disabled.
Also, I am not sure how we can make bpf_hid_get_data() work with that. We need to teach the verifier how much memory is provided, and I do not see how you can do that with kfunc.
If there is anything missing in the verifier let's extend that. Everyone will benefit.
PS Sorry for delay. Looks like your emails are going into some sort of queue in my gmail and receive them later. Or some other odd filtering is going on. Maybe it's related to giant cc list in your patches.
The test is pretty basic: - create a virtual uhid device that no userspace will like (to not mess up the running system) - attach a BPF prog to it - open the matching hidraw node - inject one event and check: * that the BPF program can do something on the event stream * can modify the event stream
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - squashed "hid: rely on uhid event to know if a test device is ready" into this one - add selftests bpf VM config changes - s/hidraw_ino/hidraw_number/
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- tools/testing/selftests/bpf/config | 3 + tools/testing/selftests/bpf/prog_tests/hid.c | 444 +++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 25 ++ 3 files changed, 472 insertions(+) create mode 100644 tools/testing/selftests/bpf/prog_tests/hid.c create mode 100644 tools/testing/selftests/bpf/progs/hid.c
diff --git a/tools/testing/selftests/bpf/config b/tools/testing/selftests/bpf/config index 763db63a3890..b983c76535f8 100644 --- a/tools/testing/selftests/bpf/config +++ b/tools/testing/selftests/bpf/config @@ -53,3 +53,6 @@ CONFIG_NF_DEFRAG_IPV4=y CONFIG_NF_DEFRAG_IPV6=y CONFIG_NF_CONNTRACK=y CONFIG_USERFAULTFD=y +CONFIG_HID=y +CONFIG_HIDRAW=y +CONFIG_UHID=y diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c new file mode 100644 index 000000000000..9f9dd08d3232 --- /dev/null +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -0,0 +1,444 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Red Hat */ +#include <test_progs.h> +#include <testing_helpers.h> +#include "hid.skel.h" + +#include <fcntl.h> +#include <fnmatch.h> +#include <dirent.h> +#include <poll.h> +#include <stdbool.h> +#include <linux/uhid.h> + +static unsigned char rdesc[] = { + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x09, 0x21, /* Usage (Vendor Usage 0x21) */ + 0xa1, 0x01, /* COLLECTION (Application) */ + 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ + 0xa1, 0x00, /* COLLECTION (Physical) */ + 0x85, 0x01, /* REPORT_ID (1) */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x01, /* USAGE_MINIMUM (1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x81, 0x01, /* INPUT (Cnst,Var,Abs) */ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x30, /* USAGE (X) */ + 0x09, 0x31, /* USAGE (Y) */ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x01, /* USAGE_MINIMUM (1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x06, /* USAGE_MINIMUM (6) */ + 0x29, 0x08, /* USAGE_MAXIMUM (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0xc0, /* END_COLLECTION */ + 0xc0, /* END_COLLECTION */ +}; + +static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; + +/* no need to protect uhid_stopped, only one thread accesses it */ +static bool uhid_stopped; + +static int uhid_write(int fd, const struct uhid_event *ev) +{ + ssize_t ret; + + ret = write(fd, ev, sizeof(*ev)); + if (ret < 0) { + fprintf(stderr, "Cannot write to uhid: %m\n"); + return -errno; + } else if (ret != sizeof(*ev)) { + fprintf(stderr, "Wrong size written to uhid: %zd != %zu\n", + ret, sizeof(ev)); + return -EFAULT; + } else { + return 0; + } +} + +static int create(int fd, int rand_nb) +{ + struct uhid_event ev; + char buf[25]; + + sprintf(buf, "test-uhid-device-%d", rand_nb); + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_CREATE; + strcpy((char *)ev.u.create.name, buf); + ev.u.create.rd_data = rdesc; + ev.u.create.rd_size = sizeof(rdesc); + ev.u.create.bus = BUS_USB; + ev.u.create.vendor = 0x0001; + ev.u.create.product = 0x0a37; + ev.u.create.version = 0; + ev.u.create.country = 0; + + sprintf(buf, "%d", rand_nb); + strcpy((char *)ev.u.create.phys, buf); + + return uhid_write(fd, &ev); +} + +static void destroy(int fd) +{ + struct uhid_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_DESTROY; + + uhid_write(fd, &ev); +} + +static int event(int fd) +{ + struct uhid_event ev; + ssize_t ret; + + memset(&ev, 0, sizeof(ev)); + ret = read(fd, &ev, sizeof(ev)); + if (ret == 0) { + fprintf(stderr, "Read HUP on uhid-cdev\n"); + return -EFAULT; + } else if (ret < 0) { + fprintf(stderr, "Cannot read uhid-cdev: %m\n"); + return -errno; + } else if (ret != sizeof(ev)) { + fprintf(stderr, "Invalid size read from uhid-dev: %zd != %zu\n", + ret, sizeof(ev)); + return -EFAULT; + } + + switch (ev.type) { + case UHID_START: + pthread_mutex_lock(&uhid_started_mtx); + pthread_cond_signal(&uhid_started); + pthread_mutex_unlock(&uhid_started_mtx); + + fprintf(stderr, "UHID_START from uhid-dev\n"); + break; + case UHID_STOP: + uhid_stopped = true; + + fprintf(stderr, "UHID_STOP from uhid-dev\n"); + break; + case UHID_OPEN: + fprintf(stderr, "UHID_OPEN from uhid-dev\n"); + break; + case UHID_CLOSE: + fprintf(stderr, "UHID_CLOSE from uhid-dev\n"); + break; + case UHID_OUTPUT: + fprintf(stderr, "UHID_OUTPUT from uhid-dev\n"); + break; + case UHID_GET_REPORT: + fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); + break; + case UHID_SET_REPORT: + fprintf(stderr, "UHID_SET_REPORT from uhid-dev\n"); + break; + default: + fprintf(stderr, "Invalid event from uhid-dev: %u\n", ev.type); + } + + return 0; +} + +static void *read_uhid_events_thread(void *arg) +{ + int fd = *(int *)arg; + struct pollfd pfds[1]; + int ret = 0; + + pfds[0].fd = fd; + pfds[0].events = POLLIN; + + uhid_stopped = false; + + while (!uhid_stopped) { + ret = poll(pfds, 1, 100); + if (ret < 0) { + fprintf(stderr, "Cannot poll for fds: %m\n"); + break; + } + if (pfds[0].revents & POLLIN) { + ret = event(fd); + if (ret) + break; + } + } + + return (void *)(long)ret; +} + +static int uhid_start_listener(pthread_t *tid, int uhid_fd) +{ + int fd = uhid_fd; + + pthread_mutex_lock(&uhid_started_mtx); + if (CHECK_FAIL(pthread_create(tid, NULL, read_uhid_events_thread, + (void *)&fd))) { + pthread_mutex_unlock(&uhid_started_mtx); + close(fd); + return -EIO; + } + pthread_cond_wait(&uhid_started, &uhid_started_mtx); + pthread_mutex_unlock(&uhid_started_mtx); + + return 0; +} + +static int send_event(int fd, u8 *buf, size_t size) +{ + struct uhid_event ev; + + if (size > sizeof(ev.u.input.data)) + return -E2BIG; + + memset(&ev, 0, sizeof(ev)); + ev.type = UHID_INPUT2; + ev.u.input2.size = size; + + memcpy(ev.u.input2.data, buf, size); + + return uhid_write(fd, &ev); +} + +static int setup_uhid(int rand_nb) +{ + int fd; + const char *path = "/dev/uhid"; + int ret; + + fd = open(path, O_RDWR | O_CLOEXEC); + if (!ASSERT_GE(fd, 0, "open uhid-cdev")) + return -EPERM; + + ret = create(fd, rand_nb); + if (!ASSERT_OK(ret, "create uhid device")) { + close(fd); + return -EPERM; + } + + return fd; +} + +static int get_sysfs_fd(int rand_nb) +{ + const char *workdir = "/sys/devices/virtual/misc/uhid"; + const char *target = "0003:0001:0A37.*"; + char uevent[1024]; + char temp[512]; + char phys[512]; + DIR *d; + struct dirent *dir; + int fd, nread; + int found = -1; + + /* it would be nice to be able to use nftw, but the no_alu32 target doesn't support it */ + + sprintf(phys, "PHYS=%d", rand_nb); + + d = opendir(workdir); + if (d) { + while ((dir = readdir(d)) != NULL) { + if (fnmatch(target, dir->d_name, 0)) + continue; + + /* we found the correct VID/PID, now check for phys */ + sprintf(uevent, "%s/%s/uevent", workdir, dir->d_name); + fd = open(uevent, O_RDONLY | O_NONBLOCK); + if (fd < 0) + continue; + + nread = read(fd, temp, ARRAY_SIZE(temp)); + if (nread > 0 && (strstr(temp, phys)) != NULL) { + found = fd; + break; + } + + close(fd); + fd = 0; + } + closedir(d); + } + + return found; +} + +static int get_hidraw(struct bpf_link *link) +{ + struct bpf_link_info info = {0}; + int prog_id, i; + + /* retry 5 times in case the system is loaded */ + for (i = 5; i > 0; i--) { + usleep(10); + prog_id = link_info_prog_id(link, &info); + if (!prog_id) + continue; + if (info.hid.hidraw_number >= 0) + break; + } + + if (!prog_id) + return -1; + + return info.hid.hidraw_number; +} + +/* + * Attach hid_first_event to the given uhid device, + * retrieve and open the matching hidraw node, + * inject one event in the uhid device, + * check that the program sees it and can change the data + */ +static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, hidraw_number, hidraw_fd = -1; + char hidraw_path[64] = {0}; + u8 buf[10] = {0}; + int ret = -1; + + /* check that the program is correctly loaded */ + ASSERT_EQ(hid_skel->data->callback_check, 52, "callback_check1"); + ASSERT_EQ(hid_skel->data->callback2_check, 52, "callback2_check1"); + + /* attach the first program */ + hid_skel->links.hid_first_event = + bpf_program__attach_hid(hid_skel->progs.hid_first_event, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_first_event, + "attach_hid(hid_first_event)")) + return PTR_ERR(hid_skel->links.hid_first_event); + + hidraw_number = get_hidraw(hid_skel->links.hid_first_event); + if (!ASSERT_GE(hidraw_number, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_number); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + send_event(uhid_fd, buf, 6); + + /* check that hid_first_event() was executed */ + ASSERT_EQ(hid_skel->data->callback_check, 42, "callback_check1"); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(hidraw_fd, buf, sizeof(buf)); + if (!ASSERT_EQ(err, 6, "read_hidraw")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], 47, "hid_first_event")) + goto cleanup; + + /* inject another event */ + buf[0] = 1; + buf[1] = 47; + send_event(uhid_fd, buf, 6); + + /* check that hid_first_event() was executed */ + ASSERT_EQ(hid_skel->data->callback_check, 47, "callback_check1"); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(hidraw_fd, buf, sizeof(buf)); + if (!ASSERT_EQ(err, 6, "read_hidraw")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], 52, "hid_first_event")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + return ret; +} + +void serial_test_hid_bpf(void) +{ + struct hid *hid_skel = NULL; + int err, uhid_fd, sysfs_fd; + void *uhid_err; + time_t t; + pthread_t tid; + int rand_nb; + + /* initialize random number generator */ + srand((unsigned int)time(&t)); + + rand_nb = rand() % 1024; + + uhid_fd = setup_uhid(rand_nb); + if (!ASSERT_GE(uhid_fd, 0, "setup uhid")) + return; + + err = uhid_start_listener(&tid, uhid_fd); + ASSERT_OK(err, "uhid_start_listener"); + + /* locate the uevent file of the created device */ + sysfs_fd = get_sysfs_fd(rand_nb); + if (!ASSERT_GE(sysfs_fd, 0, "locate sysfs uhid device")) + goto cleanup; + + hid_skel = hid__open_and_load(); + if (!ASSERT_OK_PTR(hid_skel, "hid_skel_load")) + goto cleanup; + + /* start the tests! */ + err = test_hid_raw_event(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid"); + +cleanup: + hid__destroy(hid_skel); + destroy(uhid_fd); + + pthread_join(tid, &uhid_err); + err = (int)(long)uhid_err; + CHECK_FAIL(err); +} diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c new file mode 100644 index 000000000000..a28ba19ed933 --- /dev/null +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2022 Red hat */ +#include <linux/bpf.h> +#include <bpf/bpf_helpers.h> +#include <linux/bpf_hid.h> + +char _license[] SEC("license") = "GPL"; + +__u64 callback_check = 52; +__u64 callback2_check = 52; + +SEC("hid/device_event") +int hid_first_event(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 3 /* size */); + + if (!data) + return 0; /* EPERM check */ + + callback_check = data[1]; + + data[2] = data[1] + 5; + + return 0; +}
Simple report descriptor override in HID: replace part of the report descriptor from a static definition in the bpf kernel program.
Note that this test should be run last because we disconnect/reconnect the device, meaning that it changes the overall uhid device.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - added a comment to mention that this test needs to be run last
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- tools/testing/selftests/bpf/prog_tests/hid.c | 79 ++++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 54 +++++++++++++ 2 files changed, 133 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index 9f9dd08d3232..8c8e17e7385f 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -9,6 +9,7 @@ #include <dirent.h> #include <poll.h> #include <stdbool.h> +#include <linux/hidraw.h> #include <linux/uhid.h>
static unsigned char rdesc[] = { @@ -400,6 +401,76 @@ static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd) return ret; }
+/* + * Attach hid_rdesc_fixup to the given uhid device, + * retrieve and open the matching hidraw node, + * check that the hidraw report descriptor has been updated. + */ +static int test_rdesc_fixup(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + struct hidraw_report_descriptor rpt_desc = {0}; + int err, desc_size, hidraw_ino, hidraw_fd = -1; + char hidraw_path[64] = {0}; + void *uhid_err; + int ret = -1; + pthread_t tid; + + /* attach the program */ + hid_skel->links.hid_rdesc_fixup = + bpf_program__attach_hid(hid_skel->progs.hid_rdesc_fixup, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_rdesc_fixup, + "attach_hid(hid_rdesc_fixup)")) + return PTR_ERR(hid_skel->links.hid_rdesc_fixup); + + err = uhid_start_listener(&tid, uhid_fd); + ASSERT_OK(err, "uhid_start_listener"); + + hidraw_ino = get_hidraw(hid_skel->links.hid_rdesc_fixup); + if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_ino); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* check that hid_rdesc_fixup() was executed */ + ASSERT_EQ(hid_skel->data->callback2_check, 0x21, "callback_check2"); + + /* read the exposed report descriptor from hidraw */ + err = ioctl(hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + if (!ASSERT_GE(err, 0, "HIDIOCGRDESCSIZE")) + goto cleanup; + + /* ensure the new size of the rdesc is bigger than the old one */ + if (!ASSERT_GT(desc_size, sizeof(rdesc), "new_rdesc_size")) + goto cleanup; + + rpt_desc.size = desc_size; + err = ioctl(hidraw_fd, HIDIOCGRDESC, &rpt_desc); + if (!ASSERT_GE(err, 0, "HIDIOCGRDESC")) + goto cleanup; + + if (!ASSERT_EQ(rpt_desc.value[4], 0x42, "hid_rdesc_fixup")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + pthread_join(tid, &uhid_err); + err = (int)(long)uhid_err; + CHECK_FAIL(err); + + return ret; +} + void serial_test_hid_bpf(void) { struct hid *hid_skel = NULL; @@ -434,6 +505,14 @@ void serial_test_hid_bpf(void) err = test_hid_raw_event(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid");
+ /* + * this test should be run last because we disconnect/reconnect + * the device, meaning that it changes the overall uhid device + * and messes up with the thread that reads uhid events. + */ + err = test_rdesc_fixup(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_rdesc_fixup"); + cleanup: hid__destroy(hid_skel); destroy(uhid_fd); diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index a28ba19ed933..c9ce0e36e7b9 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -23,3 +23,57 @@ int hid_first_event(struct hid_bpf_ctx *ctx)
return 0; } + +static __u8 rdesc[] = { + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ + 0x09, 0x32, /* USAGE (Z) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x01, /* USAGE_MINIMUM (1) */ + 0x29, 0x03, /* USAGE_MAXIMUM (3) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0x91, 0x02, /* Output (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ + 0x19, 0x06, /* USAGE_MINIMUM (6) */ + 0x29, 0x08, /* USAGE_MAXIMUM (8) */ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ + 0x95, 0x03, /* REPORT_COUNT (3) */ + 0x75, 0x01, /* REPORT_SIZE (1) */ + 0xb1, 0x02, /* Feature (Data,Var,Abs) */ + 0x95, 0x01, /* REPORT_COUNT (1) */ + 0x75, 0x05, /* REPORT_SIZE (5) */ + 0x91, 0x01, /* Output (Cnst,Var,Abs) */ + + 0xc0, /* END_COLLECTION */ + 0xc0, /* END_COLLECTION */ +}; + +SEC("hid/rdesc_fixup") +int hid_rdesc_fixup(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 4096 /* size */); + + if (!data) + return 0; /* EPERM check */ + + callback2_check = data[4]; + + /* insert rdesc at offset 52 */ + __builtin_memcpy(&data[52], rdesc, sizeof(rdesc)); + ctx->size = sizeof(rdesc) + 52; + + /* Change Usage Vendor globally */ + data[4] = 0x42; + + return 0; +}
Insert 3 programs to check that we are doing the correct thing: '2', '1', '3' are inserted, but '1' is supposed to be executed first.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - use the new hid_get_data API
new in v2 --- tools/testing/selftests/bpf/prog_tests/hid.c | 80 ++++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 51 +++++++++++++ 2 files changed, 131 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index 8c8e17e7385f..b8d4dcf20b05 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -401,6 +401,83 @@ static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd) return ret; }
+/* + * Attach hid_insert{0,1,2} to the given uhid device, + * retrieve and open the matching hidraw node, + * inject one event in the uhid device, + * check that the programs have been inserted in the correct order. + */ +static int test_hid_attach_flags(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, hidraw_ino, hidraw_fd = -1; + char hidraw_path[64] = {0}; + u8 buf[10] = {0}; + int ret = -1; + + /* attach hid_test_insert2 program */ + hid_skel->links.hid_test_insert2 = + bpf_program__attach_hid(hid_skel->progs.hid_test_insert2, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_test_insert2, + "attach_hid(hid_test_insert2)")) + return PTR_ERR(hid_skel->links.hid_test_insert2); + + /* then attach hid_test_insert1 program before the previous*/ + hid_skel->links.hid_test_insert1 = + bpf_program__attach_hid(hid_skel->progs.hid_test_insert1, + sysfs_fd, + BPF_F_INSERT_HEAD); + if (!ASSERT_OK_PTR(hid_skel->links.hid_test_insert1, + "attach_hid(hid_test_insert1)")) + return PTR_ERR(hid_skel->links.hid_test_insert1); + + /* finally attach hid_test_insert3 at the end */ + hid_skel->links.hid_test_insert3 = + bpf_program__attach_hid(hid_skel->progs.hid_test_insert3, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_test_insert3, + "attach_hid(hid_test_insert3)")) + return PTR_ERR(hid_skel->links.hid_test_insert3); + + hidraw_ino = get_hidraw(hid_skel->links.hid_test_insert1); + if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_ino); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* inject one event */ + buf[0] = 1; + send_event(uhid_fd, buf, 6); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(hidraw_fd, buf, sizeof(buf)); + if (!ASSERT_EQ(err, 6, "read_hidraw")) + goto cleanup; + + if (!ASSERT_EQ(buf[1], 1, "hid_test_insert1")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], 2, "hid_test_insert2")) + goto cleanup; + + if (!ASSERT_EQ(buf[3], 3, "hid_test_insert3")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -505,6 +582,9 @@ void serial_test_hid_bpf(void) err = test_hid_raw_event(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid");
+ err = test_hid_attach_flags(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_user_raw_request"); + /* * this test should be run last because we disconnect/reconnect * the device, meaning that it changes the overall uhid device diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index c9ce0e36e7b9..390c1bb8d850 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -77,3 +77,54 @@ int hid_rdesc_fixup(struct hid_bpf_ctx *ctx)
return 0; } + +SEC("hid/device_event") +int hid_test_insert1(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 4 /* size */); + + if (!data) + return 0; /* EPERM check */ + + /* we need to be run first */ + if (data[2] || data[3]) + return -1; + + data[1] = 1; + + return 0; +} + +SEC("hid/device_event") +int hid_test_insert2(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 4 /* size */); + + if (!data) + return 0; /* EPERM check */ + + /* after insert0 and before insert2 */ + if (!data[1] || data[3]) + return -1; + + data[2] = 2; + + return 0; +} + +SEC("hid/device_event") +int hid_test_insert3(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 4 /* size */); + + if (!data) + return 0; /* EPERM check */ + + /* at the end */ + if (!data[1] || !data[2]) + return -1; + + data[3] = 3; + + return 0; +}
Add a simple test to see if we can trigger a bpf program of type "hid/user_event".
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - use the new hid_get_data API
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- tools/testing/selftests/bpf/prog_tests/hid.c | 56 ++++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 15 ++++++ 2 files changed, 71 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index b8d4dcf20b05..edc3af71e9ed 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -478,6 +478,59 @@ static int test_hid_attach_flags(struct hid *hid_skel, int uhid_fd, int sysfs_fd return ret; }
+/* + * Attach hid_user to the given uhid device, + * call the bpf program from userspace + * check that the program is called and does the expected. + */ +static int test_hid_user_call(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, prog_fd; + u8 buf[10] = {0}; + int ret = -1; + + LIBBPF_OPTS(bpf_test_run_opts, run_attrs, + .repeat = 1, + .ctx_in = &sysfs_fd, + .ctx_size_in = sizeof(sysfs_fd), + .data_in = buf, + .data_size_in = sizeof(buf), + .data_out = buf, + .data_size_out = sizeof(buf), + ); + + /* attach hid_user program */ + hid_skel->links.hid_user = bpf_program__attach_hid(hid_skel->progs.hid_user, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_user, + "attach_hid(hid_user)")) + return PTR_ERR(hid_skel->links.hid_user); + + buf[0] = 39; + + prog_fd = bpf_program__fd(hid_skel->progs.hid_user); + + err = bpf_prog_test_run_opts(prog_fd, &run_attrs); + if (!ASSERT_EQ(err, 0, "bpf_prog_test_run_xattr")) + goto cleanup; + + if (!ASSERT_EQ(run_attrs.retval, 72, "bpf_prog_test_run_opts")) + goto cleanup; + + if (!ASSERT_EQ(buf[1], 42, "hid_user_check_in")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], 4, "hid_user_check_static_out")) + goto cleanup; + + ret = 0; + +cleanup: + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -585,6 +638,9 @@ void serial_test_hid_bpf(void) err = test_hid_attach_flags(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_user_raw_request");
+ err = test_hid_user_call(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_user"); + /* * this test should be run last because we disconnect/reconnect * the device, meaning that it changes the overall uhid device diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index 390c1bb8d850..fbdbe9d1b605 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -128,3 +128,18 @@ int hid_test_insert3(struct hid_bpf_ctx *ctx)
return 0; } + +SEC("hid/user_event") +int hid_user(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 3 /* size */); + + if (!data) + return 0; /* EPERM check */ + + data[1] = data[0] + 3; + data[2] = 4; + ctx->retval = 72; + + return 0; +}
Everything should be available in the selftest part of the tree, but providing an example without uhid and hidraw will be more easy to follow for users.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - use the new hid_get_data API - add a comment for the report descriptor fixup to explain what is done
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- samples/bpf/.gitignore | 1 + samples/bpf/Makefile | 4 ++ samples/bpf/hid_mouse_kern.c | 117 +++++++++++++++++++++++++++++++ samples/bpf/hid_mouse_user.c | 129 +++++++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+) create mode 100644 samples/bpf/hid_mouse_kern.c create mode 100644 samples/bpf/hid_mouse_user.c
diff --git a/samples/bpf/.gitignore b/samples/bpf/.gitignore index 0e7bfdbff80a..65440bd618b2 100644 --- a/samples/bpf/.gitignore +++ b/samples/bpf/.gitignore @@ -2,6 +2,7 @@ cpustat fds_example hbm +hid_mouse ibumad lathist lwt_len_hist diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile index 38638845db9d..84ef458487df 100644 --- a/samples/bpf/Makefile +++ b/samples/bpf/Makefile @@ -60,6 +60,8 @@ tprogs-y += xdp_redirect_map tprogs-y += xdp_redirect tprogs-y += xdp_monitor
+tprogs-y += hid_mouse + # Libbpf dependencies LIBBPF_SRC = $(TOOLS_PATH)/lib/bpf LIBBPF_OUTPUT = $(abspath $(BPF_SAMPLES_PATH))/libbpf @@ -124,6 +126,7 @@ xdp_redirect_cpu-objs := xdp_redirect_cpu_user.o $(XDP_SAMPLE) xdp_redirect_map-objs := xdp_redirect_map_user.o $(XDP_SAMPLE) xdp_redirect-objs := xdp_redirect_user.o $(XDP_SAMPLE) xdp_monitor-objs := xdp_monitor_user.o $(XDP_SAMPLE) +hid_mouse-objs := hid_mouse_user.o
# Tell kbuild to always build the programs always-y := $(tprogs-y) @@ -181,6 +184,7 @@ always-y += ibumad_kern.o always-y += hbm_out_kern.o always-y += hbm_edt_kern.o always-y += xdpsock_kern.o +always-y += hid_mouse_kern.o
ifeq ($(ARCH), arm) # Strip all except -D__LINUX_ARM_ARCH__ option needed to handle linux diff --git a/samples/bpf/hid_mouse_kern.c b/samples/bpf/hid_mouse_kern.c new file mode 100644 index 000000000000..32285b1920c0 --- /dev/null +++ b/samples/bpf/hid_mouse_kern.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022 Benjamin Tissoires + */ +#include <linux/version.h> +#include <uapi/linux/bpf.h> +#include <uapi/linux/bpf_hid.h> +#include <bpf/bpf_helpers.h> + +SEC("hid/device_event") +int hid_y_event(struct hid_bpf_ctx *ctx) +{ + s16 y; + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 9 /* size */); + + if (!data) + return 0; /* EPERM check */ + + bpf_printk("event: %02x size: %d", ctx->type, ctx->size); + bpf_printk("incoming event: %02x %02x %02x", + data[0], + data[1], + data[2]); + bpf_printk(" %02x %02x %02x", + data[3], + data[4], + data[5]); + bpf_printk(" %02x %02x %02x", + data[6], + data[7], + data[8]); + + y = data[3] | (data[4] << 8); + + y = -y; + + data[3] = y & 0xFF; + data[4] = (y >> 8) & 0xFF; + + bpf_printk("modified event: %02x %02x %02x", + data[0], + data[1], + data[2]); + bpf_printk(" %02x %02x %02x", + data[3], + data[4], + data[5]); + bpf_printk(" %02x %02x %02x", + data[6], + data[7], + data[8]); + + return 0; +} + +SEC("hid/device_event") +int hid_x_event(struct hid_bpf_ctx *ctx) +{ + s16 x; + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 9 /* size */); + + if (!data) + return 0; /* EPERM check */ + + x = data[1] | (data[2] << 8); + + x = -x; + + data[1] = x & 0xFF; + data[2] = (x >> 8) & 0xFF; + return 0; +} + +SEC("hid/rdesc_fixup") +int hid_rdesc_fixup(struct hid_bpf_ctx *ctx) +{ + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 4096 /* size */); + + if (!data) + return 0; /* EPERM check */ + + if (ctx->type != HID_BPF_RDESC_FIXUP) + return 0; + + bpf_printk("rdesc: %02x %02x %02x", + data[0], + data[1], + data[2]); + bpf_printk(" %02x %02x %02x", + data[3], + data[4], + data[5]); + bpf_printk(" %02x %02x %02x ...", + data[6], + data[7], + data[8]); + + /* + * The original report descriptor contains: + * + * 0x05, 0x01, // Usage Page (Generic Desktop) 30 + * 0x16, 0x01, 0x80, // Logical Minimum (-32767) 32 + * 0x26, 0xff, 0x7f, // Logical Maximum (32767) 35 + * 0x09, 0x30, // Usage (X) 38 + * 0x09, 0x31, // Usage (Y) 40 + * + * So byte 39 contains Usage X and byte 41 Usage Y. + * + * We simply swap the axes here. + */ + data[39] = 0x31; + data[41] = 0x30; + + return 0; +} + +char _license[] SEC("license") = "GPL"; +u32 _version SEC("version") = LINUX_VERSION_CODE; diff --git a/samples/bpf/hid_mouse_user.c b/samples/bpf/hid_mouse_user.c new file mode 100644 index 000000000000..a8a9e7a99b14 --- /dev/null +++ b/samples/bpf/hid_mouse_user.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2022 Benjamin Tissoires + */ +#include <linux/bpf.h> +#include <linux/if_link.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <libgen.h> +#include <sys/resource.h> + +#include "bpf_util.h" +#include <bpf/bpf.h> +#include <bpf/libbpf.h> + +static char *sysfs_path; +static int sysfs_fd; +static int prog_count; + +struct prog { + int fd; + struct bpf_link *link; + enum bpf_attach_type type; +}; + +static struct prog progs[10]; + +static void int_exit(int sig) +{ + for (prog_count--; prog_count >= 0; prog_count--) + bpf_link__destroy(progs[prog_count].link); + + close(sysfs_fd); + exit(0); +} + +static void usage(const char *prog) +{ + fprintf(stderr, + "%s: %s /sys/bus/hid/devices/0BUS:0VID:0PID:00ID/uevent\n\n", + __func__, prog); +} + +int main(int argc, char **argv) +{ + struct bpf_prog_info info = {}; + __u32 info_len = sizeof(info); + const char *optstr = ""; + struct bpf_object *obj; + struct bpf_program *prog; + int opt; + char filename[256]; + int err; + + while ((opt = getopt(argc, argv, optstr)) != -1) { + switch (opt) { + default: + usage(basename(argv[0])); + return 1; + } + } + + if (optind == argc) { + usage(basename(argv[0])); + return 1; + } + + sysfs_path = argv[optind]; + if (!sysfs_path) { + perror("sysfs"); + return 1; + } + + snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]); + obj = bpf_object__open_file(filename, NULL); + err = libbpf_get_error(obj); + if (err) { + fprintf(stderr, "ERROR: opening BPF object file failed\n"); + obj = NULL; + err = 1; + goto cleanup; + } + + /* load BPF program */ + err = bpf_object__load(obj); + if (err) { + fprintf(stderr, "ERROR: loading BPF object file failed\n"); + goto cleanup; + } + + sysfs_fd = open(sysfs_path, O_RDONLY); + + bpf_object__for_each_program(prog, obj) { + progs[prog_count].fd = bpf_program__fd(prog); + progs[prog_count].type = bpf_program__get_expected_attach_type(prog); + progs[prog_count].link = bpf_program__attach_hid(prog, sysfs_fd, 0); + if (libbpf_get_error(progs[prog_count].link)) { + fprintf(stderr, "bpf_prog_attach: err=%m\n"); + progs[prog_count].fd = 0; + progs[prog_count].link = NULL; + goto cleanup; + } + prog_count++; + } + + signal(SIGINT, int_exit); + signal(SIGTERM, int_exit); + + err = bpf_obj_get_info_by_fd(progs[0].fd, &info, &info_len); + if (err) { + printf("can't get prog info - %s\n", strerror(errno)); + goto cleanup; + } + + while (1) + ; + + cleanup: + for (prog_count--; prog_count >= 0; prog_count--) + bpf_link__destroy(progs[prog_count].link); + + bpf_object__close(obj); + return err; +}
When we process an incoming HID report, it is common to have to account for fields that are not aligned in the report. HID is using 2 helpers hid_field_extract() and implement() to pick up any data at any offset within the report.
Export those 2 helpers in BPF programs so users can also rely on them. The second net worth advantage of those helpers is that now we can fetch data anywhere in the report without knowing at compile time the location of it. The boundary checks are done in hid-bpf.c, to prevent a memory leak.
The third exported helper allows to communicate with the HID device. We give a data buffer, and call either HID_GET_REPORT or HID_SET_REPORT on the device.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - renamed hid_{get|set}_data into hid_{get|set}_bits - squashed with bpf/hid: add bpf_hid_raw_request helper function
changes in v2: - split the patch with libbpf and HID left outside. --- include/linux/bpf-hid.h | 6 +++ include/uapi/linux/bpf.h | 36 +++++++++++++++++ kernel/bpf/hid.c | 73 ++++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 36 +++++++++++++++++ 4 files changed, 151 insertions(+)
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 7f596554fe8c..82b7466b5008 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -102,6 +102,12 @@ struct bpf_hid_hooks { int (*pre_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*post_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*array_detach)(struct hid_device *hdev, enum bpf_hid_attach_type type); + int (*hid_get_bits)(struct hid_device *hdev, u8 *buf, size_t buf_size, + u64 offset, u32 n, u32 *data); + int (*hid_set_bits)(struct hid_device *hdev, u8 *buf, size_t buf_size, + u64 offset, u32 n, u32 data); + int (*hid_raw_request)(struct hid_device *hdev, u8 *buf, size_t size, + u8 rtype, u8 reqtype); };
#ifdef CONFIG_BPF diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 0e8438e93768..41ab1d068369 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5155,6 +5155,39 @@ union bpf_attr { * by a call to bpf_hid_discard; * Return * The pointer to the data. On error, a null value is returned. + * + * int bpf_hid_get_bits(void *ctx, u64 offset, u32 n, u32 *data) + * Description + * Get the data of size n (in bits) at the given offset (bits) in the + * ctx->event.data field and store it into data. + * + * n must be less or equal than 32, and we can address with bit + * precision the value in the buffer. data must be a pointer + * to a u32. + * Return + * The length of data copied into data. On error, a negative value + * is returned. + * + * int bpf_hid_set_bits(void *ctx, u64 offset, u32 n, u32 data) + * Description + * Set the data of size n (in bits) at the given offset (bits) in the + * ctx->event.data field. + * + * n must be less or equal than 32, and we can address with bit + * precision the value in the buffer. + * Return + * The length of data copied into ctx->event.data. On error, a negative + * value is returned. + * + * int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 report_type, u8 request_type) + * Description + * communicate with the HID device + * + * report_type is one of HID_INPUT_REPORT, HID_OUTPUT_REPORT, HID_FEATURE_REPORT + * request_type is one of HID_REQ_SET_REPORT or HID_REQ_GET_REPORT + * Return + * 0 on success. + * negative value on error. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5352,6 +5385,9 @@ union bpf_attr { FN(skb_set_tstamp), \ FN(ima_file_hash), \ FN(hid_get_data), \ + FN(hid_get_bits), \ + FN(hid_set_bits), \ + FN(hid_raw_request), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 2dfeaaa8a83f..30a62e8e0f0a 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -66,12 +66,85 @@ static const struct bpf_func_proto bpf_hid_get_data_proto = { .arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO, };
+BPF_CALL_4(bpf_hid_get_bits, struct hid_bpf_ctx_kern*, ctx, u64, offset, u32, n, u32*, data) +{ + if (!hid_hooks.hid_get_bits) + return -EOPNOTSUPP; + + return hid_hooks.hid_get_bits(ctx->hdev, + ctx->data, + ctx->allocated_size, + offset, n, + data); +} + +static const struct bpf_func_proto bpf_hid_get_bits_proto = { + .func = bpf_hid_get_bits, + .gpl_only = true, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_ANYTHING, + .arg3_type = ARG_ANYTHING, + .arg4_type = ARG_PTR_TO_INT, +}; + +BPF_CALL_4(bpf_hid_set_bits, struct hid_bpf_ctx_kern*, ctx, u64, offset, u32, n, u32, data) +{ + if (!hid_hooks.hid_set_bits) + return -EOPNOTSUPP; + + hid_hooks.hid_set_bits(ctx->hdev, + ctx->data, + ctx->allocated_size, + offset, n, + data); + return 0; +} + +static const struct bpf_func_proto bpf_hid_set_bits_proto = { + .func = bpf_hid_set_bits, + .gpl_only = true, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_ANYTHING, + .arg3_type = ARG_ANYTHING, + .arg4_type = ARG_ANYTHING, +}; + +BPF_CALL_5(bpf_hid_raw_request, struct hid_bpf_ctx_kern*, ctx, void*, buf, u64, size, + u8, report_type, u8, request_type) +{ + if (!hid_hooks.hid_raw_request) + return -EOPNOTSUPP; + + return hid_hooks.hid_raw_request(ctx->hdev, buf, size, report_type, request_type); +} + +static const struct bpf_func_proto bpf_hid_raw_request_proto = { + .func = bpf_hid_raw_request, + .gpl_only = true, /* hid_raw_request is EXPORT_SYMBOL_GPL */ + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_CTX, + .arg2_type = ARG_PTR_TO_MEM, + .arg3_type = ARG_CONST_SIZE_OR_ZERO, + .arg4_type = ARG_ANYTHING, + .arg5_type = ARG_ANYTHING, +}; + static const struct bpf_func_proto * hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { case BPF_FUNC_hid_get_data: return &bpf_hid_get_data_proto; + case BPF_FUNC_hid_get_bits: + return &bpf_hid_get_bits_proto; + case BPF_FUNC_hid_set_bits: + return &bpf_hid_set_bits_proto; + case BPF_FUNC_hid_raw_request: + if (prog->expected_attach_type != BPF_HID_DEVICE_EVENT) + return &bpf_hid_raw_request_proto; + return NULL; default: return bpf_base_func_proto(func_id); } diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 0e8438e93768..41ab1d068369 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5155,6 +5155,39 @@ union bpf_attr { * by a call to bpf_hid_discard; * Return * The pointer to the data. On error, a null value is returned. + * + * int bpf_hid_get_bits(void *ctx, u64 offset, u32 n, u32 *data) + * Description + * Get the data of size n (in bits) at the given offset (bits) in the + * ctx->event.data field and store it into data. + * + * n must be less or equal than 32, and we can address with bit + * precision the value in the buffer. data must be a pointer + * to a u32. + * Return + * The length of data copied into data. On error, a negative value + * is returned. + * + * int bpf_hid_set_bits(void *ctx, u64 offset, u32 n, u32 data) + * Description + * Set the data of size n (in bits) at the given offset (bits) in the + * ctx->event.data field. + * + * n must be less or equal than 32, and we can address with bit + * precision the value in the buffer. + * Return + * The length of data copied into ctx->event.data. On error, a negative + * value is returned. + * + * int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 report_type, u8 request_type) + * Description + * communicate with the HID device + * + * report_type is one of HID_INPUT_REPORT, HID_OUTPUT_REPORT, HID_FEATURE_REPORT + * request_type is one of HID_REQ_SET_REPORT or HID_REQ_GET_REPORT + * Return + * 0 on success. + * negative value on error. */ #define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5352,6 +5385,9 @@ union bpf_attr { FN(skb_set_tstamp), \ FN(ima_file_hash), \ FN(hid_get_data), \ + FN(hid_get_bits), \ + FN(hid_set_bits), \ + FN(hid_raw_request), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
On Fri, Mar 18, 2022 at 9:18 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
When we process an incoming HID report, it is common to have to account for fields that are not aligned in the report. HID is using 2 helpers hid_field_extract() and implement() to pick up any data at any offset within the report.
Export those 2 helpers in BPF programs so users can also rely on them. The second net worth advantage of those helpers is that now we can fetch data anywhere in the report without knowing at compile time the location of it. The boundary checks are done in hid-bpf.c, to prevent a memory leak.
The third exported helper allows to communicate with the HID device. We give a data buffer, and call either HID_GET_REPORT or HID_SET_REPORT on the device.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- renamed hid_{get|set}_data into hid_{get|set}_bits
- squashed with bpf/hid: add bpf_hid_raw_request helper function
changes in v2:
- split the patch with libbpf and HID left outside.
include/linux/bpf-hid.h | 6 +++ include/uapi/linux/bpf.h | 36 +++++++++++++++++ kernel/bpf/hid.c | 73 ++++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 36 +++++++++++++++++ 4 files changed, 151 insertions(+)
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 7f596554fe8c..82b7466b5008 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -102,6 +102,12 @@ struct bpf_hid_hooks { int (*pre_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*post_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*array_detach)(struct hid_device *hdev, enum bpf_hid_attach_type type);
int (*hid_get_bits)(struct hid_device *hdev, u8 *buf, size_t buf_size,
u64 offset, u32 n, u32 *data);
int (*hid_set_bits)(struct hid_device *hdev, u8 *buf, size_t buf_size,
u64 offset, u32 n, u32 data);
int (*hid_raw_request)(struct hid_device *hdev, u8 *buf, size_t size,
u8 rtype, u8 reqtype);
};
#ifdef CONFIG_BPF diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 0e8438e93768..41ab1d068369 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5155,6 +5155,39 @@ union bpf_attr {
by a call to bpf_hid_discard;
Return
The pointer to the data. On error, a null value is returned.
- int bpf_hid_get_bits(void *ctx, u64 offset, u32 n, u32 *data)
Description
Get the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field and store it into data.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer. data must be a pointer
to a u32.
Return
The length of data copied into data. On error, a negative value
is returned.
- int bpf_hid_set_bits(void *ctx, u64 offset, u32 n, u32 data)
Description
Set the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer.
Return
The length of data copied into ctx->event.data. On error, a negative
value is returned.
Please use annotations like *offset*.
- int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 report_type, u8 request_type)
Description
communicate with the HID device
report_type is one of HID_INPUT_REPORT, HID_OUTPUT_REPORT, HID_FEATURE_REPORT
request_type is one of HID_REQ_SET_REPORT or HID_REQ_GET_REPORT
Return
0 on success.
*/
negative value on error.
#define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5352,6 +5385,9 @@ union bpf_attr { FN(skb_set_tstamp), \ FN(ima_file_hash), \ FN(hid_get_data), \
FN(hid_get_bits), \
FN(hid_set_bits), \
FN(hid_raw_request), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 2dfeaaa8a83f..30a62e8e0f0a 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -66,12 +66,85 @@ static const struct bpf_func_proto bpf_hid_get_data_proto = { .arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO, };
+BPF_CALL_4(bpf_hid_get_bits, struct hid_bpf_ctx_kern*, ctx, u64, offset, u32, n, u32*, data) +{
if (!hid_hooks.hid_get_bits)
return -EOPNOTSUPP;
Shall we also check offset and n are valid here?
return hid_hooks.hid_get_bits(ctx->hdev,
ctx->data,
ctx->allocated_size,
offset, n,
data);
+}
+static const struct bpf_func_proto bpf_hid_get_bits_proto = {
.func = bpf_hid_get_bits,
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_INT,
+};
+BPF_CALL_4(bpf_hid_set_bits, struct hid_bpf_ctx_kern*, ctx, u64, offset, u32, n, u32, data) +{
if (!hid_hooks.hid_set_bits)
return -EOPNOTSUPP;
hid_hooks.hid_set_bits(ctx->hdev,
ctx->data,
ctx->allocated_size,
offset, n,
data);
return 0;
+}
+static const struct bpf_func_proto bpf_hid_set_bits_proto = {
.func = bpf_hid_set_bits,
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_ANYTHING,
+};
+BPF_CALL_5(bpf_hid_raw_request, struct hid_bpf_ctx_kern*, ctx, void*, buf, u64, size,
u8, report_type, u8, request_type)
+{
if (!hid_hooks.hid_raw_request)
return -EOPNOTSUPP;
return hid_hooks.hid_raw_request(ctx->hdev, buf, size, report_type, request_type);
+}
+static const struct bpf_func_proto bpf_hid_raw_request_proto = {
.func = bpf_hid_raw_request,
.gpl_only = true, /* hid_raw_request is EXPORT_SYMBOL_GPL */
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_MEM,
.arg3_type = ARG_CONST_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
.arg5_type = ARG_ANYTHING,
+};
static const struct bpf_func_proto * hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { case BPF_FUNC_hid_get_data: return &bpf_hid_get_data_proto;
case BPF_FUNC_hid_get_bits:
return &bpf_hid_get_bits_proto;
case BPF_FUNC_hid_set_bits:
return &bpf_hid_set_bits_proto;
case BPF_FUNC_hid_raw_request:
if (prog->expected_attach_type != BPF_HID_DEVICE_EVENT)
return &bpf_hid_raw_request_proto;
return NULL; default: return bpf_base_func_proto(func_id); }
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 0e8438e93768..41ab1d068369 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5155,6 +5155,39 @@ union bpf_attr {
by a call to bpf_hid_discard;
Return
The pointer to the data. On error, a null value is returned.
- int bpf_hid_get_bits(void *ctx, u64 offset, u32 n, u32 *data)
Description
Get the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field and store it into data.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer. data must be a pointer
to a u32.
Return
The length of data copied into data. On error, a negative value
is returned.
- int bpf_hid_set_bits(void *ctx, u64 offset, u32 n, u32 data)
Description
Set the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer.
Return
The length of data copied into ctx->event.data. On error, a negative
value is returned.
- int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 report_type, u8 request_type)
Description
communicate with the HID device
report_type is one of HID_INPUT_REPORT, HID_OUTPUT_REPORT, HID_FEATURE_REPORT
request_type is one of HID_REQ_SET_REPORT or HID_REQ_GET_REPORT
Return
0 on success.
*/
negative value on error.
#define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5352,6 +5385,9 @@ union bpf_attr { FN(skb_set_tstamp), \ FN(ima_file_hash), \ FN(hid_get_data), \
FN(hid_get_bits), \
FN(hid_set_bits), \
FN(hid_raw_request), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
2.35.1
On Fri, Mar 18, 2022 at 10:19 PM Song Liu song@kernel.org wrote:
On Fri, Mar 18, 2022 at 9:18 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
When we process an incoming HID report, it is common to have to account for fields that are not aligned in the report. HID is using 2 helpers hid_field_extract() and implement() to pick up any data at any offset within the report.
Export those 2 helpers in BPF programs so users can also rely on them. The second net worth advantage of those helpers is that now we can fetch data anywhere in the report without knowing at compile time the location of it. The boundary checks are done in hid-bpf.c, to prevent a memory leak.
The third exported helper allows to communicate with the HID device. We give a data buffer, and call either HID_GET_REPORT or HID_SET_REPORT on the device.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- renamed hid_{get|set}_data into hid_{get|set}_bits
- squashed with bpf/hid: add bpf_hid_raw_request helper function
changes in v2:
- split the patch with libbpf and HID left outside.
include/linux/bpf-hid.h | 6 +++ include/uapi/linux/bpf.h | 36 +++++++++++++++++ kernel/bpf/hid.c | 73 ++++++++++++++++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 36 +++++++++++++++++ 4 files changed, 151 insertions(+)
diff --git a/include/linux/bpf-hid.h b/include/linux/bpf-hid.h index 7f596554fe8c..82b7466b5008 100644 --- a/include/linux/bpf-hid.h +++ b/include/linux/bpf-hid.h @@ -102,6 +102,12 @@ struct bpf_hid_hooks { int (*pre_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*post_link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type); void (*array_detach)(struct hid_device *hdev, enum bpf_hid_attach_type type);
int (*hid_get_bits)(struct hid_device *hdev, u8 *buf, size_t buf_size,
u64 offset, u32 n, u32 *data);
int (*hid_set_bits)(struct hid_device *hdev, u8 *buf, size_t buf_size,
u64 offset, u32 n, u32 data);
int (*hid_raw_request)(struct hid_device *hdev, u8 *buf, size_t size,
u8 rtype, u8 reqtype);
};
#ifdef CONFIG_BPF diff --git a/include/uapi/linux/bpf.h b/include/uapi/linux/bpf.h index 0e8438e93768..41ab1d068369 100644 --- a/include/uapi/linux/bpf.h +++ b/include/uapi/linux/bpf.h @@ -5155,6 +5155,39 @@ union bpf_attr {
by a call to bpf_hid_discard;
Return
The pointer to the data. On error, a null value is returned.
- int bpf_hid_get_bits(void *ctx, u64 offset, u32 n, u32 *data)
Description
Get the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field and store it into data.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer. data must be a pointer
to a u32.
Return
The length of data copied into data. On error, a negative value
is returned.
- int bpf_hid_set_bits(void *ctx, u64 offset, u32 n, u32 data)
Description
Set the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer.
Return
The length of data copied into ctx->event.data. On error, a negative
value is returned.
Please use annotations like *offset*.
Ack
- int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 report_type, u8 request_type)
Description
communicate with the HID device
report_type is one of HID_INPUT_REPORT, HID_OUTPUT_REPORT, HID_FEATURE_REPORT
request_type is one of HID_REQ_SET_REPORT or HID_REQ_GET_REPORT
Return
0 on success.
*/
negative value on error.
#define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5352,6 +5385,9 @@ union bpf_attr { FN(skb_set_tstamp), \ FN(ima_file_hash), \ FN(hid_get_data), \
FN(hid_get_bits), \
FN(hid_set_bits), \
FN(hid_raw_request), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 2dfeaaa8a83f..30a62e8e0f0a 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -66,12 +66,85 @@ static const struct bpf_func_proto bpf_hid_get_data_proto = { .arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO, };
+BPF_CALL_4(bpf_hid_get_bits, struct hid_bpf_ctx_kern*, ctx, u64, offset, u32, n, u32*, data) +{
if (!hid_hooks.hid_get_bits)
return -EOPNOTSUPP;
Shall we also check offset and n are valid here?
I've decided to put these tests in the HID code. The argument being that HID knows about the context and what is put where, when the BPF part is just the stub.
[few seconds later]
... and probably not a good answer because the line below has ctx->data and allocated_size.
I can easily change that in v4 (same for bpf_hid_set_bits).
return hid_hooks.hid_get_bits(ctx->hdev,
ctx->data,
ctx->allocated_size,
offset, n,
data);
+}
+static const struct bpf_func_proto bpf_hid_get_bits_proto = {
.func = bpf_hid_get_bits,
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_PTR_TO_INT,
+};
+BPF_CALL_4(bpf_hid_set_bits, struct hid_bpf_ctx_kern*, ctx, u64, offset, u32, n, u32, data) +{
if (!hid_hooks.hid_set_bits)
return -EOPNOTSUPP;
hid_hooks.hid_set_bits(ctx->hdev,
ctx->data,
ctx->allocated_size,
offset, n,
data);
return 0;
+}
+static const struct bpf_func_proto bpf_hid_set_bits_proto = {
.func = bpf_hid_set_bits,
.gpl_only = true,
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_ANYTHING,
.arg3_type = ARG_ANYTHING,
.arg4_type = ARG_ANYTHING,
+};
+BPF_CALL_5(bpf_hid_raw_request, struct hid_bpf_ctx_kern*, ctx, void*, buf, u64, size,
u8, report_type, u8, request_type)
+{
if (!hid_hooks.hid_raw_request)
return -EOPNOTSUPP;
return hid_hooks.hid_raw_request(ctx->hdev, buf, size, report_type, request_type);
+}
+static const struct bpf_func_proto bpf_hid_raw_request_proto = {
.func = bpf_hid_raw_request,
.gpl_only = true, /* hid_raw_request is EXPORT_SYMBOL_GPL */
.ret_type = RET_INTEGER,
.arg1_type = ARG_PTR_TO_CTX,
.arg2_type = ARG_PTR_TO_MEM,
.arg3_type = ARG_CONST_SIZE_OR_ZERO,
.arg4_type = ARG_ANYTHING,
.arg5_type = ARG_ANYTHING,
+};
static const struct bpf_func_proto * hid_func_proto(enum bpf_func_id func_id, const struct bpf_prog *prog) { switch (func_id) { case BPF_FUNC_hid_get_data: return &bpf_hid_get_data_proto;
case BPF_FUNC_hid_get_bits:
return &bpf_hid_get_bits_proto;
case BPF_FUNC_hid_set_bits:
return &bpf_hid_set_bits_proto;
case BPF_FUNC_hid_raw_request:
if (prog->expected_attach_type != BPF_HID_DEVICE_EVENT)
return &bpf_hid_raw_request_proto;
return NULL; default: return bpf_base_func_proto(func_id); }
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h index 0e8438e93768..41ab1d068369 100644 --- a/tools/include/uapi/linux/bpf.h +++ b/tools/include/uapi/linux/bpf.h @@ -5155,6 +5155,39 @@ union bpf_attr {
by a call to bpf_hid_discard;
Return
The pointer to the data. On error, a null value is returned.
- int bpf_hid_get_bits(void *ctx, u64 offset, u32 n, u32 *data)
Description
Get the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field and store it into data.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer. data must be a pointer
to a u32.
Return
The length of data copied into data. On error, a negative value
is returned.
- int bpf_hid_set_bits(void *ctx, u64 offset, u32 n, u32 data)
Description
Set the data of size n (in bits) at the given offset (bits) in the
ctx->event.data field.
n must be less or equal than 32, and we can address with bit
precision the value in the buffer.
Return
The length of data copied into ctx->event.data. On error, a negative
value is returned.
- int bpf_hid_raw_request(void *ctx, void *buf, u64 size, u8 report_type, u8 request_type)
Description
communicate with the HID device
report_type is one of HID_INPUT_REPORT, HID_OUTPUT_REPORT, HID_FEATURE_REPORT
request_type is one of HID_REQ_SET_REPORT or HID_REQ_GET_REPORT
Return
0 on success.
*/
negative value on error.
#define __BPF_FUNC_MAPPER(FN) \ FN(unspec), \ @@ -5352,6 +5385,9 @@ union bpf_attr { FN(skb_set_tstamp), \ FN(ima_file_hash), \ FN(hid_get_data), \
FN(hid_get_bits), \
FN(hid_set_bits), \
FN(hid_raw_request), \ /* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
2.35.1
Export implement() outside of hid-core.c and use this and hid_field_extract() to implement the helprs for hid-bpf.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - renamed hid_{get|set}_data into hid_{get|set}_bits
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples - allow for n > 32, by relying on memcpy --- drivers/hid/hid-bpf.c | 29 +++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 4 ++-- include/linux/hid.h | 2 ++ 3 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 45c87ff47324..650dd5e54919 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -122,6 +122,33 @@ static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_ty } }
+static int hid_bpf_get_bits(struct hid_device *hdev, u8 *buf, size_t buf_size, u64 offset, u32 n, + u32 *data) +{ + if (n > 32) + return -EINVAL; + + if (((offset + n) >> 3) >= buf_size) + return -E2BIG; + + *data = hid_field_extract(hdev, buf, offset, n); + return n; +} + +static int hid_bpf_set_bits(struct hid_device *hdev, u8 *buf, size_t buf_size, u64 offset, u32 n, + u32 data) +{ + if (n > 32) + return -EINVAL; + + if (((offset + n) >> 3) >= buf_size) + return -E2BIG; + + /* data must be a pointer to a u32 */ + implement(hdev, buf, offset, n, data); + return n; +} + static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *ctx) { enum bpf_hid_attach_type type; @@ -223,6 +250,8 @@ int __init hid_bpf_module_init(void) .pre_link_attach = hid_bpf_pre_link_attach, .post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach, + .hid_get_bits = hid_bpf_get_bits, + .hid_set_bits = hid_bpf_set_bits, };
bpf_hid_set_hooks(&hooks); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3182c39db006..4f669dcddc08 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1416,8 +1416,8 @@ static void __implement(u8 *report, unsigned offset, int n, u32 value) } }
-static void implement(const struct hid_device *hid, u8 *report, - unsigned offset, unsigned n, u32 value) +void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n, + u32 value) { if (unlikely(n > 32)) { hid_warn(hid, "%s() called with n (%d) > 32! (%s)\n", diff --git a/include/linux/hid.h b/include/linux/hid.h index 66d949d10b78..7454e844324c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -944,6 +944,8 @@ bool hid_compare_device_paths(struct hid_device *hdev_a, s32 hid_snto32(__u32 value, unsigned n); __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, unsigned offset, unsigned n); +void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n, + u32 value);
#ifdef CONFIG_PM int hid_driver_suspend(struct hid_device *hdev, pm_message_t state);
On Fri, Mar 18, 2022 at 9:18 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Export implement() outside of hid-core.c and use this and
Maybe rename implement() to something that makes sense?
hid_field_extract() to implement the helprs for hid-bpf.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
changes in v3:
- renamed hid_{get|set}_data into hid_{get|set}_bits
changes in v2:
- split the series by bpf/libbpf/hid/selftests and samples
- allow for n > 32, by relying on memcpy
drivers/hid/hid-bpf.c | 29 +++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 4 ++-- include/linux/hid.h | 2 ++ 3 files changed, 33 insertions(+), 2 deletions(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 45c87ff47324..650dd5e54919 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -122,6 +122,33 @@ static void hid_bpf_array_detach(struct hid_device *hdev, enum bpf_hid_attach_ty } }
+static int hid_bpf_get_bits(struct hid_device *hdev, u8 *buf, size_t buf_size, u64 offset, u32 n,
u32 *data)
+{
if (n > 32)
return -EINVAL;
if (((offset + n) >> 3) >= buf_size)
return -E2BIG;
*data = hid_field_extract(hdev, buf, offset, n);
return n;
+}
+static int hid_bpf_set_bits(struct hid_device *hdev, u8 *buf, size_t buf_size, u64 offset, u32 n,
u32 data)
+{
if (n > 32)
return -EINVAL;
if (((offset + n) >> 3) >= buf_size)
return -E2BIG;
/* data must be a pointer to a u32 */
implement(hdev, buf, offset, n, data);
return n;
+}
static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *ctx) { enum bpf_hid_attach_type type; @@ -223,6 +250,8 @@ int __init hid_bpf_module_init(void) .pre_link_attach = hid_bpf_pre_link_attach, .post_link_attach = hid_bpf_post_link_attach, .array_detach = hid_bpf_array_detach,
.hid_get_bits = hid_bpf_get_bits,
.hid_set_bits = hid_bpf_set_bits, }; bpf_hid_set_hooks(&hooks);
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 3182c39db006..4f669dcddc08 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1416,8 +1416,8 @@ static void __implement(u8 *report, unsigned offset, int n, u32 value) } }
-static void implement(const struct hid_device *hid, u8 *report,
unsigned offset, unsigned n, u32 value)
+void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n,
u32 value)
{ if (unlikely(n > 32)) { hid_warn(hid, "%s() called with n (%d) > 32! (%s)\n", diff --git a/include/linux/hid.h b/include/linux/hid.h index 66d949d10b78..7454e844324c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -944,6 +944,8 @@ bool hid_compare_device_paths(struct hid_device *hdev_a, s32 hid_snto32(__u32 value, unsigned n); __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, unsigned offset, unsigned n); +void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n,
u32 value);
#ifdef CONFIG_PM int hid_driver_suspend(struct hid_device *hdev, pm_message_t state); -- 2.35.1
Hook up BPF to hid_hw_raw_request. Not much to report here except that we need to export hid_get_report from hid-core so it gets available in hid-bpf.c
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - export HID_*_REPORT definitions in uapi as they are not following the specs
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- drivers/hid/hid-bpf.c | 63 ++++++++++++++++++++++++++++++++++++++++ drivers/hid/hid-core.c | 3 +- include/linux/hid.h | 12 ++------ include/uapi/linux/hid.h | 10 +++++++ 4 files changed, 76 insertions(+), 12 deletions(-)
diff --git a/drivers/hid/hid-bpf.c b/drivers/hid/hid-bpf.c index 650dd5e54919..66724aa2ff42 100644 --- a/drivers/hid/hid-bpf.c +++ b/drivers/hid/hid-bpf.c @@ -149,6 +149,68 @@ static int hid_bpf_set_bits(struct hid_device *hdev, u8 *buf, size_t buf_size, u return n; }
+static int hid_bpf_raw_request(struct hid_device *hdev, u8 *buf, size_t size, + u8 rtype, u8 reqtype) +{ + struct hid_report *report; + struct hid_report_enum *report_enum; + u8 *dma_data; + u32 report_len; + int ret; + + /* check arguments */ + switch (rtype) { + case HID_INPUT_REPORT: + case HID_OUTPUT_REPORT: + case HID_FEATURE_REPORT: + break; + default: + return -EINVAL; + } + + switch (reqtype) { + case HID_REQ_GET_REPORT: + case HID_REQ_GET_IDLE: + case HID_REQ_GET_PROTOCOL: + case HID_REQ_SET_REPORT: + case HID_REQ_SET_IDLE: + case HID_REQ_SET_PROTOCOL: + break; + default: + return -EINVAL; + } + + if (size < 1) + return -EINVAL; + + report_enum = hdev->report_enum + rtype; + report = hid_get_report(report_enum, buf); + if (!report) + return -EINVAL; + + report_len = hid_report_len(report); + + if (size > report_len) + size = report_len; + + dma_data = kmemdup(buf, size, GFP_KERNEL); + if (!dma_data) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, + dma_data[0], + dma_data, + size, + rtype, + reqtype); + + if (ret > 0) + memcpy(buf, dma_data, ret); + + kfree(dma_data); + return ret; +} + static int hid_bpf_run_progs(struct hid_device *hdev, struct hid_bpf_ctx_kern *ctx) { enum bpf_hid_attach_type type; @@ -252,6 +314,7 @@ int __init hid_bpf_module_init(void) .array_detach = hid_bpf_array_detach, .hid_get_bits = hid_bpf_get_bits, .hid_set_bits = hid_bpf_set_bits, + .hid_raw_request = hid_bpf_raw_request, };
bpf_hid_set_hooks(&hooks); diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 4f669dcddc08..f4b5d22f0831 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1686,8 +1686,7 @@ int hid_set_field(struct hid_field *field, unsigned offset, __s32 value) } EXPORT_SYMBOL_GPL(hid_set_field);
-static struct hid_report *hid_get_report(struct hid_report_enum *report_enum, - const u8 *data) +struct hid_report *hid_get_report(struct hid_report_enum *report_enum, const u8 *data) { struct hid_report *report; unsigned int n = 0; /* Normally report number is 0 */ diff --git a/include/linux/hid.h b/include/linux/hid.h index 7454e844324c..2f1b40d48265 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -316,15 +316,6 @@ struct hid_item { #define HID_BAT_ABSOLUTESTATEOFCHARGE 0x00850065
#define HID_VD_ASUS_CUSTOM_MEDIA_KEYS 0xff310076 -/* - * HID report types --- Ouch! HID spec says 1 2 3! - */ - -#define HID_INPUT_REPORT 0 -#define HID_OUTPUT_REPORT 1 -#define HID_FEATURE_REPORT 2 - -#define HID_REPORT_TYPES 3
/* * HID connect requests @@ -344,7 +335,7 @@ struct hid_item { * HID device quirks. */
-/* +/* * Increase this if you need to configure more HID quirks at module load time */ #define MAX_USBHID_BOOT_QUIRKS 4 @@ -946,6 +937,7 @@ __u32 hid_field_extract(const struct hid_device *hid, __u8 *report, unsigned offset, unsigned n); void implement(const struct hid_device *hid, u8 *report, unsigned int offset, unsigned int n, u32 value); +struct hid_report *hid_get_report(struct hid_report_enum *report_enum, const u8 *data);
#ifdef CONFIG_PM int hid_driver_suspend(struct hid_device *hdev, pm_message_t state); diff --git a/include/uapi/linux/hid.h b/include/uapi/linux/hid.h index b34492a87a8a..bb690343cf9a 100644 --- a/include/uapi/linux/hid.h +++ b/include/uapi/linux/hid.h @@ -42,6 +42,16 @@ #define USB_INTERFACE_PROTOCOL_KEYBOARD 1 #define USB_INTERFACE_PROTOCOL_MOUSE 2
+/* + * HID report types --- Ouch! HID spec says 1 2 3! + */ + +#define HID_INPUT_REPORT 0 +#define HID_OUTPUT_REPORT 1 +#define HID_FEATURE_REPORT 2 + +#define HID_REPORT_TYPES 3 + /* * HID class requests */
Simple test added here, with one use of each helper.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - renamed hid_{get|set}_data into hid_{get|set}_bits
changes in v2: - split the patch with libbpf left outside. --- tools/testing/selftests/bpf/prog_tests/hid.c | 59 ++++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 19 +++++++ 2 files changed, 78 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index edc3af71e9ed..e8aa1c6357e8 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -531,6 +531,62 @@ static int test_hid_user_call(struct hid *hid_skel, int uhid_fd, int sysfs_fd) return ret; }
+/* + * Attach hid_set_get_bits to the given uhid device, + * retrieve and open the matching hidraw node, + * inject one event in the uhid device, + * check that the program makes correct use of bpf_hid_{set|get}_bits. + */ +static int test_hid_set_get_bits(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, hidraw_ino, hidraw_fd = -1; + char hidraw_path[64] = {0}; + u8 buf[10] = {0}; + int ret = -1; + + /* attach hid_set_get_bits program */ + hid_skel->links.hid_set_get_bits = + bpf_program__attach_hid(hid_skel->progs.hid_set_get_bits, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_set_get_bits, + "attach_hid(hid_set_get_bits)")) + return PTR_ERR(hid_skel->links.hid_set_get_bits); + + hidraw_ino = get_hidraw(hid_skel->links.hid_set_get_bits); + if (!ASSERT_GE(hidraw_ino, 0, "get_hidraw")) + goto cleanup; + + /* open hidraw node to check the other side of the pipe */ + sprintf(hidraw_path, "/dev/hidraw%d", hidraw_ino); + hidraw_fd = open(hidraw_path, O_RDWR | O_NONBLOCK); + + if (!ASSERT_GE(hidraw_fd, 0, "open_hidraw")) + goto cleanup; + + /* inject one event */ + buf[0] = 1; + buf[1] = 42; + send_event(uhid_fd, buf, 6); + + /* read the data from hidraw */ + memset(buf, 0, sizeof(buf)); + err = read(hidraw_fd, buf, sizeof(buf)); + if (!ASSERT_EQ(err, 6, "read_hidraw")) + goto cleanup; + + if (!ASSERT_EQ(buf[2], (42 >> 2), "hid_set_get_bits")) + goto cleanup; + + ret = 0; + +cleanup: + if (hidraw_fd >= 0) + close(hidraw_fd); + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -641,6 +697,9 @@ void serial_test_hid_bpf(void) err = test_hid_user_call(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_user");
+ err = test_hid_set_get_bits(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_set_get_data"); + /* * this test should be run last because we disconnect/reconnect * the device, meaning that it changes the overall uhid device diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index fbdbe9d1b605..d57571b9af9a 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -143,3 +143,22 @@ int hid_user(struct hid_bpf_ctx *ctx)
return 0; } + +SEC("hid/device_event") +int hid_set_get_bits(struct hid_bpf_ctx *ctx) +{ + int ret; + __u32 data = 0; + + /* extract data at bit offset 10 of size 4 (half a byte) */ + ret = bpf_hid_get_bits(ctx, 10, 4, &data); + if (ret < 0) + return ret; + + /* reinject it */ + ret = bpf_hid_set_bits(ctx, 16, 4, data); + if (ret < 0) + return ret; + + return 0; +}
Add tests for the newly implemented function. We test here only the GET_REPORT part because the other calls are pure HID protocol and won't infer the result of the test of the bpf hook.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
changes in v3: - use the new hid_get_data API - directly use HID_FEATURE_REPORT and HID_REQ_GET_REPORT from uapi
changes in v2: - split the series by bpf/libbpf/hid/selftests and samples --- tools/testing/selftests/bpf/prog_tests/hid.c | 72 +++++++++++++++++++- tools/testing/selftests/bpf/progs/hid.c | 41 +++++++++++ 2 files changed, 112 insertions(+), 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/prog_tests/hid.c b/tools/testing/selftests/bpf/prog_tests/hid.c index e8aa1c6357e8..27b6ea7da599 100644 --- a/tools/testing/selftests/bpf/prog_tests/hid.c +++ b/tools/testing/selftests/bpf/prog_tests/hid.c @@ -11,6 +11,7 @@ #include <stdbool.h> #include <linux/hidraw.h> #include <linux/uhid.h> +#include <linux/hid.h>
static unsigned char rdesc[] = { 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ @@ -67,6 +68,8 @@ static unsigned char rdesc[] = { 0xc0, /* END_COLLECTION */ };
+static u8 feature_data[] = { 1, 2 }; + static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER;
@@ -126,7 +129,7 @@ static void destroy(int fd)
static int event(int fd) { - struct uhid_event ev; + struct uhid_event ev, answer; ssize_t ret;
memset(&ev, 0, sizeof(ev)); @@ -143,6 +146,8 @@ static int event(int fd) return -EFAULT; }
+ memset(&answer, 0, sizeof(answer)); + switch (ev.type) { case UHID_START: pthread_mutex_lock(&uhid_started_mtx); @@ -167,6 +172,15 @@ static int event(int fd) break; case UHID_GET_REPORT: fprintf(stderr, "UHID_GET_REPORT from uhid-dev\n"); + + answer.type = UHID_GET_REPORT_REPLY; + answer.u.get_report_reply.id = ev.u.get_report.id; + answer.u.get_report_reply.err = ev.u.get_report.rnum == 1 ? 0 : -EIO; + answer.u.get_report_reply.size = sizeof(feature_data); + memcpy(answer.u.get_report_reply.data, feature_data, sizeof(feature_data)); + + uhid_write(fd, &answer); + break; case UHID_SET_REPORT: fprintf(stderr, "UHID_SET_REPORT from uhid-dev\n"); @@ -587,6 +601,59 @@ static int test_hid_set_get_bits(struct hid *hid_skel, int uhid_fd, int sysfs_fd return ret; }
+/* + * Attach hid_user_raw_request to the given uhid device, + * call the bpf program from userspace + * check that the program is called and does the expected. + */ +static int test_hid_user_raw_request_call(struct hid *hid_skel, int uhid_fd, int sysfs_fd) +{ + int err, prog_fd; + u8 buf[10] = {0}; + int ret = -1; + + LIBBPF_OPTS(bpf_test_run_opts, run_attrs, + .repeat = 1, + .ctx_in = &sysfs_fd, + .ctx_size_in = sizeof(sysfs_fd), + .data_in = buf, + .data_size_in = sizeof(buf), + .data_out = buf, + .data_size_out = sizeof(buf), + ); + + /* attach hid_user_raw_request program */ + hid_skel->links.hid_user_raw_request = + bpf_program__attach_hid(hid_skel->progs.hid_user_raw_request, sysfs_fd, 0); + if (!ASSERT_OK_PTR(hid_skel->links.hid_user_raw_request, + "attach_hid(hid_user_raw_request)")) + return PTR_ERR(hid_skel->links.hid_user_raw_request); + + buf[0] = HID_FEATURE_REPORT; + buf[1] = HID_REQ_GET_REPORT; + buf[2] = 1; /* report ID */ + + prog_fd = bpf_program__fd(hid_skel->progs.hid_user_raw_request); + + err = bpf_prog_test_run_opts(prog_fd, &run_attrs); + if (!ASSERT_EQ(err, 0, "bpf_prog_test_run_xattr")) + goto cleanup; + + if (!ASSERT_EQ(run_attrs.retval, 2, "bpf_prog_test_run_xattr_retval")) + goto cleanup; + + if (!ASSERT_EQ(buf[3], 2, "hid_user_raw_request_check_in")) + goto cleanup; + + ret = 0; + +cleanup: + + hid__detach(hid_skel); + + return ret; +} + /* * Attach hid_rdesc_fixup to the given uhid device, * retrieve and open the matching hidraw node, @@ -700,6 +767,9 @@ void serial_test_hid_bpf(void) err = test_hid_set_get_bits(hid_skel, uhid_fd, sysfs_fd); ASSERT_OK(err, "hid_set_get_data");
+ err = test_hid_user_raw_request_call(hid_skel, uhid_fd, sysfs_fd); + ASSERT_OK(err, "hid_user_raw_request"); + /* * this test should be run last because we disconnect/reconnect * the device, meaning that it changes the overall uhid device diff --git a/tools/testing/selftests/bpf/progs/hid.c b/tools/testing/selftests/bpf/progs/hid.c index d57571b9af9a..d7b75c0cba4b 100644 --- a/tools/testing/selftests/bpf/progs/hid.c +++ b/tools/testing/selftests/bpf/progs/hid.c @@ -162,3 +162,44 @@ int hid_set_get_bits(struct hid_bpf_ctx *ctx)
return 0; } + +SEC("hid/user_event") +int hid_user_raw_request(struct hid_bpf_ctx *ctx) +{ + int ret; + __u32 size; + __u8 rtype, reqtype; + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 10 /* size */); + + if (!data) + return 0; /* EPERM check */ + + /* + * build up a custom API for our needs: + * offset 0, size 1: report type + * offset 1, size 1: request type + * offset 2+: data + */ + rtype = data[0]; + reqtype = data[1]; + size = ctx->size - 2; + + if (size <= 8) { /* 8 = len(data) - 2 */ + ret = bpf_hid_raw_request(ctx, + &data[2], + size, + rtype, + reqtype); + if (ret < 0) + return ret; + } else { + return -7; /* -E2BIG */ + } + + ctx->size = ret + 2; + ctx->retval = ret; + + ret = 0; + + return ret; +}
Gives a primer on HID-BPF.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
---
new in v3 --- Documentation/hid/hid-bpf.rst | 444 ++++++++++++++++++++++++++++++++++ Documentation/hid/index.rst | 1 + include/uapi/linux/bpf_hid.h | 54 ++++- 3 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 Documentation/hid/hid-bpf.rst
diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst new file mode 100644 index 000000000000..0bf0d937b0e1 --- /dev/null +++ b/Documentation/hid/hid-bpf.rst @@ -0,0 +1,444 @@ +.. SPDX-License-Identifier: GPL-2.0 + +======= +HID-BPF +======= + +HID is a standard protocol for input devices and it can greatly make use +of the eBPF capabilities to speed up development and add new capabilities +to the existing HID interfaces. + +.. contents:: + :local: + :depth: 2 + + +When (and why) using HID-BPF +============================ + +We can enumerate several use cases for when using HID-BPF is better than +using a standard kernel driver fix. + +dead zone of a joystick +----------------------- + +Assuming you have a joystick that is getting older, it is common to see it +wobbling around its neutral point. This is usually filtered at the application +level by adding a *dead zone* for this specific axis. + +With HID-BPF, we can put the filtering of this dead zone in the kernel directly +so we don't wake up userspace when nothing else is happening on the input +controller. + +Of course, given that this dead zone is device specific, we can not create a +generic fix for all of the same joysticks. We *could* create a custom kernel +API for this (by adding a sysfs for instance), but there is no guarantees this +new kernel API will be broadly adopted and maintained. + +HID-BPF allows the userspace program who knows it will make use of this capability +to load the program itself, ensuring we only load the custom API when we have a user. + +simple fixup of report descriptor +--------------------------------- + +In the HID tree, we have half of the drivers that are "simple" and +that just fix one key or one byte in the report descriptor. +Currently, for users of such devices, the process of fixing them +is long and painful. + +With eBPF, we can reduce the burden of building those fixup kernel patches +by providing an eBPF program that does it. Once this has been validated by +the user, we can then embed the source code into the kernel tree and ship it +and load it directly instead of loading a specific kernel module for it. + +Note: the distribution and inclusion in the kernel is still not there yet. + +add a new fancy feature that requires a new kernel API +------------------------------------------------------ + +We have the case currently for the Universal Stylus Interface pens for example. +Basically, USI pens are requiring a new kernel API because there are +some channels of communication our HID and input stack are not capable +of. Instead of using hidraw or creating new sysfs or ioctls, we can rely +on eBPF to have the kernel API controlled by the consumer and to not +impact the performances by waking up userspace every time there is an +event. + +morph a device into something else and control that from userspace +------------------------------------------------------------------ + +Right now, the kernel has to make a choice on how a device looks like. +For that, it can not decide to transform a given device into something else +because that would be lying to userspace and will be even more harder to +unwind when we need the actual definition of the device. + +However, sometimes some new devices are useless with that sane way of defining +devices. For example, the Microsoft Surface Dial is a pushbutton with haptic +feedback that is barely usable as of today. + +With eBPF, userspace can morph that device into a mouse, and convert the dial +events into wheel events. Also, the userspace program can set/unset the haptic +feedback depending on the context. For example, if a menu is popped-up on the +screen we likely need to have a haptic click every 5 degrees, while when +we are fine-grain scrolling in a web page, we probably want the best resolution +without those annoying clicks. + +firewall +-------- + +What if we want to prevent other users to access a specific feature of a +device? (think a possibly bonker firmware update entry popint) + +With eBPF, we can intercept any HID command emitted to the device and +validate it or not. + +This also allows to sync the state between the userspace and the +kernel/bpf program because we can intercept any incoming command. + +tracing +------- + +The last usage is tracing events and all the fun we can do we BPF to summarize +and analyze events. + +Right now, tracing relies on hidraw. It works well except for a couple +of issues: + +1. if the driver doesn't export a hidraw node, we can't trace anything + (eBPF will be a "god-mode" there, so it might raise some eyebrows) +2. hidraw doesn't catch the other process requests to the device, which + means that we have cases where we need to add printks to the kernel + to understand what is happening. + +High-level view of HID-BPF +========================== + +The main idea behind HID-BPF is that it works at an array of bytes level. +Thus, all of the parsing of the HID report and the HID report descriptor +must be implemented in the userspace component that loads the eBPF +program. + +For example, in the dead zone joystick from above, knowing which fields +in the data stream needs to be set to ``0`` needs to be computed by userspace. + +A corrolar of this is that HID-BPF doesn't know about the other subsystems +available in the kernel. *You can not directly emit input event through the +input API from eBPF*. + +When a BPF program need to emit input events, it needs to talk HID, and rely +on the HID kernel processing to translate the HID data into input events. + +Available types of programs +=========================== + +HID-BPF has the following attachment types available: + +1. ``BPF_HID_DEVICE_EVENT`` defined with a ``SEC("hid/device_event")`` in libbpf +2. ``BPF_HID_USER_EVENT`` defined with a ``SEC("hid/user_event")`` in libbpf +3. ``BPF_HID_DRIVER_EVENT`` defined with a ``SEC("hid/driver_event")`` in libbpf +4. ``BPF_HID_RDESC_FIXUP`` defined with a ``SEC("hid/rdesc_fixup")`` in libbpf + +The above types are defined based on where the event came from. + +A ``BPF_HID_DEVICE_EVENT`` is calling a BPF program when an event is received from +the device. Thus we are in IRQ context and can act on the data or notify userspace. + +In the same way, a ``BPF_HID_USER_EVENT`` means that userspace called the syscall +``BPF_PROG_RUN`` facility. + +A ``BPF_HID_DRIVER_EVENT`` means that the kernel driver emitted an event that the bpf +programs want to be notified of. Such events are resume, reset, probed, but also +a call to a request toward the device (a call to ``hid_hw_raw_request()`` for example). + +Last, ``BPF_HID_RDESC_FIXUP`` is different from the others as there can be only one +BPF program of this type. This is called on ``probe`` from the driver and allows to +change the report descriptor from the BPF program. + +General overview of a HID-BPF program +===================================== + +User API available as a context in programs +------------------------------------------- + +.. kernel-doc:: include/uapi/linux/bpf_hid.h + +Accessing the data attached to the context +------------------------------------------ + +The ``struct hid_bpf_ctx`` doesn't export the ``data`` fields directly and to access +it, a bpf program needs to first call ``bpf_hid_get_data(context, offset, size)``. + +``offset`` can be any integer, but ``size`` needs to be constant, known at compile +time. + +This allows the following: + +1. for a given device, if we know that the report length will always be of a certain value, + we can request the ``data`` pointer to point at the full report length. + + The kernel will ensure we are using a correct size and offset and eBPF will ensure + the code will not attempt to read or write outside of the boundaries:: + + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 256 /* size */); + + if (!data) + return 0; /* ensure data is correct, now the verifier knows we + * have 256 bytes available */ + + bpf_printk("hello world: %02x %02x %02x", data[0], data[128], data[255]); + +2. if the report length is variable, but we know the value of ``X`` is always a 16-bits + integer, we can then have a pointer to that value only:: + + __u16 *x = bpf_hid_get_data(ctx, offset, sizeof(*x)); + + if (!x) + return 0; /* something when wrong */ + + *x += 1; /* increment X by one */ + +Bit access of data field (an alternative to ``bpf_hid_get_data``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The HID devices often work at the bit level in the emitted report. +For instance, a given button will be at a given offset (in bits) in the report +and usually of size 1 bit. + +In order to easily access those data, BPF program can rely on ``bpf_hid_get_bits()`` +and ``bpf_hid_set_bits()``. + +Those 2 functions do not require any of the arguments to be a constant like +``bpf_hid_get_data()``, meaning that they are appropriate for accessing +a field unknown at compile time of an unknown size. +The counterpart of those 2 helpers is that they are effectively copying the +data to/from a ``__u32`` instead of having a direct pointer access to the +field. + +Effect of a HID-BPF program +--------------------------- + +For all HID-BPF attachment types except for ``BPF_HID_RDESC_FIXUP``, several eBPF +programs can be attached to the same device. + +Unless ``BPF_F_INSERT_HEAD`` is added to the flags while attaching the program, the +new program is appended at the end of the list. In some cases (tracing for instance) +we need to get the unprocessed events from the device, and ``BPF_F_INSERT_HEAD`` will +insert the new program at the beginning of the list. + +Note however that there is no guarantees that another program is loaded with that +same flag, and thus our program may be second in the list. + +``BPF_HID_DEVICE_EVENT`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +Whenever a matching event is raised, the eBPF programs are called one after the other +and are working on the same data buffer. + +If a program changes the data associated with the context, the next one will see +the new data and will have *no* idea of what the original data was. + +Once all the programs are run and return ``0``, the rest of the HID stack will +work on the modified data, with the ``size`` field of the hid_bpf_ctx being the new +size of the input stream of data. + +If a BPF program returns a negative error, this has the same effect than setting +the ``size`` field of ``hid_bpf_ctx`` to ``0``: the event is dropped from the HID +processing. No clients (hidraw, input, LEDs) will ever see that event coming in. + +``BPF_HID_USER_EVENT`` +~~~~~~~~~~~~~~~~~~~~~~ + +One can attach several ``BPF_HID_USER_EVENT`` on a given device. But because the caller +needs to set which BPF program is used when calling the syscall ``BPF_PROG_RUN``, only +this particular BPF program will be run. + +The ``data`` associated with the ``hid_bpf_ctx`` contains the input data of the given +syscall and is big enough to contain both the input data and the requested output data. + +The output data from the syscall, if ``size_out`` is set to a positive value, is copied +from the content of the ``data`` field of ``hid_bpf_ctx``. + +``BPF_HID_DRIVER_EVENT`` +~~~~~~~~~~~~~~~~~~~~~~~~ + +The availability of the ``data`` field of ``hid_bpf_ctx`` depends on the event subtype: + +* **probe** event: ``data`` contains the report descriptor of the device (this is read-only) +* **reset**, **resume** events: no data is provided +* **device request**: ``data`` contains the incoming request made to the device. The BPF + program can decide whether or not forwarding the request to the device. +* **device request answer**: ``data`` contains the answer of the request. + +``BPF_HID_RDESC_FIXUP`` +~~~~~~~~~~~~~~~~~~~~~~~ + +Last, the ``BPF_HID_RDESC_FIXUP`` program works in the similar maneer than +``.report_fixup`` of ``struct hid_driver``. + +When the device is probed, the kernel sets the data buffer of the context with the +content of the report descriptor. The memory associated with that buffer is +``HID_MAX_DESCRIPTOR_SIZE`` (4 kB as of now). + +The eBPF program can modify the data buffer at will and then the kernel uses the +new content and size as the report descriptor. + +Whenever a ``BPF_HID_RDESC_FIXUP`` program is attached (if no program was attached +before), the kernel immediately disconnects the HID device, and do a reprobe. + +In the same way, when the ``BPF_HID_RDESC_FIXUP`` program is detached, the kernel +issues a disconnect on the device. + +To update the report descriptor in place, users can replace the current +``BPF_HID_RDESC_FIXUP`` program through the usual ``BPF_LINK_UPDATE`` syscall. +There will be only one disconnect emitted by the kernel. + +Attaching a bpf program to a device +=================================== + +``libbpf`` exports a helper to attach a HID-BPF program: ``bpf_program__attach_hid(program, fd, flags)``. + +The ``program`` and ``flags`` parameters are relatively obvious, but what about the +``fd`` argument? + +While working on HID-BPF it came out quickly enough that we can not rely on +hidraw to bind a BPF program to a HID device. hidraw is an artefact of the processing +of the HID device, and is not stable. Some drivers even disable it, so that removes the +tracing capabilies on those devices (where it is interesting to get the non-hidraw +traces). + +The solution is to use the sysfs tree and more specifically the ``uevent`` sysfs entry. + +For a given HID device, we have the ``uevent`` node at ``/sys/bus/hid/devices/BUS:VID:PID.000N/uevent``. + +``uevent`` is convenient as it contains already some information about the device itself: +the driver in use, the name of the HID device, its ID, its Unique field and the modalias. + +However, as mentioned previously, we can not really rely on hidraw for HID-BPF. +Which means we need to get the report descriptor from the device thorugh the sysfs too. +This is available at ``/sys/bus/hid/devices/BUS:VID:PID.000N/report_descriptor`` as a +binary stream. + +Parsing the report descriptor is the responsibility of the BPF programmer or the userspace +component that loads the eBPF program. + +An (almost) complete example of a BPF enhanced HID device +========================================================= + +*Foreword: for most parts, this could be implemented as a kernel driver* + +Let's imagine we have a new tablet device that has some haptic capabilities +to simulate the surface the user is scratching on. This device would also have +a specific 3 positions switch to toggle between *pencil on paper*, *cray on a wall* +and *brush on a painting canvas*. To make things even better, we can control the +physical position of the switch through a feature report. + +And of course, the switch is relying on some userspace component to control the +haptic feature of the device itself. + +Filtering events +---------------- + +The first step consists in filtering events from the device. Given that the switch +position is actually reported in the flow of the pen events, using hidraw to implement +that filtering would mean that we wake up userspace for every single event. + +This is OK for libinput, but having an external library that is just interested in +one byte in the report is less than ideal. + +For that, we can create a basic skeleton for our BPF program:: + + #include <linux/bpf.h> + #include <bpf/bpf_helpers.h> + #include <linux/bpf_hid.h> + + /* HID programs need to be GPL */ + char _license[] SEC("license") = "GPL"; + + struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 4096 * 64); + } ringbuf SEC(".maps"); + + __u8 current_value = 0; + + SEC("hid/device_event") + int filter_switch(struct hid_bpf_ctx *ctx) + { + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 192 /* size */); + __u8 *buf; + + if (!data) + return 0; /* EPERM check */ + + if (current_value != data[152]) { + buf = bpf_ringbuf_reserve(&ringbuf, 1, 0); + if (!buf) + return 0; + + *buf = data[152]; + + bpf_ringbuf_commit(buf, 0); + + current_value = data[152]; + } + + return 0; + } + +Our userspace program can now listen to notifications on the ring buffer, and +is awaken only when the value changes. + +Controlling the device +---------------------- + +To be able to change the haptic feedback from the tablet, the userspace program +needs to emit a feature report on the device itself. + +Instead of using hidraw for that, we can create a ``BPF_HID_USER_EVENT`` program +that talks to the device:: + + SEC("hid/user_event") + int send_haptic(struct hid_bpf_ctx *ctx) + { + __u8 *data = bpf_hid_get_data(ctx, 0 /* offset */, 1 /* size */); + int ret; + + if (!data) + return 0; /* EPERM check */ + + ret = bpf_hid_raw_request(ctx, data, HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + ctx->retval = ret; + + return 0; + } + +And then userspace needs to call that program directly:: + + static int set_haptic(struct hid *hid_skel, int sysfs_fd, __u8 haptic_value) + int err, prog_fd; + int ret = -1; + + LIBBPF_OPTS(bpf_test_run_opts, run_attrs, + .repeat = 1, + .ctx_in = &sysfs_fd, + .ctx_size_in = sizeof(sysfs_fd), + .data_in = &haptic_value, + .data_size_in = 1, + ); + + prog_fd = bpf_program__fd(hid_skel->progs.set_haptic); + + err = bpf_prog_test_run_opts(prog_fd, &run_attrs); + return err; + } + +Now the interesting bit is that our userspace program is aware of the haptic +state and can control it. + +Which means, that we can also export a dbus API for applications to change the +haptic feedback. The dbus API would be simple (probably just a string property). + +The interesting bit here is that we did not created a new kernel API for this. +Which means that if there is a bug in our implementation, we can change the +interface with the kernel as will, because the userspace application is responsible +for its own usage. diff --git a/Documentation/hid/index.rst b/Documentation/hid/index.rst index e50f513c579c..b2028f382f11 100644 --- a/Documentation/hid/index.rst +++ b/Documentation/hid/index.rst @@ -11,6 +11,7 @@ Human Interface Devices (HID) hidraw hid-sensor hid-transport + hid-bpf
uhid
diff --git a/include/uapi/linux/bpf_hid.h b/include/uapi/linux/bpf_hid.h index 64a8b9dd8809..8e907e7fe77b 100644 --- a/include/uapi/linux/bpf_hid.h +++ b/include/uapi/linux/bpf_hid.h @@ -11,20 +11,60 @@
#include <linux/types.h>
+/** + * enum hid_bpf_event - event types in struct hid_bpf_ctx + * + * Event types are not tied to a given attach type, there might + * be multiple event types for one attach type. + * + * @HID_BPF_UNDEF: sentinel value, should never be set by the kernel + * @HID_BPF_DEVICE_EVENT: used when attach type is ``BPF_HID_DEVICE_EVENT`` + * @HID_BPF_RDESC_FIXUP: used when attach type is ``BPF_HID_RDESC_FIXUP`` + * @HID_BPF_USER_EVENT: used when attach type is ``BPF_HID_USER_EVENT`` + */ enum hid_bpf_event { HID_BPF_UNDEF = 0, - HID_BPF_DEVICE_EVENT, /* when attach type is BPF_HID_DEVICE_EVENT */ - HID_BPF_RDESC_FIXUP, /* ................... BPF_HID_RDESC_FIXUP */ - HID_BPF_USER_EVENT, /* ................... BPF_HID_USER_EVENT */ + HID_BPF_DEVICE_EVENT, + HID_BPF_RDESC_FIXUP, + HID_BPF_USER_EVENT, };
/* User accessible data for HID programs. Add new fields at the end. */ +/** + * 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 ``bpf_hid_get_data()`` in order to get a pointer to that field. + * + * @type: Of type enum hid_bpf_event. This value is read-only and matters when there + * is more than one event type per attachment type. + * @allocated_size: Allocated size of data, read-only. + * + * This is how much memory is available and can be requested + * by the HID program. + * Note that for ``HID_BPF_RDESC_FIXUP``, that memory is set to + * ``4096`` (4 KB) + * @size: Valid data in the data field, read-write. + * + * Programs can get the available valid size in data by fetching this field. + * Programs can also change this value and even set it to ``0`` to discard the + * data from this event. + * + * ``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 program when type is ``HID_BPF_USER_EVENT``, read-write. + * + * Returning from the program an error means that the execution of the program + * failed. However, one may want to express that the program executed correctly, + * but the underlying calls failed for a specific reason. This is when we use + * retval. + */ struct hid_bpf_ctx { - __u32 type; /* enum hid_bpf_event, RO */ - __u32 allocated_size; /* allocated size of data, RO */ + __u32 type; + __u32 allocated_size;
- __u32 size; /* valid data in data field, RW */ - __s32 retval; /* when type is HID_BPF_USER_EVENT, RW */ + __u32 size; + __s32 retval; };
#endif /* _UAPI__LINUX_BPF_HID_H__ */
On Fri, Mar 18, 2022 at 9:22 AM Benjamin Tissoires benjamin.tissoires@redhat.com wrote:
Gives a primer on HID-BPF.
Signed-off-by: Benjamin Tissoires benjamin.tissoires@redhat.com
new in v3
Documentation/hid/hid-bpf.rst | 444 ++++++++++++++++++++++++++++++++++ Documentation/hid/index.rst | 1 + include/uapi/linux/bpf_hid.h | 54 ++++- 3 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 Documentation/hid/hid-bpf.rst
diff --git a/Documentation/hid/hid-bpf.rst b/Documentation/hid/hid-bpf.rst new file mode 100644 index 000000000000..0bf0d937b0e1 --- /dev/null +++ b/Documentation/hid/hid-bpf.rst @@ -0,0 +1,444 @@ +.. SPDX-License-Identifier: GPL-2.0
+======= +HID-BPF +=======
+HID is a standard protocol for input devices and it can greatly make use +of the eBPF capabilities to speed up development and add new capabilities +to the existing HID interfaces.
+.. contents::
- :local:
- :depth: 2
+When (and why) using HID-BPF +============================
+We can enumerate several use cases for when using HID-BPF is better than +using a standard kernel driver fix.
+dead zone of a joystick +-----------------------
+Assuming you have a joystick that is getting older, it is common to see it +wobbling around its neutral point. This is usually filtered at the application +level by adding a *dead zone* for this specific axis.
+With HID-BPF, we can put the filtering of this dead zone in the kernel directly +so we don't wake up userspace when nothing else is happening on the input +controller.
+Of course, given that this dead zone is device specific, we can not create a
nit: s/can not/cannot
There are a few more "can not" below.
[...]
+firewall +--------
+What if we want to prevent other users to access a specific feature of a +device? (think a possibly bonker firmware update entry popint)
nit: point
+With eBPF, we can intercept any HID command emitted to the device and +validate it or not.
+This also allows to sync the state between the userspace and the +kernel/bpf program because we can intercept any incoming command.
[...]
+The main idea behind HID-BPF is that it works at an array of bytes level. +Thus, all of the parsing of the HID report and the HID report descriptor +must be implemented in the userspace component that loads the eBPF +program.
+For example, in the dead zone joystick from above, knowing which fields +in the data stream needs to be set to ``0`` needs to be computed by userspace.
+A corrolar of this is that HID-BPF doesn't know about the other subsystems
nit: corollary?
+available in the kernel. *You can not directly emit input event through the +input API from eBPF*.
+When a BPF program need to emit input events, it needs to talk HID, and rely +on the HID kernel processing to translate the HID data into input events.
+Available types of programs +===========================
[...]
+``BPF_HID_RDESC_FIXUP`` +~~~~~~~~~~~~~~~~~~~~~~~
+Last, the ``BPF_HID_RDESC_FIXUP`` program works in the similar maneer than
nit: manner.
+``.report_fixup`` of ``struct hid_driver``.
[...]
Hi Benjamin,
I tested this iteration of the set, and I faced couple of problems with it.
1) There were some conflicts as I could not figure out the correct kernel commit on which to apply the series on. I applied this on top of last weeks bpf-next (see below) with some local merge fixes.
commit 2af7e566a8616c278e1d7287ce86cd3900bed943 (bpf-next/master, bpf-next/for-next) Author: Saeed Mahameed saeedm@nvidia.com Date: Tue Mar 22 10:22:24 2022 -0700
net/mlx5e: Fix build warning, detected write beyond size of field
2) hid_is_valid_access() causes some trouble and it rejects pretty much every BPF program which tries to use ctx->retval. This appears to be because prog->expected_attach_type is not populated, I had to apply below local tweak to overcome this problem:
diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 30a62e8e0f0a..bf64411e6e9b 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -180,8 +180,7 @@ static bool hid_is_valid_access(int off, int size, case offsetof(struct hid_bpf_ctx, retval): if (size != size_default) return false; - return (prog->expected_attach_type == BPF_HID_USER_EVENT || - prog->expected_attach_type == BPF_HID_DRIVER_EVENT); + return true; default: if (size != size_default) return false;
Proper fix would probably be to actually populate the expected_attach_type, but I could not figure out quickly where this should be done, or whether it is actually done on some other base commit.
With those, for the whole series:
Tested-by: Tero Kristo tero.kristo@linux.intel.com
On 18/03/2022 18:15, Benjamin Tissoires wrote:
Hi,
This is a followup of my v1 at [0] and v2 at [1].
The short summary of the previous cover letter and discussions is that HID could benefit from BPF for the following use cases:
simple fixup of report descriptor: benefits are faster development time and testing, with the produced bpf program being shipped in the kernel directly (the shipping part is *not* addressed here).
Universal Stylus Interface: allows a user-space program to define its own kernel interface
Surface Dial: somehow similar to the previous one except that userspace can decide to change the shape of the exported device
firewall: still partly missing there, there is not yet interception of hidraw calls, but it's coming in a followup series, I promise
tracing: well, tracing.
I think I addressed the comments from the previous version, but there are a few things I'd like to note here:
I did not take the various rev-by and tested-by (thanks a lot for those) because the uapi changed significantly in v3, so I am not very confident in taking those rev-by blindly
I mentioned in my discussion with Song that I'll put a summary of the uapi in the cover letter, but I ended up adding a (long) file in the Documentation directory. So please maybe start by reading 17/17 to have an overview of what I want to achieve
I added in the libbpf and bpf the new type BPF_HID_DRIVER_EVENT, even though I don't have a user of it right now in the kernel. I wanted to have them in the docs, but we might not want to have them ready here. In terms of code, it just means that we can attach such programs types but that they will never get triggered.
Anyway, I have been mulling on this for the past 2 weeks, and I think that maybe sharing this now is better than me just starring at the code over and over.
Short summary of changes:
v3:
squashed back together most of the libbpf and bpf changes into bigger commits that give a better overview of the whole interactions
reworked the user API to not expose .data as a directly accessible field from the context, but instead forces everyone to use hid_bpf_get_data (or get/set_bits)
added BPF_HID_DRIVER_EVENT (see note above)
addressed the various nitpicks from v2
added a big Documentation file (and so adding now the doc maintainers to the long list of recipients)
v2:
split the series by subsystem (bpf, HID, libbpf, selftests and samples)
Added an extra patch at the beginning to not require CAP_NET_ADMIN for BPF_PROG_TYPE_LIRC_MODE2 (please shout if this is wrong)
made the bpf context attached to HID program of dynamic size:
- the first 1 kB will be able to be addressed directly
- the rest can be retrieved through bpf_hid_{set|get}_data (note that I am definitivey not happy with that API, because there is part of it in bits and other in bytes. ouch)
added an extra patch to prevent non GPL HID bpf programs to be loaded of type BPF_PROG_TYPE_HID
- same here, not really happy but I don't know where to put that check in verifier.c
added a new flag BPF_F_INSERT_HEAD for BPF_LINK_CREATE syscall when in used with HID program types.
- this flag is used for tracing, to be able to load a program before any others that might already have been inserted and that might change the data stream.
Cheers, Benjamin
[0] https://lore.kernel.org/linux-input/20220224110828.2168231-1-benjamin.tissoi... [1] https://lore.kernel.org/linux-input/20220304172852.274126-1-benjamin.tissoir...
Benjamin Tissoires (17): bpf: add new is_sys_admin_prog_type() helper bpf: introduce hid program type bpf/verifier: prevent non GPL programs to be loaded against HID libbpf: add HID program type and API HID: hook up with bpf HID: allow to change the report descriptor from an eBPF program selftests/bpf: add tests for the HID-bpf initial implementation selftests/bpf: add report descriptor fixup tests selftests/bpf: Add a test for BPF_F_INSERT_HEAD selftests/bpf: add test for user call of HID bpf programs samples/bpf: add new hid_mouse example bpf/hid: add more HID helpers HID: bpf: implement hid_bpf_get|set_bits HID: add implementation of bpf_hid_raw_request selftests/bpf: add tests for hid_{get|set}_bits helpers selftests/bpf: add tests for bpf_hid_hw_request Documentation: add HID-BPF docs
Documentation/hid/hid-bpf.rst | 444 +++++++++++ Documentation/hid/index.rst | 1 + drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 328 ++++++++ drivers/hid/hid-core.c | 34 +- include/linux/bpf-hid.h | 127 +++ include/linux/bpf_types.h | 4 + include/linux/hid.h | 36 +- include/uapi/linux/bpf.h | 67 ++ include/uapi/linux/bpf_hid.h | 71 ++ include/uapi/linux/hid.h | 10 + kernel/bpf/Makefile | 3 + kernel/bpf/btf.c | 1 + kernel/bpf/hid.c | 728 +++++++++++++++++ kernel/bpf/syscall.c | 27 +- kernel/bpf/verifier.c | 7 + samples/bpf/.gitignore | 1 + samples/bpf/Makefile | 4 + samples/bpf/hid_mouse_kern.c | 117 +++ samples/bpf/hid_mouse_user.c | 129 +++ tools/include/uapi/linux/bpf.h | 67 ++ tools/lib/bpf/libbpf.c | 23 +- tools/lib/bpf/libbpf.h | 2 + tools/lib/bpf/libbpf.map | 1 + tools/testing/selftests/bpf/config | 3 + tools/testing/selftests/bpf/prog_tests/hid.c | 788 +++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 205 +++++ 27 files changed, 3204 insertions(+), 25 deletions(-) create mode 100644 Documentation/hid/hid-bpf.rst create mode 100644 drivers/hid/hid-bpf.c create mode 100644 include/linux/bpf-hid.h create mode 100644 include/uapi/linux/bpf_hid.h create mode 100644 kernel/bpf/hid.c create mode 100644 samples/bpf/hid_mouse_kern.c create mode 100644 samples/bpf/hid_mouse_user.c create mode 100644 tools/testing/selftests/bpf/prog_tests/hid.c create mode 100644 tools/testing/selftests/bpf/progs/hid.c
Hi Tero,
On Tue, Mar 29, 2022 at 3:04 PM Tero Kristo tero.kristo@linux.intel.com wrote:
Hi Benjamin,
I tested this iteration of the set, and I faced couple of problems with it.
- There were some conflicts as I could not figure out the correct
kernel commit on which to apply the series on. I applied this on top of last weeks bpf-next (see below) with some local merge fixes.
Right, there was a new conflict in bpf-next, but you managed it :)
commit 2af7e566a8616c278e1d7287ce86cd3900bed943 (bpf-next/master, bpf-next/for-next) Author: Saeed Mahameed saeedm@nvidia.com Date: Tue Mar 22 10:22:24 2022 -0700
net/mlx5e: Fix build warning, detected write beyond size of field
- hid_is_valid_access() causes some trouble and it rejects pretty much
every BPF program which tries to use ctx->retval. This appears to be because prog->expected_attach_type is not populated, I had to apply below local tweak to overcome this problem:
diff --git a/kernel/bpf/hid.c b/kernel/bpf/hid.c index 30a62e8e0f0a..bf64411e6e9b 100644 --- a/kernel/bpf/hid.c +++ b/kernel/bpf/hid.c @@ -180,8 +180,7 @@ static bool hid_is_valid_access(int off, int size, case offsetof(struct hid_bpf_ctx, retval): if (size != size_default) return false;
return (prog->expected_attach_type == BPF_HID_USER_EVENT ||
prog->expected_attach_type == BPF_HID_DRIVER_EVENT);
return true; default: if (size != size_default) return false;
Proper fix would probably be to actually populate the expected_attach_type, but I could not figure out quickly where this should be done, or whether it is actually done on some other base commit.
Hmm, this is not what I would have expected. Anyway, "return true" would be a valid solution too, but...
With those, for the whole series:
Tested-by: Tero Kristo tero.kristo@linux.intel.com
Thanks a lot. Unfortunately, if you saw the discussion with Alexei in patch 6/17, you'll see that there is a push toward a slightly different implementation.
I had a meeting with Alexei, and a few other BPF folks yesterday, and they convinced me that this series is implementing a BPF feature the "old way", and that we should aim at having HID using standard BPF facilities instead of having HID messing up with bpf-core.
This will be beneficial in the long term as we won't depend on BPF to be able to add new UAPI, being BPF calls or functions.
I'll reply in more detail on 6/17.
Cheers, Benjamin
On 18/03/2022 18:15, Benjamin Tissoires wrote:
Hi,
This is a followup of my v1 at [0] and v2 at [1].
The short summary of the previous cover letter and discussions is that HID could benefit from BPF for the following use cases:
simple fixup of report descriptor: benefits are faster development time and testing, with the produced bpf program being shipped in the kernel directly (the shipping part is *not* addressed here).
Universal Stylus Interface: allows a user-space program to define its own kernel interface
Surface Dial: somehow similar to the previous one except that userspace can decide to change the shape of the exported device
firewall: still partly missing there, there is not yet interception of hidraw calls, but it's coming in a followup series, I promise
tracing: well, tracing.
I think I addressed the comments from the previous version, but there are a few things I'd like to note here:
I did not take the various rev-by and tested-by (thanks a lot for those) because the uapi changed significantly in v3, so I am not very confident in taking those rev-by blindly
I mentioned in my discussion with Song that I'll put a summary of the uapi in the cover letter, but I ended up adding a (long) file in the Documentation directory. So please maybe start by reading 17/17 to have an overview of what I want to achieve
I added in the libbpf and bpf the new type BPF_HID_DRIVER_EVENT, even though I don't have a user of it right now in the kernel. I wanted to have them in the docs, but we might not want to have them ready here. In terms of code, it just means that we can attach such programs types but that they will never get triggered.
Anyway, I have been mulling on this for the past 2 weeks, and I think that maybe sharing this now is better than me just starring at the code over and over.
Short summary of changes:
v3:
squashed back together most of the libbpf and bpf changes into bigger commits that give a better overview of the whole interactions
reworked the user API to not expose .data as a directly accessible field from the context, but instead forces everyone to use hid_bpf_get_data (or get/set_bits)
added BPF_HID_DRIVER_EVENT (see note above)
addressed the various nitpicks from v2
added a big Documentation file (and so adding now the doc maintainers to the long list of recipients)
v2:
split the series by subsystem (bpf, HID, libbpf, selftests and samples)
Added an extra patch at the beginning to not require CAP_NET_ADMIN for BPF_PROG_TYPE_LIRC_MODE2 (please shout if this is wrong)
made the bpf context attached to HID program of dynamic size:
- the first 1 kB will be able to be addressed directly
- the rest can be retrieved through bpf_hid_{set|get}_data (note that I am definitivey not happy with that API, because there is part of it in bits and other in bytes. ouch)
added an extra patch to prevent non GPL HID bpf programs to be loaded of type BPF_PROG_TYPE_HID
- same here, not really happy but I don't know where to put that check in verifier.c
added a new flag BPF_F_INSERT_HEAD for BPF_LINK_CREATE syscall when in used with HID program types.
- this flag is used for tracing, to be able to load a program before any others that might already have been inserted and that might change the data stream.
Cheers, Benjamin
[0] https://lore.kernel.org/linux-input/20220224110828.2168231-1-benjamin.tissoi... [1] https://lore.kernel.org/linux-input/20220304172852.274126-1-benjamin.tissoir...
Benjamin Tissoires (17): bpf: add new is_sys_admin_prog_type() helper bpf: introduce hid program type bpf/verifier: prevent non GPL programs to be loaded against HID libbpf: add HID program type and API HID: hook up with bpf HID: allow to change the report descriptor from an eBPF program selftests/bpf: add tests for the HID-bpf initial implementation selftests/bpf: add report descriptor fixup tests selftests/bpf: Add a test for BPF_F_INSERT_HEAD selftests/bpf: add test for user call of HID bpf programs samples/bpf: add new hid_mouse example bpf/hid: add more HID helpers HID: bpf: implement hid_bpf_get|set_bits HID: add implementation of bpf_hid_raw_request selftests/bpf: add tests for hid_{get|set}_bits helpers selftests/bpf: add tests for bpf_hid_hw_request Documentation: add HID-BPF docs
Documentation/hid/hid-bpf.rst | 444 +++++++++++ Documentation/hid/index.rst | 1 + drivers/hid/Makefile | 1 + drivers/hid/hid-bpf.c | 328 ++++++++ drivers/hid/hid-core.c | 34 +- include/linux/bpf-hid.h | 127 +++ include/linux/bpf_types.h | 4 + include/linux/hid.h | 36 +- include/uapi/linux/bpf.h | 67 ++ include/uapi/linux/bpf_hid.h | 71 ++ include/uapi/linux/hid.h | 10 + kernel/bpf/Makefile | 3 + kernel/bpf/btf.c | 1 + kernel/bpf/hid.c | 728 +++++++++++++++++ kernel/bpf/syscall.c | 27 +- kernel/bpf/verifier.c | 7 + samples/bpf/.gitignore | 1 + samples/bpf/Makefile | 4 + samples/bpf/hid_mouse_kern.c | 117 +++ samples/bpf/hid_mouse_user.c | 129 +++ tools/include/uapi/linux/bpf.h | 67 ++ tools/lib/bpf/libbpf.c | 23 +- tools/lib/bpf/libbpf.h | 2 + tools/lib/bpf/libbpf.map | 1 + tools/testing/selftests/bpf/config | 3 + tools/testing/selftests/bpf/prog_tests/hid.c | 788 +++++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 205 +++++ 27 files changed, 3204 insertions(+), 25 deletions(-) create mode 100644 Documentation/hid/hid-bpf.rst create mode 100644 drivers/hid/hid-bpf.c create mode 100644 include/linux/bpf-hid.h create mode 100644 include/uapi/linux/bpf_hid.h create mode 100644 kernel/bpf/hid.c create mode 100644 samples/bpf/hid_mouse_kern.c create mode 100644 samples/bpf/hid_mouse_user.c create mode 100644 tools/testing/selftests/bpf/prog_tests/hid.c create mode 100644 tools/testing/selftests/bpf/progs/hid.c
linux-kselftest-mirror@lists.linaro.org