QLowEnergyControllerPrivateBluez: guard against malformed replies
The QLowEnergyControllerPrivateBluez::l2cpReadyRead() slot reads the data from a Bluetooth L2CAP socket and then tries to process it according to ATT protocol specs. However, the code was missing length and sanity checks at some codepaths in processUnsolicitedReply() and processReply() helper methods, simply relying on the data to be in the proper format. This patch adds some minimal checks to make sure that we do not read past the end of the received array and do not divide by zero. This problem was originally pointed out by Marc Mutz in an unrelated patch. Pick-to: 6.9 6.8 6.5 5.15 Change-Id: I8dcfe031f70ad61fa3d87dc9d771c3fabc6d0edc Reviewed-by: Alex Blasche <alexander.blasche@qt.io> Reviewed-by: Juha Vuolle <juha.vuolle@qt.io>
This commit is contained in:
parent
dd12b8a40a
commit
aecbd657c8
|
@ -64,14 +64,15 @@ using namespace QBluetooth;
|
|||
|
||||
const int maxPrepareQueueSize = 1024;
|
||||
|
||||
static void dumpErrorInformation(const QByteArray &response)
|
||||
/* returns false if the format is incorrect */
|
||||
static bool dumpErrorInformation(const QByteArray &response)
|
||||
{
|
||||
const char *data = response.constData();
|
||||
if (response.size() != 5
|
||||
|| (static_cast<QBluezConst::AttCommand>(data[0])
|
||||
!= QBluezConst::AttCommand::ATT_OP_ERROR_RESPONSE)) {
|
||||
qCWarning(QT_BT_BLUEZ) << QLatin1String("Not a valid error response");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
QBluezConst::AttCommand lastCommand = static_cast<QBluezConst::AttCommand>(data[1]);
|
||||
|
@ -126,6 +127,8 @@ static void dumpErrorInformation(const QByteArray &response)
|
|||
|
||||
qCDebug(QT_BT_BLUEZ) << "Error:" << errorCode << "Error description:" << errorString
|
||||
<< "last command:" << lastCommand << "handle:" << handle;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int getUuidSize(const QBluetoothUuid &uuid)
|
||||
|
@ -903,6 +906,7 @@ QLowEnergyHandle parseReadByTypeCharDiscovery(
|
|||
{
|
||||
Q_ASSERT(charData);
|
||||
Q_ASSERT(data);
|
||||
Q_ASSERT(elementLength >= 5);
|
||||
|
||||
QLowEnergyHandle attributeHandle = bt_get_le16(&data[0]);
|
||||
charData->properties =
|
||||
|
@ -912,7 +916,7 @@ QLowEnergyHandle parseReadByTypeCharDiscovery(
|
|||
// Bluetooth LE data comes as little endian
|
||||
if (elementLength == 7) // 16 bit uuid
|
||||
charData->uuid = QBluetoothUuid(bt_get_le16(&data[5]));
|
||||
else
|
||||
else if (elementLength == 21) // 128 bit uuid
|
||||
charData->uuid = QUuid::fromBytes(&data[5], QSysInfo::LittleEndian);
|
||||
|
||||
qCDebug(QT_BT_BLUEZ) << "Found handle:" << Qt::hex << attributeHandle
|
||||
|
@ -929,6 +933,7 @@ QLowEnergyHandle parseReadByTypeIncludeDiscovery(
|
|||
{
|
||||
Q_ASSERT(foundServices);
|
||||
Q_ASSERT(data);
|
||||
Q_ASSERT(elementLength >= 6);
|
||||
|
||||
QLowEnergyHandle attributeHandle = bt_get_le16(&data[0]);
|
||||
|
||||
|
@ -938,9 +943,14 @@ QLowEnergyHandle parseReadByTypeIncludeDiscovery(
|
|||
// data[2] -> included service start handle
|
||||
// data[4] -> included service end handle
|
||||
|
||||
// TODO: Spec v. 5.3, Vol. 3, Part G, 4.5.1 mentions that only
|
||||
// 16-bit UUID can be returned here. If the UUID is 128-bit,
|
||||
// then it is omitted from the response, and should be requested
|
||||
// separately with the ATT_READ_REQ command.
|
||||
|
||||
if (elementLength == 8) //16 bit uuid
|
||||
foundServices->append(QBluetoothUuid(bt_get_le16(&data[6])));
|
||||
else
|
||||
else if (elementLength == 22) // 128 bit uuid
|
||||
foundServices->append(QUuid::fromBytes(&data[6], QSysInfo::LittleEndian));
|
||||
|
||||
qCDebug(QT_BT_BLUEZ) << "Found included service: " << Qt::hex
|
||||
|
@ -949,17 +959,29 @@ QLowEnergyHandle parseReadByTypeIncludeDiscovery(
|
|||
return attributeHandle;
|
||||
}
|
||||
|
||||
Q_DECL_COLD_FUNCTION
|
||||
static void reportMalformedData(QBluezConst::AttCommand cmd, const QByteArray &response)
|
||||
{
|
||||
qCDebug(QT_BT_BLUEZ, "%s malformed data: %s", qt_getEnumName(cmd),
|
||||
response.toHex().constData());
|
||||
}
|
||||
|
||||
void QLowEnergyControllerPrivateBluez::processReply(
|
||||
const Request &request, const QByteArray &response)
|
||||
{
|
||||
Q_Q(QLowEnergyController);
|
||||
|
||||
// We already have an isEmpty() check at the only calling site that reads
|
||||
// incoming data, so Q_ASSERT is enough.
|
||||
Q_ASSERT(!response.isEmpty());
|
||||
|
||||
QBluezConst::AttCommand command = static_cast<QBluezConst::AttCommand>(response.constData()[0]);
|
||||
|
||||
bool isErrorResponse = false;
|
||||
// if error occurred 2. byte is previous request type
|
||||
if (command == QBluezConst::AttCommand::ATT_OP_ERROR_RESPONSE) {
|
||||
dumpErrorInformation(response);
|
||||
if (!dumpErrorInformation(response))
|
||||
return;
|
||||
command = static_cast<QBluezConst::AttCommand>(response.constData()[1]);
|
||||
isErrorResponse = true;
|
||||
}
|
||||
|
@ -972,6 +994,10 @@ void QLowEnergyControllerPrivateBluez::processReply(
|
|||
if (isErrorResponse) {
|
||||
mtuSize = ATT_DEFAULT_LE_MTU;
|
||||
} else {
|
||||
if (response.size() < 3) {
|
||||
reportMalformedData(command, response);
|
||||
break;
|
||||
}
|
||||
const char *data = response.constData();
|
||||
quint16 mtu = bt_get_le16(&data[1]);
|
||||
mtuSize = mtu;
|
||||
|
@ -1000,8 +1026,15 @@ void QLowEnergyControllerPrivateBluez::processReply(
|
|||
break;
|
||||
}
|
||||
|
||||
// response[1] == elementLength. According to the spec it should be
|
||||
// at least 4 bytes. See Spec v5.3, Vol 3, Part F, 3.4.4.10
|
||||
if (response.size() < 2 || response[1] < 4) {
|
||||
reportMalformedData(command, response);
|
||||
break;
|
||||
}
|
||||
|
||||
QLowEnergyHandle start = 0, end = 0;
|
||||
const quint16 elementLength = response.constData()[1];
|
||||
const quint16 elementLength = response.constData()[1]; // value checked above
|
||||
const quint16 numElements = (response.size() - 2) / elementLength;
|
||||
quint16 offset = 2;
|
||||
const char *data = response.constData();
|
||||
|
@ -1077,16 +1110,25 @@ void QLowEnergyControllerPrivateBluez::processReply(
|
|||
}
|
||||
|
||||
/* packet format:
|
||||
* if GATT_CHARACTERISTIC discovery
|
||||
* if GATT_CHARACTERISTIC discovery (Spec 5.3, Vol. 3, Part G, 4.6)
|
||||
* <opcode><elementLength>
|
||||
* [<handle><property><charHandle><uuid>]+
|
||||
* The minimum elementLength is 7 bytes (uuid is always included)
|
||||
*
|
||||
* if GATT_INCLUDE discovery
|
||||
* if GATT_INCLUDE discovery (Spec 5.3, Vol. 3, Part G, 4.5.1)
|
||||
* <opcode><elementLength>
|
||||
* [<handle><startHandle_included><endHandle_included><uuid>]+
|
||||
* The minimum elementLength is 6 bytes (uuid can be omitted).
|
||||
*
|
||||
* The uuid can be 16 or 128 bit.
|
||||
*/
|
||||
|
||||
const quint8 minimumElementLength = attributeType == GATT_CHARACTERISTIC ? 7 : 6;
|
||||
if (response.size() < 2 || response[1] < minimumElementLength) {
|
||||
reportMalformedData(command, response);
|
||||
break;
|
||||
}
|
||||
|
||||
QLowEnergyHandle lastHandle;
|
||||
const quint16 elementLength = response.constData()[1];
|
||||
const quint16 numElements = (response.size() - 2) / elementLength;
|
||||
|
@ -1283,6 +1325,12 @@ void QLowEnergyControllerPrivateBluez::processReply(
|
|||
break;
|
||||
}
|
||||
|
||||
// Spec 5.3, Vol. 3, Part F, 3.4.3.2
|
||||
if (response.size() < 6) {
|
||||
reportMalformedData(command, response);
|
||||
break;
|
||||
}
|
||||
|
||||
const quint8 format = response[1];
|
||||
quint16 elementLength;
|
||||
switch (format) {
|
||||
|
@ -1720,9 +1768,18 @@ void QLowEnergyControllerPrivateBluez::discoverServiceDescriptors(
|
|||
|
||||
void QLowEnergyControllerPrivateBluez::processUnsolicitedReply(const QByteArray &payload)
|
||||
{
|
||||
Q_ASSERT(!payload.isEmpty());
|
||||
|
||||
const char *data = payload.constData();
|
||||
bool isNotification = (static_cast<QBluezConst::AttCommand>(data[0])
|
||||
const auto command = static_cast<QBluezConst::AttCommand>(data[0]);
|
||||
bool isNotification = (command
|
||||
== QBluezConst::AttCommand::ATT_OP_HANDLE_VAL_NOTIFICATION);
|
||||
|
||||
if (payload.size() < 3) {
|
||||
reportMalformedData(command, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
const QLowEnergyHandle changedHandle = bt_get_le16(&data[1]);
|
||||
|
||||
if (QT_BT_BLUEZ().isDebugEnabled()) {
|
||||
|
|
Loading…
Reference in New Issue