// 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[] = {
0 x05, 0 x01, /* USAGE_PAGE (Generic Desktop) */
0 x09, 0 x32, /* USAGE (Z) */
0 x95, 0 x01, /* REPORT_COUNT (1) */
0 x81, 0 x06, /* INPUT (Data,Var,Rel) */
0 x06, 0 x00, 0 xff, /* Usage Page (Vendor Defined Page 1) */
0 x19, 0 x01, /* USAGE_MINIMUM (1) */
0 x29, 0 x03, /* USAGE_MAXIMUM (3) */
0 x15, 0 x00, /* LOGICAL_MINIMUM (0) */
0 x25, 0 x01, /* LOGICAL_MAXIMUM (1) */
0 x95, 0 x03, /* REPORT_COUNT (3) */
0 x75, 0 x01, /* REPORT_SIZE (1) */
0 x91, 0 x02, /* Output (Data,Var,Abs) */
0 x95, 0 x01, /* REPORT_COUNT (1) */
0 x75, 0 x05, /* REPORT_SIZE (5) */
0 x91, 0 x01, /* Output (Cnst,Var,Abs) */
0 x06, 0 x00, 0 xff, /* Usage Page (Vendor Defined Page 1) */
0 x19, 0 x06, /* USAGE_MINIMUM (6) */
0 x29, 0 x08, /* USAGE_MAXIMUM (8) */
0 x15, 0 x00, /* LOGICAL_MINIMUM (0) */
0 x25, 0 x01, /* LOGICAL_MAXIMUM (1) */
0 x95, 0 x03, /* REPORT_COUNT (3) */
0 x75, 0 x01, /* REPORT_SIZE (1) */
0 xb1, 0 x02, /* Feature (Data,Var,Abs) */
0 x95, 0 x01, /* REPORT_COUNT (1) */
0 x75, 0 x05, /* REPORT_SIZE (5) */
0 x91, 0 x01, /* Output (Cnst,Var,Abs) */
0 xc0, /* END_COLLECTION */
0 xc0, /* 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 ] = 0 x42;
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,
};
Messung V0.5 in Prozent C=96 H=88 G=91
¤ Dauer der Verarbeitung: 0.23 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland