Commit ad190df1 authored by Benjamin Tissoires's avatar Benjamin Tissoires Committed by Jiri Kosina
Browse files

HID: bpf: allow to change the report descriptor



Add a new tracepoint hid_bpf_rdesc_fixup() so we can trigger a
report descriptor fixup in the bpf world.

Whenever the program gets attached/detached, the device is reconnected
meaning that userspace will see it disappearing and reappearing with
the new report descriptor.

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 4f7153cf
Loading
Loading
Loading
Loading
+79 −1
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
#include <linux/hid_bpf.h>
#include <linux/init.h>
#include <linux/kfifo.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/workqueue.h>
#include "hid_bpf_dispatch.h"
@@ -85,6 +86,65 @@ dispatch_hid_bpf_device_event(struct hid_device *hdev, enum hid_report_type type
}
EXPORT_SYMBOL_GPL(dispatch_hid_bpf_device_event);

/**
 * hid_bpf_rdesc_fixup - Called when the probe function parses the report
 * descriptor of the HID device
 *
 * @ctx: The HID-BPF context
 *
 * @return 0 on success and keep processing; a positive value to change the
 * incoming size buffer; 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 before any
 * parsing of the report descriptor by HID.
 */
/* never used by the kernel but declared so we can load and attach a tracepoint */
__weak noinline int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx)
{
	return 0;
}
ALLOW_ERROR_INJECTION(hid_bpf_rdesc_fixup, ERRNO);

u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
{
	int ret;
	struct hid_bpf_ctx_kern ctx_kern = {
		.ctx = {
			.hid = hdev,
			.size = *size,
			.allocated_size = HID_MAX_DESCRIPTOR_SIZE,
		},
	};

	ctx_kern.data = kzalloc(ctx_kern.ctx.allocated_size, GFP_KERNEL);
	if (!ctx_kern.data)
		goto ignore_bpf;

	memcpy(ctx_kern.data, rdesc, min_t(unsigned int, *size, HID_MAX_DESCRIPTOR_SIZE));

	ret = hid_bpf_prog_run(hdev, HID_BPF_PROG_TYPE_RDESC_FIXUP, &ctx_kern);
	if (ret < 0)
		goto ignore_bpf;

	if (ret) {
		if (ret > ctx_kern.ctx.allocated_size)
			goto ignore_bpf;

		*size = ret;
	}

	rdesc = krealloc(ctx_kern.data, *size, GFP_KERNEL);

	return rdesc;

 ignore_bpf:
	kfree(ctx_kern.data);
	return kmemdup(rdesc, *size, GFP_KERNEL);
}
EXPORT_SYMBOL_GPL(call_hid_bpf_rdesc_fixup);

/**
 * hid_bpf_get_data - Get the kernel memory pointer associated with the context @ctx
 *
@@ -176,6 +236,14 @@ static int hid_bpf_allocate_event_data(struct hid_device *hdev)
	return __hid_bpf_allocate_data(hdev, &hdev->bpf.device_data, &hdev->bpf.allocated_data);
}

int hid_bpf_reconnect(struct hid_device *hdev)
{
	if (!test_and_set_bit(ffs(HID_STAT_REPROBED), &hdev->status))
		return device_reprobe(&hdev->dev);

	return 0;
}

/**
 * hid_bpf_attach_prog - Attach the given @prog_fd to the given HID device
 *
@@ -217,7 +285,17 @@ hid_bpf_attach_prog(unsigned int hid_id, int prog_fd, __u32 flags)
			return err;
	}

	return __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
	err = __hid_bpf_attach_prog(hdev, prog_type, prog_fd, flags);
	if (err)
		return err;

	if (prog_type == HID_BPF_PROG_TYPE_RDESC_FIXUP) {
		err = hid_bpf_reconnect(hdev);
		if (err)
			return err;
	}

	return 0;
}

/**
+1 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ int __hid_bpf_attach_prog(struct hid_device *hdev, enum hid_bpf_prog_type prog_t
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);
int hid_bpf_reconnect(struct hid_device *hdev);

struct bpf_prog;

+7 −0
Original line number Diff line number Diff line
@@ -58,12 +58,15 @@ static DECLARE_WORK(release_work, hid_bpf_release_progs);

BTF_ID_LIST(hid_bpf_btf_ids)
BTF_ID(func, hid_bpf_device_event)			/* HID_BPF_PROG_TYPE_DEVICE_EVENT */
BTF_ID(func, hid_bpf_rdesc_fixup)			/* HID_BPF_PROG_TYPE_RDESC_FIXUP */

static int hid_bpf_max_programs(enum hid_bpf_prog_type type)
{
	switch (type) {
	case HID_BPF_PROG_TYPE_DEVICE_EVENT:
		return HID_BPF_MAX_PROGS_PER_DEV;
	case HID_BPF_PROG_TYPE_RDESC_FIXUP:
		return 1;
	default:
		return -EINVAL;
	}
@@ -233,6 +236,10 @@ static void hid_bpf_release_progs(struct work_struct *work)
				if (next->hdev == hdev && next->type == type)
					next->hdev = NULL;
			}

			/* if type was rdesc fixup, reconnect device */
			if (type == HID_BPF_PROG_TYPE_RDESC_FIXUP)
				hid_bpf_reconnect(hdev);
		}
	}

+2 −1
Original line number Diff line number Diff line
@@ -1218,7 +1218,8 @@ int hid_open_report(struct hid_device *device)
		return -ENODEV;
	size = device->dev_rsize;

	buf = kmemdup(start, size, GFP_KERNEL);
	/* call_hid_bpf_rdesc_fixup() ensures we work on a copy of rdesc */
	buf = call_hid_bpf_rdesc_fixup(device, start, &size);
	if (buf == NULL)
		return -ENOMEM;

+8 −0
Original line number Diff line number Diff line
@@ -74,6 +74,7 @@ enum hid_bpf_attach_flags {

/* Following functions are tracepoints that BPF programs can attach to */
int hid_bpf_device_event(struct hid_bpf_ctx *ctx);
int hid_bpf_rdesc_fixup(struct hid_bpf_ctx *ctx);

/* Following functions are kfunc that we export to BPF programs */
/* available everywhere in HID-BPF */
@@ -100,6 +101,7 @@ int __hid_bpf_tail_call(struct hid_bpf_ctx *ctx);
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_RDESC_FIXUP,
	HID_BPF_PROG_TYPE_MAX,
};

@@ -143,6 +145,7 @@ int hid_bpf_connect_device(struct hid_device *hdev);
void hid_bpf_disconnect_device(struct hid_device *hdev);
void hid_bpf_destroy_device(struct hid_device *hid);
void hid_bpf_device_init(struct hid_device *hid);
u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size);
#else /* CONFIG_HID_BPF */
static inline u8 *dispatch_hid_bpf_device_event(struct hid_device *hid, enum hid_report_type type,
						u8 *data, u32 *size, int interrupt) { return NULL; }
@@ -150,6 +153,11 @@ static inline int hid_bpf_connect_device(struct hid_device *hdev) { return 0; }
static inline void hid_bpf_disconnect_device(struct hid_device *hdev) {}
static inline void hid_bpf_destroy_device(struct hid_device *hid) {}
static inline void hid_bpf_device_init(struct hid_device *hid) {}
static inline u8 *call_hid_bpf_rdesc_fixup(struct hid_device *hdev, u8 *rdesc, unsigned int *size)
{
	return kmemdup(rdesc, *size, GFP_KERNEL);
}

#endif /* CONFIG_HID_BPF */

#endif /* __HID_BPF_H */