From c2c46862280df428aa3c037be52f562c962c4c1d Mon Sep 17 00:00:00 2001 From: "Vinita S. Maloo" Date: Tue, 27 Oct 2020 23:33:59 +0530 Subject: [PATCH] Set NLA_F_NESTED flag with NL80211_ATTR_VENDOR_DATA conditionally The newer kernel versions enforce strict netlink attribute policy validation and will cause cfg80211 to reject vendor commands with NL80211_ATTR_VENDOR_DATA if NLA_F_NESTED attribute is not set but if the vendor command is expecting nested data within NL80211_ATTR_VENDOR_DATA attribute. Most of the earlier instances were addressed by adding NLA_F_NESTED flag in nla_nest_start(). This commit addresses the remaining instance in which NL80211_ATTR_VENDOR_DATA is populated using data set by user through the control interface. Enhance the control interface VENDOR command to indicate whether the vendor subcommand uses nested attributes within NL80211_ATTR_VENDOR_DATA attribute or not. Set NLA_F_NESTED flag for existing QCA vendor commands which use nested attributes within the NL80211_ATTR_VENDOR_DATA attributes so that the old frameworks implementations for already existing commands work without any issues. Signed-off-by: Jouni Malinen --- hostapd/ctrl_iface.c | 19 +++++++++++++++---- hostapd/hostapd_cli.c | 9 +++++---- src/ap/ap_drv_ops.h | 3 ++- src/drivers/driver.h | 17 ++++++++++++++--- src/drivers/driver_nl80211.c | 36 +++++++++++++++++++++++++++++++++--- wpa_supplicant/ctrl_iface.c | 19 +++++++++++++++---- wpa_supplicant/driver_i.h | 6 ++++-- 7 files changed, 88 insertions(+), 21 deletions(-) diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 30fa47f2d..e2ae0ad44 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -2707,13 +2707,17 @@ static int hostapd_ctrl_iface_vendor(struct hostapd_data *hapd, char *cmd, char *buf, size_t buflen) { int ret; - char *pos; + char *pos, *temp = NULL; u8 *data = NULL; unsigned int vendor_id, subcmd; + enum nested_attr nested_attr_flag = NESTED_ATTR_UNSPECIFIED; struct wpabuf *reply; size_t data_len = 0; - /* cmd: [] */ + /** + * cmd: [] + * [nested=<0|1>] + */ vendor_id = strtoul(cmd, &pos, 16); if (!isblank((unsigned char) *pos)) return -EINVAL; @@ -2723,7 +2727,9 @@ static int hostapd_ctrl_iface_vendor(struct hostapd_data *hapd, char *cmd, if (*pos != '\0') { if (!isblank((unsigned char) *pos++)) return -EINVAL; - data_len = os_strlen(pos); + + temp = os_strchr(pos, ' '); + data_len = temp ? (size_t) (temp - pos) : os_strlen(pos); } if (data_len) { @@ -2740,6 +2746,11 @@ static int hostapd_ctrl_iface_vendor(struct hostapd_data *hapd, char *cmd, } } + pos = os_strstr(cmd, "nested="); + if (pos) + nested_attr_flag = atoi(pos + 7) ? NESTED_ATTR_USED : + NESTED_ATTR_NOT_USED; + reply = wpabuf_alloc((buflen - 1) / 2); if (!reply) { os_free(data); @@ -2747,7 +2758,7 @@ static int hostapd_ctrl_iface_vendor(struct hostapd_data *hapd, char *cmd, } ret = hostapd_drv_vendor_cmd(hapd, vendor_id, subcmd, data, data_len, - reply); + nested_attr_flag, reply); if (ret == 0) ret = wpa_snprintf_hex(buf, buflen, wpabuf_head_u8(reply), diff --git a/hostapd/hostapd_cli.c b/hostapd/hostapd_cli.c index ad4398175..dac00e01a 100644 --- a/hostapd/hostapd_cli.c +++ b/hostapd/hostapd_cli.c @@ -1227,14 +1227,15 @@ static int hostapd_cli_cmd_vendor(struct wpa_ctrl *ctrl, int argc, char *argv[]) char cmd[256]; int res; - if (argc < 2 || argc > 3) { + if (argc < 2 || argc > 4) { printf("Invalid vendor command\n" - "usage: []\n"); + "usage: [] [nested=<0|1>]\n"); return -1; } - res = os_snprintf(cmd, sizeof(cmd), "VENDOR %s %s %s", argv[0], argv[1], - argc == 3 ? argv[2] : ""); + res = os_snprintf(cmd, sizeof(cmd), "VENDOR %s %s %s%s%s", argv[0], + argv[1], argc >= 3 ? argv[2] : "", + argc == 4 ? " " : "", argc == 4 ? argv[3] : ""); if (os_snprintf_error(sizeof(cmd), res)) { printf("Too long VENDOR command.\n"); return -1; diff --git a/src/ap/ap_drv_ops.h b/src/ap/ap_drv_ops.h index 5738c1c97..cc7ea07a2 100644 --- a/src/ap/ap_drv_ops.h +++ b/src/ap/ap_drv_ops.h @@ -350,12 +350,13 @@ static inline int hostapd_drv_br_set_net_param(struct hostapd_data *hapd, static inline int hostapd_drv_vendor_cmd(struct hostapd_data *hapd, int vendor_id, int subcmd, const u8 *data, size_t data_len, + enum nested_attr nested_attr_flag, struct wpabuf *buf) { if (hapd->driver == NULL || hapd->driver->vendor_cmd == NULL) return -1; return hapd->driver->vendor_cmd(hapd->drv_priv, vendor_id, subcmd, data, - data_len, buf); + data_len, nested_attr_flag, buf); } static inline int hostapd_drv_stop_ap(struct hostapd_data *hapd) diff --git a/src/drivers/driver.h b/src/drivers/driver.h index e8defaba2..0019f54f9 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -2432,6 +2432,13 @@ struct external_auth { const u8 *pmkid; }; +/* enum nested_attr - Used to specify if subcommand uses nested attributes */ +enum nested_attr { + NESTED_ATTR_NOT_USED = 0, + NESTED_ATTR_USED = 1, + NESTED_ATTR_UNSPECIFIED = 2, +}; + /** * struct wpa_driver_ops - Driver interface API definition * @@ -3717,6 +3724,8 @@ struct wpa_driver_ops { * @priv: Private driver interface data * @vendor_id: Vendor id * @subcmd: Vendor command id + * @nested_attr_flag: Specifies if vendor subcommand uses nested + * attributes or not * @data: Vendor command parameters (%NULL if no parameters) * @data_len: Data length * @buf: Return buffer (%NULL to ignore reply) @@ -3724,9 +3733,10 @@ struct wpa_driver_ops { * * This function handles vendor specific commands that are passed to * the driver/device. The command is identified by vendor id and - * command id. Parameters can be passed as argument to the command - * in the data buffer. Reply (if any) will be filled in the supplied - * return buffer. + * command id. The nested_attr_flag specifies whether the subcommand + * uses nested attributes or not. Parameters can be passed + * as argument to the command in the data buffer. Reply (if any) will be + * filled in the supplied return buffer. * * The exact driver behavior is driver interface and vendor specific. As * an example, this will be converted to a vendor specific cfg80211 @@ -3734,6 +3744,7 @@ struct wpa_driver_ops { */ int (*vendor_cmd)(void *priv, unsigned int vendor_id, unsigned int subcmd, const u8 *data, size_t data_len, + enum nested_attr nested_attr_flag, struct wpabuf *buf); /** diff --git a/src/drivers/driver_nl80211.c b/src/drivers/driver_nl80211.c index ffa0c467e..477443050 100644 --- a/src/drivers/driver_nl80211.c +++ b/src/drivers/driver_nl80211.c @@ -9704,14 +9704,35 @@ static int vendor_reply_handler(struct nl_msg *msg, void *arg) } +static bool is_cmd_with_nested_attrs(unsigned int vendor_id, + unsigned int subcmd) +{ + if (vendor_id != OUI_QCA) + return true; + + switch (subcmd) { + case QCA_NL80211_VENDOR_SUBCMD_AVOID_FREQUENCY: + case QCA_NL80211_VENDOR_SUBCMD_STATS_EXT: + case QCA_NL80211_VENDOR_SUBCMD_SCANNING_MAC_OUI: + case QCA_NL80211_VENDOR_SUBCMD_KEY_MGMT_SET_KEY: + case QCA_NL80211_VENDOR_SUBCMD_SPECTRAL_SCAN_GET_STATUS: + case QCA_NL80211_VENDOR_SUBCMD_NAN: + return false; + default: + return true; + } +} + + static int nl80211_vendor_cmd(void *priv, unsigned int vendor_id, unsigned int subcmd, const u8 *data, - size_t data_len, struct wpabuf *buf) + size_t data_len, enum nested_attr nested_attr, + struct wpabuf *buf) { struct i802_bss *bss = priv; struct wpa_driver_nl80211_data *drv = bss->drv; struct nl_msg *msg; - int ret; + int ret, nla_flag; #ifdef CONFIG_TESTING_OPTIONS if (vendor_id == 0xffffffff) { @@ -9737,11 +9758,20 @@ static int nl80211_vendor_cmd(void *priv, unsigned int vendor_id, } #endif /* CONFIG_TESTING_OPTIONS */ + if (nested_attr == NESTED_ATTR_USED) + nla_flag = NLA_F_NESTED; + else if (nested_attr == NESTED_ATTR_UNSPECIFIED && + is_cmd_with_nested_attrs(vendor_id, subcmd)) + nla_flag = NLA_F_NESTED; + else + nla_flag = 0; + if (!(msg = nl80211_cmd_msg(bss, 0, NL80211_CMD_VENDOR)) || nla_put_u32(msg, NL80211_ATTR_VENDOR_ID, vendor_id) || nla_put_u32(msg, NL80211_ATTR_VENDOR_SUBCMD, subcmd) || (data && - nla_put(msg, NL80211_ATTR_VENDOR_DATA, data_len, data))) + nla_put(msg, nla_flag | NL80211_ATTR_VENDOR_DATA, + data_len, data))) goto fail; ret = send_and_recv_msgs(drv, msg, vendor_reply_handler, buf, diff --git a/wpa_supplicant/ctrl_iface.c b/wpa_supplicant/ctrl_iface.c index 56a6aaaad..aeea5ddbc 100644 --- a/wpa_supplicant/ctrl_iface.c +++ b/wpa_supplicant/ctrl_iface.c @@ -8236,13 +8236,17 @@ static int wpa_supplicant_vendor_cmd(struct wpa_supplicant *wpa_s, char *cmd, char *buf, size_t buflen) { int ret; - char *pos; + char *pos, *temp = NULL; u8 *data = NULL; unsigned int vendor_id, subcmd; + enum nested_attr nested_attr_flag = NESTED_ATTR_UNSPECIFIED; struct wpabuf *reply; size_t data_len = 0; - /* cmd: [] */ + /** + * cmd: [] + * [nested=<0|1>] + */ vendor_id = strtoul(cmd, &pos, 16); if (!isblank((unsigned char) *pos)) return -EINVAL; @@ -8252,7 +8256,9 @@ static int wpa_supplicant_vendor_cmd(struct wpa_supplicant *wpa_s, char *cmd, if (*pos != '\0') { if (!isblank((unsigned char) *pos++)) return -EINVAL; - data_len = os_strlen(pos); + + temp = os_strchr(pos, ' '); + data_len = temp ? (size_t) (temp - pos) : os_strlen(pos); } if (data_len) { @@ -8269,6 +8275,11 @@ static int wpa_supplicant_vendor_cmd(struct wpa_supplicant *wpa_s, char *cmd, } } + pos = os_strstr(cmd, "nested="); + if (pos) + nested_attr_flag = atoi(pos + 7) ? NESTED_ATTR_USED : + NESTED_ATTR_NOT_USED; + reply = wpabuf_alloc((buflen - 1) / 2); if (!reply) { os_free(data); @@ -8276,7 +8287,7 @@ static int wpa_supplicant_vendor_cmd(struct wpa_supplicant *wpa_s, char *cmd, } ret = wpa_drv_vendor_cmd(wpa_s, vendor_id, subcmd, data, data_len, - reply); + nested_attr_flag, reply); if (ret == 0) ret = wpa_snprintf_hex(buf, buflen, wpabuf_head_u8(reply), diff --git a/wpa_supplicant/driver_i.h b/wpa_supplicant/driver_i.h index ba8cc552b..3a2828982 100644 --- a/wpa_supplicant/driver_i.h +++ b/wpa_supplicant/driver_i.h @@ -719,12 +719,14 @@ static inline int wpa_drv_wowlan(struct wpa_supplicant *wpa_s, static inline int wpa_drv_vendor_cmd(struct wpa_supplicant *wpa_s, int vendor_id, int subcmd, const u8 *data, - size_t data_len, struct wpabuf *buf) + size_t data_len, + enum nested_attr nested_attr_flag, + struct wpabuf *buf) { if (!wpa_s->driver->vendor_cmd) return -1; return wpa_s->driver->vendor_cmd(wpa_s->drv_priv, vendor_id, subcmd, - data, data_len, buf); + data, data_len, nested_attr_flag, buf); } static inline int wpa_drv_roaming(struct wpa_supplicant *wpa_s, int allowed,