Race condition in regmap-debugfs dummy name allocation

HIGH
torvalds/linux
Commit: 8e258317dd01
Affected: v7.0-rc6 and earlier (pre-7.1 regmap)
2026-04-16 06:19 UTC

Description

The commit fixes a race condition in regmap-debugfs dummy name allocation. Previously, regmap-debugfs used a static dummy_index for naming dummy entries, which could race when multiple regmaps create dummy debugfs entries concurrently, potentially leading to name collisions, incorrect mapping, or use-after-free scenarios during cleanup. The fix introduces per-instance ID allocation (via IDA) and per-map tracking (debugfs_dummy_id) with proper cleanup, eliminating the shared global naming race and ensuring each dummy debugfs entry has a unique, correctly freed name.

Commit Details

Author: Linus Torvalds

Date: 2026-04-15 21:27 UTC

Message:

Merge tag 'regmap-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap Pull regmap updates from Mark Brown: "This has been quite a busy release for regmap, the user visible changes are quite minor but there's some quite good work on internal code improvements: - Cleanup helper for __free()ing regmap_fields - Support non-devm I3C regmaps - A bunch of cleanup work, mostly from Andy Shevchenko - Fix for bootstrapping issues with hardware initialised regmaps, which was the main inspiration for some of the cleanups" * tag 'regmap-v7.1' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regmap: regmap: i3c: Add non-devm regmap_init_i3c() helper regmap: debugfs: fix race condition in dummy name allocation regmap: Synchronize cache for the page selector regmap: Simplify devres handling regcache: Move HW readback after cache initialisation regcache: Allocate and free reg_defaults on the same level regcache: Move count check and cache_bypass assignment to the caller regcache: Factor out regcache_hw_exit() helper regcache: Amend printf() specifiers when printing registers regcache: Define iterator inside for-loop and align their types regmap: define cleanup helper for regmap_field regmap: sort header includes regcache: Split regcache_count_cacheable_registers() helper regcache: Remove duplicate check in regcache_hw_init()

Triage Assessment

Vulnerability Type: Race condition

Confidence: HIGH

Reasoning:

Commit message explicitly mentions fixing a race condition in regmap-debugfs dummy name allocation. The code changes implement a safer allocation path (using IDA for dummy names and tracking IDs) and proper cleanup, which mitigates a race condition that could lead to security issues such as leakage, instability, or exploitation via race conditions.

Verification Assessment

Vulnerability Type: Race condition in regmap-debugfs dummy name allocation

Confidence: HIGH

Affected Versions: v7.0-rc6 and earlier (pre-7.1 regmap)

Code Diff

diff --git a/drivers/base/regmap/internal.h b/drivers/base/regmap/internal.h index 5bf99316543872..55273a6178f8d7 100644 --- a/drivers/base/regmap/internal.h +++ b/drivers/base/regmap/internal.h @@ -84,6 +84,7 @@ struct regmap { bool debugfs_disable; struct dentry *debugfs; const char *debugfs_name; + int debugfs_dummy_id; unsigned int debugfs_reg_len; unsigned int debugfs_val_len; @@ -162,7 +163,7 @@ struct regmap { bool no_sync_defaults; struct reg_sequence *patch; - int patch_regs; + unsigned int patch_regs; /* if set, the regmap core can sleep */ bool can_sleep; diff --git a/drivers/base/regmap/regcache.c b/drivers/base/regmap/regcache.c index a35f2b20298b27..27616b05111cc7 100644 --- a/drivers/base/regmap/regcache.c +++ b/drivers/base/regmap/regcache.c @@ -42,33 +42,25 @@ void regcache_sort_defaults(struct reg_default *defaults, unsigned int ndefaults } EXPORT_SYMBOL_GPL(regcache_sort_defaults); -static int regcache_hw_init(struct regmap *map) +static int regcache_count_cacheable_registers(struct regmap *map) { - int i, j; - int ret; - int count; - unsigned int reg, val; - void *tmp_buf; - - if (!map->num_reg_defaults_raw) - return -EINVAL; + unsigned int count; /* calculate the size of reg_defaults */ - for (count = 0, i = 0; i < map->num_reg_defaults_raw; i++) + count = 0; + for (unsigned int i = 0; i < map->num_reg_defaults_raw; i++) if (regmap_readable(map, i * map->reg_stride) && !regmap_volatile(map, i * map->reg_stride)) count++; - /* all registers are unreadable or volatile, so just bypass */ - if (!count) { - map->cache_bypass = true; - return 0; - } + return count; +} - map->num_reg_defaults = count; - map->reg_defaults = kmalloc_objs(struct reg_default, count); - if (!map->reg_defaults) - return -ENOMEM; +static int regcache_hw_init(struct regmap *map) +{ + int ret; + unsigned int reg, val; + void *tmp_buf; if (!map->reg_defaults_raw) { bool cache_bypass = map->cache_bypass; @@ -77,10 +69,8 @@ static int regcache_hw_init(struct regmap *map) /* Bypass the cache access till data read from HW */ map->cache_bypass = true; tmp_buf = kmalloc(map->cache_size_raw, GFP_KERNEL); - if (!tmp_buf) { - ret = -ENOMEM; - goto err_free; - } + if (!tmp_buf) + return -ENOMEM; ret = regmap_raw_read(map, 0, tmp_buf, map->cache_size_raw); map->cache_bypass = cache_bypass; @@ -93,7 +83,7 @@ static int regcache_hw_init(struct regmap *map) } /* fill the reg_defaults */ - for (i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { + for (unsigned int i = 0, j = 0; i < map->num_reg_defaults_raw; i++) { reg = i * map->reg_stride; if (!regmap_readable(map, reg)) @@ -111,9 +101,9 @@ static int regcache_hw_init(struct regmap *map) ret = regmap_read(map, reg, &val); map->cache_bypass = cache_bypass; if (ret != 0) { - dev_err(map->dev, "Failed to read %d: %d\n", + dev_err(map->dev, "Failed to read %x: %d\n", reg, ret); - goto err_free; + return ret; } } @@ -123,15 +113,17 @@ static int regcache_hw_init(struct regmap *map) } return 0; +} -err_free: - kfree(map->reg_defaults); - - return ret; +static void regcache_hw_exit(struct regmap *map) +{ + if (map->cache_free) + kfree(map->reg_defaults_raw); } int regcache_init(struct regmap *map, const struct regmap_config *config) { + int count = 0; int ret; int i; void *tmp_buf; @@ -196,15 +188,18 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) return -ENOMEM; map->reg_defaults = tmp_buf; } else if (map->num_reg_defaults_raw) { - /* Some devices such as PMICs don't have cache defaults, - * we cope with this by reading back the HW registers and - * crafting the cache defaults by hand. - */ - ret = regcache_hw_init(map); - if (ret < 0) - return ret; + count = regcache_count_cacheable_registers(map); + if (!count) + map->cache_bypass = true; + + /* All registers are unreadable or volatile, so just bypass */ if (map->cache_bypass) return 0; + + map->num_reg_defaults = count; + map->reg_defaults = kmalloc_objs(struct reg_default, count); + if (!map->reg_defaults) + return -ENOMEM; } if (!map->max_register_is_set && map->num_reg_defaults_raw) { @@ -219,7 +214,18 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = map->cache_ops->init(map); map->unlock(map->lock_arg); if (ret) - goto err_free; + goto err_free_reg_defaults; + } + + /* + * Some devices such as PMICs don't have cache defaults, + * we cope with this by reading back the HW registers and + * crafting the cache defaults by hand. + */ + if (count) { + ret = regcache_hw_init(map); + if (ret) + goto err_exit; } if (map->cache_ops->populate && @@ -229,10 +235,12 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = map->cache_ops->populate(map); map->unlock(map->lock_arg); if (ret) - goto err_exit; + goto err_free; } return 0; +err_free: + regcache_hw_exit(map); err_exit: if (map->cache_ops->exit) { dev_dbg(map->dev, "Destroying %s cache\n", map->cache_ops->name); @@ -240,10 +248,8 @@ int regcache_init(struct regmap *map, const struct regmap_config *config) ret = map->cache_ops->exit(map); map->unlock(map->lock_arg); } -err_free: +err_free_reg_defaults: kfree(map->reg_defaults); - if (map->cache_free) - kfree(map->reg_defaults_raw); return ret; } @@ -255,9 +261,7 @@ void regcache_exit(struct regmap *map) BUG_ON(!map->cache_ops); - kfree(map->reg_defaults); - if (map->cache_free) - kfree(map->reg_defaults_raw); + regcache_hw_exit(map); if (map->cache_ops->exit) { dev_dbg(map->dev, "Destroying %s cache\n", @@ -266,6 +270,8 @@ void regcache_exit(struct regmap *map) map->cache_ops->exit(map); map->unlock(map->lock_arg); } + + kfree(map->reg_defaults); } /** @@ -504,7 +510,7 @@ int regcache_sync_region(struct regmap *map, unsigned int min, bypass = map->cache_bypass; name = map->cache_ops->name; - dev_dbg(map->dev, "Syncing %s cache from %d-%d\n", name, min, max); + dev_dbg(map->dev, "Syncing %s cache from %#x-%#x\n", name, min, max); trace_regcache_sync(map, name, "start region"); @@ -835,13 +841,13 @@ static int regcache_sync_block_raw(struct regmap *map, void *block, unsigned int block_base, unsigned int start, unsigned int end) { - unsigned int i, val; unsigned int regtmp = 0; unsigned int base = 0; const void *data = NULL; + unsigned int val; int ret; - for (i = start; i < end; i++) { + for (unsigned int i = start; i < end; i++) { regtmp = block_base + (i * map->reg_stride); if (!regcache_reg_present(cache_present, i) || diff --git a/drivers/base/regmap/regmap-debugfs.c b/drivers/base/regmap/regmap-debugfs.c index 5a46ce5fee72a3..18f1c60749fed5 100644 --- a/drivers/base/regmap/regmap-debugfs.c +++ b/drivers/base/regmap/regmap-debugfs.c @@ -12,6 +12,7 @@ #include <linux/uaccess.h> #include <linux/device.h> #include <linux/list.h> +#include <linux/idr.h> #include "internal.h" @@ -20,7 +21,7 @@ struct regmap_debugfs_node { struct list_head link; }; -static unsigned int dummy_index; +static DEFINE_IDA(dummy_ida); static struct dentry *regmap_debugfs_root; static LIST_HEAD(regmap_debugfs_early_list); static DEFINE_MUTEX(regmap_debugfs_early_lock); @@ -539,6 +540,7 @@ void regmap_debugfs_init(struct regmap *map) struct regmap_range_node *range_node; const char *devname = "dummy"; const char *name = map->name; + int id; /* * Userspace can initiate reads from the hardware over debugfs. @@ -567,6 +569,7 @@ void regmap_debugfs_init(struct regmap *map) INIT_LIST_HEAD(&map->debugfs_off_cache); mutex_init(&map->cache_lock); + map->debugfs_dummy_id = -1; if (map->dev) devname = dev_name(map->dev); @@ -585,12 +588,16 @@ void regmap_debugfs_init(struct regmap *map) if (!strcmp(name, "dummy")) { kfree(map->debugfs_name); - map->debugfs_name = kasprintf(GFP_KERNEL, "dummy%d", - dummy_index); - if (!map->debugfs_name) + id = ida_alloc(&dummy_ida, GFP_KERNEL); + if (id < 0) return; + map->debugfs_name = kasprintf(GFP_KERNEL, "dummy%d", id); + if (!map->debugfs_name) { + ida_free(&dummy_ida, id); + return; + } + map->debugfs_dummy_id = id; name = map->debugfs_name; - dummy_index++; } map->debugfs = debugfs_create_dir(name, regmap_debugfs_root); @@ -660,6 +667,10 @@ void regmap_debugfs_exit(struct regmap *map) mutex_lock(&map->cache_lock); regmap_debugfs_free_dump_cache(map); mutex_unlock(&map->cache_lock); + if (map->debugfs_dummy_id >= 0) { + ida_free(&dummy_ida, map->debugfs_dummy_id); + map->debugfs_dummy_id = -1; + } kfree(map->debugfs_name); map->debugfs_name = NULL; } else { diff --git a/drivers/base/regmap/regmap-i3c.c b/drivers/base/regmap/regmap-i3c.c index 863b348704dcca..5af583d472ddd6 100644 --- a/drivers/base/regmap/regmap-i3c.c +++ b/drivers/base/regmap/regmap-i3c.c @@ -46,6 +46,16 @@ static const struct regmap_bus regmap_i3c = { .read = regmap_i3c_read, }; +struct regmap *__regmap_init_i3c(struct i3c_device *i3c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name) +{ + return __regmap_init(&i3c->dev, &regmap_i3c, &i3c->dev, config, + lock_key, lock_name); +} +EXPORT_SYMBOL_GPL(__regmap_init_i3c); + struct regmap *__devm_regmap_init_i3c(struct i3c_device *i3c, const struct regmap_config *config, struct lock_class_key *lock_key, diff --git a/drivers/base/regmap/regmap.c b/drivers/base/regmap/regmap.c index e388b19850e37c..b2b26f07f4e3f7 100644 --- a/drivers/base/regmap/regmap.c +++ b/drivers/base/regmap/regmap.c @@ -1182,9 +1182,9 @@ struct regmap *__regmap_init(struct device *dev, } EXPORT_SYMBOL_GPL(__regmap_init); -static void devm_regmap_release(struct device *dev, void *res) +static void devm_regmap_release(void *regmap) { - regmap_exit(*(struct regmap **)res); + regmap_exit(regmap); } struct regmap *__devm_regmap_init(struct device *dev, @@ -1194,20 +1194,17 @@ struct regmap *__devm_regmap_init(struct device *dev, struct lock_class_key *lock_key, const char *lock_name) { - struct regmap **ptr, *regmap; - - ptr = devres_alloc(devm_regmap_release, sizeof(*ptr), GFP_KERNEL); - if (!ptr) - return ERR_PTR(-ENOMEM); + struct regmap *regmap; + int ret; regmap = __regmap_init(dev, bus, bus_context, config, lock_key, lock_name); - if (!IS_ERR(regmap)) { - *ptr = regmap; - devres_add(dev, ptr); - } else { - devres_free(ptr); - } + if (IS_ERR(regmap)) + return regmap; + + ret = devm_add_action_or_reset(dev, devm_regmap_release, regmap); + if (ret) + return ERR_PTR(ret); return regmap; } diff --git a/include/linux/regmap.h b/include/linux/regmap.h index caff2240bdab79..df44cb30f53b9f 100644 --- a/include/linux/regmap.h +++ b/include/linux/regmap.h @@ -10,15 +10,16 @@ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> */ -#include <linux/list.h> -#include <linux/rbtree.h> -#include <linux/ktime.h> +#include <linux/bug.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/err.h> -#include <linux/bug.h> -#include <linux/lockdep.h> -#include <linux/iopoll.h> #include <linux/fwnode.h> +#include <linux/iopoll.h> +#include <linux/ktime.h> +#include <linux/list.h> +#include <linux/lockdep.h> +#include <linux/rbtree.h> struct module; struct clk; @@ -692,6 +693,10 @@ struct regmap *__regmap_init_sdw_mbq(struct device *dev, struct sdw_slave *sdw, const struct regmap_sdw_mbq_cfg *mbq_config, struct lock_class_key *lock_key, const char *lock_name); +struct regmap *__regmap_init_i3c(struct i3c_device *i3c, + const struct regmap_config *config, + struct lock_class_key *lock_key, + const char *lock_name); struct regmap *__regmap_init_spi_avmm(struct spi_device *spi, const struct regmap_config *config, struct lock_class_key *lock_key, @@ -998,6 +1003,19 @@ bool regmap_ac97_default_volatile(struct device *dev, unsigned int reg); __regmap_lockdep_wrapper(__regmap_init_sdw_mbq, #config, \ dev, sdw, config, mbq_config) +/** + * regmap_init_i3c() - Initialise register map + * + * @i3c: Device that will be interacted with + * @config: Configuration for register map + * + * The return value will be an ERR_PTR() on error or a valid pointer to + * a struct regmap. + */ +#define regmap_init_i3c(i3c, config) \ + __regmap_lockdep_wrapper(__regmap_init_i3c, #config, \ + i3c, config) + /** * regmap_init_spi_avmm() - Initialize register map for Intel SPI Slave * to AVMM Bus Bridge @@ -1460,6 +1478,8 @@ struct regmap_field *regmap_field_alloc(struct regmap *regmap, struct reg_field reg_field); void regmap_field_free(struct regmap_field *field); +DEFINE_FREE(regmap_field, struct regmap_field *, if (_T) regmap_field_free(_T)) + struct regmap_field *devm_regmap_field_alloc(struct device *dev, struct regmap *regmap, struct reg_field reg_field); void devm_regmap_field_free(struct device *dev, struct regmap_field *field);
← Back to Alerts View on GitHub →