Commit cfb4be1a authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull gpio fixes from Bartosz Golaszewski:
 "Some last-minute fixes for this release from the GPIO subsystem.

  The first two address a regression in performance reported to me after
  the conversion to using SRCU in GPIOLIB that was merged during the
  v6.9 merge window. The second patch is not technically a fix but since
  after the first one we no longer need to use a per-descriptor SRCU
  struct, I think it's worth to simplify the code before it gets
  released on Sunday.

  The next two commits fix two memory issues: one use-after-free bug and
  one instance of possibly leaking kernel stack memory to user-space.

  Summary:

   - fix a performance regression in GPIO requesting and releasing after
     the conversion to SRCU

   - fix a use-after-free bug due to a race-condition

   - fix leaking stack memory to user-space in a GPIO uABI corner case"

* tag 'gpio-fixes-for-v6.9' of git://git.kernel.org/pub/scm/linux/kernel/git/brgl/linux:
  gpiolib: cdev: fix uninitialised kfifo
  gpiolib: cdev: Fix use after free in lineinfo_changed_notify
  gpiolib: use a single SRCU struct for all GPIO descriptors
  gpiolib: fix the speed of descriptor label setting with SRCU
parents f4345f05 ee0166b6
Loading
Loading
Loading
Loading
+16 −2
Original line number Diff line number Diff line
@@ -1193,6 +1193,8 @@ static int edge_detector_update(struct line *line,
				struct gpio_v2_line_config *lc,
				unsigned int line_idx, u64 edflags)
{
	u64 eflags;
	int ret;
	u64 active_edflags = READ_ONCE(line->edflags);
	unsigned int debounce_period_us =
			gpio_v2_line_config_debounce_period(lc, line_idx);
@@ -1204,6 +1206,18 @@ static int edge_detector_update(struct line *line,
	/* sw debounced and still will be...*/
	if (debounce_period_us && READ_ONCE(line->sw_debounced)) {
		line_set_debounce_period(line, debounce_period_us);
		/*
		 * ensure event fifo is initialised if edge detection
		 * is now enabled.
		 */
		eflags = edflags & GPIO_V2_LINE_EDGE_FLAGS;
		if (eflags && !kfifo_initialized(&line->req->events)) {
			ret = kfifo_alloc(&line->req->events,
					  line->req->event_buffer_size,
					  GFP_KERNEL);
			if (ret)
				return ret;
		}
		return 0;
	}

@@ -2351,7 +2365,7 @@ static void gpio_desc_to_lineinfo(struct gpio_desc *desc,

	dflags = READ_ONCE(desc->flags);

	scoped_guard(srcu, &desc->srcu) {
	scoped_guard(srcu, &desc->gdev->desc_srcu) {
		label = gpiod_get_label(desc);
		if (label && test_bit(FLAG_REQUESTED, &dflags))
			strscpy(info->consumer, label,
@@ -2799,11 +2813,11 @@ static int gpio_chrdev_release(struct inode *inode, struct file *file)
	struct gpio_chardev_data *cdev = file->private_data;
	struct gpio_device *gdev = cdev->gdev;

	bitmap_free(cdev->watched_lines);
	blocking_notifier_chain_unregister(&gdev->device_notifier,
					   &cdev->device_unregistered_nb);
	blocking_notifier_chain_unregister(&gdev->line_state_notifier,
					   &cdev->lineinfo_changed_nb);
	bitmap_free(cdev->watched_lines);
	gpio_device_put(gdev);
	kfree(cdev);

+36 −22
Original line number Diff line number Diff line
@@ -101,6 +101,7 @@ static bool gpiolib_initialized;

const char *gpiod_get_label(struct gpio_desc *desc)
{
	struct gpio_desc_label *label;
	unsigned long flags;

	flags = READ_ONCE(desc->flags);
@@ -108,23 +109,36 @@ const char *gpiod_get_label(struct gpio_desc *desc)
	    !test_bit(FLAG_REQUESTED, &flags))
		return "interrupt";

	return test_bit(FLAG_REQUESTED, &flags) ?
			srcu_dereference(desc->label, &desc->srcu) : NULL;
	if (!test_bit(FLAG_REQUESTED, &flags))
		return NULL;

	label = srcu_dereference_check(desc->label, &desc->gdev->desc_srcu,
				srcu_read_lock_held(&desc->gdev->desc_srcu));

	return label->str;
}

static void desc_free_label(struct rcu_head *rh)
{
	kfree(container_of(rh, struct gpio_desc_label, rh));
}

static int desc_set_label(struct gpio_desc *desc, const char *label)
{
	const char *new = NULL, *old;
	struct gpio_desc_label *new = NULL, *old;

	if (label) {
		new = kstrdup_const(label, GFP_KERNEL);
		new = kzalloc(struct_size(new, str, strlen(label) + 1),
			      GFP_KERNEL);
		if (!new)
			return -ENOMEM;

		strcpy(new->str, label);
	}

	old = rcu_replace_pointer(desc->label, new, 1);
	synchronize_srcu(&desc->srcu);
	kfree_const(old);
	if (old)
		call_srcu(&desc->gdev->desc_srcu, &old->rh, desc_free_label);

	return 0;
}
@@ -695,10 +709,10 @@ EXPORT_SYMBOL_GPL(gpiochip_line_is_valid);
static void gpiodev_release(struct device *dev)
{
	struct gpio_device *gdev = to_gpio_device(dev);
	unsigned int i;

	for (i = 0; i < gdev->ngpio; i++)
		cleanup_srcu_struct(&gdev->descs[i].srcu);
	/* Call pending kfree()s for descriptor labels. */
	synchronize_srcu(&gdev->desc_srcu);
	cleanup_srcu_struct(&gdev->desc_srcu);

	ida_free(&gpio_ida, gdev->id);
	kfree_const(gdev->label);
@@ -975,6 +989,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
	if (ret)
		goto err_remove_from_list;

	ret = init_srcu_struct(&gdev->desc_srcu);
	if (ret)
		goto err_cleanup_gdev_srcu;

#ifdef CONFIG_PINCTRL
	INIT_LIST_HEAD(&gdev->pin_ranges);
#endif
@@ -982,23 +1000,19 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
	if (gc->names) {
		ret = gpiochip_set_desc_names(gc);
		if (ret)
			goto err_cleanup_gdev_srcu;
			goto err_cleanup_desc_srcu;
	}
	ret = gpiochip_set_names(gc);
	if (ret)
		goto err_cleanup_gdev_srcu;
		goto err_cleanup_desc_srcu;

	ret = gpiochip_init_valid_mask(gc);
	if (ret)
		goto err_cleanup_gdev_srcu;
		goto err_cleanup_desc_srcu;

	for (desc_index = 0; desc_index < gc->ngpio; desc_index++) {
		struct gpio_desc *desc = &gdev->descs[desc_index];

		ret = init_srcu_struct(&desc->srcu);
		if (ret)
			goto err_cleanup_desc_srcu;

		if (gc->get_direction && gpiochip_line_is_valid(gc, desc_index)) {
			assign_bit(FLAG_IS_OUT,
				   &desc->flags, !gc->get_direction(gc, desc_index));
@@ -1010,7 +1024,7 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,

	ret = of_gpiochip_add(gc);
	if (ret)
		goto err_cleanup_desc_srcu;
		goto err_free_valid_mask;

	ret = gpiochip_add_pin_ranges(gc);
	if (ret)
@@ -1057,10 +1071,10 @@ int gpiochip_add_data_with_key(struct gpio_chip *gc, void *data,
	gpiochip_remove_pin_ranges(gc);
err_remove_of_chip:
	of_gpiochip_remove(gc);
err_cleanup_desc_srcu:
	while (desc_index--)
		cleanup_srcu_struct(&gdev->descs[desc_index].srcu);
err_free_valid_mask:
	gpiochip_free_valid_mask(gc);
err_cleanup_desc_srcu:
	cleanup_srcu_struct(&gdev->desc_srcu);
err_cleanup_gdev_srcu:
	cleanup_srcu_struct(&gdev->srcu);
err_remove_from_list:
@@ -2390,7 +2404,7 @@ char *gpiochip_dup_line_label(struct gpio_chip *gc, unsigned int offset)
	if (!test_bit(FLAG_REQUESTED, &desc->flags))
		return NULL;

	guard(srcu)(&desc->srcu);
	guard(srcu)(&desc->gdev->desc_srcu);

	label = kstrdup(gpiod_get_label(desc), GFP_KERNEL);
	if (!label)
@@ -4781,7 +4795,7 @@ static void gpiolib_dbg_show(struct seq_file *s, struct gpio_device *gdev)
	}

	for_each_gpio_desc(gc, desc) {
		guard(srcu)(&desc->srcu);
		guard(srcu)(&desc->gdev->desc_srcu);
		if (test_bit(FLAG_REQUESTED, &desc->flags)) {
			gpiod_get_direction(desc);
			is_out = test_bit(FLAG_IS_OUT, &desc->flags);
+11 −6
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@
 * @chip: pointer to the corresponding gpiochip, holding static
 * data for this device
 * @descs: array of ngpio descriptors.
 * @desc_srcu: ensures consistent state of GPIO descriptors exposed to users
 * @ngpio: the number of GPIO lines on this GPIO device, equal to the size
 * of the @descs array.
 * @can_sleep: indicate whether the GPIO chip driver's callbacks can sleep
@@ -61,6 +62,7 @@ struct gpio_device {
	struct module		*owner;
	struct gpio_chip __rcu	*chip;
	struct gpio_desc	*descs;
	struct srcu_struct	desc_srcu;
	int			base;
	u16			ngpio;
	bool			can_sleep;
@@ -137,6 +139,11 @@ int gpiod_set_transitory(struct gpio_desc *desc, bool transitory);

void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action);

struct gpio_desc_label {
	struct rcu_head rh;
	char str[];
};

/**
 * struct gpio_desc - Opaque descriptor for a GPIO
 *
@@ -145,7 +152,6 @@ void gpiod_line_state_notify(struct gpio_desc *desc, unsigned long action);
 * @label:		Name of the consumer
 * @name:		Line name
 * @hog:		Pointer to the device node that hogs this line (if any)
 * @srcu:		SRCU struct protecting the label pointer.
 *
 * These are obtained using gpiod_get() and are preferable to the old
 * integer-based handles.
@@ -177,13 +183,12 @@ struct gpio_desc {
#define FLAG_EVENT_CLOCK_HTE		19 /* GPIO CDEV reports hardware timestamps in events */

	/* Connection label */
	const char __rcu	*label;
	struct gpio_desc_label __rcu *label;
	/* Name of the GPIO */
	const char		*name;
#ifdef CONFIG_OF_DYNAMIC
	struct device_node	*hog;
#endif
	struct srcu_struct	srcu;
};

#define gpiod_not_found(desc)		(IS_ERR(desc) && PTR_ERR(desc) == -ENOENT)
@@ -251,7 +256,7 @@ static inline int gpio_chip_hwgpio(const struct gpio_desc *desc)

#define gpiod_err(desc, fmt, ...) \
do { \
	scoped_guard(srcu, &desc->srcu) { \
	scoped_guard(srcu, &desc->gdev->desc_srcu) { \
		pr_err("gpio-%d (%s): " fmt, desc_to_gpio(desc), \
		       gpiod_get_label(desc) ? : "?", ##__VA_ARGS__); \
	} \
@@ -259,7 +264,7 @@ do { \

#define gpiod_warn(desc, fmt, ...) \
do { \
	scoped_guard(srcu, &desc->srcu) { \
	scoped_guard(srcu, &desc->gdev->desc_srcu) { \
		pr_warn("gpio-%d (%s): " fmt, desc_to_gpio(desc), \
			gpiod_get_label(desc) ? : "?", ##__VA_ARGS__); \
	} \
@@ -267,7 +272,7 @@ do { \

#define gpiod_dbg(desc, fmt, ...) \
do { \
	scoped_guard(srcu, &desc->srcu) { \
	scoped_guard(srcu, &desc->gdev->desc_srcu) { \
		pr_debug("gpio-%d (%s): " fmt, desc_to_gpio(desc), \
			 gpiod_get_label(desc) ? : "?", ##__VA_ARGS__); \
	} \