sctp: improve computation of an alternate net

Espeially handle the case where the net passed in is about to
be deleted and therefore not in the list of nets anymore.

MFC after:	3 days
Reported by:	syzbot+9756917a7c8381adf5e8@syzkaller.appspotmail.com
This commit is contained in:
Michael Tuexen 2021-02-21 17:13:06 +01:00
parent 5ac839029d
commit b963ce4588

View File

@ -164,8 +164,8 @@ sctp_threshold_management(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
}
/*
* sctp_find_alternate_net() returns a non-NULL pointer as long
* the argument net is non-NULL.
* sctp_find_alternate_net() returns a non-NULL pointer as long as there
* exists nets, which are not being deleted.
*/
struct sctp_nets *
sctp_find_alternate_net(struct sctp_tcb *stcb,
@ -174,14 +174,14 @@ sctp_find_alternate_net(struct sctp_tcb *stcb,
{
/* Find and return an alternate network if possible */
struct sctp_nets *alt, *mnet, *min_errors_net = NULL, *max_cwnd_net = NULL;
int once;
bool looped;
/* JRS 5/14/07 - Initialize min_errors to an impossible value. */
int min_errors = -1;
uint32_t max_cwnd = 0;
if (stcb->asoc.numnets == 1) {
/* No others but net */
/* No selection can be made. */
return (TAILQ_FIRST(&stcb->asoc.nets));
}
/*
@ -328,25 +328,22 @@ sctp_find_alternate_net(struct sctp_tcb *stcb,
return (max_cwnd_net);
}
}
mnet = net;
once = 0;
if (mnet == NULL) {
mnet = TAILQ_FIRST(&stcb->asoc.nets);
if (mnet == NULL) {
return (NULL);
}
/* Look for an alternate net, which is active. */
if ((net != NULL) && ((net->dest_state & SCTP_ADDR_BEING_DELETED) == 0)) {
alt = TAILQ_NEXT(net, sctp_next);;
} else {
alt = TAILQ_FIRST(&stcb->asoc.nets);
}
looped = false;
for (;;) {
alt = TAILQ_NEXT(mnet, sctp_next);
if (alt == NULL) {
once++;
if (once > 1) {
break;
if (!looped) {
alt = TAILQ_FIRST(&stcb->asoc.nets);
looped = true;
}
alt = TAILQ_FIRST(&stcb->asoc.nets);
/* Definitely out of candidates. */
if (alt == NULL) {
return (NULL);
break;
}
}
if (alt->ro.ro_nh == NULL) {
@ -358,43 +355,59 @@ sctp_find_alternate_net(struct sctp_tcb *stcb,
}
if (((alt->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE) &&
(alt->ro.ro_nh != NULL) &&
(!(alt->dest_state & SCTP_ADDR_UNCONFIRMED))) {
/* Found a reachable address */
(!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) &&
(alt != net)) {
/* Found an alternate net, which is reachable. */
break;
}
mnet = alt;
alt = TAILQ_NEXT(alt, sctp_next);
}
if (alt == NULL) {
/* Case where NO insv network exists (dormant state) */
/* we rotate destinations */
once = 0;
mnet = net;
/*
* In case no active alternate net has been found, look for
* an alternate net, which is confirmed.
*/
if ((net != NULL) && ((net->dest_state & SCTP_ADDR_BEING_DELETED) == 0)) {
alt = TAILQ_NEXT(net, sctp_next);;
} else {
alt = TAILQ_FIRST(&stcb->asoc.nets);
}
looped = false;
for (;;) {
if (mnet == NULL) {
return (TAILQ_FIRST(&stcb->asoc.nets));
}
alt = TAILQ_NEXT(mnet, sctp_next);
if (alt == NULL) {
once++;
if (once > 1) {
break;
if (!looped) {
alt = TAILQ_FIRST(&stcb->asoc.nets);
looped = true;
}
alt = TAILQ_FIRST(&stcb->asoc.nets);
/* Definitely out of candidates. */
if (alt == NULL) {
break;
}
}
if ((!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) &&
(alt != net)) {
/* Found an alternate address */
/*
* Found an alternate net, which is
* confirmed.
*/
break;
}
mnet = alt;
alt = TAILQ_NEXT(alt, sctp_next);
}
}
if (alt == NULL) {
return (net);
/*
* In case no confirmed alternate net has been found, just
* return net, if it is not being deleted. In the other case
* just return the first net.
*/
if ((net != NULL) && ((net->dest_state & SCTP_ADDR_BEING_DELETED) == 0)) {
alt = net;
}
if (alt == NULL) {
alt = TAILQ_FIRST(&stcb->asoc.nets);
}
}
return (alt);
}