4.14-stable review patch.  If anyone has any objections, please let me know.

------------------

From: Xin Long <lucien....@gmail.com>

commit ecca8f88da5c4260cc2bccfefd2a24976704c366 upstream.

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.leit...@gmail.com>
Reported-by: Hangbin Liu <liuhang...@gmail.com>
Signed-off-by: Xin Long <lucien....@gmail.com>
Acked-by: Marcelo Ricardo Leitner <marcelo.leit...@gmail.com>
Signed-off-by: David S. Miller <da...@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 include/net/sctp/sctp.h |    3 ++-
 net/sctp/socket.c       |   29 +++++++++++++++++++----------
 2 files changed, 21 insertions(+), 11 deletions(-)

--- a/include/net/sctp/sctp.h
+++ b/include/net/sctp/sctp.h
@@ -444,7 +444,8 @@ static inline int sctp_frag_point(const
        if (asoc->user_frag)
                frag = min_t(int, frag, asoc->user_frag);
 
-       frag = SCTP_TRUNC4(min_t(int, frag, SCTP_MAX_CHUNK_LEN));
+       frag = SCTP_TRUNC4(min_t(int, frag, SCTP_MAX_CHUNK_LEN -
+                                           sizeof(struct sctp_data_chunk)));
 
        return frag;
 }
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -3136,9 +3136,9 @@ static int sctp_setsockopt_mappedv4(stru
  */
 static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, 
unsigned int optlen)
 {
+       struct sctp_sock *sp = sctp_sk(sk);
        struct sctp_assoc_value params;
        struct sctp_association *asoc;
-       struct sctp_sock *sp = sctp_sk(sk);
        int val;
 
        if (optlen == sizeof(int)) {
@@ -3154,26 +3154,35 @@ static int sctp_setsockopt_maxseg(struct
                if (copy_from_user(&params, optval, optlen))
                        return -EFAULT;
                val = params.assoc_value;
-       } else
+       } else {
                return -EINVAL;
+       }
 
-       if ((val != 0) && ((val < 8) || (val > SCTP_MAX_CHUNK_LEN)))
-               return -EINVAL;
+       if (val) {
+               int min_len, max_len;
 
-       asoc = sctp_id2assoc(sk, params.assoc_id);
-       if (!asoc && params.assoc_id && sctp_style(sk, UDP))
-               return -EINVAL;
+               min_len = SCTP_DEFAULT_MINSEGMENT - sp->pf->af->net_header_len;
+               min_len -= sizeof(struct sctphdr) +
+                          sizeof(struct sctp_data_chunk);
+
+               max_len = SCTP_MAX_CHUNK_LEN - sizeof(struct sctp_data_chunk);
 
+               if (val < min_len || val > max_len)
+                       return -EINVAL;
+       }
+
+       asoc = sctp_id2assoc(sk, params.assoc_id);
        if (asoc) {
                if (val == 0) {
-                       val = asoc->pathmtu;
-                       val -= sp->pf->af->net_header_len;
+                       val = asoc->pathmtu - sp->pf->af->net_header_len;
                        val -= sizeof(struct sctphdr) +
-                                       sizeof(struct sctp_data_chunk);
+                              sizeof(struct sctp_data_chunk);
                }
                asoc->user_frag = val;
                asoc->frag_point = sctp_frag_point(asoc, asoc->pathmtu);
        } else {
+               if (params.assoc_id && sctp_style(sk, UDP))
+                       return -EINVAL;
                sp->user_frag = val;
        }
 


Reply via email to