Improve handling of PKTDROP chunks. This includes the input validation

to address two issues found by ossfuzz testing the userland stack:
* https://oss-fuzz.com/testcase-detail/5387560242380800
* https://oss-fuzz.com/testcase-detail/4887954068865024
and adding support for I-DATA chunks in addition to DATA chunks.
This commit is contained in:
Michael Tuexen 2020-07-08 12:25:19 +00:00
parent de402d6322
commit 32df1c9ebb
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=363008

View File

@ -3046,7 +3046,8 @@ process_chunk_drop(struct sctp_tcb *stcb, struct sctp_chunk_desc *desc,
{
switch (desc->chunk_type) {
case SCTP_DATA:
/* find the tsn to resend (possibly */
case SCTP_IDATA:
/* find the tsn to resend (possibly) */
{
uint32_t tsn;
struct sctp_tmit_chunk *tp1;
@ -3080,8 +3081,6 @@ process_chunk_drop(struct sctp_tcb *stcb, struct sctp_chunk_desc *desc,
SCTP_STAT_INCR(sctps_pdrptsnnf);
}
if ((tp1) && (tp1->sent < SCTP_DATAGRAM_ACKED)) {
uint8_t *ddp;
if (((flg & SCTP_BADCRC) == 0) &&
((flg & SCTP_FROM_MIDDLE_BOX) == 0)) {
return (0);
@ -3096,20 +3095,18 @@ process_chunk_drop(struct sctp_tcb *stcb, struct sctp_chunk_desc *desc,
SCTP_STAT_INCR(sctps_pdrpdizrw);
return (0);
}
ddp = (uint8_t *)(mtod(tp1->data, caddr_t)+
sizeof(struct sctp_data_chunk));
{
unsigned int iii;
for (iii = 0; iii < sizeof(desc->data_bytes);
iii++) {
if (ddp[iii] != desc->data_bytes[iii]) {
SCTP_STAT_INCR(sctps_pdrpbadd);
return (-1);
}
}
if ((uint32_t)SCTP_BUF_LEN(tp1->data) <
SCTP_DATA_CHUNK_OVERHEAD(stcb) + SCTP_NUM_DB_TO_VERIFY) {
/* Payload not matching. */
SCTP_STAT_INCR(sctps_pdrpbadd);
return (-1);
}
if (memcmp(mtod(tp1->data, caddr_t)+SCTP_DATA_CHUNK_OVERHEAD(stcb),
desc->data_bytes, SCTP_NUM_DB_TO_VERIFY) != 0) {
/* Payload not matching. */
SCTP_STAT_INCR(sctps_pdrpbadd);
return (-1);
}
if (tp1->do_rtt) {
/*
* this guy had a RTO calculation
@ -4135,104 +4132,126 @@ static void
sctp_handle_packet_dropped(struct sctp_pktdrop_chunk *cp,
struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t limit)
{
uint32_t bottle_bw, on_queue;
uint16_t trunc_len;
unsigned int chlen;
unsigned int at;
struct sctp_chunk_desc desc;
struct sctp_chunkhdr *ch;
struct sctp_chunkhdr *chk_hdr;
struct sctp_data_chunk *data_chunk;
struct sctp_idata_chunk *idata_chunk;
uint32_t bottle_bw, on_queue;
uint32_t offset, chk_len;
uint16_t trunc_len;
uint16_t pktdrp_len;
uint8_t pktdrp_flags;
chlen = ntohs(cp->ch.chunk_length);
chlen -= sizeof(struct sctp_pktdrop_chunk);
/* XXX possible chlen underflow */
if (chlen == 0) {
ch = NULL;
if (cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX)
SCTP_STAT_INCR(sctps_pdrpbwrpt);
KASSERT(sizeof(struct sctp_pktdrop_chunk) <= limit,
("PKTDROP chunk too small"));
pktdrp_flags = cp->ch.chunk_flags;
pktdrp_len = ntohs(cp->ch.chunk_length);
KASSERT(limit <= pktdrp_len, ("Inconsistent limit"));
if (pktdrp_flags & SCTP_PACKET_TRUNCATED) {
trunc_len = ntohs(cp->trunc_len);
if (trunc_len <= pktdrp_len - sizeof(struct sctp_pktdrop_chunk)) {
/* The peer plays games with us. */
return;
}
} else {
ch = (struct sctp_chunkhdr *)(cp->data + sizeof(struct sctphdr));
chlen -= sizeof(struct sctphdr);
/* XXX possible chlen underflow */
memset(&desc, 0, sizeof(desc));
trunc_len = 0;
}
trunc_len = (uint16_t)ntohs(cp->trunc_len);
if (trunc_len > limit) {
trunc_len = limit;
limit -= sizeof(struct sctp_pktdrop_chunk);
offset = 0;
if (offset == limit) {
if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
SCTP_STAT_INCR(sctps_pdrpbwrpt);
}
} else if (offset + sizeof(struct sctphdr) > limit) {
/* Only a partial SCTP common header. */
SCTP_STAT_INCR(sctps_pdrpcrupt);
offset = limit;
} else {
/* XXX: Check embedded SCTP common header. */
offset += sizeof(struct sctphdr);
}
/* now the chunks themselves */
while ((ch != NULL) && (chlen >= sizeof(struct sctp_chunkhdr))) {
desc.chunk_type = ch->chunk_type;
/* get amount we need to move */
at = ntohs(ch->chunk_length);
if (at < sizeof(struct sctp_chunkhdr)) {
/* corrupt chunk, maybe at the end? */
/* Now parse through the chunks themselves. */
while (offset < limit) {
if (offset + sizeof(struct sctp_chunkhdr) > limit) {
SCTP_STAT_INCR(sctps_pdrpcrupt);
break;
}
if (trunc_len == 0) {
/* we are supposed to have all of it */
if (at > chlen) {
/* corrupt skip it */
SCTP_STAT_INCR(sctps_pdrpcrupt);
break;
}
} else {
/* is there enough of it left ? */
if (desc.chunk_type == SCTP_DATA) {
if (chlen < (sizeof(struct sctp_data_chunk) +
sizeof(desc.data_bytes))) {
break;
}
} else {
if (chlen < sizeof(struct sctp_chunkhdr)) {
break;
}
}
chk_hdr = (struct sctp_chunkhdr *)(cp->data + offset);
desc.chunk_type = chk_hdr->chunk_type;
/* get amount we need to move */
chk_len = (uint32_t)ntohs(chk_hdr->chunk_length);
if (chk_len < sizeof(struct sctp_chunkhdr)) {
/* Someone is lying... */
break;
}
if (desc.chunk_type == SCTP_DATA) {
/* can we get out the tsn? */
if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX))
SCTP_STAT_INCR(sctps_pdrpmbda);
if (chlen >= (sizeof(struct sctp_data_chunk) + sizeof(uint32_t))) {
/* yep */
struct sctp_data_chunk *dcp;
uint8_t *ddp;
unsigned int iii;
dcp = (struct sctp_data_chunk *)ch;
ddp = (uint8_t *)(dcp + 1);
for (iii = 0; iii < sizeof(desc.data_bytes); iii++) {
desc.data_bytes[iii] = ddp[iii];
}
desc.tsn_ifany = dcp->dp.tsn;
} else {
/* nope we are done. */
SCTP_STAT_INCR(sctps_pdrpnedat);
if (stcb->asoc.idata_supported) {
/* Some is playing games with us. */
break;
}
if (chk_len <= sizeof(struct sctp_data_chunk)) {
/* Some is playing games with us. */
break;
}
if (chk_len < sizeof(struct sctp_data_chunk) + SCTP_NUM_DB_TO_VERIFY) {
/*
* Not enough data bytes available in the
* chunk.
*/
SCTP_STAT_INCR(sctps_pdrpnedat);
goto next_chunk;
}
if (offset + sizeof(struct sctp_data_chunk) + SCTP_NUM_DB_TO_VERIFY > limit) {
/* Not enough data in buffer. */
break;
}
data_chunk = (struct sctp_data_chunk *)(cp->data + offset);
memcpy(desc.data_bytes, data_chunk + 1, SCTP_NUM_DB_TO_VERIFY);
desc.tsn_ifany = data_chunk->dp.tsn;
if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
SCTP_STAT_INCR(sctps_pdrpmbda);
}
} else if (desc.chunk_type == SCTP_IDATA) {
if (!stcb->asoc.idata_supported) {
/* Some is playing games with us. */
break;
}
if (chk_len <= sizeof(struct sctp_idata_chunk)) {
/* Some is playing games with us. */
break;
}
if (chk_len < sizeof(struct sctp_idata_chunk) + SCTP_NUM_DB_TO_VERIFY) {
/*
* Not enough data bytes available in the
* chunk.
*/
SCTP_STAT_INCR(sctps_pdrpnedat);
goto next_chunk;
}
if (offset + sizeof(struct sctp_idata_chunk) + SCTP_NUM_DB_TO_VERIFY > limit) {
/* Not enough data in buffer. */
break;
}
idata_chunk = (struct sctp_idata_chunk *)(cp->data + offset);
memcpy(desc.data_bytes, idata_chunk + 1, SCTP_NUM_DB_TO_VERIFY);
desc.tsn_ifany = idata_chunk->dp.tsn;
if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
SCTP_STAT_INCR(sctps_pdrpmbda);
}
} else {
if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX))
if (pktdrp_flags & SCTP_FROM_MIDDLE_BOX) {
SCTP_STAT_INCR(sctps_pdrpmbct);
}
}
if (process_chunk_drop(stcb, &desc, net, cp->ch.chunk_flags)) {
if (process_chunk_drop(stcb, &desc, net, pktdrp_flags)) {
SCTP_STAT_INCR(sctps_pdrppdbrk);
break;
}
if (SCTP_SIZE32(at) > chlen) {
break;
}
chlen -= SCTP_SIZE32(at);
if (chlen < sizeof(struct sctp_chunkhdr)) {
/* done, none left */
break;
}
ch = (struct sctp_chunkhdr *)((caddr_t)ch + SCTP_SIZE32(at));
next_chunk:
offset += SCTP_SIZE32(chk_len);
}
/* Now update any rwnd --- possibly */
if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) == 0) {
if ((pktdrp_flags & SCTP_FROM_MIDDLE_BOX) == 0) {
/* From a peer, we get a rwnd report */
uint32_t a_rwnd;
@ -4268,7 +4287,7 @@ sctp_handle_packet_dropped(struct sctp_pktdrop_chunk *cp,
}
/* now middle boxes in sat networks get a cwnd bump */
if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) &&
if ((pktdrp_flags & SCTP_FROM_MIDDLE_BOX) &&
(stcb->asoc.sat_t3_loss_recovery == 0) &&
(stcb->asoc.sat_network)) {
/*