]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
OPP: Fix race between OPP addition and lookup
authorDi Shen <di.shen@unisoc.com>
Mon, 27 Apr 2026 12:00:47 +0000 (20:00 +0800)
committerViresh Kumar <viresh.kumar@linaro.org>
Thu, 7 May 2026 03:24:10 +0000 (08:54 +0530)
A race exists between dev_pm_opp_add_dynamic() and
dev_pm_opp_find_freq_exact():

  CPU0 (add)                          CPU1 (lookup)
  -------------------------------     ------------------------------
  _opp_add()
    mutex_lock()
    list_add(&new_opp->node, head)
    mutex_unlock()                    _opp_table_find_key()
                                        mutex_lock()
                                        dev_pm_opp_get(opp)
                                          kref_get()
                                        mutex_unlock()
    kref_init(&new_opp->kref)
                                      dev_pm_opp_put()
                                        kref_put_mutex()

The newly added OPP is inserted into the list before its kref is
initialized. A concurrent lookup can find this OPP and increment its
reference count while it is still uninitialized, leading to refcount
corruption and a potential premature free.

Fix this by initializing ->kref and ->opp_table before making the OPP
visible via list_add(). This ensures any concurrent lookup observes a
fully initialized object.

Fixes: 7034764a1e4a (PM / OPP: Add 'struct kref' to struct dev_pm_opp)
Co-developed-by: Ling Xu <ling_ling.xu@unisoc.com>
Signed-off-by: Ling Xu <ling_ling.xu@unisoc.com>
Signed-off-by: Di Shen <di.shen@unisoc.com>
[ Viresh: Updated commit log ]
Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
drivers/opp/core.c

index da3f5eba434197475938981f7031b6ca6a698dd7..ab0b0a2f85a1787af567d2ab6698d0defd920bcd 100644 (file)
@@ -2088,11 +2088,10 @@ int _opp_add(struct device *dev, struct dev_pm_opp *new_opp,
                        return ret;
 
                list_add(&new_opp->node, head);
+               new_opp->opp_table = opp_table;
+               kref_init(&new_opp->kref);
        }
 
-       new_opp->opp_table = opp_table;
-       kref_init(&new_opp->kref);
-
        opp_debug_create_one(new_opp, opp_table);
 
        if (!_opp_supported_by_regulators(new_opp, opp_table)) {