summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Berg <johannes.berg@intel.com>2026-10-06 12:03:59 +0200
committerJohannes Berg <johannes.berg@intel.com>2026-10-09 16:12:45 +0200
commit298618f28bd3d1da677828354b504c12e2f375e4 (patch)
tree61cbe3da38525dc90a82f2cfc1bbd0565f4b63eb
parent81a6e0248d8362c69636f312ae78708cad543e93 (diff)
downloadlinux-next-298618f28bd3d1da677828354b504c12e2f375e4.tar.gz
linux-next-298618f28bd3d1da677828354b504c12e2f375e4.zip
wifi: cfg80211: reg: protect regdb pointer with RCU
reg_query_regdb_wmm() is only used by iwlwifi, but even when it was introduced was already broken, as it reads the global db without any locking (drivers can't really guarantee any). Certainly iwlwifi doesn't since it calls it for FW messages. Therefore, it can race against regdb updates. The function uses a local pointer, but doesn't help. Use RCU to protect the regdb pointer to fix that, and update the documentation to mention this (and do some cleanups). Fixes: 19d3577e35e0 ("cfg80211: Add API to allow querying regdb for wmm_rule") Link: https://patch.msgid.link/20261006120655.7c261d237cae.If21cf0af3b0d7e8c62c45ece5dc654b30c9d7757@changeid Signed-off-by: Johannes Berg <johannes.berg@intel.com>
-rw-r--r--include/net/cfg80211.h12
-rw-r--r--net/wireless/reg.c41
2 files changed, 31 insertions, 22 deletions
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 725dc8053727..a3fb7dd743c8 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -8236,20 +8236,20 @@ bool regulatory_pre_cac_allowed(struct wiphy *wiphy);
*/
/**
- * reg_query_regdb_wmm - Query internal regulatory db for wmm rule
- * Regulatory self-managed driver can use it to proactively
- *
+ * reg_query_regdb_wmm - Query internal regulatory db for WMM rules
* @alpha2: the ISO/IEC 3166 alpha2 wmm rule to be queried.
* @freq: the frequency (in MHz) to be queried.
- * @rule: pointer to store the wmm rule from the regulatory db.
+ * @rule: pointer to store the WMM rule from the regulatory db.
*
- * Self-managed wireless drivers can use this function to query
+ * Self-managed wireless drivers can use this function to query
* the internal regulatory database to check whether the given
- * ISO/IEC 3166 alpha2 country and freq have wmm rule limitations.
+ * ISO/IEC 3166 alpha2 country and freq have WMM rule limitations.
*
* Drivers should check the return value, its possible you can get
* an -ENODATA.
*
+ * This uses RCU internally so can be called in just about any context.
+ *
* Return: 0 on success. -ENODATA.
*/
int reg_query_regdb_wmm(char *alpha2, int freq,
diff --git a/net/wireless/reg.c b/net/wireless/reg.c
index b7ef479a74f0..27ba860f0391 100644
--- a/net/wireless/reg.c
+++ b/net/wireless/reg.c
@@ -590,7 +590,7 @@ static inline int call_crda(const char *alpha2)
#endif /* CONFIG_CFG80211_CRDA_SUPPORT */
/* code to directly load a firmware database through request_firmware */
-static const struct fwdb_header *regdb;
+static const struct fwdb_header __rcu *global_regdb;
struct fwdb_country {
u8 alpha2[2];
@@ -892,16 +892,19 @@ static int __regdb_query_wmm(const struct fwdb_header *db,
int reg_query_regdb_wmm(char *alpha2, int freq, struct ieee80211_reg_rule *rule)
{
- const struct fwdb_header *hdr = regdb;
const struct fwdb_country *country;
+ const struct fwdb_header *regdb;
+ guard(rcu)();
+
+ regdb = rcu_dereference(global_regdb);
if (!regdb)
return -ENODATA;
if (IS_ERR(regdb))
return PTR_ERR(regdb);
- country = &hdr->country[0];
+ country = &regdb->country[0];
while (country->coll_ptr) {
if (alpha2_equal(alpha2, country->alpha2))
return __regdb_query_wmm(regdb, country, freq, rule);
@@ -970,15 +973,13 @@ static int regdb_query_country(const struct fwdb_header *db,
static int query_regdb(const char *alpha2)
{
- const struct fwdb_header *hdr = regdb;
+ const struct fwdb_header *regdb = rtnl_dereference(global_regdb);
const struct fwdb_country *country;
- ASSERT_RTNL();
-
if (IS_ERR(regdb))
return PTR_ERR(regdb);
- country = &hdr->country[0];
+ country = &regdb->country[0];
while (country->coll_ptr) {
if (alpha2_equal(alpha2, country->alpha2))
return regdb_query_country(regdb, country);
@@ -990,6 +991,7 @@ static int query_regdb(const char *alpha2)
static void regdb_fw_cb(const struct firmware *fw, void *context)
{
+ const struct fwdb_header *regdb;
int set_error = 0;
bool restore = true;
void *db;
@@ -1003,6 +1005,7 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
}
rtnl_lock();
+ regdb = rtnl_dereference(global_regdb);
if (regdb && !IS_ERR(regdb)) {
/* negative case - a bug
* positive case - can happen due to race in case of multiple cb's in
@@ -1011,11 +1014,11 @@ static void regdb_fw_cb(const struct firmware *fw, void *context)
* Either case, just restore and free new db.
*/
} else if (set_error) {
- regdb = ERR_PTR(set_error);
+ rcu_assign_pointer(global_regdb, ERR_PTR(set_error));
} else if (fw) {
db = kmemdup(fw->data, fw->size, GFP_KERNEL);
if (db) {
- regdb = db;
+ rcu_assign_pointer(global_regdb, db);
restore = context && query_regdb(context);
} else {
restore = true;
@@ -1040,7 +1043,7 @@ static int query_regdb_file(const char *alpha2)
ASSERT_RTNL();
- if (regdb)
+ if (rcu_access_pointer(global_regdb))
return query_regdb(alpha2);
alpha2 = kmemdup(alpha2, 2, GFP_KERNEL);
@@ -1058,8 +1061,8 @@ static int query_regdb_file(const char *alpha2)
int reg_reload_regdb(void)
{
+ const struct fwdb_header *db, *old;
const struct firmware *fw;
- void *db;
int err;
const struct ieee80211_regdomain *current_regdomain;
struct regulatory_request *request;
@@ -1080,9 +1083,7 @@ int reg_reload_regdb(void)
}
rtnl_lock();
- if (!IS_ERR_OR_NULL(regdb))
- kfree(regdb);
- regdb = db;
+ old = rcu_replace_pointer_rtnl(global_regdb, db);
/* reset regulatory domain */
current_regdomain = get_cfg80211_regdom();
@@ -1103,6 +1104,11 @@ int reg_reload_regdb(void)
out_unlock:
rtnl_unlock();
+
+ if (!IS_ERR_OR_NULL(old)) {
+ synchronize_rcu();
+ kfree(old);
+ }
out:
release_firmware(fw);
return err;
@@ -4390,6 +4396,7 @@ void regulatory_exit(void)
{
struct regulatory_request *reg_request, *tmp;
struct reg_beacon *reg_beacon, *btmp;
+ const struct fwdb_header *db;
cancel_work_sync(&reg_work);
cancel_crda_timeout_sync();
@@ -4419,8 +4426,10 @@ void regulatory_exit(void)
kfree(reg_request);
}
- if (!IS_ERR_OR_NULL(regdb))
- kfree(regdb);
+ /* RCU is irrelevant on the way out */
+ db = rcu_dereference_protected(global_regdb, true);
+ if (!IS_ERR_OR_NULL(db))
+ kfree(db);
if (!IS_ERR_OR_NULL(cfg80211_user_regdom))
kfree(cfg80211_user_regdom);