Commit 576db0f3 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull thermal control fixes from Rafael Wysocki:
 "Address potential races between thermal zone removal and system
  resume that may lead to a use-after-free (in two different ways)
  and a potential use-after-free in the thermal zone unregistration
  path (Rafael Wysocki)"

* tag 'thermal-7.0-rc7' of git://git.kernel.org/pub/scm/linux/kernel/git/rafael/linux-pm:
  thermal: core: Fix thermal zone device registration error path
  thermal: core: Address thermal zone removal races with resume
parents 116a3308 9e07e3b8
Loading
Loading
Loading
Loading
+27 −5
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ static struct thermal_governor *def_governor;

static bool thermal_pm_suspended;

static struct workqueue_struct *thermal_wq __ro_after_init;

/*
 * Governor section: set of functions to handle thermal governors
 *
@@ -313,7 +315,7 @@ static void thermal_zone_device_set_polling(struct thermal_zone_device *tz,
	if (delay > HZ)
		delay = round_jiffies_relative(delay);

	mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, delay);
	mod_delayed_work(thermal_wq, &tz->poll_queue, delay);
}

static void thermal_zone_recheck(struct thermal_zone_device *tz, int error)
@@ -1640,6 +1642,7 @@ thermal_zone_device_register_with_trips(const char *type,
	device_del(&tz->device);
release_device:
	put_device(&tz->device);
	wait_for_completion(&tz->removal);
remove_id:
	ida_free(&thermal_tz_ida, id);
free_tzp:
@@ -1785,6 +1788,10 @@ static void thermal_zone_device_resume(struct work_struct *work)

	guard(thermal_zone)(tz);

	/* If the thermal zone is going away, there's nothing to do. */
	if (tz->state & TZ_STATE_FLAG_EXIT)
		return;

	tz->state &= ~(TZ_STATE_FLAG_SUSPENDED | TZ_STATE_FLAG_RESUMING);

	thermal_debug_tz_resume(tz);
@@ -1811,6 +1818,9 @@ static void thermal_zone_pm_prepare(struct thermal_zone_device *tz)
	}

	tz->state |= TZ_STATE_FLAG_SUSPENDED;

	/* Prevent new work from getting to the workqueue subsequently. */
	cancel_delayed_work(&tz->poll_queue);
}

static void thermal_pm_notify_prepare(void)
@@ -1829,8 +1839,6 @@ static void thermal_zone_pm_complete(struct thermal_zone_device *tz)
{
	guard(thermal_zone)(tz);

	cancel_delayed_work(&tz->poll_queue);

	reinit_completion(&tz->resume);
	tz->state |= TZ_STATE_FLAG_RESUMING;

@@ -1840,7 +1848,7 @@ static void thermal_zone_pm_complete(struct thermal_zone_device *tz)
	 */
	INIT_DELAYED_WORK(&tz->poll_queue, thermal_zone_device_resume);
	/* Queue up the work without a delay. */
	mod_delayed_work(system_freezable_power_efficient_wq, &tz->poll_queue, 0);
	mod_delayed_work(thermal_wq, &tz->poll_queue, 0);
}

static void thermal_pm_notify_complete(void)
@@ -1863,6 +1871,11 @@ static int thermal_pm_notify(struct notifier_block *nb,
	case PM_RESTORE_PREPARE:
	case PM_SUSPEND_PREPARE:
		thermal_pm_notify_prepare();
		/*
		 * Allow any leftover thermal work items already on the
		 * worqueue to complete so they don't get in the way later.
		 */
		flush_workqueue(thermal_wq);
		break;
	case PM_POST_HIBERNATION:
	case PM_POST_RESTORE:
@@ -1895,9 +1908,16 @@ static int __init thermal_init(void)
	if (result)
		goto error;

	thermal_wq = alloc_workqueue("thermal_events",
				      WQ_FREEZABLE | WQ_POWER_EFFICIENT | WQ_PERCPU, 0);
	if (!thermal_wq) {
		result = -ENOMEM;
		goto unregister_netlink;
	}

	result = thermal_register_governors();
	if (result)
		goto unregister_netlink;
		goto destroy_workqueue;

	thermal_class = kzalloc_obj(*thermal_class);
	if (!thermal_class) {
@@ -1924,6 +1944,8 @@ static int __init thermal_init(void)

unregister_governors:
	thermal_unregister_governors();
destroy_workqueue:
	destroy_workqueue(thermal_wq);
unregister_netlink:
	thermal_netlink_exit();
error: