// 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; 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[10]; 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/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, };