mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 14:43:16 +00:00
66fbfb35da
Problem can be reproduced by unloading snd_soc_simple_card, because in devm_get_clk_from_child() devres data is allocated as `struct clk`, but devm_clk_release() expects devres data to be `struct devm_clk_state`. KASAN report: ================================================================== BUG: KASAN: slab-out-of-bounds in devm_clk_release+0x20/0x54 Read of size 8 at addr ffffff800ee09688 by task (udev-worker)/287 Call trace: dump_backtrace+0xe8/0x11c show_stack+0x1c/0x30 dump_stack_lvl+0x60/0x78 print_report+0x150/0x450 kasan_report+0xa8/0xf0 __asan_load8+0x78/0xa0 devm_clk_release+0x20/0x54 release_nodes+0x84/0x120 devres_release_all+0x144/0x210 device_unbind_cleanup+0x1c/0xac really_probe+0x2f0/0x5b0 __driver_probe_device+0xc0/0x1f0 driver_probe_device+0x68/0x120 __driver_attach+0x140/0x294 bus_for_each_dev+0xec/0x160 driver_attach+0x38/0x44 bus_add_driver+0x24c/0x300 driver_register+0xf0/0x210 __platform_driver_register+0x48/0x54 asoc_simple_card_init+0x24/0x1000 [snd_soc_simple_card] do_one_initcall+0xac/0x340 do_init_module+0xd0/0x300 load_module+0x2ba4/0x3100 __do_sys_init_module+0x2c8/0x300 __arm64_sys_init_module+0x48/0x5c invoke_syscall+0x64/0x190 el0_svc_common.constprop.0+0x124/0x154 do_el0_svc+0x44/0xdc el0_svc+0x14/0x50 el0t_64_sync_handler+0xec/0x11c el0t_64_sync+0x14c/0x150 Allocated by task 287: kasan_save_stack+0x38/0x60 kasan_set_track+0x28/0x40 kasan_save_alloc_info+0x20/0x30 __kasan_kmalloc+0xac/0xb0 __kmalloc_node_track_caller+0x6c/0x1c4 __devres_alloc_node+0x44/0xb4 devm_get_clk_from_child+0x44/0xa0 asoc_simple_parse_clk+0x1b8/0x1dc [snd_soc_simple_card_utils] simple_parse_node.isra.0+0x1ec/0x230 [snd_soc_simple_card] simple_dai_link_of+0x1bc/0x334 [snd_soc_simple_card] __simple_for_each_link+0x2ec/0x320 [snd_soc_simple_card] asoc_simple_probe+0x468/0x4dc [snd_soc_simple_card] platform_probe+0x90/0xf0 really_probe+0x118/0x5b0 __driver_probe_device+0xc0/0x1f0 driver_probe_device+0x68/0x120 __driver_attach+0x140/0x294 bus_for_each_dev+0xec/0x160 driver_attach+0x38/0x44 bus_add_driver+0x24c/0x300 driver_register+0xf0/0x210 __platform_driver_register+0x48/0x54 asoc_simple_card_init+0x24/0x1000 [snd_soc_simple_card] do_one_initcall+0xac/0x340 do_init_module+0xd0/0x300 load_module+0x2ba4/0x3100 __do_sys_init_module+0x2c8/0x300 __arm64_sys_init_module+0x48/0x5c invoke_syscall+0x64/0x190 el0_svc_common.constprop.0+0x124/0x154 do_el0_svc+0x44/0xdc el0_svc+0x14/0x50 el0t_64_sync_handler+0xec/0x11c el0t_64_sync+0x14c/0x150 The buggy address belongs to the object at ffffff800ee09600 which belongs to the cache kmalloc-256 of size 256 The buggy address is located 136 bytes inside of 256-byte region [ffffff800ee09600, ffffff800ee09700) The buggy address belongs to the physical page: page:000000002d97303b refcount:1 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x4ee08 head:000000002d97303b order:1 compound_mapcount:0 compound_pincount:0 flags: 0x10200(slab|head|zone=0) raw: 0000000000010200 0000000000000000 dead000000000122 ffffff8002c02480 raw: 0000000000000000 0000000080100010 00000001ffffffff 0000000000000000 page dumped because: kasan: bad access detected Memory state around the buggy address: ffffff800ee09580: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffffff800ee09600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 >ffffff800ee09680: 00 fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ^ ffffff800ee09700: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ffffff800ee09780: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc ================================================================== Fixes: abae8e57e49a ("clk: generalize devm_clk_get() a bit") Signed-off-by: Andrey Skvortsov <andrej.skvortzov@gmail.com> Link: https://lore.kernel.org/r/20230805084847.3110586-1-andrej.skvortzov@gmail.com Signed-off-by: Stephen Boyd <sboyd@kernel.org>
226 lines
4.8 KiB
C
226 lines
4.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/clk.h>
|
|
#include <linux/device.h>
|
|
#include <linux/export.h>
|
|
#include <linux/gfp.h>
|
|
|
|
struct devm_clk_state {
|
|
struct clk *clk;
|
|
void (*exit)(struct clk *clk);
|
|
};
|
|
|
|
static void devm_clk_release(struct device *dev, void *res)
|
|
{
|
|
struct devm_clk_state *state = res;
|
|
|
|
if (state->exit)
|
|
state->exit(state->clk);
|
|
|
|
clk_put(state->clk);
|
|
}
|
|
|
|
static struct clk *__devm_clk_get(struct device *dev, const char *id,
|
|
struct clk *(*get)(struct device *dev, const char *id),
|
|
int (*init)(struct clk *clk),
|
|
void (*exit)(struct clk *clk))
|
|
{
|
|
struct devm_clk_state *state;
|
|
struct clk *clk;
|
|
int ret;
|
|
|
|
state = devres_alloc(devm_clk_release, sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
clk = get(dev, id);
|
|
if (IS_ERR(clk)) {
|
|
ret = PTR_ERR(clk);
|
|
goto err_clk_get;
|
|
}
|
|
|
|
if (init) {
|
|
ret = init(clk);
|
|
if (ret)
|
|
goto err_clk_init;
|
|
}
|
|
|
|
state->clk = clk;
|
|
state->exit = exit;
|
|
|
|
devres_add(dev, state);
|
|
|
|
return clk;
|
|
|
|
err_clk_init:
|
|
|
|
clk_put(clk);
|
|
err_clk_get:
|
|
|
|
devres_free(state);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
struct clk *devm_clk_get(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get, NULL, NULL);
|
|
}
|
|
EXPORT_SYMBOL(devm_clk_get);
|
|
|
|
struct clk *devm_clk_get_prepared(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get, clk_prepare, clk_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_prepared);
|
|
|
|
struct clk *devm_clk_get_enabled(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get,
|
|
clk_prepare_enable, clk_disable_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_enabled);
|
|
|
|
struct clk *devm_clk_get_optional(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get_optional, NULL, NULL);
|
|
}
|
|
EXPORT_SYMBOL(devm_clk_get_optional);
|
|
|
|
struct clk *devm_clk_get_optional_prepared(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get_optional,
|
|
clk_prepare, clk_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_optional_prepared);
|
|
|
|
struct clk *devm_clk_get_optional_enabled(struct device *dev, const char *id)
|
|
{
|
|
return __devm_clk_get(dev, id, clk_get_optional,
|
|
clk_prepare_enable, clk_disable_unprepare);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_get_optional_enabled);
|
|
|
|
struct clk_bulk_devres {
|
|
struct clk_bulk_data *clks;
|
|
int num_clks;
|
|
};
|
|
|
|
static void devm_clk_bulk_release(struct device *dev, void *res)
|
|
{
|
|
struct clk_bulk_devres *devres = res;
|
|
|
|
clk_bulk_put(devres->num_clks, devres->clks);
|
|
}
|
|
|
|
static int __devm_clk_bulk_get(struct device *dev, int num_clks,
|
|
struct clk_bulk_data *clks, bool optional)
|
|
{
|
|
struct clk_bulk_devres *devres;
|
|
int ret;
|
|
|
|
devres = devres_alloc(devm_clk_bulk_release,
|
|
sizeof(*devres), GFP_KERNEL);
|
|
if (!devres)
|
|
return -ENOMEM;
|
|
|
|
if (optional)
|
|
ret = clk_bulk_get_optional(dev, num_clks, clks);
|
|
else
|
|
ret = clk_bulk_get(dev, num_clks, clks);
|
|
if (!ret) {
|
|
devres->clks = clks;
|
|
devres->num_clks = num_clks;
|
|
devres_add(dev, devres);
|
|
} else {
|
|
devres_free(devres);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int __must_check devm_clk_bulk_get(struct device *dev, int num_clks,
|
|
struct clk_bulk_data *clks)
|
|
{
|
|
return __devm_clk_bulk_get(dev, num_clks, clks, false);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_bulk_get);
|
|
|
|
int __must_check devm_clk_bulk_get_optional(struct device *dev, int num_clks,
|
|
struct clk_bulk_data *clks)
|
|
{
|
|
return __devm_clk_bulk_get(dev, num_clks, clks, true);
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_bulk_get_optional);
|
|
|
|
static void devm_clk_bulk_release_all(struct device *dev, void *res)
|
|
{
|
|
struct clk_bulk_devres *devres = res;
|
|
|
|
clk_bulk_put_all(devres->num_clks, devres->clks);
|
|
}
|
|
|
|
int __must_check devm_clk_bulk_get_all(struct device *dev,
|
|
struct clk_bulk_data **clks)
|
|
{
|
|
struct clk_bulk_devres *devres;
|
|
int ret;
|
|
|
|
devres = devres_alloc(devm_clk_bulk_release_all,
|
|
sizeof(*devres), GFP_KERNEL);
|
|
if (!devres)
|
|
return -ENOMEM;
|
|
|
|
ret = clk_bulk_get_all(dev, &devres->clks);
|
|
if (ret > 0) {
|
|
*clks = devres->clks;
|
|
devres->num_clks = ret;
|
|
devres_add(dev, devres);
|
|
} else {
|
|
devres_free(devres);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(devm_clk_bulk_get_all);
|
|
|
|
static int devm_clk_match(struct device *dev, void *res, void *data)
|
|
{
|
|
struct clk **c = res;
|
|
if (!c || !*c) {
|
|
WARN_ON(!c || !*c);
|
|
return 0;
|
|
}
|
|
return *c == data;
|
|
}
|
|
|
|
void devm_clk_put(struct device *dev, struct clk *clk)
|
|
{
|
|
int ret;
|
|
|
|
ret = devres_release(dev, devm_clk_release, devm_clk_match, clk);
|
|
|
|
WARN_ON(ret);
|
|
}
|
|
EXPORT_SYMBOL(devm_clk_put);
|
|
|
|
struct clk *devm_get_clk_from_child(struct device *dev,
|
|
struct device_node *np, const char *con_id)
|
|
{
|
|
struct devm_clk_state *state;
|
|
struct clk *clk;
|
|
|
|
state = devres_alloc(devm_clk_release, sizeof(*state), GFP_KERNEL);
|
|
if (!state)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
clk = of_clk_get_by_name(np, con_id);
|
|
if (!IS_ERR(clk)) {
|
|
state->clk = clk;
|
|
devres_add(dev, state);
|
|
} else {
|
|
devres_free(state);
|
|
}
|
|
|
|
return clk;
|
|
}
|
|
EXPORT_SYMBOL(devm_get_clk_from_child);
|