| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * HID-BPF support for Linux | |
| * | |
| * Copyright (c) 2024 Benjamin Tissoires | |
| */ | |
| #include <linux/bitops.h> | |
| #include <linux/bpf_verifier.h> | |
| #include <linux/bpf.h> | |
| #include <linux/btf.h> | |
| #include <linux/btf_ids.h> | |
| #include <linux/filter.h> | |
| #include <linux/hid.h> | |
| #include <linux/hid_bpf.h> | |
| #include <linux/init.h> | |
| #include <linux/module.h> | |
| #include <linux/stddef.h> | |
| #include <linux/workqueue.h> | |
| #include "hid_bpf_dispatch.h" | |
| static struct btf *hid_bpf_ops_btf; | |
| static int hid_bpf_ops_init(struct btf *btf) | |
| { | |
| hid_bpf_ops_btf = btf; | |
| return 0; | |
| } | |
| static bool hid_bpf_ops_is_valid_access(int off, int size, | |
| enum bpf_access_type type, | |
| const struct bpf_prog *prog, | |
| struct bpf_insn_access_aux *info) | |
| { | |
| return bpf_tracing_btf_ctx_access(off, size, type, prog, info); | |
| } | |
| static int hid_bpf_ops_check_member(const struct btf_type *t, | |
| const struct btf_member *member, | |
| const struct bpf_prog *prog) | |
| { | |
| u32 moff = __btf_member_bit_offset(t, member) / 8; | |
| switch (moff) { | |
| case offsetof(struct hid_bpf_ops, hid_rdesc_fixup): | |
| case offsetof(struct hid_bpf_ops, hid_hw_request): | |
| case offsetof(struct hid_bpf_ops, hid_hw_output_report): | |
| break; | |
| default: | |
| if (prog->sleepable) | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| struct hid_bpf_offset_write_range { | |
| const char *struct_name; | |
| u32 struct_length; | |
| u32 start; | |
| u32 end; | |
| }; | |
| static int hid_bpf_ops_btf_struct_access(struct bpf_verifier_log *log, | |
| const struct bpf_reg_state *reg, | |
| int off, int size) | |
| { | |
| #define WRITE_RANGE(_name, _field, _is_string) \ | |
| { \ | |
| .struct_name = #_name, \ | |
| .struct_length = sizeof(struct _name), \ | |
| .start = offsetof(struct _name, _field), \ | |
| .end = offsetofend(struct _name, _field) - !!(_is_string), \ | |
| } | |
| const struct hid_bpf_offset_write_range write_ranges[] = { | |
| WRITE_RANGE(hid_bpf_ctx, retval, false), | |
| WRITE_RANGE(hid_device, name, true), | |
| WRITE_RANGE(hid_device, uniq, true), | |
| WRITE_RANGE(hid_device, phys, true), | |
| }; | |
| #undef WRITE_RANGE | |
| const struct btf_type *state = NULL; | |
| const struct btf_type *t; | |
| const char *cur = NULL; | |
| int i; | |
| t = btf_type_by_id(reg->btf, reg->btf_id); | |
| for (i = 0; i < ARRAY_SIZE(write_ranges); i++) { | |
| const struct hid_bpf_offset_write_range *write_range = &write_ranges[i]; | |
| s32 type_id; | |
| /* we already found a writeable struct, but there is a | |
| * new one, let's break the loop. | |
| */ | |
| if (t == state && write_range->struct_name != cur) | |
| break; | |
| /* new struct to look for */ | |
| if (write_range->struct_name != cur) { | |
| type_id = btf_find_by_name_kind(reg->btf, write_range->struct_name, | |
| BTF_KIND_STRUCT); | |
| if (type_id < 0) | |
| return -EINVAL; | |
| state = btf_type_by_id(reg->btf, type_id); | |
| } | |
| /* this is not the struct we are looking for */ | |
| if (t != state) { | |
| cur = write_range->struct_name; | |
| continue; | |
| } | |
| /* first time we see this struct, check for out of bounds */ | |
| if (cur != write_range->struct_name && | |
| off + size > write_range->struct_length) { | |
| bpf_log(log, "write access for struct %s at off %d with size %d\n", | |
| write_range->struct_name, off, size); | |
| return -EACCES; | |
| } | |
| /* now check if we are in our boundaries */ | |
| if (off >= write_range->start && off + size <= write_range->end) | |
| return NOT_INIT; | |
| cur = write_range->struct_name; | |
| } | |
| if (t != state) | |
| bpf_log(log, "write access to this struct is not supported\n"); | |
| else | |
| bpf_log(log, | |
| "write access at off %d with size %d on read-only part of %s\n", | |
| off, size, cur); | |
| return -EACCES; | |
| } | |
| static const struct bpf_verifier_ops hid_bpf_verifier_ops = { | |
| .get_func_proto = bpf_base_func_proto, | |
| .is_valid_access = hid_bpf_ops_is_valid_access, | |
| .btf_struct_access = hid_bpf_ops_btf_struct_access, | |
| }; | |
| static int hid_bpf_ops_init_member(const struct btf_type *t, | |
| const struct btf_member *member, | |
| void *kdata, const void *udata) | |
| { | |
| const struct hid_bpf_ops *uhid_bpf_ops; | |
| struct hid_bpf_ops *khid_bpf_ops; | |
| u32 moff; | |
| uhid_bpf_ops = (const struct hid_bpf_ops *)udata; | |
| khid_bpf_ops = (struct hid_bpf_ops *)kdata; | |
| moff = __btf_member_bit_offset(t, member) / 8; | |
| switch (moff) { | |
| case offsetof(struct hid_bpf_ops, hid_id): | |
| /* For hid_id and flags fields, this function has to copy it | |
| * and return 1 to indicate that the data has been handled by | |
| * the struct_ops type, or the verifier will reject the map if | |
| * the value of those fields is not zero. | |
| */ | |
| khid_bpf_ops->hid_id = uhid_bpf_ops->hid_id; | |
| return 1; | |
| case offsetof(struct hid_bpf_ops, flags): | |
| if (uhid_bpf_ops->flags & ~BPF_F_BEFORE) | |
| return -EINVAL; | |
| khid_bpf_ops->flags = uhid_bpf_ops->flags; | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static int hid_bpf_reg(void *kdata, struct bpf_link *link) | |
| { | |
| struct hid_bpf_ops *ops = kdata; | |
| struct hid_device *hdev; | |
| int count, err = 0; | |
| /* prevent multiple attach of the same struct_ops */ | |
| if (ops->hdev) | |
| return -EINVAL; | |
| hdev = hid_get_device(ops->hid_id); | |
| if (IS_ERR(hdev)) | |
| return PTR_ERR(hdev); | |
| ops->hdev = hdev; | |
| mutex_lock(&hdev->bpf.prog_list_lock); | |
| count = list_count_nodes(&hdev->bpf.prog_list); | |
| if (count >= HID_BPF_MAX_PROGS_PER_DEV) { | |
| err = -E2BIG; | |
| goto out_unlock; | |
| } | |
| if (ops->hid_rdesc_fixup) { | |
| if (hdev->bpf.rdesc_ops) { | |
| err = -EINVAL; | |
| goto out_unlock; | |
| } | |
| hdev->bpf.rdesc_ops = ops; | |
| } | |
| if (ops->hid_device_event) { | |
| err = hid_bpf_allocate_event_data(hdev); | |
| if (err) | |
| goto out_unlock; | |
| } | |
| if (ops->flags & BPF_F_BEFORE) | |
| list_add_rcu(&ops->list, &hdev->bpf.prog_list); | |
| else | |
| list_add_tail_rcu(&ops->list, &hdev->bpf.prog_list); | |
| synchronize_srcu(&hdev->bpf.srcu); | |
| out_unlock: | |
| mutex_unlock(&hdev->bpf.prog_list_lock); | |
| if (err) { | |
| if (hdev->bpf.rdesc_ops == ops) | |
| hdev->bpf.rdesc_ops = NULL; | |
| hid_put_device(hdev); | |
| } else if (ops->hid_rdesc_fixup) { | |
| hid_bpf_reconnect(hdev); | |
| } | |
| return err; | |
| } | |
| static void hid_bpf_unreg(void *kdata, struct bpf_link *link) | |
| { | |
| struct hid_bpf_ops *ops = kdata; | |
| struct hid_device *hdev; | |
| bool reconnect = false; | |
| hdev = ops->hdev; | |
| /* check if __hid_bpf_ops_destroy_device() has been called */ | |
| if (!hdev) | |
| return; | |
| mutex_lock(&hdev->bpf.prog_list_lock); | |
| list_del_rcu(&ops->list); | |
| synchronize_srcu(&hdev->bpf.srcu); | |
| ops->hdev = NULL; | |
| reconnect = hdev->bpf.rdesc_ops == ops; | |
| if (reconnect) | |
| hdev->bpf.rdesc_ops = NULL; | |
| mutex_unlock(&hdev->bpf.prog_list_lock); | |
| if (reconnect) | |
| hid_bpf_reconnect(hdev); | |
| hid_put_device(hdev); | |
| } | |
| static int __hid_bpf_device_event(struct hid_bpf_ctx *ctx, enum hid_report_type type, u64 source) | |
| { | |
| return 0; | |
| } | |
| static int __hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx) | |
| { | |
| return 0; | |
| } | |
| static int __hid_bpf_hw_request(struct hid_bpf_ctx *ctx, unsigned char reportnum, | |
| enum hid_report_type rtype, enum hid_class_request reqtype, | |
| u64 source) | |
| { | |
| return 0; | |
| } | |
| static int __hid_bpf_hw_output_report(struct hid_bpf_ctx *ctx, u64 source) | |
| { | |
| return 0; | |
| } | |
| static struct hid_bpf_ops __bpf_hid_bpf_ops = { | |
| .hid_device_event = __hid_bpf_device_event, | |
| .hid_rdesc_fixup = __hid_bpf_rdesc_fixup, | |
| .hid_hw_request = __hid_bpf_hw_request, | |
| .hid_hw_output_report = __hid_bpf_hw_output_report, | |
| }; | |
| static struct bpf_struct_ops bpf_hid_bpf_ops = { | |
| .verifier_ops = &hid_bpf_verifier_ops, | |
| .init = hid_bpf_ops_init, | |
| .check_member = hid_bpf_ops_check_member, | |
| .init_member = hid_bpf_ops_init_member, | |
| .reg = hid_bpf_reg, | |
| .unreg = hid_bpf_unreg, | |
| .name = "hid_bpf_ops", | |
| .cfi_stubs = &__bpf_hid_bpf_ops, | |
| .owner = THIS_MODULE, | |
| }; | |
| void __hid_bpf_ops_destroy_device(struct hid_device *hdev) | |
| { | |
| struct hid_bpf_ops *e; | |
| rcu_read_lock(); | |
| list_for_each_entry_rcu(e, &hdev->bpf.prog_list, list) { | |
| hid_put_device(hdev); | |
| e->hdev = NULL; | |
| } | |
| rcu_read_unlock(); | |
| } | |
| static int __init hid_bpf_struct_ops_init(void) | |
| { | |
| return register_bpf_struct_ops(&bpf_hid_bpf_ops, hid_bpf_ops); | |
| } | |
| late_initcall(hid_bpf_struct_ops_init); |