From e128f82f7006991c99a58114f70ef61e937b1ac1 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sun, 5 Jan 2025 16:34:02 +0800 Subject: driver core: class: Fix wild pointer dereferences in API class_dev_iter_next() There are a potential wild pointer dereferences issue regarding APIs class_dev_iter_(init|next|exit)(), as explained by below typical usage: // All members of @iter are wild pointers. struct class_dev_iter iter; // class_dev_iter_init(@iter, @class, ...) checks parameter @class for // potential class_to_subsys() error, and it returns void type and does // not initialize its output parameter @iter, so caller can not detect // the error and continues to invoke class_dev_iter_next(@iter) even if // @iter still contains wild pointers. class_dev_iter_init(&iter, ...); // Dereference these wild pointers in @iter here once suffer the error. while (dev = class_dev_iter_next(&iter)) { ... }; // Also dereference these wild pointers here. class_dev_iter_exit(&iter); Actually, all callers of these APIs have such usage pattern in kernel tree. Fix by: - Initialize output parameter @iter by memset() in class_dev_iter_init() and give callers prompt by pr_crit() for the error. - Check if @iter is valid in class_dev_iter_next(). Fixes: 7b884b7f24b4 ("driver core: class.c: convert to only use class_to_subsys") Reviewed-by: Jonathan Cameron Signed-off-by: Zijun Hu Link: https://lore.kernel.org/r/20250105-class_fix-v6-1-3a2f1768d4d4@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/class.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'drivers/base/class.c') diff --git a/drivers/base/class.c b/drivers/base/class.c index 582b5a02a5c4..d57f277978dc 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -323,8 +323,12 @@ void class_dev_iter_init(struct class_dev_iter *iter, const struct class *class, struct subsys_private *sp = class_to_subsys(class); struct klist_node *start_knode = NULL; - if (!sp) + memset(iter, 0, sizeof(*iter)); + if (!sp) { + pr_crit("%s: class %p was not registered yet\n", + __func__, class); return; + } if (start) start_knode = &start->p->knode_class; @@ -351,6 +355,9 @@ struct device *class_dev_iter_next(struct class_dev_iter *iter) struct klist_node *knode; struct device *dev; + if (!iter->sp) + return NULL; + while (1) { knode = klist_next(&iter->ki); if (!knode) -- cgit v1.2.3 From 767b74e0d1fc7890a94d1770acf05a442474bd87 Mon Sep 17 00:00:00 2001 From: Zijun Hu Date: Sun, 5 Jan 2025 16:34:08 +0800 Subject: driver core: Introduce device_iter_t for device iterating APIs There are several for_each APIs which has parameter with type below: int (*fn)(struct device *dev, void *data) They iterate over various device lists and call @fn() for each device with caller provided data @*data, and they usually need to modify @*data. Give the type an dedicated typedef with advantages shown below: typedef int (*device_iter_t)(struct device *dev, void *data) - Shorter API declarations and definitions - Prevent further for_each APIs from using bad parameter type So introduce device_iter_t and apply it to various existing APIs below: bus_for_each_dev() (class|driver)_for_each_device() device_for_each_child(_reverse|_reverse_from)(). Reviewed-by: Jonathan Cameron Signed-off-by: Zijun Hu Link: https://lore.kernel.org/r/20250105-class_fix-v6-7-3a2f1768d4d4@quicinc.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/bus.c | 2 +- drivers/base/class.c | 2 +- drivers/base/core.c | 6 +++--- drivers/base/driver.c | 2 +- include/linux/device.h | 6 +++--- include/linux/device/bus.h | 7 +++++-- include/linux/device/class.h | 4 ++-- include/linux/device/driver.h | 2 +- 8 files changed, 17 insertions(+), 14 deletions(-) (limited to 'drivers/base/class.c') diff --git a/drivers/base/bus.c b/drivers/base/bus.c index 73a56f376d3a..6b9e65a42cd2 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -354,7 +354,7 @@ static struct device *next_device(struct klist_iter *i) * count in the supplied callback. */ int bus_for_each_dev(const struct bus_type *bus, struct device *start, - void *data, int (*fn)(struct device *, void *)) + void *data, device_iter_t fn) { struct subsys_private *sp = bus_to_subsys(bus); struct klist_iter i; diff --git a/drivers/base/class.c b/drivers/base/class.c index d57f277978dc..70ee6a7ba5a3 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -402,7 +402,7 @@ EXPORT_SYMBOL_GPL(class_dev_iter_exit); * code. There's no locking restriction. */ int class_for_each_device(const struct class *class, const struct device *start, - void *data, int (*fn)(struct device *, void *)) + void *data, device_iter_t fn) { struct subsys_private *sp = class_to_subsys(class); struct class_dev_iter iter; diff --git a/drivers/base/core.c b/drivers/base/core.c index 5ee53b3bca6a..7d79a0549ac7 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -3980,7 +3980,7 @@ const char *device_get_devnode(const struct device *dev, * other than 0, we break out and return that value. */ int device_for_each_child(struct device *parent, void *data, - int (*fn)(struct device *dev, void *data)) + device_iter_t fn) { struct klist_iter i; struct device *child; @@ -4010,7 +4010,7 @@ EXPORT_SYMBOL_GPL(device_for_each_child); * other than 0, we break out and return that value. */ int device_for_each_child_reverse(struct device *parent, void *data, - int (*fn)(struct device *dev, void *data)) + device_iter_t fn) { struct klist_iter i; struct device *child; @@ -4044,7 +4044,7 @@ EXPORT_SYMBOL_GPL(device_for_each_child_reverse); */ int device_for_each_child_reverse_from(struct device *parent, struct device *from, void *data, - int (*fn)(struct device *, void *)) + device_iter_t fn) { struct klist_iter i; struct device *child; diff --git a/drivers/base/driver.c b/drivers/base/driver.c index 6f033a741aa7..8ab010ddf709 100644 --- a/drivers/base/driver.c +++ b/drivers/base/driver.c @@ -115,7 +115,7 @@ EXPORT_SYMBOL_GPL(driver_set_override); * Iterate over the @drv's list of devices calling @fn for each one. */ int driver_for_each_device(struct device_driver *drv, struct device *start, - void *data, int (*fn)(struct device *, void *)) + void *data, device_iter_t fn) { struct klist_iter i; struct device *dev; diff --git a/include/linux/device.h b/include/linux/device.h index 025bac08fca7..36d1a1607712 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -1075,12 +1075,12 @@ void device_del(struct device *dev); DEFINE_FREE(device_del, struct device *, if (_T) device_del(_T)) int device_for_each_child(struct device *parent, void *data, - int (*fn)(struct device *dev, void *data)); + device_iter_t fn); int device_for_each_child_reverse(struct device *parent, void *data, - int (*fn)(struct device *dev, void *data)); + device_iter_t fn); int device_for_each_child_reverse_from(struct device *parent, struct device *from, void *data, - int (*fn)(struct device *, void *)); + device_iter_t fn); struct device *device_find_child(struct device *parent, const void *data, device_match_t match); struct device *device_find_child_by_name(struct device *parent, diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h index bc3fd74bb763..3d3517da41a1 100644 --- a/include/linux/device/bus.h +++ b/include/linux/device/bus.h @@ -139,9 +139,12 @@ int device_match_acpi_dev(struct device *dev, const void *adev); int device_match_acpi_handle(struct device *dev, const void *handle); int device_match_any(struct device *dev, const void *unused); +/* Device iterating function type for various driver core for_each APIs */ +typedef int (*device_iter_t)(struct device *dev, void *data); + /* iterator helpers for buses */ -int bus_for_each_dev(const struct bus_type *bus, struct device *start, void *data, - int (*fn)(struct device *dev, void *data)); +int bus_for_each_dev(const struct bus_type *bus, struct device *start, + void *data, device_iter_t fn); struct device *bus_find_device(const struct bus_type *bus, struct device *start, const void *data, device_match_t match); /** diff --git a/include/linux/device/class.h b/include/linux/device/class.h index 518c9c83d64b..aa67d4736816 100644 --- a/include/linux/device/class.h +++ b/include/linux/device/class.h @@ -92,8 +92,8 @@ void class_dev_iter_init(struct class_dev_iter *iter, const struct class *class, struct device *class_dev_iter_next(struct class_dev_iter *iter); void class_dev_iter_exit(struct class_dev_iter *iter); -int class_for_each_device(const struct class *class, const struct device *start, void *data, - int (*fn)(struct device *dev, void *data)); +int class_for_each_device(const struct class *class, const struct device *start, + void *data, device_iter_t fn); struct device *class_find_device(const struct class *class, const struct device *start, const void *data, device_match_t match); diff --git a/include/linux/device/driver.h b/include/linux/device/driver.h index 5c04b8e3833b..cd8e0f0a634b 100644 --- a/include/linux/device/driver.h +++ b/include/linux/device/driver.h @@ -154,7 +154,7 @@ void driver_remove_file(const struct device_driver *driver, int driver_set_override(struct device *dev, const char **override, const char *s, size_t len); int __must_check driver_for_each_device(struct device_driver *drv, struct device *start, - void *data, int (*fn)(struct device *dev, void *)); + void *data, device_iter_t fn); struct device *driver_find_device(const struct device_driver *drv, struct device *start, const void *data, device_match_t match); -- cgit v1.2.3 From 827ed8b1590d4d29dae837283d606709ffeebe37 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Thu, 19 Dec 2024 22:48:17 +0100 Subject: drivers: core: remove device_link argument from class_compat_[create|remove]_link After 7e722083fcc3 ("i2c: Remove I2C_COMPAT config symbol and related code") there's no caller left passing a non-null device_link argument. So remove this argument to simplify the code. Signed-off-by: Heiner Kallweit Link: https://lore.kernel.org/r/db49131d-fd79-4f23-93f2-0ab541a345fa@gmail.com Signed-off-by: Greg Kroah-Hartman --- drivers/base/class.c | 31 +++---------------------------- drivers/vfio/mdev/mdev_core.c | 4 ++-- include/linux/device/class.h | 6 ++---- 3 files changed, 7 insertions(+), 34 deletions(-) (limited to 'drivers/base/class.c') diff --git a/drivers/base/class.c b/drivers/base/class.c index 70ee6a7ba5a3..2526c57d924e 100644 --- a/drivers/base/class.c +++ b/drivers/base/class.c @@ -601,30 +601,10 @@ EXPORT_SYMBOL_GPL(class_compat_unregister); * a bus device * @cls: the compatibility class * @dev: the target bus device - * @device_link: an optional device to which a "device" link should be created */ -int class_compat_create_link(struct class_compat *cls, struct device *dev, - struct device *device_link) +int class_compat_create_link(struct class_compat *cls, struct device *dev) { - int error; - - error = sysfs_create_link(cls->kobj, &dev->kobj, dev_name(dev)); - if (error) - return error; - - /* - * Optionally add a "device" link (typically to the parent), as a - * class device would have one and we want to provide as much - * backwards compatibility as possible. - */ - if (device_link) { - error = sysfs_create_link(&dev->kobj, &device_link->kobj, - "device"); - if (error) - sysfs_remove_link(cls->kobj, dev_name(dev)); - } - - return error; + return sysfs_create_link(cls->kobj, &dev->kobj, dev_name(dev)); } EXPORT_SYMBOL_GPL(class_compat_create_link); @@ -633,14 +613,9 @@ EXPORT_SYMBOL_GPL(class_compat_create_link); * a bus device * @cls: the compatibility class * @dev: the target bus device - * @device_link: an optional device to which a "device" link was previously - * created */ -void class_compat_remove_link(struct class_compat *cls, struct device *dev, - struct device *device_link) +void class_compat_remove_link(struct class_compat *cls, struct device *dev) { - if (device_link) - sysfs_remove_link(&dev->kobj, "device"); sysfs_remove_link(cls->kobj, dev_name(dev)); } EXPORT_SYMBOL_GPL(class_compat_remove_link); diff --git a/drivers/vfio/mdev/mdev_core.c b/drivers/vfio/mdev/mdev_core.c index ed4737de4528..f2e686f8f1ef 100644 --- a/drivers/vfio/mdev/mdev_core.c +++ b/drivers/vfio/mdev/mdev_core.c @@ -76,7 +76,7 @@ int mdev_register_parent(struct mdev_parent *parent, struct device *dev, if (ret) return ret; - ret = class_compat_create_link(mdev_bus_compat_class, dev, NULL); + ret = class_compat_create_link(mdev_bus_compat_class, dev); if (ret) dev_warn(dev, "Failed to create compatibility class link\n"); @@ -98,7 +98,7 @@ void mdev_unregister_parent(struct mdev_parent *parent) dev_info(parent->dev, "MDEV: Unregistering\n"); down_write(&parent->unreg_sem); - class_compat_remove_link(mdev_bus_compat_class, parent->dev, NULL); + class_compat_remove_link(mdev_bus_compat_class, parent->dev); device_for_each_child(parent->dev, NULL, mdev_device_remove_cb); parent_remove_sysfs_files(parent); up_write(&parent->unreg_sem); diff --git a/include/linux/device/class.h b/include/linux/device/class.h index aa67d4736816..45ee3a634999 100644 --- a/include/linux/device/class.h +++ b/include/linux/device/class.h @@ -82,10 +82,8 @@ bool class_is_registered(const struct class *class); struct class_compat; struct class_compat *class_compat_register(const char *name); void class_compat_unregister(struct class_compat *cls); -int class_compat_create_link(struct class_compat *cls, struct device *dev, - struct device *device_link); -void class_compat_remove_link(struct class_compat *cls, struct device *dev, - struct device *device_link); +int class_compat_create_link(struct class_compat *cls, struct device *dev); +void class_compat_remove_link(struct class_compat *cls, struct device *dev); void class_dev_iter_init(struct class_dev_iter *iter, const struct class *class, const struct device *start, const struct device_type *type); -- cgit v1.2.3