hyperv/hn: Stringent RNDIS packet message length/offset check.

While I'm here, use definition in net/rndis.h

MFC after:	1 week
Sponsored by:	Microsoft
Differential Revision:	https://reviews.freebsd.org/D7782
This commit is contained in:
Sepherosa Ziehau 2016-09-06 03:20:06 +00:00
parent 7be8de4271
commit b349357819
2 changed files with 181 additions and 48 deletions

View File

@ -159,39 +159,22 @@ hv_rf_receive_indicate_status(struct hn_softc *sc, const void *data, int dlen)
}
static int
hv_rf_find_recvinfo(const rndis_packet *rpkt, struct hn_recvinfo *info)
hn_rndis_rxinfo(const void *info_data, int info_dlen, struct hn_recvinfo *info)
{
const struct rndis_pktinfo *pi;
uint32_t mask = 0, len;
const struct rndis_pktinfo *pi = info_data;
uint32_t mask = 0;
info->vlan_info = HN_NDIS_VLAN_INFO_INVALID;
info->csum_info = HN_NDIS_RXCSUM_INFO_INVALID;
info->hash_info = HN_NDIS_HASH_INFO_INVALID;
if (rpkt->per_pkt_info_offset == 0)
return (0);
if (__predict_false(rpkt->per_pkt_info_offset &
(RNDIS_PKTINFO_ALIGN - 1)))
return (EINVAL);
if (__predict_false(rpkt->per_pkt_info_offset <
RNDIS_PACKET_MSG_OFFSET_MIN))
return (EINVAL);
pi = (const struct rndis_pktinfo *)
((const uint8_t *)rpkt + rpkt->per_pkt_info_offset);
len = rpkt->per_pkt_info_length;
while (len != 0) {
while (info_dlen != 0) {
const void *data;
uint32_t dlen;
if (__predict_false(len < sizeof(*pi)))
if (__predict_false(info_dlen < sizeof(*pi)))
return (EINVAL);
if (__predict_false(len < pi->rm_size))
if (__predict_false(info_dlen < pi->rm_size))
return (EINVAL);
len -= pi->rm_size;
info_dlen -= pi->rm_size;
if (__predict_false(pi->rm_size & (RNDIS_PKTINFO_ALIGN - 1)))
if (__predict_false(pi->rm_size & RNDIS_PKTINFO_SIZE_ALIGNMASK))
return (EINVAL);
if (__predict_false(pi->rm_size < pi->rm_pktinfooffset))
return (EINVAL);
@ -249,43 +232,183 @@ hv_rf_find_recvinfo(const rndis_packet *rpkt, struct hn_recvinfo *info)
return (0);
}
static __inline bool
hn_rndis_check_overlap(int off, int len, int check_off, int check_len)
{
if (off < check_off) {
if (__predict_true(off + len <= check_off))
return (false);
} else if (off > check_off) {
if (__predict_true(check_off + check_len <= off))
return (false);
}
return (true);
}
/*
* RNDIS filter receive data
*/
static void
hv_rf_receive_data(struct hn_rx_ring *rxr, const void *data, int dlen)
{
const rndis_msg *message = data;
const rndis_packet *rndis_pkt;
uint32_t data_offset;
const struct rndis_packet_msg *pkt;
struct hn_recvinfo info;
rndis_pkt = &message->msg.packet;
int data_off, pktinfo_off, data_len, pktinfo_len;
/*
* Fixme: Handle multiple rndis pkt msgs that may be enclosed in this
* netvsc packet (ie tot_data_buf_len != message_length)
* Check length.
*/
if (__predict_false(dlen < sizeof(*pkt))) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg\n");
return;
}
pkt = data;
/* Remove rndis header, then pass data packet up the stack */
data_offset = RNDIS_HEADER_SIZE + rndis_pkt->data_offset;
dlen -= data_offset;
if (dlen < rndis_pkt->data_length) {
if_printf(rxr->hn_ifp,
"total length %u is less than data length %u\n",
dlen, rndis_pkt->data_length);
if (__predict_false(dlen < pkt->rm_len)) {
if_printf(rxr->hn_ifp, "truncated RNDIS packet msg, "
"dlen %d, msglen %u\n", dlen, pkt->rm_len);
return;
}
if (__predict_false(pkt->rm_len <
pkt->rm_datalen + pkt->rm_oobdatalen + pkt->rm_pktinfolen)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msglen, "
"msglen %u, data %u, oob %u, pktinfo %u\n",
pkt->rm_len, pkt->rm_datalen, pkt->rm_oobdatalen,
pkt->rm_pktinfolen);
return;
}
if (__predict_false(pkt->rm_datalen == 0)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, no data\n");
return;
}
dlen = rndis_pkt->data_length;
data = (const uint8_t *)data + data_offset;
/*
* Check offests.
*/
#define IS_OFFSET_INVALID(ofs) \
((ofs) < RNDIS_PACKET_MSG_OFFSET_MIN || \
((ofs) & RNDIS_PACKET_MSG_OFFSET_ALIGNMASK))
if (hv_rf_find_recvinfo(rndis_pkt, &info)) {
if_printf(rxr->hn_ifp, "recvinfo parsing failed\n");
/* XXX Hyper-V does not meet data offset alignment requirement */
if (__predict_false(pkt->rm_dataoffset < RNDIS_PACKET_MSG_OFFSET_MIN)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"data offset %u\n", pkt->rm_dataoffset);
return;
}
netvsc_recv(rxr, data, dlen, &info);
if (__predict_false(pkt->rm_oobdataoffset > 0 &&
IS_OFFSET_INVALID(pkt->rm_oobdataoffset))) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"oob offset %u\n", pkt->rm_oobdataoffset);
return;
}
if (__predict_true(pkt->rm_pktinfooffset > 0) &&
__predict_false(IS_OFFSET_INVALID(pkt->rm_pktinfooffset))) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"pktinfo offset %u\n", pkt->rm_pktinfooffset);
return;
}
#undef IS_OFFSET_INVALID
data_off = RNDIS_PACKET_MSG_OFFSET_ABS(pkt->rm_dataoffset);
data_len = pkt->rm_datalen;
pktinfo_off = RNDIS_PACKET_MSG_OFFSET_ABS(pkt->rm_pktinfooffset);
pktinfo_len = pkt->rm_pktinfolen;
/*
* Check OOB coverage.
*/
if (__predict_false(pkt->rm_oobdatalen != 0)) {
int oob_off, oob_len;
if_printf(rxr->hn_ifp, "got oobdata\n");
oob_off = RNDIS_PACKET_MSG_OFFSET_ABS(pkt->rm_oobdataoffset);
oob_len = pkt->rm_oobdatalen;
if (__predict_false(oob_off + oob_len > pkt->rm_len)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"oob overflow, msglen %u, oob abs %d len %d\n",
pkt->rm_len, oob_off, oob_len);
return;
}
/*
* Check against data.
*/
if (hn_rndis_check_overlap(oob_off, oob_len,
data_off, data_len)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"oob overlaps data, oob abs %d len %d, "
"data abs %d len %d\n",
oob_off, oob_len, data_off, data_len);
return;
}
/*
* Check against pktinfo.
*/
if (pktinfo_len != 0 &&
hn_rndis_check_overlap(oob_off, oob_len,
pktinfo_off, pktinfo_len)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"oob overlaps pktinfo, oob abs %d len %d, "
"pktinfo abs %d len %d\n",
oob_off, oob_len, pktinfo_off, pktinfo_len);
return;
}
}
/*
* Check per-packet-info coverage and find useful per-packet-info.
*/
info.vlan_info = HN_NDIS_VLAN_INFO_INVALID;
info.csum_info = HN_NDIS_RXCSUM_INFO_INVALID;
info.hash_info = HN_NDIS_HASH_INFO_INVALID;
if (__predict_true(pktinfo_len != 0)) {
bool overlap;
int error;
if (__predict_false(pktinfo_off + pktinfo_len > pkt->rm_len)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"pktinfo overflow, msglen %u, "
"pktinfo abs %d len %d\n",
pkt->rm_len, pktinfo_off, pktinfo_len);
return;
}
/*
* Check packet info coverage.
*/
overlap = hn_rndis_check_overlap(pktinfo_off, pktinfo_len,
data_off, data_len);
if (__predict_false(overlap)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"pktinfo overlap data, pktinfo abs %d len %d, "
"data abs %d len %d\n",
pktinfo_off, pktinfo_len, data_off, data_len);
return;
}
/*
* Find useful per-packet-info.
*/
error = hn_rndis_rxinfo(((const uint8_t *)pkt) + pktinfo_off,
pktinfo_len, &info);
if (__predict_false(error)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg "
"pktinfo\n");
return;
}
}
if (__predict_false(data_off + data_len > pkt->rm_len)) {
if_printf(rxr->hn_ifp, "invalid RNDIS packet msg, "
"data overflow, msglen %u, data abs %d len %d\n",
pkt->rm_len, data_off, data_len);
return;
}
netvsc_recv(rxr, ((const uint8_t *)pkt) + data_off, data_len, &info);
}
/*
@ -565,7 +688,7 @@ hn_rndis_query(struct hn_softc *sc, uint32_t oid,
* Check output data length and offset.
*/
/* ofs is the offset from the beginning of comp. */
ofs = RNDIS_QUERY_COMP_INFOBUFABS(comp->rm_infobufoffset);
ofs = RNDIS_QUERY_COMP_INFOBUFOFFSET_ABS(comp->rm_infobufoffset);
if (ofs < sizeof(*comp) || ofs + comp->rm_infobuflen > comp_len) {
if_printf(sc->hn_ifp, "RNDIS query invalid comp ib off/len, "
"%u/%u\n", comp->rm_infobufoffset, comp->rm_infobuflen);

View File

@ -127,6 +127,14 @@ struct rndis_packet_msg {
(sizeof(struct rndis_packet_msg) - \
__offsetof(struct rndis_packet_msg, rm_dataoffset))
/* Offset from the beginning of rndis_packet_msg. */
#define RNDIS_PACKET_MSG_OFFSET_ABS(ofs) \
((ofs) + __offsetof(struct rndis_packet_msg, rm_dataoffset))
#define RNDIS_PACKET_MSG_OFFSET_ALIGN 4
#define RNDIS_PACKET_MSG_OFFSET_ALIGNMASK \
(RNDIS_PACKET_MSG_OFFSET_ALIGN - 1)
/* Per-packet-info for RNDIS data message */
struct rndis_pktinfo {
uint32_t rm_size;
@ -137,7 +145,8 @@ struct rndis_pktinfo {
#define RNDIS_PKTINFO_OFFSET \
__offsetof(struct rndis_pktinfo, rm_data[0])
#define RNDIS_PKTINFO_ALIGN 4
#define RNDIS_PKTINFO_SIZE_ALIGN 4
#define RNDIS_PKTINFO_SIZE_ALIGNMASK (RNDIS_PKTINFO_SIZE_ALIGN - 1)
#define NDIS_PKTINFO_TYPE_CSUM 0
#define NDIS_PKTINFO_TYPE_IPSEC 1
@ -236,7 +245,8 @@ struct rndis_query_comp {
uint32_t rm_infobufoffset;
};
#define RNDIS_QUERY_COMP_INFOBUFABS(ofs) \
/* infobuf offset from the beginning of rndis_query_comp. */
#define RNDIS_QUERY_COMP_INFOBUFOFFSET_ABS(ofs) \
((ofs) + __offsetof(struct rndis_query_req, rm_rid))
/* Send a set object request. */