blob: 07c521e3f5859475a73977cc2cb5fca9c5e08fbd [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <array>
#include <wlan/common/buffer_reader.h>
#include <wlan/common/mac_frame.h>
#include <wlan/common/span.h>
#include <wlan/mlme/debug.h>
namespace wlan {
using namespace element_id;
class ErrorAccumulator {
public:
__attribute__((__format__ (__printf__, 3, 4)))
void Add(size_t offset, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
char buf[200];
snprintf(buf, sizeof(buf), "(at 0x%04zx) ", offset);
message_ += buf;
vsnprintf(buf, sizeof(buf), fmt, ap);
message_ += buf;
message_ += '\n';
}
bool HaveErrors() {
return !message_.empty();
}
const char* GetMessage() {
return message_.c_str();
}
private:
std::string message_;
};
struct AllowedElement {
ElementId id;
bool required;
constexpr AllowedElement(ElementId id, bool required = false) : id(id), required(required) {}
};
constexpr AllowedElement Required(ElementId id) {
return AllowedElement(id, true);
}
// IEEE Std 802.11-2016, 9.3.3.3
static constexpr AllowedElement kBeaconElements[] = {
Required(kSsid),
Required(kSuppRates),
kDsssParamSet,
kCfParamSet,
kIbssParamSet,
kTim,
kCountry,
kPowerConstraint,
kChannelSwitchAnn,
kQuiet,
kIbssDfs,
kTpcReport,
kErp,
kExtSuppRates,
kRsn,
kBssLoad,
kEdcaParamSet,
kQosCapability,
kApChannelReport,
kBssAvgAccessDelay,
kAntenna,
kBssAvailAdmissionCapacity,
kBssAcAccessDelay,
kMeasurementPilotTrans,
kMultipleBssid,
kRmEnabledCapabilities,
kMobilityDomain,
kDseRegisteredLocation,
kExtChannelSwitchAnn,
kSuppOperatingClasses,
kHtCapabilities,
kHtOperation,
k2040BssCoex,
kOverlappingBssScanParams,
kExtCapabilities,
kFmsDescriptor,
kQosTrafficCapability,
kTimeAdvertisement,
kInterworking,
kAdvertisementProtocol,
kRoamingConsortium,
kEmergencyAlertId,
kMeshId,
kMeshConfiguration,
kMeshAwakeWindow,
kBeaconTiming,
kMccaopAdvertisementOverview,
kMccaopAdvertisement,
kMeshChannelSwitchParams,
kQmfPolicy,
kQloadReport,
kHccaTxopUpdateCount,
kMultiband,
kVhtCapabilities,
kVhtOperation,
kTransmitPowerEnvelope,
kChannelSwitchWrapper,
kExtBssLoad,
kQuietChannel,
kOperatingModeNotification,
kReducedNeighborReport,
kTvhtOperation,
// TODO: Estimated Service Parameters (2-byte ID)
// TODO: Future Channel Guidance (2-byte ID)
};
// IEEE Std 802.11-2016, 9.3.3.5
static constexpr AllowedElement kDisassocElements[] = {
kManagementMic
};
// IEEE Std 802.11-2016, 9.3.3.6
static constexpr AllowedElement kAssocReqElements[] = {
Required(kSsid),
Required(kSuppRates),
kExtSuppRates,
kPowerCapability,
kSupportedChannels,
kRsn,
kQosCapability,
kRmEnabledCapabilities,
kMobilityDomain,
kSuppOperatingClasses,
kHtCapabilities,
k2040BssCoex,
kExtCapabilities,
kQosTrafficCapability,
kTimBroadcastRequest,
kInterworking,
kMultiband,
kDmgCapabilities,
kMultipleMacSublayers,
kVhtCapabilities,
kOperatingModeNotification,
};
// IEEE Std 802.11-2016, 9.3.3.7
static constexpr AllowedElement kAssocRespElements[] = {
Required(kSuppRates),
kExtSuppRates,
kEdcaParamSet,
kRcpi,
kRsni,
kRmEnabledCapabilities,
kMobilityDomain,
kFastBssTransition,
kDseRegisteredLocation,
kTimeoutInterval,
kHtCapabilities,
kHtOperation,
k2040BssCoex,
kOverlappingBssScanParams,
kExtCapabilities,
kBssMaxIdlePeriod,
kTimBroadcastResponse,
kQosMap,
kQmfPolicy,
kMultiband,
kDmgCapabilities,
kDmgOperation,
kMultipleMacSublayers,
kNeighborReport,
kVhtCapabilities,
kVhtOperation,
kOperatingModeNotification,
// TODO: Future Channel Guidance (2-byte ID)
};
// IEEE Std 802.11-2016, 9.3.3.8
static constexpr AllowedElement kReassocReqElements[] = {
Required(kSsid),
Required(kSuppRates),
kExtSuppRates,
kPowerCapability,
kSupportedChannels,
kRsn,
kQosCapability,
kRmEnabledCapabilities,
kMobilityDomain,
kFastBssTransition,
// TODO: RIC container? (can be several elements)
kSuppOperatingClasses,
kHtCapabilities,
k2040BssCoex,
kExtCapabilities,
kQosTrafficCapability,
kTimBroadcastRequest,
kFmsRequest,
kDmsRequest,
kInterworking,
kMultiband,
kDmgCapabilities,
kMultipleMacSublayers,
kVhtCapabilities,
kOperatingModeNotification
};
// IEEE Std 802.11-2016, 9.3.3.9
static constexpr AllowedElement kReassocRespElements[] = {
Required(kSuppRates),
kExtSuppRates,
kEdcaParamSet,
kRcpi,
kRsni,
kRmEnabledCapabilities,
kRsn,
kMobilityDomain,
kFastBssTransition,
// TODO: RIC container? (can be several elements)
kDseRegisteredLocation,
kTimeoutInterval,
kHtCapabilities,
kHtOperation,
k2040BssCoex,
kOverlappingBssScanParams,
kExtCapabilities,
kBssMaxIdlePeriod,
kTimBroadcastResponse,
kFmsResponse,
kDmsResponse,
kQosMap,
kQmfPolicy,
kMultiband,
kDmgCapabilities,
kDmgOperation,
kMultipleMacSublayers,
kNeighborReport,
kVhtCapabilities,
kVhtOperation,
kOperatingModeNotification
// TODO: Future Channel Guidance (2-byte ID)
};
// IEEE Std 802.11-2016, 9.3.3.10
static constexpr AllowedElement kProbeReqElements[] = {
Required(kSsid),
Required(kSuppRates),
kRequest,
kExtSuppRates,
kDsssParamSet,
kSuppOperatingClasses,
kHtCapabilities,
k2040BssCoex,
kExtCapabilities,
kSsidList,
kChannelUsage,
kInterworking,
kMeshId,
kMultiband,
kDmgCapabilities,
kMultipleMacSublayers,
kVhtCapabilities,
// TODO: Estimated Service Parameters (2-byte ID)
// TODO: Extended Request (2-byte ID)
};
// IEEE Std 802.11-2016, 9.3.3.11
static constexpr AllowedElement kProbeRespElements[] = {
Required(kSsid),
Required(kSuppRates),
kDsssParamSet,
kCfParamSet,
kIbssParamSet,
kCountry,
kPowerConstraint,
kChannelSwitchAnn,
kQuiet,
kIbssDfs,
kTpcReport,
kErp,
kExtSuppRates,
kRsn,
kBssLoad,
kEdcaParamSet,
kMeasurementPilotTrans,
kMultipleBssid,
kRmEnabledCapabilities,
kApChannelReport,
kBssAvgAccessDelay,
kAntenna,
kBssAvailAdmissionCapacity,
kBssAcAccessDelay,
kMobilityDomain,
kDseRegisteredLocation,
kExtChannelSwitchAnn,
kSuppOperatingClasses,
kHtCapabilities,
kHtOperation,
k2040BssCoex,
kOverlappingBssScanParams,
kExtCapabilities,
kQosTrafficCapability,
kChannelUsage,
kTimeAdvertisement,
kTimeZone,
kInterworking,
kAdvertisementProtocol,
kRoamingConsortium,
kEmergencyAlertId,
kMeshId,
kMeshConfiguration,
kMeshAwakeWindow,
kBeaconTiming,
kMccaopAdvertisementOverview,
kMccaopAdvertisement,
kMeshChannelSwitchParams,
kQmfPolicy,
kQloadReport,
kMultiband,
kDmgCapabilities,
kDmgOperation,
kMultipleMacSublayers,
kAntennaSectorIdPattern,
kVhtCapabilities,
kVhtOperation,
kTransmitPowerEnvelope,
kChannelSwitchWrapper,
kExtBssLoad,
kQuietChannel,
kOperatingModeNotification,
kReducedNeighborReport,
kTvhtOperation,
// TODO: Estimated Service Parameters (2-byte ID)
kRelayCapabilities
};
// IEEE Std 802.11-2016, 9.3.3.12
static constexpr AllowedElement kAuthElements[] = {
kChallengeText,
kRsn,
kMobilityDomain,
kFastBssTransition,
kTimeoutInterval
// TODO: RIC (can be several elements)
};
// IEEE Std 802.11-2016, 9.3.3.13
static constexpr AllowedElement kDeauthElements[] = {
kManagementMic
};
// IEEE Std 802.11-2016, 9.3.3.16
static constexpr AllowedElement kTimingAdElements[] = {
kCountry,
kPowerConstraint,
kTimeAdvertisement,
kExtCapabilities
};
static void ValidateFixedSizeElement(size_t offset, Span<const uint8_t> body, size_t expected_size,
const char* element_name, ErrorAccumulator* errors) {
if (body.size() != expected_size) {
errors->Add(offset, "%s element has invalid length (%zu bytes vs %zu expected)",
element_name, body.size(), expected_size);
}
}
static void ValidateElement(size_t offset,
ElementId id,
Span<const uint8_t> body,
ErrorAccumulator* errors) {
switch (id) {
case kSsid:
if (body.size() > 32) {
errors->Add(offset, "SSID element is too long (%zu bytes)", body.size());
}
break;
case kSuppRates:
if (body.empty() || body.size() > 8) {
errors->Add(offset, "Supported Rates element has invalid length (%zu bytes)",
body.size());
}
break;
case kDsssParamSet:
ValidateFixedSizeElement(offset, body, 1, "DSSS Parameter Set", errors);
break;
case kCfParamSet:
ValidateFixedSizeElement(offset, body, 6, "CF Parameter Set", errors);
break;
case kTim:
if (body.size() < 4) {
errors->Add(offset, "TIM element is too short (%zu bytes)", body.size());
}
break;
case kCountry:
if (body.size() < 3) {
errors->Add(offset, "Country element is too short (%zu bytes)", body.size());
} else if (body.size() % 2 != 0) {
errors->Add(offset, "Country element is not padded to even length");
} else if (body.size() % 3 == 2) {
errors->Add(offset, "Country element includes an extra padding byte");
}
break;
case kExtSuppRates:
if (body.empty()) {
errors->Add(offset, "Extended Supported Rates element is empty");
}
break;
case kMeshId:
if (body.size() > 32) {
errors->Add(offset, "Mesh ID element is too long (%zu bytes)", body.size());
}
break;
case kMeshConfiguration:
ValidateFixedSizeElement(offset, body, 7, "Mesh Configuration", errors);
break;
case kMeshPeeringManagement:
if (body.size() < 4) {
errors->Add(offset, "Mesh Peering Management element is too short (%zu bytes)",
body.size());
} else if (body.size() > 24) {
errors->Add(offset, "Mesh Peering Management element is too long (%zu bytes)",
body.size());
} else {
size_t opt = (body.size() - 4) % 16;
if (opt != 0 && opt != 2 && opt != 4) {
errors->Add(
offset,
"Mesh Peering Management element has invalid length (%zu bytes)",
body.size());
}
}
break;
case kQosCapability:
ValidateFixedSizeElement(offset, body, 1, "QoS Capability", errors);
break;
case kGcrGroupAddress:
ValidateFixedSizeElement(offset, body, 6, "GCR Group Address", errors);
break;
case kHtCapabilities:
ValidateFixedSizeElement(offset, body, 26, "HT Capabilities", errors);
break;
case kHtOperation:
ValidateFixedSizeElement(offset, body, 22, "HT Operation", errors);
break;
case kVhtCapabilities:
ValidateFixedSizeElement(offset, body, 12, "VHT Capabilities", errors);
break;
case kVhtOperation:
ValidateFixedSizeElement(offset, body, 5, "VHT Operation", errors);
break;
default:
break;
}
}
static void ValidateElements(BufferReader* r,
Span<const AllowedElement> allowed,
ErrorAccumulator* errors) {
bool seen[allowed.size()];
std::fill_n(seen, allowed.size(), false);
size_t prev_order = 0;
uint8_t prev_id = 0;
while (r->RemainingBytes() > 0) {
size_t hdr_offset = r->ReadBytes();
auto hdr = r->ReadValue<ElementHeader>();
if (!hdr) {
errors->Add(hdr_offset, "Incomplete element header at end of frame");
break;
}
auto id = static_cast<element_id::ElementId>(hdr->id);
auto len = hdr->len;
auto it = std::find_if(allowed.begin(), allowed.end(),
[id] (const AllowedElement& ae) { return ae.id == id; });
if (it == allowed.end()) {
errors->Add(hdr_offset, "Unexpected element ID %u", id);
} else {
size_t order = it - allowed.begin();
if (order < prev_order) {
errors->Add(hdr_offset, "Wrong element order: %u is expected to appear before %u",
id, prev_id);
}
prev_order = order;
prev_id = id;
if (seen[order]) {
errors->Add(hdr_offset, "Duplicate element %u", id);
}
seen[order] = true;
}
auto ie_body = r->Read(len);
if (len > 0 && ie_body.empty()) {
errors->Add(hdr_offset + 1,
"Element length %u exceeds the number of remaining bytes %zu",
len, r->RemainingBytes());
break;
}
ValidateElement(hdr_offset, id, ie_body, errors);
}
for (size_t i = 0; i < allowed.size(); ++i) {
if (allowed[i].required && !seen[i]) {
errors->Add(r->ReadBytes() + r->RemainingBytes(),
"Required element %u is not present", allowed[i].id);
}
}
}
static void ValidateFrameWithElements(BufferReader* r,
size_t fixed_header_len,
const char* frame_name,
Span<const AllowedElement> allowed_elements,
ErrorAccumulator* errors) {
if (r->Read(fixed_header_len).empty()) {
errors->Add(r->ReadBytes(), "Expected a %s header but the frame is too short", frame_name);
return;
}
ValidateElements(r, allowed_elements, errors);
}
static void ValidateMgmtFrame(BufferReader* r, ErrorAccumulator* errors) {
auto mgmt_header = r->Read<MgmtFrameHeader>();
if (mgmt_header == nullptr) {
errors->Add(r->ReadBytes(), "Frame is shorter than minimum mgmt header length");
return;
}
if (mgmt_header->fc.HasHtCtrl()) {
auto ht_ctrl = r->Read<HtControl>();
if (ht_ctrl == nullptr) {
errors->Add(r->ReadBytes(),
"FC indicates that HTC is present but the frame is too short");
return;
}
}
switch (mgmt_header->fc.subtype()) {
case kAssociationRequest:
ValidateFrameWithElements(r, sizeof(AssociationRequest), "Association Request",
kAssocReqElements, errors);
break;
case kAssociationResponse:
ValidateFrameWithElements(r, sizeof(AssociationResponse), "Association Response",
kAssocRespElements, errors);
break;
case kReassociationRequest:
ValidateFrameWithElements(r, sizeof(ReassociationRequest), "Reassociation Request",
kReassocReqElements, errors);
break;
case kReassociationResponse:
ValidateFrameWithElements(r, sizeof(ReassociationResponse), "Reassociation Response",
kReassocRespElements, errors);
break;
case kProbeRequest:
ValidateFrameWithElements(r, sizeof(ProbeRequest), "Probe Request",
kProbeReqElements, errors);
break;
case kProbeResponse:
ValidateFrameWithElements(r, sizeof(ProbeResponse), "Probe Response",
kProbeRespElements, errors);
break;
case kTimingAdvertisement:
ValidateFrameWithElements(r, sizeof(TimingAdvertisement), "Timing Advertisement",
kTimingAdElements, errors);
break;
case kBeacon:
ValidateFrameWithElements(r, sizeof(Beacon), "Beacon", kBeaconElements, errors);
break;
case kAtim:
if (r->RemainingBytes() > 0) {
errors->Add(r->ReadBytes(), "ATIM frame has a non-null body");
}
break;
case kDisassociation:
ValidateFrameWithElements(r, sizeof(Disassociation), "Disassociation",
kDisassocElements, errors);
break;
case kAuthentication:
// This will report a false positive if we attempt to write an auth frame
// with trailing non-element fields, e.g. "Finite Cyclic Group".
// If we get there one day, we can delete this check (or write a proper validator,
// which is probably not worth the effort, given how complicated the encoding is).
ValidateFrameWithElements(r, sizeof(Authentication), "Authentication",
kAuthElements, errors);
break;
case kDeauthentication:
ValidateFrameWithElements(r, sizeof(Deauthentication), "Deauthentication",
kDeauthElements, errors);
break;
case kAction:
case kActionNoAck:
break;
}
}
static void DoValidateFrame(Span<const uint8_t> data, ErrorAccumulator* errors) {
BufferReader r { data };
auto fc = r.Peek<FrameControl>();
if (fc == nullptr) {
errors->Add(0, "Frame is too short to contain a Frame Control field");
return;
}
switch (fc->type()) {
case kManagement:
ValidateMgmtFrame(&r, errors);
break;
case kControl:
break;
case kData:
break;
case kExtension:
break;
}
}
bool ValidateFrame(const char* context_msg, Span<const uint8_t> data) {
ErrorAccumulator errors;
DoValidateFrame(data, &errors);
if (errors.HaveErrors()) {
errorf("%s:\n%sFrame contents: %s\n",
context_msg, errors.GetMessage(), debug::HexDump(data).c_str());
}
return !errors.HaveErrors();
}
} // namespace wlan