Commit f5c27da4 authored by Benjamin Tissoires's avatar Benjamin Tissoires Committed by Jiri Kosina

HID: initial BPF implementation

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: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarBenjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 25621bcc
......@@ -1284,6 +1284,8 @@ config HID_KUNIT_TEST
endmenu
source "drivers/hid/bpf/Kconfig"
endif # HID
source "drivers/hid/usbhid/Kconfig"
......
......@@ -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
......
# 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
# 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
# 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
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
// 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";
This diff is collapsed.
// SPDX-License-Identifier: GPL-2.0-only
/*
* HID-BPF support for Linux
*
* Copyright (c) 2022 Benjamin Tissoires
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/bitops.h>
#include <linux/btf.h>
#include <linux/btf_ids.h>
#include <linux/filter.h>
#include <linux/hid.h>
#include <linux/hid_bpf.h>
#include <linux/init.h>
#include <linux/kfifo.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include "hid_bpf_dispatch.h"
#include "entrypoints/entrypoints.lskel.h"
struct hid_bpf_ops *hid_bpf_ops;
EXPORT_SYMBOL(hid_bpf_ops);
/**
* hid_bpf_device_event - Called whenever an event is coming in from the device
*
* @ctx: The HID-BPF context
*
* @return %0 on success and keep processing; a negative error code to interrupt
* the processing of this event
*
* Declare an %fmod_ret tracing bpf program to this function and attach this
* program through hid_bpf_attach_prog() to have this helper called for
* any incoming event from the device itself.
*
* The function is called while on IRQ context, so we can not sleep.
*/
/* never used by the kernel but declared so we can load and attach a tracepoint */
__weak noinline int hid_bpf_device_event(struct hid_bpf_ctx *ctx)
{
return 0;
}
ALLOW_ERROR_INJECTION(hid_bpf_device_event, ERRNO);
int
dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type, u8 *data,
u32 size, int interrupt)
{
struct hid_bpf_ctx_kern ctx_kern = {
.ctx = {
.hid = hdev,
.report_type = type,
.size = size,
},
.data = data,
};
if (type >= HID_REPORT_TYPES)
return -EINVAL;
return hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_DEVICE_EVENT, &ctx_kern);
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);
/**
* hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
*
* @ctx: The HID-BPF context
* @offset: The offset within the memory
* @rdwr_buf_size: the const size of the buffer
*
* @returns %NULL on error, an %__u8 memory pointer on success
*/
noinline __u8 *
hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t rdwr_buf_size)
{
struct hid_bpf_ctx_kern *ctx_kern;
if (!ctx)
return NULL;
ctx_kern = container_of(ctx, struct hid_bpf_ctx_kern, ctx);
if (rdwr_buf_size + offset > ctx->size)
return NULL;
return ctx_kern->data + offset;
}
/*
* The following set contains all functions we agree BPF programs
* can use.
*/
BTF_SET8_START(hid_bpf_kfunc_ids)
BTF_ID_FLAGS(func, call_hid_bpf_prog_release)
BTF_ID_FLAGS(func, hid_bpf_get_data, KF_RET_NULL)
BTF_SET8_END(hid_bpf_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_kfunc_set = {
.owner = THIS_MODULE,
.set = &hid_bpf_kfunc_ids,
};
static int device_match_id(struct device *dev, const void *id)
{
struct hid_device *hdev = to_hid_device(dev);
return hdev->id == *(int *)id;
}
/**
* hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
*
* @hid_id: the system unique identifier of the HID device
* @prog_fd: an fd in the user process representing the program to attach
* @flags: any logical OR combination of &enum hid_bpf_attach_flags
*
* @returns %0 on success, an error code otherwise.
*/
/* called from syscall */
noinline int
hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
{
struct hid_device *hdev;
struct device *dev;
int prog_type = hid_bpf_get_prog_attach_type(prog_fd);
if (!hid_bpf_ops)
return -EINVAL;
if (prog_type < 0)
return prog_type;
if (prog_type >= HID_BPF_PROG_TYPE_MAX)
return -EINVAL;
if ((flags & ~HID_BPF_FLAG_MASK))
return -EINVAL;
dev = bus_find_device(hid_bpf_ops->bus_type, NULL, &hid_id, device_match_id);
if (!dev)
return -EINVAL;
hdev = to_hid_device(dev);
return __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
}
/* for syscall HID-BPF */
BTF_SET8_START(hid_bpf_syscall_kfunc_ids)
BTF_ID_FLAGS(func, hid_bpf_attach_prog)
BTF_SET8_END(hid_bpf_syscall_kfunc_ids)
static const struct btf_kfunc_id_set hid_bpf_syscall_kfunc_set = {
.owner = THIS_MODULE,
.set = &hid_bpf_syscall_kfunc_ids,
};
void hid_bpf_destroy_device(struct hid_device *hdev)
{
if (!hdev)
return;
/* mark the device as destroyed in bpf so we don't reattach it */
hdev->bpf.destroyed = true;
__hid_bpf_destroy_device(hdev);
}
EXPORT_SYMBOL_GPL(hid_bpf_destroy_device);
void hid_bpf_device_init(struct hid_device *hdev)
{
spin_lock_init(&hdev->bpf.progs_lock);
}
EXPORT_SYMBOL_GPL(hid_bpf_device_init);
static int __init hid_bpf_init(void)
{
int err;
/* Note: if we exit with an error any time here, we would entirely break HID, which
* is probably not something we want. So we log an error and return success.
*
* This is not a big deal: the syscall allowing to attach a BPF program to a HID device
* will not be available, so nobody will be able to use the functionality.
*/
err = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &hid_bpf_kfunc_set);
if (err) {
pr_warn("error while setting HID BPF tracing kfuncs: %d", err);
return 0;
}
err = hid_bpf_preload_skel();
if (err) {
pr_warn("error while preloading HID BPF dispatcher: %d", err);
return 0;
}
/* register syscalls after we are sure we can load our preloaded bpf program */
err = register_btf_kfunc_id_set(BPF_PROG_TYPE_SYSCALL, &hid_bpf_syscall_kfunc_set);
if (err) {
pr_warn("error while setting HID BPF syscall kfuncs: %d", err);
return 0;
}
return 0;
}
static void __exit hid_bpf_exit(void)
{
/* HID depends on us, so if we hit that code, we are guaranteed that hid
* has been removed and thus we do not need to clear the HID devices
*/
hid_bpf_free_links_and_skel();
}
late_initcall(hid_bpf_init);
module_exit(hid_bpf_exit);
MODULE_AUTHOR("Benjamin Tissoires");
MODULE_LICENSE("GPL");
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _BPF_HID_BPF_DISPATCH_H
#define _BPF_HID_BPF_DISPATCH_H
#include <linux/hid.h>
struct hid_bpf_ctx_kern {
struct hid_bpf_ctx ctx;
u8 *data;
};
int hid_bpf_preload_skel(void);
void hid_bpf_free_links_and_skel(void);
int hid_bpf_get_prog_attach_type(int prog_fd);
int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_type, int prog_fd,
__u32 flags);
void __hid_bpf_destroy_device(struct hid_device *hdev);
int hid_bpf_prog_run(struct hid_device *hdev, enum hid_bpf_prog_type type,
struct hid_bpf_ctx_kern *ctx_kern);
struct bpf_prog;
/* HID-BPF internal kfuncs API */
bool call_hid_bpf_prog_release(u64 prog, int table_cnt);
#endif
This diff is collapsed.
......@@ -2040,6 +2040,10 @@ int hid_input_report(struct hid_device *hid, enum hid_report_type type, u8 *data
report_enum = hid->report_enum + type;
hdrv = hid->driver;
ret = dispatch_hid_bpf_device_event(hid, type, data, size, interrupt);
if (ret)
goto unlock;
if (!size) {
dbg_hid("empty report\n");
ret = -1;
......@@ -2790,6 +2794,8 @@ struct hid_device *hid_allocate_device(void)
sema_init(&hdev->driver_input_lock, 1);
mutex_init(&hdev->ll_open_lock);
hid_bpf_device_init(hdev);
return hdev;
}
EXPORT_SYMBOL_GPL(hid_allocate_device);
......@@ -2816,6 +2822,7 @@ static void hid_remove_device(struct hid_device *hdev)
*/
void hid_destroy_device(struct hid_device *hdev)
{
hid_bpf_destroy_device(hdev);
hid_remove_device(hdev);
put_device(&hdev->dev);
}
......@@ -2902,6 +2909,13 @@ int hid_check_keys_pressed(struct hid_device *hid)
}
EXPORT_SYMBOL_GPL(hid_check_keys_pressed);
#ifdef CONFIG_HID_BPF
static struct hid_bpf_ops hid_ops = {
.owner = THIS_MODULE,
.bus_type = &hid_bus_type,
};
#endif
static int __init hid_init(void)
{
int ret;
......@@ -2916,6 +2930,10 @@ static int __init hid_init(void)
goto err;
}
#ifdef CONFIG_HID_BPF
hid_bpf_ops = &hid_ops;
#endif
ret = hidraw_init();
if (ret)
goto err_bus;
......@@ -2931,6 +2949,9 @@ static int __init hid_init(void)
static void __exit hid_exit(void)
{
#ifdef CONFIG_HID_BPF
hid_bpf_ops = NULL;
#endif
hid_debug_exit();
hidraw_exit();
bus_unregister(&hid_bus_type);
......
......@@ -26,6 +26,7 @@
#include <linux/mutex.h>
#include <linux/power_supply.h>
#include <uapi/linux/hid.h>
#include <linux/hid_bpf.h>
/*
* We parse each description item into this structure. Short items data
......@@ -651,6 +652,10 @@ struct hid_device { /* device report descriptor */
wait_queue_head_t debug_wait;
unsigned int id; /* system unique id */
#ifdef CONFIG_BPF
struct hid_bpf bpf; /* hid-bpf data */
#endif /* CONFIG_BPF */
};
#define to_hid_device(pdev) \
......
/* SPDX-License-Identifier: GPL-2.0+ */
#ifndef __HID_BPF_H
#define __HID_BPF_H
#include <linux/spinlock.h>
#include <uapi/linux/hid.h>
struct hid_device;
/*
* The following is the user facing HID BPF API.
*
* Extra care should be taken when editing this part, as
* it might break existing out of the tree bpf programs.
*/
/**
* struct hid_bpf_ctx - User accessible data for all HID programs
*
* ``data`` is not directly accessible from the context. We need to issue
* a call to ``hid_bpf_get_data()`` in order to get a pointer to that field.
*
* All of these fields are currently read-only.
*
* @index: program index in the jump table. No special meaning (a smaller index
* doesn't mean the program will be executed before another program with
* a bigger index).
* @hid: the ``struct hid_device`` representing the device itself
* @report_type: used for ``hid_bpf_device_event()``
* @size: Valid data in the data field.
*
* Programs can get the available valid size in data by fetching this field.
*/
struct hid_bpf_ctx {
__u32 index;
const struct hid_device *hid;
enum hid_report_type report_type;
__s32 size;
};
/**
* enum hid_bpf_attach_flags - flags used when attaching a HIF-BPF program
*
* @HID_BPF_FLAG_NONE: no specific flag is used, the kernel choses where to
* insert the program
* @HID_BPF_FLAG_INSERT_HEAD: insert the given program before any other program
* currently attached to the device. This doesn't
* guarantee that this program will always be first
* @HID_BPF_FLAG_MAX: sentinel value, not to be used by the callers
*/
enum hid_bpf_attach_flags {
HID_BPF_FLAG_NONE = 0,
HID_BPF_FLAG_INSERT_HEAD = _BITUL(0),
HID_BPF_FLAG_MAX,
};
/* Following functions are tracepoints that BPF programs can attach to */
int hid_bpf_device_event(struct hid_bpf_ctx *ctx);
/* Following functions are kfunc that we export to BPF programs */
/* only available in tracing */
__u8 *hid_bpf_get_data(struct hid_bpf_ctx *ctx, unsigned int offset, const size_t __sz);
/* only available in syscall */
int hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags);
/*
* Below is HID internal
*/
/* internal function to call eBPF programs, not to be used by anybody */
int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx);
#define HID_BPF_MAX_PROGS_PER_DEV 64
#define HID_BPF_FLAG_MASK (((HID_BPF_FLAG_MAX - 1) << 1) - 1)
/* types of HID programs to attach to */
enum hid_bpf_prog_type {
HID_BPF_PROG_TYPE_UNDEF = -1,
HID_BPF_PROG_TYPE_DEVICE_EVENT, /* an event is emitted from the device */
HID_BPF_PROG_TYPE_MAX,
};
struct hid_bpf_ops {
struct module *owner;
struct bus_type *bus_type;
};
extern struct hid_bpf_ops *hid_bpf_ops;
struct hid_bpf_prog_list {
u16 prog_idx[HID_BPF_MAX_PROGS_PER_DEV];
u8 prog_cnt;
};
/* stored in each device */
struct hid_bpf {
struct hid_bpf_prog_list __rcu *progs[HID_BPF_PROG_TYPE_MAX]; /* attached BPF progs */
bool destroyed; /* prevents the assignment of any progs */
spinlock_t progs_lock; /* protects RCU update of progs */
};
#ifdef CONFIG_HID_BPF
int dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type, u8 *data,
u32 size, int interrupt);
void hid_bpf_destroy_device(struct hid_device *hid);
void hid_bpf_device_init(struct hid_device *hid);
#else /* CONFIG_HID_BPF */
static inline int dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
u8 *data, u32 size, int interrupt) { return 0; }
static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
static inline void hid_bpf_device_init(struct hid_device *hid) {}
#endif /* CONFIG_HID_BPF */
#endif /* __HID_BPF_H */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment