| // SPDX-License-Identifier: GPL-2.0 | |
| /* Copyright (c) 2022 Red hat */ | |
| #include "hid_bpf_helpers.h" | |
| char _license[] SEC("license") = "GPL"; | |
| struct attach_prog_args { | |
| int prog_fd; | |
| unsigned int hid; | |
| int retval; | |
| int insert_head; | |
| }; | |
| __u64 callback_check = 52; | |
| __u64 callback2_check = 52; | |
| __u64 get_data_overflow_check; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */); | |
| if (!rw_data) | |
| return 0; /* EPERM check */ | |
| callback_check = rw_data[1]; | |
| rw_data[2] = rw_data[1] + 5; | |
| return hid_ctx->size; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops first_event = { | |
| .hid_device_event = (void *)hid_first_event, | |
| .hid_id = 2, | |
| }; | |
| int __hid_subprog_first_event(struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */); | |
| if (!rw_data) | |
| return 0; /* EPERM check */ | |
| rw_data[2] = rw_data[1] + 5; | |
| return hid_ctx->size; | |
| } | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_subprog_first_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| return __hid_subprog_first_event(hid_ctx, type); | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops subprog_first_event = { | |
| .hid_device_event = (void *)hid_subprog_first_event, | |
| .hid_id = 2, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_second_event, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4 /* size */); | |
| if (!rw_data) | |
| return 0; /* EPERM check */ | |
| rw_data[3] = rw_data[2] + 5; | |
| return hid_ctx->size; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops second_event = { | |
| .hid_device_event = (void *)hid_second_event, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_change_report_id, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *rw_data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 3 /* size */); | |
| if (!rw_data) | |
| return 0; /* EPERM check */ | |
| rw_data[0] = 2; | |
| return 9; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops change_report_id = { | |
| .hid_device_event = (void *)hid_change_report_id, | |
| }; | |
| struct hid_hw_request_syscall_args { | |
| /* data needs to come at offset 0 so we can use it in calls */ | |
| __u8 data[128]; | |
| unsigned int hid; | |
| int retval; | |
| size_t size; | |
| enum hid_report_type type; | |
| __u8 request_type; | |
| }; | |
| SEC("syscall") | |
| int hid_user_raw_request(struct hid_hw_request_syscall_args *args) | |
| { | |
| struct hid_bpf_ctx *ctx; | |
| const size_t size = args->size; | |
| int i, ret = 0; | |
| if (size > sizeof(args->data)) | |
| return -7; /* -E2BIG */ | |
| ctx = hid_bpf_allocate_context(args->hid); | |
| if (!ctx) | |
| return -1; /* EPERM check */ | |
| ret = hid_bpf_hw_request(ctx, | |
| args->data, | |
| size, | |
| args->type, | |
| args->request_type); | |
| args->retval = ret; | |
| hid_bpf_release_context(ctx); | |
| return 0; | |
| } | |
| SEC("syscall") | |
| int hid_user_output_report(struct hid_hw_request_syscall_args *args) | |
| { | |
| struct hid_bpf_ctx *ctx; | |
| const size_t size = args->size; | |
| int i, ret = 0; | |
| if (size > sizeof(args->data)) | |
| return -7; /* -E2BIG */ | |
| ctx = hid_bpf_allocate_context(args->hid); | |
| if (!ctx) | |
| return -1; /* EPERM check */ | |
| ret = hid_bpf_hw_output_report(ctx, | |
| args->data, | |
| size); | |
| args->retval = ret; | |
| hid_bpf_release_context(ctx); | |
| return 0; | |
| } | |
| SEC("syscall") | |
| int hid_user_input_report(struct hid_hw_request_syscall_args *args) | |
| { | |
| struct hid_bpf_ctx *ctx; | |
| const size_t size = args->size; | |
| int i, ret = 0; | |
| if (size > sizeof(args->data)) | |
| return -7; /* -E2BIG */ | |
| ctx = hid_bpf_allocate_context(args->hid); | |
| if (!ctx) | |
| return -1; /* EPERM check */ | |
| ret = hid_bpf_input_report(ctx, HID_INPUT_REPORT, args->data, size); | |
| args->retval = ret; | |
| hid_bpf_release_context(ctx); | |
| return 0; | |
| } | |
| static const __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 */ | |
| }; | |
| /* | |
| * the following program is marked as sleepable (struct_ops.s). | |
| * This is not strictly mandatory but is a nice test for | |
| * sleepable struct_ops | |
| */ | |
| SEC("?struct_ops.s/hid_rdesc_fixup") | |
| int BPF_PROG(hid_rdesc_fixup, struct hid_bpf_ctx *hid_ctx) | |
| { | |
| __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 4096 /* size */); | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| callback2_check = data[4]; | |
| /* insert rdesc at offset 73 */ | |
| __builtin_memcpy(&data[73], rdesc, sizeof(rdesc)); | |
| /* Change Usage Vendor globally */ | |
| data[4] = 0x42; | |
| return sizeof(rdesc) + 73; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops rdesc_fixup = { | |
| .hid_rdesc_fixup = (void *)hid_rdesc_fixup, | |
| }; | |
| SEC("?struct_ops.s/hid_rdesc_fixup") | |
| int BPF_PROG(hid_rdesc_fixup_get_data_overflow, struct hid_bpf_ctx *hid_ctx) | |
| { | |
| if (!hid_bpf_get_data(hid_ctx, 2 /* offset */, ~0ULL /* size */)) | |
| get_data_overflow_check = 1; | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops rdesc_fixup_get_data_overflow = { | |
| .hid_rdesc_fixup = (void *)hid_rdesc_fixup_get_data_overflow, | |
| }; | |
| SEC("?struct_ops.s/hid_rdesc_fixup") | |
| int BPF_PROG(hid_rdesc_fixup_change_uniq_name_phys, struct hid_bpf_ctx *hid_ctx) | |
| { | |
| #define HID_BPF_MEMCPY(target, str) \ | |
| __builtin_memcpy(target, str, sizeof(str)) | |
| HID_BPF_MEMCPY(hid_ctx->hid->name, "name coming from bpf"); | |
| HID_BPF_MEMCPY(hid_ctx->hid->uniq, "uniq:coming:from:bpf"); | |
| /* hid_bpf relies on a phys being a rand % 1024 */ | |
| for (int i = 0; i < 5; i++) { | |
| if (!hid_ctx->hid->phys[i]) { | |
| HID_BPF_MEMCPY(hid_ctx->hid->phys + i, " phys:coming:from:bpf"); | |
| break; | |
| } | |
| } | |
| #undef HID_BPF_MEMCPY | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops rdesc_fixup_change_uniq_name_phys = { | |
| .hid_rdesc_fixup = (void *)hid_rdesc_fixup_change_uniq_name_phys, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_test_insert1, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *data = hid_bpf_get_data(hid_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(".struct_ops.link") | |
| struct hid_bpf_ops test_insert1 = { | |
| .hid_device_event = (void *)hid_test_insert1, | |
| .flags = BPF_F_BEFORE, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_test_insert2, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *data = hid_bpf_get_data(hid_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(".struct_ops.link") | |
| struct hid_bpf_ops test_insert2 = { | |
| .hid_device_event = (void *)hid_test_insert2, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_test_insert3, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *data = hid_bpf_get_data(hid_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; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_insert3 = { | |
| .hid_device_event = (void *)hid_test_insert3, | |
| }; | |
| SEC("?struct_ops/hid_hw_request") | |
| int BPF_PROG(hid_test_filter_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum, | |
| enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) | |
| { | |
| return -20; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_filter_raw_request = { | |
| .hid_hw_request = (void *)hid_test_filter_raw_request, | |
| }; | |
| static struct file *current_file; | |
| SEC("fentry/hidraw_open") | |
| int BPF_PROG(hidraw_open, struct inode *inode, struct file *file) | |
| { | |
| current_file = file; | |
| return 0; | |
| } | |
| SEC("?struct_ops.s/hid_hw_request") | |
| int BPF_PROG(hid_test_hidraw_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum, | |
| enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) | |
| { | |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); | |
| int ret; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| /* check if the incoming request comes from our hidraw operation */ | |
| if (source == (__u64)current_file) { | |
| data[0] = reportnum; | |
| ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype); | |
| if (ret != 2) | |
| return -1; | |
| data[0] = reportnum + 1; | |
| data[1] = reportnum + 2; | |
| data[2] = reportnum + 3; | |
| return 3; | |
| } | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_hidraw_raw_request = { | |
| .hid_hw_request = (void *)hid_test_hidraw_raw_request, | |
| }; | |
| SEC("?struct_ops.s/hid_hw_request") | |
| int BPF_PROG(hid_test_infinite_loop_raw_request, struct hid_bpf_ctx *hctx, unsigned char reportnum, | |
| enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) | |
| { | |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); | |
| int ret; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| /* always forward the request as-is to the device, hid-bpf should prevent | |
| * infinite loops. | |
| */ | |
| data[0] = reportnum; | |
| ret = hid_bpf_hw_request(hctx, data, 2, rtype, reqtype); | |
| if (ret == 2) | |
| return 3; | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_infinite_loop_raw_request = { | |
| .hid_hw_request = (void *)hid_test_infinite_loop_raw_request, | |
| }; | |
| SEC("?struct_ops/hid_hw_output_report") | |
| int BPF_PROG(hid_test_filter_output_report, struct hid_bpf_ctx *hctx, unsigned char reportnum, | |
| enum hid_report_type rtype, enum hid_class_request reqtype, __u64 source) | |
| { | |
| return -25; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_filter_output_report = { | |
| .hid_hw_output_report = (void *)hid_test_filter_output_report, | |
| }; | |
| SEC("?struct_ops.s/hid_hw_output_report") | |
| int BPF_PROG(hid_test_hidraw_output_report, struct hid_bpf_ctx *hctx, __u64 source) | |
| { | |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); | |
| int ret; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| /* check if the incoming request comes from our hidraw operation */ | |
| if (source == (__u64)current_file) | |
| return hid_bpf_hw_output_report(hctx, data, 2); | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_hidraw_output_report = { | |
| .hid_hw_output_report = (void *)hid_test_hidraw_output_report, | |
| }; | |
| SEC("?struct_ops.s/hid_hw_output_report") | |
| int BPF_PROG(hid_test_infinite_loop_output_report, struct hid_bpf_ctx *hctx, __u64 source) | |
| { | |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 3 /* size */); | |
| int ret; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| /* always forward the request as-is to the device, hid-bpf should prevent | |
| * infinite loops. | |
| */ | |
| ret = hid_bpf_hw_output_report(hctx, data, 2); | |
| if (ret == 2) | |
| return 2; | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_infinite_loop_output_report = { | |
| .hid_hw_output_report = (void *)hid_test_infinite_loop_output_report, | |
| }; | |
| struct elem { | |
| struct bpf_wq work; | |
| }; | |
| struct { | |
| __uint(type, BPF_MAP_TYPE_HASH); | |
| __uint(max_entries, 1); | |
| __type(key, int); | |
| __type(value, struct elem); | |
| } hmap SEC(".maps"); | |
| static int wq_cb_sleepable(void *map, int *key, void *work) | |
| { | |
| __u8 buf[9] = {2, 3, 4, 5, 6, 7, 8, 9, 10}; | |
| struct hid_bpf_ctx *hid_ctx; | |
| hid_ctx = hid_bpf_allocate_context(*key); | |
| if (!hid_ctx) | |
| return 0; /* EPERM check */ | |
| hid_bpf_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf)); | |
| hid_bpf_release_context(hid_ctx); | |
| return 0; | |
| } | |
| static int test_inject_input_report_callback(int *key) | |
| { | |
| struct elem init = {}, *val; | |
| struct bpf_wq *wq; | |
| if (bpf_map_update_elem(&hmap, key, &init, 0)) | |
| return -1; | |
| val = bpf_map_lookup_elem(&hmap, key); | |
| if (!val) | |
| return -2; | |
| wq = &val->work; | |
| if (bpf_wq_init(wq, &hmap, 0) != 0) | |
| return -3; | |
| if (bpf_wq_set_callback(wq, wq_cb_sleepable, 0)) | |
| return -4; | |
| if (bpf_wq_start(wq, 0)) | |
| return -5; | |
| return 0; | |
| } | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_test_multiply_events_wq, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */); | |
| int hid = hid_ctx->hid->id; | |
| int ret; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| if (data[0] != 1) | |
| return 0; | |
| ret = test_inject_input_report_callback(&hid); | |
| if (ret) | |
| return ret; | |
| data[1] += 5; | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_multiply_events_wq = { | |
| .hid_device_event = (void *)hid_test_multiply_events_wq, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_test_multiply_events, struct hid_bpf_ctx *hid_ctx, enum hid_report_type type) | |
| { | |
| __u8 *data = hid_bpf_get_data(hid_ctx, 0 /* offset */, 9 /* size */); | |
| __u8 buf[9]; | |
| int ret; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| if (data[0] != 1) | |
| return 0; | |
| /* | |
| * we have to use an intermediate buffer as hid_bpf_input_report | |
| * will memset data to \0 | |
| */ | |
| __builtin_memcpy(buf, data, sizeof(buf)); | |
| buf[0] = 2; | |
| buf[1] += 5; | |
| ret = hid_bpf_try_input_report(hid_ctx, HID_INPUT_REPORT, buf, sizeof(buf)); | |
| if (ret < 0) | |
| return ret; | |
| /* | |
| * In real world we should reset the original buffer as data might be garbage now, | |
| * but it actually now has the content of 'buf' | |
| */ | |
| data[1] += 5; | |
| return 9; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_multiply_events = { | |
| .hid_device_event = (void *)hid_test_multiply_events, | |
| }; | |
| SEC("?struct_ops/hid_device_event") | |
| int BPF_PROG(hid_test_infinite_loop_input_report, struct hid_bpf_ctx *hctx, | |
| enum hid_report_type report_type, __u64 source) | |
| { | |
| __u8 *data = hid_bpf_get_data(hctx, 0 /* offset */, 6 /* size */); | |
| __u8 buf[6]; | |
| if (!data) | |
| return 0; /* EPERM check */ | |
| /* | |
| * we have to use an intermediate buffer as hid_bpf_input_report | |
| * will memset data to \0 | |
| */ | |
| __builtin_memcpy(buf, data, sizeof(buf)); | |
| /* always forward the request as-is to the device, hid-bpf should prevent | |
| * infinite loops. | |
| * the return value is ignored so the event is passing to userspace. | |
| */ | |
| hid_bpf_try_input_report(hctx, report_type, buf, sizeof(buf)); | |
| /* each time we process the event, we increment by one data[1]: | |
| * after each successful call to hid_bpf_try_input_report, buf | |
| * has been memcopied into data by the kernel. | |
| */ | |
| data[1] += 1; | |
| return 0; | |
| } | |
| SEC(".struct_ops.link") | |
| struct hid_bpf_ops test_infinite_loop_input_report = { | |
| .hid_device_event = (void *)hid_test_infinite_loop_input_report, | |
| }; |