hostap/wpa_supplicant/rrm.c
Avraham Stern 332bf5d3fc wpa_supplicant: Validate RRM request frame format
RRM request frame should contain only information elements of type Radio
Measurement Request. Go through all the frame and validate that only
elements of this type are included.

In addition, if a truncated element is encountered, or the element
length field indicates that the element length is more than the
entire frame, abort the request.

Signed-off-by: Avraham Stern <avraham.stern@intel.com>
2017-01-03 15:18:29 +02:00

522 lines
14 KiB
C

/*
* wpa_supplicant - Radio Measurements
* Copyright (c) 2003-2016, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#include "includes.h"
#include "utils/common.h"
#include "utils/eloop.h"
#include "common/ieee802_11_common.h"
#include "wpa_supplicant_i.h"
#include "driver_i.h"
#include "bss.h"
static void wpas_rrm_neighbor_rep_timeout_handler(void *data, void *user_ctx)
{
struct rrm_data *rrm = data;
if (!rrm->notify_neighbor_rep) {
wpa_printf(MSG_ERROR,
"RRM: Unexpected neighbor report timeout");
return;
}
wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report - NONE");
rrm->notify_neighbor_rep(rrm->neighbor_rep_cb_ctx, NULL);
rrm->notify_neighbor_rep = NULL;
rrm->neighbor_rep_cb_ctx = NULL;
}
/*
* wpas_rrm_reset - Clear and reset all RRM data in wpa_supplicant
* @wpa_s: Pointer to wpa_supplicant
*/
void wpas_rrm_reset(struct wpa_supplicant *wpa_s)
{
wpa_s->rrm.rrm_used = 0;
eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm,
NULL);
if (wpa_s->rrm.notify_neighbor_rep)
wpas_rrm_neighbor_rep_timeout_handler(&wpa_s->rrm, NULL);
wpa_s->rrm.next_neighbor_rep_token = 1;
}
/*
* wpas_rrm_process_neighbor_rep - Handle incoming neighbor report
* @wpa_s: Pointer to wpa_supplicant
* @report: Neighbor report buffer, prefixed by a 1-byte dialog token
* @report_len: Length of neighbor report buffer
*/
void wpas_rrm_process_neighbor_rep(struct wpa_supplicant *wpa_s,
const u8 *report, size_t report_len)
{
struct wpabuf *neighbor_rep;
wpa_hexdump(MSG_DEBUG, "RRM: New Neighbor Report", report, report_len);
if (report_len < 1)
return;
if (report[0] != wpa_s->rrm.next_neighbor_rep_token - 1) {
wpa_printf(MSG_DEBUG,
"RRM: Discarding neighbor report with token %d (expected %d)",
report[0], wpa_s->rrm.next_neighbor_rep_token - 1);
return;
}
eloop_cancel_timeout(wpas_rrm_neighbor_rep_timeout_handler, &wpa_s->rrm,
NULL);
if (!wpa_s->rrm.notify_neighbor_rep) {
wpa_printf(MSG_ERROR, "RRM: Unexpected neighbor report");
return;
}
/* skipping the first byte, which is only an id (dialog token) */
neighbor_rep = wpabuf_alloc(report_len - 1);
if (neighbor_rep == NULL)
return;
wpabuf_put_data(neighbor_rep, report + 1, report_len - 1);
wpa_printf(MSG_DEBUG, "RRM: Notifying neighbor report (token = %d)",
report[0]);
wpa_s->rrm.notify_neighbor_rep(wpa_s->rrm.neighbor_rep_cb_ctx,
neighbor_rep);
wpa_s->rrm.notify_neighbor_rep = NULL;
wpa_s->rrm.neighbor_rep_cb_ctx = NULL;
}
#if defined(__CYGWIN__) || defined(CONFIG_NATIVE_WINDOWS)
/* Workaround different, undefined for Windows, error codes used here */
#define ENOTCONN -1
#define EOPNOTSUPP -1
#define ECANCELED -1
#endif
/* Measurement Request element + Location Subject + Maximum Age subelement */
#define MEASURE_REQUEST_LCI_LEN (3 + 1 + 4)
/* Measurement Request element + Location Civic Request */
#define MEASURE_REQUEST_CIVIC_LEN (3 + 5)
/**
* wpas_rrm_send_neighbor_rep_request - Request a neighbor report from our AP
* @wpa_s: Pointer to wpa_supplicant
* @ssid: if not null, this is sent in the request. Otherwise, no SSID IE
* is sent in the request.
* @lci: if set, neighbor request will include LCI request
* @civic: if set, neighbor request will include civic location request
* @cb: Callback function to be called once the requested report arrives, or
* timed out after RRM_NEIGHBOR_REPORT_TIMEOUT seconds.
* In the former case, 'neighbor_rep' is a newly allocated wpabuf, and it's
* the requester's responsibility to free it.
* In the latter case NULL will be sent in 'neighbor_rep'.
* @cb_ctx: Context value to send the callback function
* Returns: 0 in case of success, negative error code otherwise
*
* In case there is a previous request which has not been answered yet, the
* new request fails. The caller may retry after RRM_NEIGHBOR_REPORT_TIMEOUT.
* Request must contain a callback function.
*/
int wpas_rrm_send_neighbor_rep_request(struct wpa_supplicant *wpa_s,
const struct wpa_ssid_value *ssid,
int lci, int civic,
void (*cb)(void *ctx,
struct wpabuf *neighbor_rep),
void *cb_ctx)
{
struct wpabuf *buf;
const u8 *rrm_ie;
if (wpa_s->wpa_state != WPA_COMPLETED || wpa_s->current_ssid == NULL) {
wpa_printf(MSG_DEBUG, "RRM: No connection, no RRM.");
return -ENOTCONN;
}
if (!wpa_s->rrm.rrm_used) {
wpa_printf(MSG_DEBUG, "RRM: No RRM in current connection.");
return -EOPNOTSUPP;
}
rrm_ie = wpa_bss_get_ie(wpa_s->current_bss,
WLAN_EID_RRM_ENABLED_CAPABILITIES);
if (!rrm_ie || !(wpa_s->current_bss->caps & IEEE80211_CAP_RRM) ||
!(rrm_ie[2] & WLAN_RRM_CAPS_NEIGHBOR_REPORT)) {
wpa_printf(MSG_DEBUG,
"RRM: No network support for Neighbor Report.");
return -EOPNOTSUPP;
}
if (!cb) {
wpa_printf(MSG_DEBUG,
"RRM: Neighbor Report request must provide a callback.");
return -EINVAL;
}
/* Refuse if there's a live request */
if (wpa_s->rrm.notify_neighbor_rep) {
wpa_printf(MSG_DEBUG,
"RRM: Currently handling previous Neighbor Report.");
return -EBUSY;
}
/* 3 = action category + action code + dialog token */
buf = wpabuf_alloc(3 + (ssid ? 2 + ssid->ssid_len : 0) +
(lci ? 2 + MEASURE_REQUEST_LCI_LEN : 0) +
(civic ? 2 + MEASURE_REQUEST_CIVIC_LEN : 0));
if (buf == NULL) {
wpa_printf(MSG_DEBUG,
"RRM: Failed to allocate Neighbor Report Request");
return -ENOMEM;
}
wpa_printf(MSG_DEBUG, "RRM: Neighbor report request (for %s), token=%d",
(ssid ? wpa_ssid_txt(ssid->ssid, ssid->ssid_len) : ""),
wpa_s->rrm.next_neighbor_rep_token);
wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(buf, WLAN_RRM_NEIGHBOR_REPORT_REQUEST);
wpabuf_put_u8(buf, wpa_s->rrm.next_neighbor_rep_token);
if (ssid) {
wpabuf_put_u8(buf, WLAN_EID_SSID);
wpabuf_put_u8(buf, ssid->ssid_len);
wpabuf_put_data(buf, ssid->ssid, ssid->ssid_len);
}
if (lci) {
/* IEEE P802.11-REVmc/D5.0 9.4.2.21 */
wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
wpabuf_put_u8(buf, MEASURE_REQUEST_LCI_LEN);
/*
* Measurement token; nonzero number that is unique among the
* Measurement Request elements in a particular frame.
*/
wpabuf_put_u8(buf, 1); /* Measurement Token */
/*
* Parallel, Enable, Request, and Report bits are 0, Duration is
* reserved.
*/
wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
wpabuf_put_u8(buf, MEASURE_TYPE_LCI); /* Measurement Type */
/* IEEE P802.11-REVmc/D5.0 9.4.2.21.10 - LCI request */
/* Location Subject */
wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
/* Optional Subelements */
/*
* IEEE P802.11-REVmc/D5.0 Figure 9-170
* The Maximum Age subelement is required, otherwise the AP can
* send only data that was determined after receiving the
* request. Setting it here to unlimited age.
*/
wpabuf_put_u8(buf, LCI_REQ_SUBELEM_MAX_AGE);
wpabuf_put_u8(buf, 2);
wpabuf_put_le16(buf, 0xffff);
}
if (civic) {
/* IEEE P802.11-REVmc/D5.0 9.4.2.21 */
wpabuf_put_u8(buf, WLAN_EID_MEASURE_REQUEST);
wpabuf_put_u8(buf, MEASURE_REQUEST_CIVIC_LEN);
/*
* Measurement token; nonzero number that is unique among the
* Measurement Request elements in a particular frame.
*/
wpabuf_put_u8(buf, 2); /* Measurement Token */
/*
* Parallel, Enable, Request, and Report bits are 0, Duration is
* reserved.
*/
wpabuf_put_u8(buf, 0); /* Measurement Request Mode */
/* Measurement Type */
wpabuf_put_u8(buf, MEASURE_TYPE_LOCATION_CIVIC);
/* IEEE P802.11-REVmc/D5.0 9.4.2.21.14:
* Location Civic request */
/* Location Subject */
wpabuf_put_u8(buf, LOCATION_SUBJECT_REMOTE);
wpabuf_put_u8(buf, 0); /* Civic Location Type: IETF RFC 4776 */
/* Location Service Interval Units: Seconds */
wpabuf_put_u8(buf, 0);
/* Location Service Interval: 0 - Only one report is requested
*/
wpabuf_put_le16(buf, 0);
/* No optional subelements */
}
wpa_s->rrm.next_neighbor_rep_token++;
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, wpa_s->bssid,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(buf), wpabuf_len(buf), 0) < 0) {
wpa_printf(MSG_DEBUG,
"RRM: Failed to send Neighbor Report Request");
wpabuf_free(buf);
return -ECANCELED;
}
wpa_s->rrm.neighbor_rep_cb_ctx = cb_ctx;
wpa_s->rrm.notify_neighbor_rep = cb;
eloop_register_timeout(RRM_NEIGHBOR_REPORT_TIMEOUT, 0,
wpas_rrm_neighbor_rep_timeout_handler,
&wpa_s->rrm, NULL);
wpabuf_free(buf);
return 0;
}
static struct wpabuf * wpas_rrm_build_lci_report(struct wpa_supplicant *wpa_s,
const u8 *request, size_t len,
struct wpabuf *report)
{
u8 token, type, subject;
u16 max_age = 0;
struct os_reltime t, diff;
unsigned long diff_l;
u8 *ptoken;
const u8 *subelem;
if (!wpa_s->lci || len < 3 + 4)
return report;
token = *request++;
/* Measurement request mode isn't used */
request++;
type = *request++;
subject = *request++;
len -= 4;
wpa_printf(MSG_DEBUG,
"Measurement request token %u type %u location subject %u",
token, type, subject);
if (type != MEASURE_TYPE_LCI || subject != LOCATION_SUBJECT_REMOTE) {
wpa_printf(MSG_INFO,
"Not building LCI report - bad type or location subject");
return report;
}
/* Subelements are formatted exactly like elements */
subelem = get_ie(request, len, LCI_REQ_SUBELEM_MAX_AGE);
if (subelem && subelem[1] == 2)
max_age = WPA_GET_LE16(subelem + 2);
if (os_get_reltime(&t))
return report;
os_reltime_sub(&t, &wpa_s->lci_time, &diff);
/* LCI age is calculated in 10th of a second units. */
diff_l = diff.sec * 10 + diff.usec / 100000;
if (max_age != 0xffff && max_age < diff_l)
return report;
if (wpabuf_resize(&report, 2 + wpabuf_len(wpa_s->lci)))
return report;
wpabuf_put_u8(report, WLAN_EID_MEASURE_REPORT);
wpabuf_put_u8(report, wpabuf_len(wpa_s->lci));
/* We'll override user's measurement token */
ptoken = wpabuf_put(report, 0);
wpabuf_put_buf(report, wpa_s->lci);
*ptoken = token;
return report;
}
void wpas_rrm_handle_radio_measurement_request(struct wpa_supplicant *wpa_s,
const u8 *src,
const u8 *frame, size_t len)
{
struct wpabuf *buf, *report;
u8 token;
const u8 *end;
if (wpa_s->wpa_state != WPA_COMPLETED) {
wpa_printf(MSG_INFO,
"RRM: Ignoring radio measurement request: Not associated");
return;
}
if (!wpa_s->rrm.rrm_used) {
wpa_printf(MSG_INFO,
"RRM: Ignoring radio measurement request: Not RRM network");
return;
}
if (len < 3) {
wpa_printf(MSG_INFO,
"RRM: Ignoring too short radio measurement request");
return;
}
end = frame + len;
token = *frame++;
/* Ignore number of repetitions because it's not used in LCI request */
frame += 2;
report = NULL;
while (end - frame) {
const struct rrm_measurement_request_element *req;
if (end - frame < 2) {
wpa_printf(MSG_DEBUG, "RRM: Truncated element");
goto out;
}
req = (const struct rrm_measurement_request_element *) frame;
if (req->eid != WLAN_EID_MEASURE_REQUEST) {
wpa_printf(MSG_DEBUG,
"RRM: Expected Measurement Request element, but EID is %u",
req->eid);
goto out;
}
if (req->len < 3) {
wpa_printf(MSG_DEBUG, "RRM: Element length too short");
goto out;
}
frame += 2;
if (req->len > end - frame) {
wpa_printf(MSG_DEBUG, "RRM: Element length too long");
goto out;
}
wpa_printf(MSG_DEBUG, "RRM request type: %u", req->type);
switch (req->type) {
case MEASURE_TYPE_LCI:
report = wpas_rrm_build_lci_report(wpa_s, frame,
req->len, report);
break;
default:
wpa_printf(MSG_INFO,
"RRM: Unsupported radio measurement request %d",
req->type);
break;
}
frame += req->len;
}
if (!report)
return;
buf = wpabuf_alloc(3 + wpabuf_len(report));
if (!buf)
goto out;
wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(buf, WLAN_RRM_RADIO_MEASUREMENT_REPORT);
wpabuf_put_u8(buf, token);
wpabuf_put_buf(buf, report);
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(buf), wpabuf_len(buf), 0)) {
wpa_printf(MSG_ERROR,
"RRM: Radio measurement report failed: Sending Action frame failed");
}
wpabuf_free(buf);
out:
wpabuf_free(report);
}
void wpas_rrm_handle_link_measurement_request(struct wpa_supplicant *wpa_s,
const u8 *src,
const u8 *frame, size_t len,
int rssi)
{
struct wpabuf *buf;
const struct rrm_link_measurement_request *req;
struct rrm_link_measurement_report report;
if (wpa_s->wpa_state != WPA_COMPLETED) {
wpa_printf(MSG_INFO,
"RRM: Ignoring link measurement request. Not associated");
return;
}
if (!wpa_s->rrm.rrm_used) {
wpa_printf(MSG_INFO,
"RRM: Ignoring link measurement request. Not RRM network");
return;
}
if (!(wpa_s->drv_rrm_flags & WPA_DRIVER_FLAGS_TX_POWER_INSERTION)) {
wpa_printf(MSG_INFO,
"RRM: Measurement report failed. TX power insertion not supported");
return;
}
req = (const struct rrm_link_measurement_request *) frame;
if (len < sizeof(*req)) {
wpa_printf(MSG_INFO,
"RRM: Link measurement report failed. Request too short");
return;
}
os_memset(&report, 0, sizeof(report));
report.tpc.eid = WLAN_EID_TPC_REPORT;
report.tpc.len = 2;
report.rsni = 255; /* 255 indicates that RSNI is not available */
report.dialog_token = req->dialog_token;
/*
* It's possible to estimate RCPI based on RSSI in dBm. This
* calculation will not reflect the correct value for high rates,
* but it's good enough for Action frames which are transmitted
* with up to 24 Mbps rates.
*/
if (!rssi)
report.rcpi = 255; /* not available */
else if (rssi < -110)
report.rcpi = 0;
else if (rssi > 0)
report.rcpi = 220;
else
report.rcpi = (rssi + 110) * 2;
/* action_category + action_code */
buf = wpabuf_alloc(2 + sizeof(report));
if (buf == NULL) {
wpa_printf(MSG_ERROR,
"RRM: Link measurement report failed. Buffer allocation failed");
return;
}
wpabuf_put_u8(buf, WLAN_ACTION_RADIO_MEASUREMENT);
wpabuf_put_u8(buf, WLAN_RRM_LINK_MEASUREMENT_REPORT);
wpabuf_put_data(buf, &report, sizeof(report));
wpa_hexdump(MSG_DEBUG, "RRM: Link measurement report:",
wpabuf_head(buf), wpabuf_len(buf));
if (wpa_drv_send_action(wpa_s, wpa_s->assoc_freq, 0, src,
wpa_s->own_addr, wpa_s->bssid,
wpabuf_head(buf), wpabuf_len(buf), 0)) {
wpa_printf(MSG_ERROR,
"RRM: Link measurement report failed. Send action failed");
}
wpabuf_free(buf);
}