sctp: cleanup the SCTP_MAXSEG socket option.

This patch makes the handling of the SCTP_MAXSEG socket option
compliant with RFC 6458 (SCTP socket API) and fixes an issue
found by syzkaller.

Reported by:	syzbot+a2791b89ab99121e3333@syzkaller.appspotmail.com
MFC after:	3 days
This commit is contained in:
Michael Tuexen 2021-12-27 23:40:31 +01:00
parent 7b36da4843
commit 989453da05
6 changed files with 60 additions and 80 deletions

View File

@ -673,8 +673,6 @@ __FBSDID("$FreeBSD$");
/* amount peer is obligated to have in rwnd or I will abort */
#define SCTP_MIN_RWND 1500
#define SCTP_DEFAULT_MAXSEGMENT 65535
#define SCTP_CHUNK_BUFFER_SIZE 512
#define SCTP_PARAM_BUFFER_SIZE 512

View File

@ -6217,43 +6217,48 @@ sctp_prune_prsctp(struct sctp_tcb *stcb,
} /* if enabled in asoc */
}
int
sctp_get_frag_point(struct sctp_tcb *stcb,
struct sctp_association *asoc)
uint32_t
sctp_get_frag_point(struct sctp_tcb *stcb)
{
int siz, ovh;
struct sctp_association *asoc;
uint32_t frag_point, overhead;
/*
* For endpoints that have both v6 and v4 addresses we must reserve
* room for the ipv6 header, for those that are only dealing with V4
* we use a larger frag point.
*/
asoc = &stcb->asoc;
/* Consider IP header and SCTP common header. */
if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
ovh = SCTP_MIN_OVERHEAD;
overhead = SCTP_MIN_OVERHEAD;
} else {
ovh = SCTP_MIN_V4_OVERHEAD;
overhead = SCTP_MIN_V4_OVERHEAD;
}
ovh += SCTP_DATA_CHUNK_OVERHEAD(stcb);
if (stcb->asoc.sctp_frag_point > asoc->smallest_mtu)
siz = asoc->smallest_mtu - ovh;
else
siz = (stcb->asoc.sctp_frag_point - ovh);
/*
* if (siz > (MCLBYTES-sizeof(struct sctp_data_chunk))) {
*/
/* A data chunk MUST fit in a cluster */
/* siz = (MCLBYTES - sizeof(struct sctp_data_chunk)); */
/* } */
/* adjust for an AUTH chunk if DATA requires auth */
if (sctp_auth_is_required_chunk(SCTP_DATA, stcb->asoc.peer_auth_chunks))
siz -= sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
if (siz % 4) {
/* make it an even word boundary please */
siz -= (siz % 4);
/* Consider DATA/IDATA chunk header and AUTH header, if needed. */
if (asoc->idata_supported) {
overhead += sizeof(struct sctp_idata_chunk);
if (sctp_auth_is_required_chunk(SCTP_IDATA, asoc->peer_auth_chunks)) {
overhead += sctp_get_auth_chunk_len(asoc->peer_hmac_id);
}
} else {
overhead += sizeof(struct sctp_idata_chunk);
if (sctp_auth_is_required_chunk(SCTP_DATA, asoc->peer_auth_chunks)) {
overhead += sctp_get_auth_chunk_len(asoc->peer_hmac_id);
}
}
return (siz);
/* Consider padding. */
if (asoc->smallest_mtu % 4) {
overhead += (asoc->smallest_mtu % 4);
}
KASSERT(overhead % 4 == 0,
("overhead (%u) not a multiple of 4", overhead));
KASSERT(asoc->smallest_mtu > overhead,
("Association MTU (%u) too small for overhead (%u)",
asoc->smallest_mtu, overhead));
frag_point = asoc->smallest_mtu - overhead;
/* Honor MAXSEG socket option. */
if ((asoc->sctp_frag_point > 0) &&
(asoc->sctp_frag_point < frag_point)) {
frag_point = asoc->sctp_frag_point;
}
return (frag_point);
}
static void
@ -6571,7 +6576,8 @@ sctp_med_chunk_output(struct sctp_inpcb *inp,
int *num_out,
int *reason_code,
int control_only, int from_where,
struct timeval *now, int *now_filled, int frag_point, int so_locked);
struct timeval *now, int *now_filled,
uint32_t frag_point, int so_locked);
static void
sctp_sendall_iterator(struct sctp_inpcb *inp, struct sctp_tcb *stcb, void *ptr,
@ -6740,13 +6746,13 @@ sctp_sendall_iterator(struct sctp_inpcb *inp, struct sctp_tcb *stcb, void *ptr,
if (do_chunk_output)
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_NOT_LOCKED);
else if (added_control) {
int num_out, reason, now_filled = 0;
struct timeval now;
int frag_point;
int num_out, reason, now_filled = 0;
frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
(void)sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out,
&reason, 1, 1, &now, &now_filled, frag_point, SCTP_SO_NOT_LOCKED);
&reason, 1, 1, &now, &now_filled,
sctp_get_frag_point(stcb),
SCTP_SO_NOT_LOCKED);
}
no_chunk_output:
if (ret) {
@ -7674,8 +7680,9 @@ sctp_move_to_outqueue(struct sctp_tcb *stcb,
}
static void
sctp_fill_outqueue(struct sctp_tcb *stcb, struct sctp_nets *net, int frag_point,
int eeor_mode, int *quit_now, int so_locked)
sctp_fill_outqueue(struct sctp_tcb *stcb, struct sctp_nets *net,
uint32_t frag_point, int eeor_mode, int *quit_now,
int so_locked)
{
struct sctp_association *asoc;
struct sctp_stream_out *strq;
@ -7794,7 +7801,8 @@ sctp_med_chunk_output(struct sctp_inpcb *inp,
int *num_out,
int *reason_code,
int control_only, int from_where,
struct timeval *now, int *now_filled, int frag_point, int so_locked)
struct timeval *now, int *now_filled,
uint32_t frag_point, int so_locked)
{
/**
* Ok this is the generic chunk service queue. we must do the
@ -9975,7 +9983,7 @@ sctp_chunk_output(struct sctp_inpcb *inp,
struct timeval now;
int now_filled = 0;
int nagle_on;
int frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
uint32_t frag_point = sctp_get_frag_point(stcb);
int un_sent = 0;
int fr_done;
unsigned int tot_frs = 0;
@ -13663,16 +13671,17 @@ sctp_lower_sosend(struct socket *so,
}
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
} else if (some_on_control) {
int num_out, reason, frag_point;
int num_out, reason;
/* Here we do control only */
if (hold_tcblock == 0) {
hold_tcblock = 1;
SCTP_TCB_LOCK(stcb);
}
frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
(void)sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out,
&reason, 1, 1, &now, &now_filled, frag_point, SCTP_SO_LOCKED);
&reason, 1, 1, &now, &now_filled,
sctp_get_frag_point(stcb),
SCTP_SO_LOCKED);
}
NET_EPOCH_EXIT(et);
SCTPDBG(SCTP_DEBUG_OUTPUT1, "USR Send complete qo:%d prw:%d unsent:%d tf:%d cooq:%d toqs:%d err:%d\n",

View File

@ -117,7 +117,7 @@ void sctp_send_asconf(struct sctp_tcb *, struct sctp_nets *, int addr_locked);
void sctp_send_asconf_ack(struct sctp_tcb *);
int sctp_get_frag_point(struct sctp_tcb *, struct sctp_association *);
uint32_t sctp_get_frag_point(struct sctp_tcb *);
void sctp_toss_old_cookies(struct sctp_tcb *, struct sctp_association *);

View File

@ -2422,7 +2422,7 @@ sctp_inpcb_alloc(struct socket *so, uint32_t vrf_id)
#endif
inp->sctp_associd_counter = 1;
inp->partial_delivery_point = SCTP_SB_LIMIT_RCV(so) >> SCTP_PARTIAL_DELIVERY_SHIFT;
inp->sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT;
inp->sctp_frag_point = 0;
inp->max_cwnd = 0;
inp->sctp_cmt_on_off = SCTP_BASE_SYSCTL(sctp_cmt_on_off);
inp->ecn_supported = (uint8_t)SCTP_BASE_SYSCTL(sctp_ecn_enable);

View File

@ -2032,13 +2032,12 @@ sctp_getopt(struct socket *so, int optname, void *optval, size_t *optsize,
case SCTP_MAXSEG:
{
struct sctp_assoc_value *av;
int ovh;
SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, *optsize);
SCTP_FIND_STCB(inp, stcb, av->assoc_id);
if (stcb) {
av->assoc_value = sctp_get_frag_point(stcb, &stcb->asoc);
av->assoc_value = stcb->asoc.sctp_frag_point;
SCTP_TCB_UNLOCK(stcb);
} else {
if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
@ -2046,15 +2045,7 @@ sctp_getopt(struct socket *so, int optname, void *optval, size_t *optsize,
((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
(av->assoc_id == SCTP_FUTURE_ASSOC))) {
SCTP_INP_RLOCK(inp);
if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
ovh = SCTP_MED_OVERHEAD;
} else {
ovh = SCTP_MED_V4_OVERHEAD;
}
if (inp->sctp_frag_point >= SCTP_DEFAULT_MAXSEGMENT)
av->assoc_value = 0;
else
av->assoc_value = inp->sctp_frag_point - ovh;
av->assoc_value = inp->sctp_frag_point;
SCTP_INP_RUNLOCK(inp);
} else {
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);
@ -2623,7 +2614,7 @@ sctp_getopt(struct socket *so, int optname, void *optval, size_t *optsize,
stcb->asoc.cnt_on_all_streams);
sstat->sstat_instrms = stcb->asoc.streamincnt;
sstat->sstat_outstrms = stcb->asoc.streamoutcnt;
sstat->sstat_fragmentation_point = sctp_get_frag_point(stcb, &stcb->asoc);
sstat->sstat_fragmentation_point = sctp_get_frag_point(stcb);
net = stcb->asoc.primary_destination;
if (net != NULL) {
memcpy(&sstat->sstat_primary.spinfo_address,
@ -4977,22 +4968,12 @@ sctp_setopt(struct socket *so, int optname, void *optval, size_t optsize,
case SCTP_MAXSEG:
{
struct sctp_assoc_value *av;
int ovh;
SCTP_CHECK_AND_CAST(av, optval, struct sctp_assoc_value, optsize);
SCTP_FIND_STCB(inp, stcb, av->assoc_id);
if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
ovh = SCTP_MED_OVERHEAD;
} else {
ovh = SCTP_MED_V4_OVERHEAD;
}
if (stcb) {
if (av->assoc_value) {
stcb->asoc.sctp_frag_point = (av->assoc_value + ovh);
} else {
stcb->asoc.sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT;
}
stcb->asoc.sctp_frag_point = av->assoc_value;
SCTP_TCB_UNLOCK(stcb);
} else {
if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
@ -5000,15 +4981,7 @@ sctp_setopt(struct socket *so, int optname, void *optval, size_t optsize,
((inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) &&
(av->assoc_id == SCTP_FUTURE_ASSOC))) {
SCTP_INP_WLOCK(inp);
/*
* FIXME MT: I think this is not in
* tune with the API ID
*/
if (av->assoc_value) {
inp->sctp_frag_point = (av->assoc_value + ovh);
} else {
inp->sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT;
}
inp->sctp_frag_point = av->assoc_value;
SCTP_INP_WUNLOCK(inp);
} else {
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_USRREQ, EINVAL);

View File

@ -1248,7 +1248,7 @@ sctp_init_asoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
asoc->my_rwnd = max(SCTP_SB_LIMIT_RCV(inp->sctp_socket), SCTP_MINIMAL_RWND);
asoc->peers_rwnd = SCTP_SB_LIMIT_RCV(inp->sctp_socket);
asoc->smallest_mtu = inp->sctp_frag_point;
asoc->smallest_mtu = 0;
asoc->minrto = inp->sctp_ep.sctp_minrto;
asoc->maxrto = inp->sctp_ep.sctp_maxrto;