diff options
| author | Johannes Berg <johannes.berg@intel.com> | 2026-10-06 12:03:59 +0200 |
|---|---|---|
| committer | Johannes Berg <johannes.berg@intel.com> | 2026-10-09 16:12:45 +0200 |
| commit | 298618f28bd3d1da677828354b504c12e2f375e4 (patch) | |
| tree | 61cbe3da38525dc90a82f2cfc1bbd0565f4b63eb /net/wireless | |
| parent | 81a6e0248d8362c69636f312ae78708cad543e93 (diff) | |
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>
Diffstat (limited to 'net/wireless')
| -rw-r--r-- | net/wireless/reg.c | 41 |
1 files changed, 25 insertions, 16 deletions
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 = ®db->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 = ®db->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(®_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); |
