From 72c1afffa4c645fe0e0f1c03e5f34395ed65b5f4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Apr 2024 19:52:12 +0200 Subject: thermal/debugfs: Free all thermal zone debug memory on zone removal Because thermal_debug_tz_remove() does not free all memory allocated for thermal zone diagnostics, some of that memory becomes unreachable after freeing the thermal zone's struct thermal_debugfs object. Address this by making thermal_debug_tz_remove() free all of the memory in question. Fixes: 7ef01f228c9f ("thermal/debugfs: Add thermal debugfs information for mitigation episodes") Cc :6.8+ # 6.8+ Signed-off-by: Rafael J. Wysocki Reviewed-by: Lukasz Luba --- drivers/thermal/thermal_debugfs.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/thermal/thermal_debugfs.c b/drivers/thermal/thermal_debugfs.c index d78d54ae2605..8d66b3a96be1 100644 --- a/drivers/thermal/thermal_debugfs.c +++ b/drivers/thermal/thermal_debugfs.c @@ -826,15 +826,28 @@ void thermal_debug_tz_add(struct thermal_zone_device *tz) void thermal_debug_tz_remove(struct thermal_zone_device *tz) { struct thermal_debugfs *thermal_dbg = tz->debugfs; + struct tz_episode *tze, *tmp; + struct tz_debugfs *tz_dbg; + int *trips_crossed; if (!thermal_dbg) return; + tz_dbg = &thermal_dbg->tz_dbg; + mutex_lock(&thermal_dbg->lock); + trips_crossed = tz_dbg->trips_crossed; + + list_for_each_entry_safe(tze, tmp, &tz_dbg->tz_episodes, node) { + list_del(&tze->node); + kfree(tze); + } + tz->debugfs = NULL; mutex_unlock(&thermal_dbg->lock); thermal_debugfs_remove_id(thermal_dbg); + kfree(trips_crossed); } -- cgit v1.2.3 From c7f7c37271787a7f77d7eedc132b0b419a76b4c8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 25 Apr 2024 20:00:33 +0200 Subject: thermal/debugfs: Fix two locking issues with thermal zone debug With the current thermal zone locking arrangement in the debugfs code, user space can open the "mitigations" file for a thermal zone before the zone's debugfs pointer is set which will result in a NULL pointer dereference in tze_seq_start(). Moreover, thermal_debug_tz_remove() is not called under the thermal zone lock, so it can run in parallel with the other functions accessing the thermal zone's struct thermal_debugfs object. Then, it may clear tz->debugfs after one of those functions has checked it and the struct thermal_debugfs object may be freed prematurely. To address the first problem, pass a pointer to the thermal zone's struct thermal_debugfs object to debugfs_create_file() in thermal_debug_tz_add() and make tze_seq_start(), tze_seq_next(), tze_seq_stop(), and tze_seq_show() retrieve it from s->private instead of a pointer to the thermal zone object. This will ensure that tz_debugfs will be valid across the "mitigations" file accesses until thermal_debugfs_remove_id() called by thermal_debug_tz_remove() removes that file. To address the second problem, use tz->lock in thermal_debug_tz_remove() around the tz->debugfs value check (in case the same thermal zone is removed at the same time in two different threads) and its reset to NULL. Fixes: 7ef01f228c9f ("thermal/debugfs: Add thermal debugfs information for mitigation episodes") Cc :6.8+ # 6.8+ Signed-off-by: Rafael J. Wysocki Reviewed-by: Lukasz Luba --- drivers/thermal/thermal_debugfs.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/drivers/thermal/thermal_debugfs.c b/drivers/thermal/thermal_debugfs.c index 8d66b3a96be1..1fe2914a8853 100644 --- a/drivers/thermal/thermal_debugfs.c +++ b/drivers/thermal/thermal_debugfs.c @@ -139,11 +139,13 @@ struct tz_episode { * we keep track of the current position in the history array. * * @tz_episodes: a list of thermal mitigation episodes + * @tz: thermal zone this object belongs to * @trips_crossed: an array of trip points crossed by id * @nr_trips: the number of trip points currently being crossed */ struct tz_debugfs { struct list_head tz_episodes; + struct thermal_zone_device *tz; int *trips_crossed; int nr_trips; }; @@ -716,8 +718,7 @@ out: static void *tze_seq_start(struct seq_file *s, loff_t *pos) { - struct thermal_zone_device *tz = s->private; - struct thermal_debugfs *thermal_dbg = tz->debugfs; + struct thermal_debugfs *thermal_dbg = s->private; struct tz_debugfs *tz_dbg = &thermal_dbg->tz_dbg; mutex_lock(&thermal_dbg->lock); @@ -727,8 +728,7 @@ static void *tze_seq_start(struct seq_file *s, loff_t *pos) static void *tze_seq_next(struct seq_file *s, void *v, loff_t *pos) { - struct thermal_zone_device *tz = s->private; - struct thermal_debugfs *thermal_dbg = tz->debugfs; + struct thermal_debugfs *thermal_dbg = s->private; struct tz_debugfs *tz_dbg = &thermal_dbg->tz_dbg; return seq_list_next(v, &tz_dbg->tz_episodes, pos); @@ -736,15 +736,15 @@ static void *tze_seq_next(struct seq_file *s, void *v, loff_t *pos) static void tze_seq_stop(struct seq_file *s, void *v) { - struct thermal_zone_device *tz = s->private; - struct thermal_debugfs *thermal_dbg = tz->debugfs; + struct thermal_debugfs *thermal_dbg = s->private; mutex_unlock(&thermal_dbg->lock); } static int tze_seq_show(struct seq_file *s, void *v) { - struct thermal_zone_device *tz = s->private; + struct thermal_debugfs *thermal_dbg = s->private; + struct thermal_zone_device *tz = thermal_dbg->tz_dbg.tz; struct thermal_trip *trip; struct tz_episode *tze; const char *type; @@ -810,6 +810,8 @@ void thermal_debug_tz_add(struct thermal_zone_device *tz) tz_dbg = &thermal_dbg->tz_dbg; + tz_dbg->tz = tz; + tz_dbg->trips_crossed = kzalloc(sizeof(int) * tz->num_trips, GFP_KERNEL); if (!tz_dbg->trips_crossed) { thermal_debugfs_remove_id(thermal_dbg); @@ -818,20 +820,30 @@ void thermal_debug_tz_add(struct thermal_zone_device *tz) INIT_LIST_HEAD(&tz_dbg->tz_episodes); - debugfs_create_file("mitigations", 0400, thermal_dbg->d_top, tz, &tze_fops); + debugfs_create_file("mitigations", 0400, thermal_dbg->d_top, + thermal_dbg, &tze_fops); tz->debugfs = thermal_dbg; } void thermal_debug_tz_remove(struct thermal_zone_device *tz) { - struct thermal_debugfs *thermal_dbg = tz->debugfs; + struct thermal_debugfs *thermal_dbg; struct tz_episode *tze, *tmp; struct tz_debugfs *tz_dbg; int *trips_crossed; - if (!thermal_dbg) + mutex_lock(&tz->lock); + + thermal_dbg = tz->debugfs; + if (!thermal_dbg) { + mutex_unlock(&tz->lock); return; + } + + tz->debugfs = NULL; + + mutex_unlock(&tz->lock); tz_dbg = &thermal_dbg->tz_dbg; @@ -844,8 +856,6 @@ void thermal_debug_tz_remove(struct thermal_zone_device *tz) kfree(tze); } - tz->debugfs = NULL; - mutex_unlock(&thermal_dbg->lock); thermal_debugfs_remove_id(thermal_dbg); -- cgit v1.2.3 From d351eb0ab04c3e8109895fc33250cebbce9c11da Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 26 Apr 2024 11:10:10 +0200 Subject: thermal/debugfs: Prevent use-after-free from occurring after cdev removal Since thermal_debug_cdev_remove() does not run under cdev->lock, it can run in parallel with thermal_debug_cdev_state_update() and it may free the struct thermal_debugfs object used by the latter after it has been checked against NULL. If that happens, thermal_debug_cdev_state_update() will access memory that has been freed already causing the kernel to crash. Address this by using cdev->lock in thermal_debug_cdev_remove() around the cdev->debugfs value check (in case the same cdev is removed at the same time in two different threads) and its reset to NULL. Fixes: 755113d76786 ("thermal/debugfs: Add thermal cooling device debugfs information") Cc :6.8+ # 6.8+ Signed-off-by: Rafael J. Wysocki Reviewed-by: Lukasz Luba --- drivers/thermal/thermal_debugfs.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/thermal/thermal_debugfs.c b/drivers/thermal/thermal_debugfs.c index 1fe2914a8853..5693cc8b231a 100644 --- a/drivers/thermal/thermal_debugfs.c +++ b/drivers/thermal/thermal_debugfs.c @@ -505,15 +505,23 @@ void thermal_debug_cdev_add(struct thermal_cooling_device *cdev) */ void thermal_debug_cdev_remove(struct thermal_cooling_device *cdev) { - struct thermal_debugfs *thermal_dbg = cdev->debugfs; + struct thermal_debugfs *thermal_dbg; - if (!thermal_dbg) + mutex_lock(&cdev->lock); + + thermal_dbg = cdev->debugfs; + if (!thermal_dbg) { + mutex_unlock(&cdev->lock); return; + } + + cdev->debugfs = NULL; + + mutex_unlock(&cdev->lock); mutex_lock(&thermal_dbg->lock); thermal_debugfs_cdev_clear(&thermal_dbg->cdev_dbg); - cdev->debugfs = NULL; mutex_unlock(&thermal_dbg->lock); -- cgit v1.2.3