Skip to content

Commit

Permalink
HID: initial BPF implementation
Browse files Browse the repository at this point in the history
Declare an entry point that can use fmod_ret BPF programs, and
also an API to access and change the incoming data.

A simpler implementation would consist in just calling
hid_bpf_device_event() for any incoming event and let users deal
with the fact that they will be called for any event of any device.

The goal of HID-BPF is to partially replace drivers, so this situation
can be problematic because we might have programs which will step on
each other toes.

For that, we add a new API hid_bpf_attach_prog() that can be called
from a syscall and we manually deal with a jump table in hid-bpf.

Whenever we add a program to the jump table (in other words, when we
attach a program to a HID device), we keep the number of time we added
this program in the jump table so we can release it whenever there are
no other users.

HID devices have an RCU protected list of available programs in the
jump table, and those programs are called one after the other thanks
to bpf_tail_call().

To achieve the detection of users losing their fds on the programs we
attached, we add 2 tracing facilities on bpf_prog_release() (for when
a fd is closed) and bpf_free_inode() (for when a pinned program gets
unpinned).

Reviewed-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
  • Loading branch information
bentiss authored and Jiri Kosina committed Nov 15, 2022
1 parent 25621bc commit f5c27da
Show file tree
Hide file tree
Showing 14 changed files with 1,804 additions and 0 deletions.
2 changes: 2 additions & 0 deletions drivers/hid/Kconfig
Expand Up @@ -1284,6 +1284,8 @@ config HID_KUNIT_TEST

endmenu

source "drivers/hid/bpf/Kconfig"

endif # HID

source "drivers/hid/usbhid/Kconfig"
Expand Down
2 changes: 2 additions & 0 deletions drivers/hid/Makefile
Expand Up @@ -5,6 +5,8 @@
hid-y := hid-core.o hid-input.o hid-quirks.o
hid-$(CONFIG_DEBUG_FS) += hid-debug.o

obj-$(CONFIG_HID_BPF) += bpf/

obj-$(CONFIG_HID) += hid.o
obj-$(CONFIG_UHID) += uhid.o

Expand Down
17 changes: 17 additions & 0 deletions drivers/hid/bpf/Kconfig
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
menu "HID-BPF support"

config HID_BPF
bool "HID-BPF support"
default HID_SUPPORT
depends on BPF && BPF_SYSCALL
help
This option allows to support eBPF programs on the HID subsystem.
eBPF programs can fix HID devices in a lighter way than a full
kernel patch and allow a lot more flexibility.

For documentation, see Documentation/hid/hid-bpf.rst

If unsure, say Y.

endmenu
11 changes: 11 additions & 0 deletions drivers/hid/bpf/Makefile
@@ -0,0 +1,11 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for HID-BPF
#

LIBBPF_INCLUDE = $(srctree)/tools/lib

obj-$(CONFIG_HID_BPF) += hid_bpf.o
CFLAGS_hid_bpf_dispatch.o += -I$(LIBBPF_INCLUDE)
CFLAGS_hid_bpf_jmp_table.o += -I$(LIBBPF_INCLUDE)
hid_bpf-objs += hid_bpf_dispatch.o hid_bpf_jmp_table.o
93 changes: 93 additions & 0 deletions drivers/hid/bpf/entrypoints/Makefile
@@ -0,0 +1,93 @@
# SPDX-License-Identifier: GPL-2.0
OUTPUT := .output
abs_out := $(abspath $(OUTPUT))

CLANG ?= clang
LLC ?= llc
LLVM_STRIP ?= llvm-strip

TOOLS_PATH := $(abspath ../../../../tools)
BPFTOOL_SRC := $(TOOLS_PATH)/bpf/bpftool
BPFTOOL_OUTPUT := $(abs_out)/bpftool
DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)/bootstrap/bpftool
BPFTOOL ?= $(DEFAULT_BPFTOOL)

LIBBPF_SRC := $(TOOLS_PATH)/lib/bpf
LIBBPF_OUTPUT := $(abs_out)/libbpf
LIBBPF_DESTDIR := $(LIBBPF_OUTPUT)
LIBBPF_INCLUDE := $(LIBBPF_DESTDIR)/include
BPFOBJ := $(LIBBPF_OUTPUT)/libbpf.a

INCLUDES := -I$(OUTPUT) -I$(LIBBPF_INCLUDE) -I$(TOOLS_PATH)/include/uapi
CFLAGS := -g -Wall

VMLINUX_BTF_PATHS ?= $(if $(O),$(O)/vmlinux) \
$(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \
../../../../vmlinux \
/sys/kernel/btf/vmlinux \
/boot/vmlinux-$(shell uname -r)
VMLINUX_BTF ?= $(abspath $(firstword $(wildcard $(VMLINUX_BTF_PATHS))))
ifeq ($(VMLINUX_BTF),)
$(error Cannot find a vmlinux for VMLINUX_BTF at any of "$(VMLINUX_BTF_PATHS)")
endif

ifeq ($(V),1)
Q =
msg =
else
Q = @
msg = @printf ' %-8s %s%s\n' "$(1)" "$(notdir $(2))" "$(if $(3), $(3))";
MAKEFLAGS += --no-print-directory
submake_extras := feature_display=0
endif

.DELETE_ON_ERROR:

.PHONY: all clean

all: entrypoints.lskel.h

clean:
$(call msg,CLEAN)
$(Q)rm -rf $(OUTPUT) entrypoints

entrypoints.lskel.h: $(OUTPUT)/entrypoints.bpf.o | $(BPFTOOL)
$(call msg,GEN-SKEL,$@)
$(Q)$(BPFTOOL) gen skeleton -L $< > $@


$(OUTPUT)/entrypoints.bpf.o: entrypoints.bpf.c $(OUTPUT)/vmlinux.h $(BPFOBJ) | $(OUTPUT)
$(call msg,BPF,$@)
$(Q)$(CLANG) -g -O2 -target bpf $(INCLUDES) \
-c $(filter %.c,$^) -o $@ && \
$(LLVM_STRIP) -g $@

$(OUTPUT)/vmlinux.h: $(VMLINUX_BTF) $(BPFTOOL) | $(INCLUDE_DIR)
ifeq ($(VMLINUX_H),)
$(call msg,GEN,,$@)
$(Q)$(BPFTOOL) btf dump file $(VMLINUX_BTF) format c > $@
else
$(call msg,CP,,$@)
$(Q)cp "$(VMLINUX_H)" $@
endif

$(OUTPUT) $(LIBBPF_OUTPUT) $(BPFTOOL_OUTPUT):
$(call msg,MKDIR,$@)
$(Q)mkdir -p $@

$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(LIBBPF_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) \
OUTPUT=$(abspath $(dir $@))/ prefix= \
DESTDIR=$(LIBBPF_DESTDIR) $(abspath $@) install_headers

ifeq ($(CROSS_COMPILE),)
$(DEFAULT_BPFTOOL): $(BPFOBJ) | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
OUTPUT=$(BPFTOOL_OUTPUT)/ \
LIBBPF_BOOTSTRAP_OUTPUT=$(LIBBPF_OUTPUT)/ \
LIBBPF_BOOTSTRAP_DESTDIR=$(LIBBPF_DESTDIR)/ bootstrap
else
$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT)
$(Q)$(MAKE) $(submake_extras) -C $(BPFTOOL_SRC) \
OUTPUT=$(BPFTOOL_OUTPUT)/ bootstrap
endif
4 changes: 4 additions & 0 deletions drivers/hid/bpf/entrypoints/README
@@ -0,0 +1,4 @@
WARNING:
If you change "entrypoints.bpf.c" do "make -j" in this directory to rebuild "entrypoints.skel.h".
Make sure to have clang 10 installed.
See Documentation/bpf/bpf_devel_QA.rst
66 changes: 66 additions & 0 deletions drivers/hid/bpf/entrypoints/entrypoints.bpf.c
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Benjamin Tissoires */

#include ".output/vmlinux.h"
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

#define HID_BPF_MAX_PROGS 1024

extern bool call_hid_bpf_prog_release(u64 prog, int table_cnt) __ksym;

struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, HID_BPF_MAX_PROGS);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} hid_jmp_table SEC(".maps");

struct {
__uint(type, BPF_MAP_TYPE_HASH);
__uint(max_entries, HID_BPF_MAX_PROGS * HID_BPF_PROG_TYPE_MAX);
__type(key, void *);
__type(value, __u8);
} progs_map SEC(".maps");

SEC("fmod_ret/__hid_bpf_tail_call")
int BPF_PROG(hid_tail_call, struct hid_bpf_ctx *hctx)
{
bpf_tail_call(ctx, &hid_jmp_table, hctx->index);

return 0;
}

static void release_prog(u64 prog)
{
u8 *value;

value = bpf_map_lookup_elem(&progs_map, &prog);
if (!value)
return;

if (call_hid_bpf_prog_release(prog, *value))
bpf_map_delete_elem(&progs_map, &prog);
}

SEC("fexit/bpf_prog_release")
int BPF_PROG(hid_prog_release, struct inode *inode, struct file *filp)
{
u64 prog = (u64)filp->private_data;

release_prog(prog);

return 0;
}

SEC("fexit/bpf_free_inode")
int BPF_PROG(hid_free_inode, struct inode *inode)
{
u64 prog = (u64)inode->i_private;

release_prog(prog);

return 0;
}

char LICENSE[] SEC("license") = "GPL";

0 comments on commit f5c27da

Please sign in to comment.