Skip to content

aya-ebpf: add HID-BPF support#1445

Draft
heeen wants to merge 24 commits intoaya-rs:mainfrom
heeen:hid-bpf
Draft

aya-ebpf: add HID-BPF support#1445
heeen wants to merge 24 commits intoaya-rs:mainfrom
heeen:hid-bpf

Conversation

@heeen
Copy link

@heeen heeen commented Jan 17, 2026

Summary

Adds support for HID-BPF programs, allowing BPF programs to intercept and modify HID device events.

  • Add HidBpfContext for HID-BPF programs
  • Add kfunc declarations for HID-BPF kernel functions (hid_bpf_get_data, hid_bpf_hw_request, etc.)
  • Add safe abstractions for working with HID report data
  • Fix bpf_printk to pass args by value instead of by pointer

Dependencies

This PR depends on #1444 (struct_ops support) and should be merged after it.

Test plan

  • Integration test included (test/integration-test/src/tests/hid_bpf.rs)
  • Tested with actual HID device using uhid

This change is Reviewable

@netlify
Copy link

netlify bot commented Jan 17, 2026

Deploy Preview for aya-rs-docs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 4e6d786
🔍 Latest deploy log https://app.netlify.com/projects/aya-rs-docs/deploys/696f90454d87680008c2d1aa
😎 Deploy Preview https://deploy-preview-1445--aya-rs-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@heeen heeen force-pushed the hid-bpf branch 7 times, most recently from 1a4cbc1 to 51c4fa0 Compare January 17, 2026 23:02
heeen and others added 22 commits January 20, 2026 14:48
Add an integration test that verifies bpf_printk correctly passes
various integer types (u8, u16, u32, u64, i32) to the kernel's
bpf_trace_printk helper.

The test:
- Attaches a uprobe that triggers bpf_printk calls with known values
- Reads from /sys/kernel/debug/tracing/trace to verify output
- Checks that all values are printed correctly (not garbage)

This test exposes a bug in the current PrintkArg implementation where
using [u8; 8] instead of u64 causes incorrect values to be passed to
the variadic bpf_trace_printk function due to C ABI differences.
Change PrintkArg from wrapping [u8; 8] to wrapping u64 directly.

The C ABI for variadic functions handles arrays differently than scalar
types. When PrintkArg([u8; 8]) is passed to the variadic bpf_trace_printk
helper, the array may be passed incorrectly (e.g., as a pointer or with
wrong register usage), resulting in garbage values being printed.

Using u64 directly ensures the value is passed by value in a register,
matching what bpf_trace_printk expects for its %d, %u, %x, %lx format
specifiers.

This was discovered when real-world HID-BPF programs printed values like
846018776 instead of 0, or 937211784 instead of the expected small
integers.
Add support for parsing `struct_ops/` and `struct_ops.s/` ELF section
names as program sections. This enables loading BPF struct_ops programs
such as those used by HID-BPF and sched_ext.

The `StructOps` variant includes a `sleepable` field to distinguish
between regular and sleepable struct_ops programs (the latter use
the `struct_ops.s/` section prefix).

Refs: aya-rs#967
Add the `StructOps` program type for loading BPF struct_ops programs.
Struct_ops programs implement kernel interfaces like `hid_bpf_ops` for
HID device handling or `sched_ext_ops` for custom schedulers.

The `load()` method takes the struct type name (e.g., "hid_bpf_ops") and
BTF information to resolve the struct type ID needed by the kernel.

Refs: aya-rs#967
Add `StructOpsContext` for struct_ops programs and the `#[struct_ops]`
attribute macro for marking functions as struct_ops callbacks.

The macro supports:
- `name` parameter to override the callback name (defaults to function name)
- `sleepable` flag for sleepable programs

Example usage:
```rust
#[struct_ops]
fn my_callback(ctx: StructOpsContext) -> i32 {
    0
}
```

Refs: aya-rs#967
Parse struct_ops maps from ELF with all necessary metadata:
- btf_vmlinux_value_type_id for kernel type resolution
- Kernel wrapper struct size for map creation
- data_offset for program data placement
- Member name extraction from section name
- expected_attach_type set to member index
- func_info population from relocations

This enables the loader to properly create struct_ops maps
that match the kernel's expectations for the BPF struct_ops
subsystem.

struct_ops: set expected_attach_type to member index

For struct_ops programs, expected_attach_type must be the member index
in the struct (e.g., 4 for hid_rdesc_fixup), not BPF_STRUCT_OPS (44).
The kernel uses this to know which callback slot the program implements.

- Add Btf::struct_member_index() to look up member index by name
- Update StructOps::load() to take member_name parameter and look up index
- Update Ebpf::load_struct_ops() to pass prog_name as member_name

struct_ops: populate func_info from relocations and add load_struct_ops

- Parse relocations in struct_ops section to find program function
  pointers and their offsets in the struct
- Add Ebpf::load_struct_ops() method that:
  1. Loads struct_ops programs with their struct name and BTF
  2. Fills program FDs into struct_ops map data at correct offsets
- This is required because libbpf skeleton fills prog FDs before
  calling bpf_map_update_elem for struct_ops registration
Add test eBPF program and integration tests for struct_ops program type:
- struct_ops_test.rs: minimal eBPF callback program
- tests/struct_ops.rs: tests for parsing and program type verification

Note: Running these tests requires bpf-linker to be installed.
Add support for BPF_PROG_TYPE_SYSCALL programs which can be invoked
directly via the bpf() syscall. This is used for HID-BPF probe functions
that determine whether to attach to a HID device.

- Add ProgramSection::Syscall variant in aya-obj/obj.rs
- Add "syscall" section name parsing
- Add syscall.rs with Syscall program type
- Add Program::Syscall variant and all required method implementations
- Handle ProgramSection::Syscall in bpf.rs program creation
Extracts the link type definitions and conversions from define_link_wrapper!
into a separate define_link_types! macro. This allows programs like StructOps
(where the link is created via StructOpsMap::attach() rather than a program
method) to reuse the same link type infrastructure.

define_link_wrapper! now calls define_link_types! internally and only adds
the detach() and take_link() methods that require a program type parameter.
Adds comprehensive unit tests for StructOpsMap covering:
- Construction and type validation (try_from_ok, try_from_wrong_map)
- Accessor methods (func_info, type_name, is_link)
- Field setting with bounds checking and data_offset handling
- Registration and attachment with mocked syscalls

Also extracts helper functions to improve testability:
- write_bytes_at_offset() in maps/struct_ops.rs for DRY field setting
- write_struct_ops_fd() in bpf.rs for FD writing with bounds checking

These tests address the review feedback about zero test coverage for
struct_ops functionality.
Add comprehensive safe Rust abstractions for HID-BPF programming:

- HidBpfData<'a>: Bounds-checked buffer access with get(), set(),
  copy_from_slice(), and starts_with() methods. Tied to context lifetime.

- AllocatedContext: RAII guard for hid_bpf_allocate_context that auto-
  releases on drop, preventing resource leaks on early returns.

- HidBpfContext: Enhanced with safe data() method returning HidBpfData
  instead of raw pointers.

- kfunc module: Low-level kernel function bindings using inline assembly
  for reliable kfunc invocation. Includes hid_bpf_get_data,
  hid_bpf_allocate_context, hid_bpf_release_context, hid_bpf_hw_request.

- HidReportType/HidClassRequest enums: Type-safe report and request types.

This enables writing HID-BPF programs with zero unsafe blocks in callback
bodies - all memory safety is enforced at compile time through the
wrapper types.
Add a test program demonstrating the HID-BPF macros for device event,
report descriptor fixup, and hardware request handlers.
The previous implementation used `std::mem::transmute` to store a struct
member index (u32) in the `expected_attach_type` field, which is typed as
`bpf_attach_type`. This created an invalid enum value, which is undefined
behavior in Rust.

This commit introduces an `ExpectedAttachType` enum that explicitly
distinguishes between:
- `AttachType(bpf_attach_type)` for normal program types
- `StructOpsMemberIndex(u32)` for struct_ops member indices

The enum provides type safety within Rust while the `as_raw()` method
extracts the plain u32 value for kernel syscalls. The kernel never sees
the enum - only the extracted integer value.
Replace the hardcoded 0x2000 value with the BPF_F_LINK constant from
the generated kernel bindings for better maintainability and clarity.
heeen added 2 commits January 20, 2026 15:04
Previously, when a kfunc (external kernel function) could not be
resolved in the kernel BTF, the code would silently log a debug message
and leave the instruction unresolved. This would later cause a confusing
kernel verifier error at program load time.

Now the code returns a clear `RelocationError::UnknownKfunc` error that
identifies the problematic kfunc by name, making it easier to diagnose
issues like missing kernel functions or incorrect BTF.
Expand the struct_ops integration tests to be more meaningful:

- Add multiple struct_ops callbacks to test multi-program parsing
- Add test for renamed callbacks using the name attribute
- Add test for sleepable struct_ops sections
- Verify program type detection works for all variants
- Verify member_name() returns the section suffix correctly
- Add BTF availability tests to verify kernel support
- Add BTF member lookup tests for struct_ops type resolution
- Add error handling test for non-existent types

These tests verify the struct_ops parsing, program extraction, and BTF
integration work correctly, even without full kernel struct_ops support.
@tamird tamird marked this pull request as draft February 17, 2026 16:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant