From: Felix Fietkau Date: Fri, 28 Aug 2026 10:04:32 +0200 Subject: [PATCH] wifi: cfg80211: add SCS and MSCS request validation The element parsers validate each classifier element on its own. Add the checks for the constraints between the request type, the classifier and the traffic description of an SCS descriptor, and the checks for an MSCS descriptor. A failed check is reported as a status code, not as an error. The request came from a peer over the air and hostapd passes it on as is, so an AP has to answer a well formed but unacceptable request with a refusal. A descriptor with an uplink or direct link traffic description carries no classifier, since the AP does not classify those streams. Every other descriptor needs one, and a TCLAS Processing element only with more than one TCLAS element. The default processing mode is not supported. A match-all descriptor whose elements require different values for the same parameter can never match, so it is declined as well. An MSCS descriptor needs a classifier mask unless it is a removal, and a non-zero stream timeout. Signed-off-by: Felix Fietkau --- a/include/net/cfg80211.h +++ b/include/net/cfg80211.h @@ -4279,21 +4279,46 @@ bool cfg80211_flow_key_build(const struc /** * struct cfg80211_scs_desc - one SCS descriptor * + * @req_type: add, remove or change * @tclas_processing: how the elements of @tclas relate + * @qos_char: the whole QoS Characteristics element, or %NULL. Set only + * after ieee80211_qos_char_size_ok() passed, so the accessors in + * may read it. * @id: SCSID, 1 to 255 * @up: user priority to assign to a matching MSDU + * @qos_char_len: length of @qos_char * @n_tclas: number of entries in @tclas * @tclas: the classifier */ struct cfg80211_scs_desc { + enum nl80211_scs_req_type req_type; enum cfg80211_tclas_processing tclas_processing; + const struct ieee80211_qos_char_elem *qos_char; u8 id; u8 up; + u16 qos_char_len; u8 n_tclas; struct cfg80211_tclas tclas[] __counted_by(n_tclas); }; /** + * struct cfg80211_mscs_desc - the MSCS of one peer + * + * @req_type: add, remove or change + * @up_bitmap: user priorities that the AP learns from, one bit each + * @up_limit: ceiling for the assigned user priority, 0 to 7 + * @stream_timeout: minimum lifetime of a learned value, in TUs + * @fields: classifier parameters, a bitmap of &enum cfg80211_flow_field + */ +struct cfg80211_mscs_desc { + enum nl80211_scs_req_type req_type; + u8 up_bitmap; + u8 up_limit; + u32 stream_timeout; + u32 fields; +}; + +/** * struct cfg80211_scs_verdict - result of an SCS evaluation * * @match: an SCS descriptor matched the MSDU --- a/net/wireless/core.h +++ b/net/wireless/core.h @@ -646,6 +646,8 @@ int cfg80211_parse_tclas(const u8 *elems struct cfg80211_tclas *out, u8 n_tclas, enum cfg80211_tclas_processing *processing); int cfg80211_parse_tclas_mask(const u8 *elems, size_t len, u32 *fields); +bool cfg80211_scs_desc_valid(const struct cfg80211_scs_desc *desc); +bool cfg80211_mscs_desc_valid(const struct cfg80211_mscs_desc *desc); #if IS_ENABLED(CPTCFG_CFG80211_KUNIT_TEST) #define EXPORT_SYMBOL_IF_CFG80211_KUNIT(sym) EXPORT_SYMBOL_IF_KUNIT(sym) --- a/net/wireless/scs.c +++ b/net/wireless/scs.c @@ -882,3 +882,96 @@ void cfg80211_scs_evaluate(struct cfg802 } } EXPORT_SYMBOL(cfg80211_scs_evaluate); + +/* Two elements of a match-all descriptor can require conflicting values */ +static bool tclas_conflict(const struct cfg80211_tclas *a, + const struct cfg80211_tclas *b) +{ + struct cfg80211_flow_key ka, kb; + u32 common = a->fields & b->fields; + + if (!common) + return false; + + flow_key_select(&a->key, common, &ka); + flow_key_select(&b->key, common, &kb); + + return memcmp(&ka, &kb, sizeof(ka)); +} + +/** + * cfg80211_scs_desc_valid - validate one SCS descriptor + * + * @desc: the descriptor, already parsed + * + * Checks the constraints between the request type, the classifier and the + * traffic description. cfg80211_parse_tclas() validates each element. + * + * Return: whether the descriptor may be installed. The caller reports a + * refusal as a status code, not as an error. + */ +bool cfg80211_scs_desc_valid(const struct cfg80211_scs_desc *desc) +{ + bool want_tclas = true; + unsigned int i, j; + + if (desc->req_type == NL80211_SCS_REQ_REMOVE) + return true; + + /* Only a downlink stream is classified by the AP */ + if (desc->qos_char && + ieee80211_qos_char_direction(desc->qos_char) != + IEEE80211_QOS_CHAR_DIR_DOWNLINK) + want_tclas = false; + + if (want_tclas != !!desc->n_tclas) + return false; + + if (!want_tclas) + return desc->tclas_processing == + CFG80211_TCLAS_PROCESSING_ABSENT; + + switch (desc->tclas_processing) { + case CFG80211_TCLAS_PROCESSING_ALL: + case CFG80211_TCLAS_PROCESSING_ANY: + if (desc->n_tclas < 2) + return false; + break; + case CFG80211_TCLAS_PROCESSING_DEFAULT: + return false; + case CFG80211_TCLAS_PROCESSING_ABSENT: + if (desc->n_tclas > 1) + return false; + break; + default: + return false; + } + + if (desc->tclas_processing != CFG80211_TCLAS_PROCESSING_ALL) + return true; + + for (i = 0; i < desc->n_tclas; i++) + for (j = i + 1; j < desc->n_tclas; j++) + if (tclas_conflict(&desc->tclas[i], &desc->tclas[j])) + return false; + + return true; +} + +/** + * cfg80211_mscs_desc_valid - validate an MSCS descriptor + * + * @desc: the descriptor, already parsed + * + * Return: whether the descriptor may be installed. + */ +bool cfg80211_mscs_desc_valid(const struct cfg80211_mscs_desc *desc) +{ + bool remove = desc->req_type == NL80211_SCS_REQ_REMOVE; + + if (remove == !!desc->fields) + return false; + + /* A zero timeout would expire every entry at the next collection */ + return remove || desc->stream_timeout; +} --- a/include/uapi/linux/nl80211.h +++ b/include/uapi/linux/nl80211.h @@ -6199,6 +6199,23 @@ enum nl80211_tx_power_setting { }; /** + * enum nl80211_scs_req_type - SCS or MSCS request type + * + * The values match the Request Type field of the descriptor on the air. + * + * @NL80211_SCS_REQ_ADD: create the stream, replacing an active one of the + * same identifier + * @NL80211_SCS_REQ_REMOVE: terminate the stream + * @NL80211_SCS_REQ_CHANGE: modify an active stream, leaving it in force if + * the request is declined + */ +enum nl80211_scs_req_type { + NL80211_SCS_REQ_ADD, + NL80211_SCS_REQ_REMOVE, + NL80211_SCS_REQ_CHANGE, +}; + +/** * enum nl80211_tid_config - TID config state * @NL80211_TID_CONFIG_ENABLE: Enable config for the TID * @NL80211_TID_CONFIG_DISABLE: Disable config for the TID