Use-after-free

HIGH
torvalds/linux
Commit: 2dec87d0b195
Affected: v7.0-rc6 and earlier (pre-thermal-7.2-rc1-2 fix)
2026-06-26 21:08 UTC

Description

The commit fixes a use-after-free condition in the thermal subsystem by ensuring testing module code cannot be executed after the module is removed and by flushing/ordering work items and resources during cleanup. It additionally fixes dangling resources in the Intel thermal_throttle driver by guarding the resource acquisition path and returning the correct error path, preventing use-after-free or use-after-uninit usage. The net effect is removal of a window where testing code or scheduled work could dereference freed objects, i.e., a classic use-after-free risk in the thermal testing path and a safer failure path in the Intel thermal_throttle path.

Proof of Concept

// Proof-of-concept demonstration of a use-after-free scenario in a simplified kernel context // Note: this is a conceptual PoC illustrating the UAF class of issue, not a full reproduction of the kernel patch #include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/workqueue.h> static struct work_struct test_work; static struct data_holder { int value; } *ptr; static void test_work_fn(struct work_struct *w) { // Potential UAF if ptr has been freed before this runs printk(KERN_INFO "[poc] test_work_fn: value=%d\n", ptr->value); } static int __init poc_init(void) { pr_info("poc_init: allocating shared data and queuing work\n"); ptr = kmalloc(sizeof(*ptr), GFP_KERNEL); if (!ptr) return -ENOMEM; ptr->value = 0xdeadbeef; INIT_WORK(&test_work, test_work_fn); queue_work(system_wq, &test_work); // Immediately free the memory to simulate a race where the work item may run later kfree(ptr); ptr = NULL; // dangling pointer in the worker context return 0; } static void __exit poc_exit(void) { // Ensure any pending work finishes before module unload flush_work(&test_work); pr_info("poc_exit: cleanup complete\n"); } module_init(poc_init); module_exit(poc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("poC researcher"); MODULE_DESCRIPTION("Use-after-free PoC for thermal testing UAF scenario (conceptual)");

Commit Details

Author: Linus Torvalds

Date: 2026-06-26 20:24 UTC

Message:

Merge tag 'thermal-7.2-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm Pull thermal control fixes from Rafael Wysocki: "These fix a failure path in an Intel thermal driver and prevent thermal testing module code from being executed after it has been freed: - Fix dangling resources on thermal_throttle_online() failure in the Intel thermal_throttle driver (Ricardo Neri) - Eliminate a possibility of running thermal testing module code after that module has been removed (Rafael Wysocki)" * tag 'thermal-7.2-rc1-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm: thermal: testing: zone: Flush work items during cleanup thermal: intel: Fix dangling resources on thermal_throttle_online() failure

Triage Assessment

Vulnerability Type: Use-after-free

Confidence: HIGH

Reasoning:

Commit message and code changes address use-after-free scenarios: it prevents execution of testing module code after the module has been freed and fixes dangling resources in thermal_throttle_online(), which are classic memory safety issues that can lead to security vulnerabilities if exploited.

Verification Assessment

Vulnerability Type: Use-after-free

Confidence: HIGH

Affected Versions: v7.0-rc6 and earlier (pre-thermal-7.2-rc1-2 fix)

Code Diff

diff --git a/drivers/thermal/intel/therm_throt.c b/drivers/thermal/intel/therm_throt.c index 44fa4dd15dd1c5..45a8ef4a608b14 100644 --- a/drivers/thermal/intel/therm_throt.c +++ b/drivers/thermal/intel/therm_throt.c @@ -529,8 +529,13 @@ static int thermal_throttle_online(unsigned int cpu) { struct thermal_state *state = &per_cpu(thermal_state, cpu); struct device *dev = get_cpu_device(cpu); + int err; u32 l; + err = thermal_throttle_add_dev(dev, cpu); + if (err) + return err; + state->package_throttle.level = PACKAGE_LEVEL; state->core_throttle.level = CORE_LEVEL; @@ -548,7 +553,7 @@ static int thermal_throttle_online(unsigned int cpu) l = apic_read(APIC_LVTTHMR); apic_write(APIC_LVTTHMR, l & ~APIC_LVT_MASKED); - return thermal_throttle_add_dev(dev, cpu); + return err; } static int thermal_throttle_offline(unsigned int cpu) diff --git a/drivers/thermal/testing/command.c b/drivers/thermal/testing/command.c index fbf7ab9729b5a5..90e06c5933278a 100644 --- a/drivers/thermal/testing/command.c +++ b/drivers/thermal/testing/command.c @@ -86,7 +86,10 @@ #include "thermal_testing.h" -struct dentry *d_testing; +struct workqueue_struct *tt_wq __ro_after_init; + +struct dentry *d_testing __ro_after_init; +static struct dentry *d_command __ro_after_init; #define TT_COMMAND_SIZE 16 @@ -203,17 +206,42 @@ static const struct file_operations tt_command_fops = { static int __init thermal_testing_init(void) { + int error; + + tt_wq = alloc_workqueue("thermal_testing", WQ_UNBOUND, 0); + if (!tt_wq) + return -ENOMEM; + d_testing = debugfs_create_dir("thermal-testing", NULL); - if (!IS_ERR(d_testing)) - debugfs_create_file("command", 0200, d_testing, NULL, - &tt_command_fops); + if (IS_ERR(d_testing)) { + error = PTR_ERR(d_testing); + goto destroy_wq; + } + + d_command = debugfs_create_file("command", 0200, d_testing, NULL, &tt_command_fops); + if (IS_ERR(d_command)) { + error = PTR_ERR(d_command); + goto remove_d_testing; + } return 0; + +remove_d_testing: + debugfs_remove(d_testing); +destroy_wq: + destroy_workqueue(tt_wq); + return error; } module_init(thermal_testing_init); static void __exit thermal_testing_exit(void) { + /* First, prevent new commands from being entered. */ + debugfs_remove(d_command); + /* Flush commands in progress (if any). */ + flush_workqueue(tt_wq); + destroy_workqueue(tt_wq); + /* Remove the directory structure and clean up. */ debugfs_remove(d_testing); tt_zone_cleanup(); } diff --git a/drivers/thermal/testing/thermal_testing.h b/drivers/thermal/testing/thermal_testing.h index c790a32aae4ed7..5880c9a63dba99 100644 --- a/drivers/thermal/testing/thermal_testing.h +++ b/drivers/thermal/testing/thermal_testing.h @@ -1,4 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ +#include <linux/workqueue.h> + +extern struct workqueue_struct *tt_wq; + +static inline void tt_queue_work(struct work_struct *work) +{ + queue_work(tt_wq, work); +} extern struct dentry *d_testing; diff --git a/drivers/thermal/testing/zone.c b/drivers/thermal/testing/zone.c index f7f9ca2f1f2cd7..6db26276dd4172 100644 --- a/drivers/thermal/testing/zone.c +++ b/drivers/thermal/testing/zone.c @@ -13,7 +13,6 @@ #include <linux/idr.h> #include <linux/list.h> #include <linux/thermal.h> -#include <linux/workqueue.h> #include "thermal_testing.h" @@ -207,7 +206,7 @@ int tt_add_tz(void) INIT_WORK(&tt_work->work, tt_add_tz_work_fn); tt_work->tt_zone = no_free_ptr(tt_zone); - schedule_work(&(no_free_ptr(tt_work)->work)); + tt_queue_work(&(no_free_ptr(tt_work)->work)); return 0; } @@ -269,7 +268,7 @@ int tt_del_tz(const char *arg) INIT_WORK(&tt_work->work, tt_del_tz_work_fn); tt_work->tt_zone = tt_zone; - schedule_work(&(no_free_ptr(tt_work)->work)); + tt_queue_work(&(no_free_ptr(tt_work)->work)); return 0; } @@ -358,7 +357,7 @@ int tt_zone_add_trip(const char *arg) INIT_WORK(&tt_work->work, tt_zone_add_trip_work_fn); tt_work->tt_zone = no_free_ptr(tt_zone); tt_work->tt_trip = no_free_ptr(tt_trip); - schedule_work(&(no_free_ptr(tt_work)->work)); + tt_queue_work(&(no_free_ptr(tt_work)->work)); return 0; }
← Back to Alerts View on GitHub →