mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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: 19d3577e35 ("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>
This commit is contained in:
parent
81a6e0248d
commit
298618f28b
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user