@@ -118,6 +118,26 @@ static void hid_bpf_array_detached(struct hid_device *hdev, enum bpf_hid_attach_
}
}
+int hid_bpf_get_data(struct hid_device *hdev, u8 *buf, u64 offset, u8 n)
+{
+ if (n > 32 ||
+ ((offset + n) >> 3) >= HID_BPF_MAX_BUFFER_SIZE)
+ return 0;
+
+ return hid_field_extract(hdev, buf, offset, n);
+}
+
+int hid_bpf_set_data(struct hid_device *hdev, u8 *buf, u64 offset, u8 n, u32 data)
+{
+ if (n > 32 ||
+ ((offset + n) >> 3) >= HID_BPF_MAX_BUFFER_SIZE)
+ return -EINVAL;
+
+ implement(hdev, buf, offset, n, data);
+
+ return 0;
+}
+
static int hid_bpf_run_progs(struct hid_device *hdev, enum bpf_hid_attach_type type,
struct hid_bpf_ctx *ctx, u8 *data, int size)
{
@@ -229,6 +249,8 @@ int __init hid_bpf_module_init(void)
.link_attach = hid_bpf_link_attach,
.link_attached = hid_bpf_link_attached,
.array_detached = hid_bpf_array_detached,
+ .hid_get_data = hid_bpf_get_data,
+ .hid_set_data = hid_bpf_set_data,
};
bpf_hid_set_hooks(&hooks);
@@ -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",
@@ -72,6 +72,8 @@ struct bpf_hid_hooks {
int (*link_attach)(struct hid_device *hdev, enum bpf_hid_attach_type type);
void (*link_attached)(struct hid_device *hdev, enum bpf_hid_attach_type type);
void (*array_detached)(struct hid_device *hdev, enum bpf_hid_attach_type type);
+ int (*hid_get_data)(struct hid_device *hdev, u8 *buf, u64 offset, u8 size);
+ int (*hid_set_data)(struct hid_device *hdev, u8 *buf, u64 offset, u8 size, u32 data);
};
#ifdef CONFIG_BPF
@@ -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);
@@ -5090,6 +5090,20 @@ union bpf_attr {
* Return
* 0 on success, or a negative error in case of failure. On error
* *dst* buffer is zeroed out.
+ *
+ * u32 bpf_hid_get_data(void *ctx, u64 offset, u8 n)
+ * Description
+ * Get the data of size n at the given offset in the
+ * ctx->event.data field
+ * Return
+ * The value at offset. In case of error: 0.
+ *
+ * int bpf_hid_set_data(void *ctx, u64 offset, u8 n, u32 data)
+ * Description
+ * Set the data of size n at the given offset in the
+ * ctx->event.data field
+ * Return
+ * 0 on success, a negative error on failure.
*/
#define __BPF_FUNC_MAPPER(FN) \
FN(unspec), \
@@ -5284,6 +5298,8 @@ union bpf_attr {
FN(xdp_load_bytes), \
FN(xdp_store_bytes), \
FN(copy_from_user_task), \
+ FN(hid_get_data), \
+ FN(hid_set_data), \
/* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -37,10 +37,78 @@ void bpf_hid_set_hooks(struct bpf_hid_hooks *hooks)
}
EXPORT_SYMBOL_GPL(bpf_hid_set_hooks);
+BPF_CALL_3(bpf_hid_get_data, void*, ctx, u64, offset, u8, n)
+{
+ struct hid_bpf_ctx *bpf_ctx = ctx;
+ u8 *buf;
+
+ if (!hid_hooks.hid_get_data)
+ return -EOPNOTSUPP;
+
+ switch (bpf_ctx->type) {
+ case HID_BPF_DEVICE_EVENT:
+ buf = bpf_ctx->u.device.data;
+ break;
+ case HID_BPF_RDESC_FIXUP:
+ buf = bpf_ctx->u.rdesc.data;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ return hid_hooks.hid_get_data(bpf_ctx->hdev, buf, offset, n);
+}
+
+static const struct bpf_func_proto bpf_hid_get_data_proto = {
+ .func = bpf_hid_get_data,
+ .gpl_only = true,
+ .ret_type = RET_INTEGER,
+ .arg1_type = ARG_PTR_TO_CTX,
+ .arg2_type = ARG_ANYTHING,
+ .arg3_type = ARG_ANYTHING,
+};
+
+BPF_CALL_4(bpf_hid_set_data, void*, ctx, u64, offset, u8, n, u32, data)
+{
+ struct hid_bpf_ctx *bpf_ctx = ctx;
+ u8 *buf;
+
+ if (!hid_hooks.hid_set_data)
+ return -EOPNOTSUPP;
+
+ switch (bpf_ctx->type) {
+ case HID_BPF_DEVICE_EVENT:
+ buf = bpf_ctx->u.device.data;
+ break;
+ case HID_BPF_RDESC_FIXUP:
+ buf = bpf_ctx->u.rdesc.data;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ hid_hooks.hid_set_data(bpf_ctx->hdev, buf, offset, n, data);
+ return 0;
+}
+
+static const struct bpf_func_proto bpf_hid_set_data_proto = {
+ .func = bpf_hid_set_data,
+ .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,
+};
+
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_set_data:
+ return &bpf_hid_set_data_proto;
default:
return bpf_base_func_proto(func_id);
}
@@ -5090,6 +5090,20 @@ union bpf_attr {
* Return
* 0 on success, or a negative error in case of failure. On error
* *dst* buffer is zeroed out.
+ *
+ * u32 bpf_hid_get_data(void *ctx, u64 offset, u8 n)
+ * Description
+ * Get the data of size n at the given offset in the
+ * ctx->event.data field
+ * Return
+ * The value at offset. In case of error: 0.
+ *
+ * int bpf_hid_set_data(void *ctx, u64 offset, u8 n, u32 data)
+ * Description
+ * Set the data of size n at the given offset in the
+ * ctx->event.data field
+ * Return
+ * 0 on success, a negative error on failure.
*/
#define __BPF_FUNC_MAPPER(FN) \
FN(unspec), \
@@ -5284,6 +5298,8 @@ union bpf_attr {
FN(xdp_load_bytes), \
FN(xdp_store_bytes), \
FN(copy_from_user_task), \
+ FN(hid_get_data), \
+ FN(hid_set_data), \
/* */
/* integer value in 'imm' field of BPF_CALL instruction selects which helper
@@ -280,6 +280,62 @@ static int test_hid_raw_event(struct hid *hid_skel, int uhid_fd, int sysfs_fd)
return ret;
}
+/*
+ * Attach hid_set_get_data 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}_data.
+ */
+static int test_hid_set_get_data(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_data program */
+ hid_skel->links.hid_set_get_data =
+ bpf_program__attach_hid(hid_skel->progs.hid_set_get_data, sysfs_fd);
+ if (!ASSERT_OK_PTR(hid_skel->links.hid_set_get_data,
+ "attach_hid(hid_set_get_data)"))
+ return PTR_ERR(hid_skel->links.hid_set_get_data);
+
+ hidraw_ino = get_hidraw(hid_skel->links.hid_set_get_data);
+ 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, 4);
+
+ /* read the data from hidraw */
+ memset(buf, 0, sizeof(buf));
+ err = read(hidraw_fd, buf, sizeof(buf));
+ if (!ASSERT_EQ(err, 4, "read_hidraw"))
+ goto cleanup;
+
+ if (!ASSERT_EQ(buf[2], (42 >> 2), "hid_set_get_data"))
+ 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,
@@ -378,6 +434,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_set_get_data(hid_skel, uhid_fd, sysfs_fd);
+ ASSERT_OK(err, "hid_set_get_data");
+
err = test_rdesc_fixup(hid_skel, uhid_fd, sysfs_fd);
ASSERT_OK(err, "hid_rdesc_fixup");
@@ -66,3 +66,17 @@ int hid_rdesc_fixup(struct hid_bpf_ctx *ctx)
return 0;
}
+
+SEC("hid/device_event")
+int hid_set_get_data(struct hid_bpf_ctx *ctx)
+{
+ __u32 x;
+
+ /* extract data at bit offset 10 of size 4 (half a byte) */
+ x = bpf_hid_get_data(ctx, 10, 4);
+
+ /* reinject it */
+ bpf_hid_set_data(ctx, 16, 4, x);
+
+ return 0;
+}
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. Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com> --- drivers/hid/hid-bpf.c | 22 +++++++ drivers/hid/hid-core.c | 4 +- include/linux/bpf-hid.h | 2 + include/linux/hid.h | 2 + include/uapi/linux/bpf.h | 16 +++++ kernel/bpf/hid.c | 68 ++++++++++++++++++++ tools/include/uapi/linux/bpf.h | 16 +++++ tools/testing/selftests/bpf/prog_tests/hid.c | 59 +++++++++++++++++ tools/testing/selftests/bpf/progs/hid.c | 14 ++++ 9 files changed, 201 insertions(+), 2 deletions(-)