diff options
| author | Xin Long <lucien.xin@gmail.com> | 2017-11-17 01:11:11 -0500 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2017-11-17 20:32:41 -0500 |
| commit | ecca8f88da5c4260cc2bccfefd2a24976704c366 (patch) | |
| tree | 763c7eece0fe70691f21ce60d1f6b3ef8e6759b5 | |
| parent | d35ef8f846c72d84bfccf239c248c84f79c3a7e8 (diff) | |
sctp: set frag_point in sctp_setsockopt_maxseg correctly
Now in sctp_setsockopt_maxseg user_frag or frag_point can be set with
val >= 8 and val <= SCTP_MAX_CHUNK_LEN. But both checks are incorrect.
val >= 8 means frag_point can even be less than SCTP_DEFAULT_MINSEGMENT.
Then in sctp_datamsg_from_user(), when it's value is greater than cookie
echo len and trying to bundle with cookie echo chunk, the first_len will
overflow.
The worse case is when it's value is equal as cookie echo len, first_len
becomes 0, it will go into a dead loop for fragment later on. In Hangbin
syzkaller testing env, oom was even triggered due to consecutive memory
allocation in that loop.
Besides, SCTP_MAX_CHUNK_LEN is the max size of the whole chunk, it should
deduct the data header for frag_point or user_frag check.
This patch does a proper check with SCTP_DEFAULT_MINSEGMENT subtracting
the sctphdr and datahdr, SCTP_MAX_CHUNK_LEN subtracting datahdr when
setting frag_point via sockopt. It also improves sctp_setsockopt_maxseg
codes.
Suggested-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Reported-by: Hangbin Liu <liuhangbin@gmail.com>
Signed-off-by: Xin Long <lucien.xin@gmail.com>
Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
| -rw-r--r-- | include/net/sctp/sctp.h | 3 | ||||
| -rw-r--r-- | net/sctp/socket.c | 29 |
2 files changed, 21 insertions, 11 deletions
diff --git a/include/net/sctp/sctp.h b/include/net/sctp/sctp.h index d7d8cba01469..749a42882437 100644 --- a/include/net/sctp/sctp.h +++ b/include/net/sctp/sctp.h | |||
| @@ -444,7 +444,8 @@ static inline int sctp_frag_point(const struct sctp_association *asoc, int pmtu) | |||
| 444 | if (asoc->user_frag) | 444 | if (asoc->user_frag) |
| 445 | frag = min_t(int, frag, asoc->user_frag); | 445 | frag = min_t(int, frag, asoc->user_frag); |
| 446 | 446 | ||
| 447 | frag = SCTP_TRUNC4(min_t(int, frag, SCTP_MAX_CHUNK_LEN)); | 447 | frag = SCTP_TRUNC4(min_t(int, frag, SCTP_MAX_CHUNK_LEN - |
| 448 | sizeof(struct sctp_data_chunk))); | ||
| 448 | 449 | ||
| 449 | return frag; | 450 | return frag; |
| 450 | } | 451 | } |
diff --git a/net/sctp/socket.c b/net/sctp/socket.c index 4c0a77292792..3204a9b29407 100644 --- a/net/sctp/socket.c +++ b/net/sctp/socket.c | |||
| @@ -3140,9 +3140,9 @@ static int sctp_setsockopt_mappedv4(struct sock *sk, char __user *optval, unsign | |||
| 3140 | */ | 3140 | */ |
| 3141 | static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned int optlen) | 3141 | static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned int optlen) |
| 3142 | { | 3142 | { |
| 3143 | struct sctp_sock *sp = sctp_sk(sk); | ||
| 3143 | struct sctp_assoc_value params; | 3144 | struct sctp_assoc_value params; |
| 3144 | struct sctp_association *asoc; | 3145 | struct sctp_association *asoc; |
| 3145 | struct sctp_sock *sp = sctp_sk(sk); | ||
| 3146 | int val; | 3146 | int val; |
| 3147 | 3147 | ||
| 3148 | if (optlen == sizeof(int)) { | 3148 | if (optlen == sizeof(int)) { |
| @@ -3158,26 +3158,35 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned | |||
| 3158 | if (copy_from_user(¶ms, optval, optlen)) | 3158 | if (copy_from_user(¶ms, optval, optlen)) |
| 3159 | return -EFAULT; | 3159 | return -EFAULT; |
| 3160 | val = params.assoc_value; | 3160 | val = params.assoc_value; |
| 3161 | } else | 3161 | } else { |
| 3162 | return -EINVAL; | 3162 | return -EINVAL; |
| 3163 | } | ||
| 3163 | 3164 | ||
| 3164 | if ((val != 0) && ((val < 8) || (val > SCTP_MAX_CHUNK_LEN))) | 3165 | if (val) { |
| 3165 | return -EINVAL; | 3166 | int min_len, max_len; |
| 3166 | 3167 | ||
| 3167 | asoc = sctp_id2assoc(sk, params.assoc_id); | 3168 | min_len = SCTP_DEFAULT_MINSEGMENT - sp->pf->af->net_header_len; |
| 3168 | if (!asoc && params.assoc_id && sctp_style(sk, UDP)) | 3169 | min_len -= sizeof(struct sctphdr) + |
| 3169 | return -EINVAL; | 3170 | sizeof(struct sctp_data_chunk); |
| 3171 | |||
| 3172 | max_len = SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_data_chunk); | ||
| 3170 | 3173 | ||
| 3174 | if (val < min_len || val > max_len) | ||
| 3175 | return -EINVAL; | ||
| 3176 | } | ||
| 3177 | |||
| 3178 | asoc = sctp_id2assoc(sk, params.assoc_id); | ||
| 3171 | if (asoc) { | 3179 | if (asoc) { |
| 3172 | if (val == 0) { | 3180 | if (val == 0) { |
| 3173 | val = asoc->pathmtu; | 3181 | val = asoc->pathmtu - sp->pf->af->net_header_len; |
| 3174 | val -= sp->pf->af->net_header_len; | ||
| 3175 | val -= sizeof(struct sctphdr) + | 3182 | val -= sizeof(struct sctphdr) + |
| 3176 | sizeof(struct sctp_data_chunk); | 3183 | sizeof(struct sctp_data_chunk); |
| 3177 | } | 3184 | } |
| 3178 | asoc->user_frag = val; | 3185 | asoc->user_frag = val; |
| 3179 | asoc->frag_point = sctp_frag_point(asoc, asoc->pathmtu); | 3186 | asoc->frag_point = sctp_frag_point(asoc, asoc->pathmtu); |
| 3180 | } else { | 3187 | } else { |
| 3188 | if (params.assoc_id && sctp_style(sk, UDP)) | ||
| 3189 | return -EINVAL; | ||
| 3181 | sp->user_frag = val; | 3190 | sp->user_frag = val; |
| 3182 | } | 3191 | } |
| 3183 | 3192 | ||
